From d8dd16e8659a36279feeb410f602d383ec285192 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 5 Aug 2016 18:34:38 +0300 Subject: [PATCH 001/163] init commit --- toxygen/avwidgets.py | 5 ++++- toxygen/callbacks.py | 8 ++++++++ toxygen/calls.py | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/toxygen/avwidgets.py b/toxygen/avwidgets.py index 480fa28..4ae329d 100644 --- a/toxygen/avwidgets.py +++ b/toxygen/avwidgets.py @@ -10,6 +10,9 @@ import wave import settings from util import curr_directory +# TODO: widget for video +# TODO: improve IncomingCallWidget + class IncomingCallWidget(widgets.CenteredWidget): @@ -56,7 +59,7 @@ class IncomingCallWidget(widgets.CenteredWidget): self.call_type.setText(text) pr = profile.Profile.get_instance() self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop()) - # self.accept_video.clicked.connect(lambda: pr.start_call(friend_number, True, True)) + self.accept_video.clicked.connect(lambda: pr.accept_call(friend_number, True, True)) self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop()) class SoundPlay(QtCore.QThread): diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index f8ddb17..e280c06 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -320,6 +320,13 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud audio_channels_count, rate) +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - video +# ----------------------------------------------------------------------------------------------------------------- + + +def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data): + pass # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization @@ -353,6 +360,7 @@ def init_callbacks(tox, window, tray): toxav.callback_call_state(call_state, 0) toxav.callback_call(call, 0) toxav.callback_audio_receive_frame(callback_audio, 0) + tox.callback_video_receive_frame(video_receive_frame, 0) tox.callback_friend_lossless_packet(lossless_packet, 0) tox.callback_friend_lossy_packet(lossy_packet, 0) diff --git a/toxygen/calls.py b/toxygen/calls.py index 16cef47..2f02b83 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -11,6 +11,7 @@ CALL_TYPE = { 'AUDIO': 1, 'VIDEO': 2 } +# TODO: rewrite (make class) class AV: From 01546f00472a1cd73473bdbb35be805252fab527 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 7 Aug 2016 00:31:38 +0300 Subject: [PATCH 002/163] dict on incoming call widgets, calls.py update --- toxygen/callbacks.py | 2 +- toxygen/calls.py | 83 +++++++++++++++++++++++++++----------------- toxygen/profile.py | 15 ++++---- 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index e280c06..5151637 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -315,7 +315,7 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud """ New audio chunk """ - Profile.get_instance().call.chunk( + Profile.get_instance().call.audio_chunk( bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), audio_channels_count, rate) diff --git a/toxygen/calls.py b/toxygen/calls.py index 2f02b83..7dc6b67 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -4,14 +4,14 @@ import threading import settings from toxav_enums import * # TODO: play sound until outgoing call will be started or cancelled and add timeout -# TODO: add widget for call -CALL_TYPE = { - 'NONE': 0, - 'AUDIO': 1, - 'VIDEO': 2 -} -# TODO: rewrite (make class) + +class Call: + + def __init__(self, audio=False, video=False): + self.audio = audio + self.video = video + # TODO: add widget for call class AV: @@ -20,7 +20,7 @@ class AV: self._toxav = toxav self._running = True - self._calls = {} # dict: key - friend number, value - call type + self._calls = {} # dict: key - friend number, value - Call instance self._audio = None self._audio_stream = None @@ -33,15 +33,30 @@ class AV: self._audio_duration = 60 self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000 + def stop(self): + self._running = False + self.stop_audio_thread() + def __contains__(self, friend_number): return friend_number in self._calls + # ----------------------------------------------------------------------------------------------------------------- + # Calls + # ----------------------------------------------------------------------------------------------------------------- + def __call__(self, friend_number, audio, video): """Call friend with specified number""" self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0) - self._calls[friend_number] = CALL_TYPE['AUDIO'] + self._calls[friend_number] = Call(audio, video) self.start_audio_thread() + def accept_call(self, friend_number, audio_enabled, video_enabled): + + if self._running: + self._calls[friend_number] = Call(audio_enabled, video_enabled) + self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0) + self.start_audio_thread() + def finish_call(self, friend_number, by_friend=False): if not by_friend: @@ -51,9 +66,21 @@ class AV: if not len(self._calls): self.stop_audio_thread() - def stop(self): - self._running = False - self.stop_audio_thread() + def toxav_call_state_cb(self, friend_number, state): + """ + New call state + """ + pass # TODO: ignore? + # if self._running: + # + # if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']: + # self._calls[friend_number].audio = True + # if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_V']: + # self._calls[friend_number].video = True + + # ----------------------------------------------------------------------------------------------------------------- + # Threads + # ----------------------------------------------------------------------------------------------------------------- def start_audio_thread(self): """ @@ -93,7 +120,11 @@ class AV: self._out_stream.close() self._out_stream = None - def chunk(self, samples, channels_count, rate): + # ----------------------------------------------------------------------------------------------------------------- + # Incoming chunks + # ----------------------------------------------------------------------------------------------------------------- + + def audio_chunk(self, samples, channels_count, rate): """ Incoming chunk """ @@ -106,6 +137,13 @@ class AV: output=True) self._out_stream.write(samples) + def video_chunk(self): + pass + + # ----------------------------------------------------------------------------------------------------------------- + # AV sending + # ----------------------------------------------------------------------------------------------------------------- + def send_audio(self): """ This method sends audio to friends @@ -116,7 +154,7 @@ class AV: pcm = self._audio_stream.read(self._audio_sample_count) if pcm: for friend in self._calls: - if self._calls[friend] & 1: + if self._calls[friend].audio: try: self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count, self._audio_channels, self._audio_rate) @@ -126,20 +164,3 @@ class AV: pass time.sleep(0.01) - - def accept_call(self, friend_number, audio_enabled, video_enabled): - - if self._running: - self._calls[friend_number] = int(video_enabled) * 2 + int(audio_enabled) - self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0) - self.start_audio_thread() - - def toxav_call_state_cb(self, friend_number, state): - """ - New call state - """ - if self._running: - - if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']: - self._calls[friend_number] |= 1 - diff --git a/toxygen/profile.py b/toxygen/profile.py index 3f48cc9..e89bf2b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -39,6 +39,7 @@ class Profile(basecontact.BaseContact, Singleton): self._tox = tox self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) self._call = calls.AV(tox.AV) # object with data about calls + self._call_widgets = {} # dict of incoming call widgets self._incoming_calls = set() self._load_history = True self._waiting_for_reconnection = False @@ -1237,10 +1238,9 @@ class Profile(basecontact.BaseContact, Singleton): self._messages.scrollToBottom() else: friend.actions = True - # TODO: dict of widgets - self._call_widget = avwidgets.IncomingCallWidget(friend_number, text, friend.name) - self._call_widget.set_pixmap(friend.get_pixmap()) - self._call_widget.show() + self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name) + self._call_widgets[friend_number].set_pixmap(friend.get_pixmap()) + self._call_widgets[friend_number].show() def accept_call(self, friend_number, audio, video): """ @@ -1250,8 +1250,7 @@ class Profile(basecontact.BaseContact, Singleton): self._screen.active_call() if friend_number in self._incoming_calls: self._incoming_calls.remove(friend_number) - if hasattr(self, '_call_widget'): - del self._call_widget + del self._call_widgets[friend_number] def stop_call(self, friend_number, by_friend): """ @@ -1265,8 +1264,8 @@ class Profile(basecontact.BaseContact, Singleton): self._screen.call_finished() self._call.finish_call(friend_number, by_friend) # finish or decline call if hasattr(self, '_call_widget'): - self._call_widget.close() - del self._call_widget + self._call_widget[friend_number].close() + del self._call_widget[friend_number] friend = self.get_friend_by_number(friend_number) friend.append_message(InfoMessage(text, time.time())) if friend_number == self.get_active_number(): From f8a7087779eddcb096438a5c22bb5c623437278d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 8 Aug 2016 12:10:18 +0300 Subject: [PATCH 003/163] video recording and thread --- setup.py | 13 +++++++--- toxygen/callbacks.py | 2 +- toxygen/calls.py | 59 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 86141f5..7155c37 100644 --- a/setup.py +++ b/setup.py @@ -8,15 +8,21 @@ import sys version = program_version + '.0' -MODULES = [] +MODULES = ['numpy'] if system() in ('Windows', 'Darwin'): - MODULES = ['PyAudio', 'PySide'] + MODULES.extend(['PyAudio', 'PySide']) else: try: import pyaudio except ImportError: - MODULES = ['PyAudio'] + MODULES.append('PyAudio') + + +if system() == 'Windows': + DEPS_LINKS = [] # TODO: add opencv.whl +else: + DEPS_LINKS = [] class InstallScript(install): @@ -55,6 +61,7 @@ setup(name='Toxygen', license='GPL3', packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'], install_requires=MODULES, + dependency_links=DEP_LINKS, include_package_data=True, classifiers=[ 'Programming Language :: Python :: 3 :: Only', diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 5151637..cc4d216 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -360,7 +360,7 @@ def init_callbacks(tox, window, tray): toxav.callback_call_state(call_state, 0) toxav.callback_call(call, 0) toxav.callback_audio_receive_frame(callback_audio, 0) - tox.callback_video_receive_frame(video_receive_frame, 0) + toxav.callback_video_receive_frame(video_receive_frame, 0) tox.callback_friend_lossless_packet(lossless_packet, 0) tox.callback_friend_lossy_packet(lossy_packet, 0) diff --git a/toxygen/calls.py b/toxygen/calls.py index 7dc6b67..02272be 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -3,6 +3,7 @@ import time import threading import settings from toxav_enums import * +import cv2 # TODO: play sound until outgoing call will be started or cancelled and add timeout @@ -33,9 +34,14 @@ class AV: self._audio_duration = 60 self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000 + self._video = None + self._video_thread = None + self._video_running = False + def stop(self): self._running = False self.stop_audio_thread() + self.stop_video_thread() def __contains__(self, friend_number): return friend_number in self._calls @@ -120,6 +126,29 @@ class AV: self._out_stream.close() self._out_stream = None + def start_video_thread(self): + if self._video_thread is not None: + return + + self._video_running = True + + self._video = cv2.VideoCapture(0) + self._video.set(cv2.CV_CAP_PROP_FPS, 25) + self._video.set(cv2.CV_CAP_PROP_FRAME_WIDTH, 640) + self._video.set(cv2.CV_CAP_PROP_FRAME_HEIGHT, 480) + + self._video_thread = threading.Thread(target=self.send_video) + self._video_thread.start() + + def stop_video_thread(self): + if self._video_thread is None: + return + + self._video_running = False + self._video_thread.join() + self._video_thread = None + self._video = None + # ----------------------------------------------------------------------------------------------------------------- # Incoming chunks # ----------------------------------------------------------------------------------------------------------------- @@ -153,10 +182,10 @@ class AV: try: pcm = self._audio_stream.read(self._audio_sample_count) if pcm: - for friend in self._calls: - if self._calls[friend].audio: + for friend_num in self._calls: + if self._calls[friend_num].audio: try: - self._toxav.audio_send_frame(friend, pcm, self._audio_sample_count, + self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count, self._audio_channels, self._audio_rate) except: pass @@ -164,3 +193,27 @@ class AV: pass time.sleep(0.01) + + def send_video(self): + while self._video_running: + try: + result, frame = self._video.read() + if result: + height, width, channels = frame.shape + for friend_num in self._calls: + if self._calls[friend_num].video: + try: # TODO: bgr => yuv + y, u, v = convert_bgr_to_yuv(frame) + self._toxav.video_send_frame(friend_num, width, height, y, u, v) + except Exception as e: + print(e) + except Exception as e: + print(e) + + time.sleep(0.01) + + +def convert_bgr_to_yuv(frame): + print(frame.tostring()) + print(dir(frame)) + return 0, 0, 0 From 9153836eadccd20be666bd37559cc244486abef9 Mon Sep 17 00:00:00 2001 From: Maxim Biro Date: Wed, 12 Apr 2017 10:10:48 -0400 Subject: [PATCH 004/163] Fix markdown formatting --- docs/contributing.md | 8 ++++---- docs/smileys_and_stickers.md | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index 35bd650..dd3e439 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -1,4 +1,4 @@ -#Issues +# Issues Help us find all bugs in Toxygen! Please provide following info: @@ -9,12 +9,12 @@ Help us find all bugs in Toxygen! Please provide following info: Want to see new feature in Toxygen? [Ask for it!](https://github.com/toxygen-project/toxygen/issues) -#Pull requests +# Pull requests Developer? Feel free to open pull request. Our dev team is small so we glad to get help. Don't know what to do? Improve UI, fix [issues](https://github.com/toxygen-project/toxygen/issues) or implement features from our TODO list. You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen. -#Translations +# Translations -Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist. \ No newline at end of file +Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist. diff --git a/docs/smileys_and_stickers.md b/docs/smileys_and_stickers.md index e34a888..91ade33 100644 --- a/docs/smileys_and_stickers.md +++ b/docs/smileys_and_stickers.md @@ -1,4 +1,4 @@ -#Smileys +# Smileys Toxygen support smileys. Smiley is small picture which replaces some symbol or combination of symbols. If you want to create your own smiley pack, create directory in src/smileys/. This directory must contain images with smileys and config.json. Example of config.json: @@ -6,7 +6,7 @@ Toxygen support smileys. Smiley is small picture which replaces some symbol or c Animated smileys (.gif) are supported too. -#Stickers +# Stickers Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there. From 4f77e2c10503b79055bfe8e66bcb9e063a55b1b5 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 17 Apr 2017 22:04:22 +0300 Subject: [PATCH 005/163] @cached --- toxygen/history.py | 3 +++ toxygen/main.py | 2 ++ toxygen/util.py | 20 +++++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/toxygen/history.py b/toxygen/history.py index 586981a..fe7d3ee 100644 --- a/toxygen/history.py +++ b/toxygen/history.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- + + from sqlite3 import connect import settings from os import chdir diff --git a/toxygen/main.py b/toxygen/main.py index 8c51fd2..c9ab9cf 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import sys from loginscreen import LoginScreen import profile diff --git a/toxygen/util.py b/toxygen/util.py index 2c9483d..f5cff4e 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import os import time import shutil @@ -7,11 +9,25 @@ import re program_version = '0.2.8' +def cached(func): + saved_result = None + + def wrapped_func(): + nonlocal saved_result + if saved_result is None: + saved_result = func() + + return saved_result + + return wrapped_func + + def log(data): with open(curr_directory() + '/logs.log', 'a') as fl: fl.write(str(data) + '\n') +@cached def curr_directory(): return os.path.dirname(os.path.realpath(__file__)) @@ -46,9 +62,8 @@ def convert_time(t): return '%02d:%02d' % (h, m) +@cached def time_offset(): - if hasattr(time_offset, 'offset'): - return time_offset.offset hours = int(time.strftime('%H')) minutes = int(time.strftime('%M')) sec = int(time.time()) - time.timezone @@ -56,7 +71,6 @@ def time_offset(): h, m = divmod(m, 60) d, h = divmod(h, 24) result = hours * 60 + minutes - h * 60 - m - time_offset.offset = result return result From ac07cb529f4991bb67d3d428421e4afbc2eca913 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 22 Apr 2017 22:35:32 +0300 Subject: [PATCH 006/163] reconnection bug fixed --- toxygen/calls.py | 1 - toxygen/profile.py | 1 + toxygen/widgets.py | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index 16cef47..2b700d7 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -141,4 +141,3 @@ class AV: if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']: self._calls[friend_number] |= 1 - diff --git a/toxygen/profile.py b/toxygen/profile.py index 3f48cc9..eda76bf 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -88,6 +88,7 @@ class Profile(basecontact.BaseContact, Singleton): if status is not None: self._tox.self_set_status(status) elif not self._waiting_for_reconnection: + self._waiting_for_reconnection = True QtCore.QTimer.singleShot(50000, self.reconnect) def set_name(self, value): diff --git a/toxygen/widgets.py b/toxygen/widgets.py index f83e02c..ff441ed 100644 --- a/toxygen/widgets.py +++ b/toxygen/widgets.py @@ -137,4 +137,3 @@ class MultilineEdit(CenteredWidget): def button_click(self): self.save(self.edit.toPlainText()) self.close() - From 81695737cdee92b8fa11bc0fa2623227d2154c1b Mon Sep 17 00:00:00 2001 From: SHooZ Date: Mon, 1 May 2017 00:44:55 +0300 Subject: [PATCH 007/163] Add ukrainian translation --- toxygen/settings.py | 3 +- toxygen/translations/uk_UA.qm | Bin 0 -> 8318 bytes toxygen/translations/uk_UA.ts | 400 ++++++++++++++++++++++++++++++++++ 3 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 toxygen/translations/uk_UA.qm create mode 100644 toxygen/translations/uk_UA.ts diff --git a/toxygen/settings.py b/toxygen/settings.py index 9604250..5fdca64 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -147,8 +147,9 @@ class Settings(dict, Singleton): def supported_languages(): return { 'English': 'en_EN', + 'French': 'fr_FR', 'Russian': 'ru_RU', - 'French': 'fr_FR' + 'Ukrainian': 'uk_UA' } def upgrade(self): diff --git a/toxygen/translations/uk_UA.qm b/toxygen/translations/uk_UA.qm new file mode 100644 index 0000000000000000000000000000000000000000..a4082efae549bfdafce5261c37b37ba6330a08c9 GIT binary patch literal 8318 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 X4h9AWUO~pcn%w_Im{|Vma0vqdOF$Op literal 0 HcmV?d00001 diff --git a/toxygen/translations/uk_UA.ts b/toxygen/translations/uk_UA.ts new file mode 100644 index 0000000..1f6a47e --- /dev/null +++ b/toxygen/translations/uk_UA.ts @@ -0,0 +1,400 @@ + + + + + Form + + IP: + IP: + + + UDP + UDP + + + HTTP + HTTP + + + IPv6 + IPv6 + + + Port: + Порт: + + + Proxy + Проксі + + + Online contacts + Контактів онлайн + + + Send request + Відправити запит + + + + tray + + Exit + Вихід + + + Open Toxygen + Відкрити Toxygen + + + + MainWindow + + About program + Про проґраму + + + Friend request + Запит дружби + + + About + Про + + + Audio + Звук + + + Friend added + Друга додано + + + Send file + Надіслати файл + + + User {} wants to add you to contact list. Message: +{} + Користувач {} хоче додати вас до списку контактів. Повідомлення +{} + + + Network + Мережа + + + Clear history + Очистити журнал + + + Copy public key + Копіювати публічний ключ + + + Send message + Надіслати повідомлення + + + Set alias + Встановити скорочення + + + Privacy + Приватність + + + Profile + Профіль + + + Toxygen is Tox client written on Python. +Version: + Toxygen — це клієнт Tox написаний на Python. +Версія: + + + Choose file + Обрати файл + + + Enter new alias for friend {} or leave empty to use friend's name: + Введіть нове скорочення для друга {} або залишіть порожнім, щоб використовувати його псевдо: + + + Add contact + Додати контакт + + + Friend added without sending friend request + Друга додано без надсилання запиту дружби + + + Interface + Зовнішній вигляд + + + Settings + Налаштування + + + Notifications + Сповіщення + + + Remove friend + Вилучити друга + + + Find contact + Знайти контакт + + + Choose folder + Обрати теку + + + Allow auto accept + Дозволити автоприймання + + + Disallow auto accept + Заборонити автоприймання + + + Start audio call with friend + Почати звуковий дзвінок + + + Send screenshot + Надіслати знімок екрану + + + + ProfileSettingsForm + + Name: + Псевдо: + + + Profile settings + Налаштування профілю + + + Reset avatar + Скинути аватар + + + New NoSpam + Новий NoSpam + + + Copy TOX ID + Копіювати TOX ID + + + New avatar + Новий аватар + + + Export profile + Експортувати профіль + + + TOX ID: + TOX ID: + + + Status: + Статус: + + + + privacySettings + + Privacy settings + Налаштування приватності + + + Add to friend list + Додати до списку друзів + + + Block by TOX ID: + Блокувати по TOX ID: + + + Blocked users: + Блоковані користувачі: + + + Change + Змінити + + + Send typing notifications + Надсилати сповіщення про те, що я друкую + + + Allow file auto accept + Дозволити автоприймання файлів + + + Allow inlines + Дозволити інлайни + + + Save chat history + Зберігати журнал бесіди + + + Block user + Блокувати користувача + + + Chat history + Журнал бесіди + + + Unblock + Розблокувати + + + History will be cleaned! Continue? + Журнал буде очищено! Продовжити? + + + Auto accept default path: + Шлях за замовчуванням для автоприймання: + + + Do you want to add this user to friend list? + Ви хочете додати цього користувача у список друзів? + + + + incoming_call + + Incoming video call + Вхідний відеодзвінок + + + Incoming audio call + Вхідний аудіодзвінок + + + + login + + Profile name: + Псевдо профілю: + + + Load profile + Завантажити профіль + + + Use as default + За замовчуванням + + + Create new profile + Створити новий профіль + + + Create + Створити + + + Log in + Увійти + + + Load existing profile + Завантажити існуючий + + + toxygen + toxygen + + + Looks like other instance of Toxygen uses this profile! Continue? + Схоже, що інша копія Toxygenʼу використовує цей профіль! Продовжити? + + + + NetworkSettings + + Network settings + Налаштування мережі + + + Restart TOX core + Перезапустити ядро Tox + + + + notificationsForm + + Enable sound notifications + Увімкнути звукові сповіщення + + + Enable notifications + Увімкнути сповіщення + + + Notification settings + Налаштування сповіщень + + + Enable call's sound + Увімкнути звук дзвінка + + + + interfaceForm + + Language: + Мова: + + + Theme: + Тема: + + + Interface settings + Налаштування зовнішнього вигляду + + + + audioSettingsForm + + Output device: + Пристрій виводу: + + + Audio settings + Налаштування звуку + + + Input device: + Пристрій вводу: + + + + AddContact + + TOX ID: + TOX ID: + + + Add contact + Додати контакт + + + Message: + Повідомлення: + + + From 06e8c79b3f23b8b28f25751a5462191417d6f122 Mon Sep 17 00:00:00 2001 From: SHooZ Date: Mon, 1 May 2017 18:09:42 +0300 Subject: [PATCH 008/163] Update toxygen.pro --- toxygen/toxygen.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toxygen/toxygen.pro b/toxygen/toxygen.pro index c768ddd..9643c8b 100644 --- a/toxygen/toxygen.pro +++ b/toxygen/toxygen.pro @@ -1,2 +1,2 @@ SOURCES = main.py profile.py menu.py list_items.py loginscreen.py mainscreen.py plugins/plugin_super_class.py callbacks.py widgets.py avwidgets.py mainscreen_widgets.py passwordscreen.py -TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts +TRANSLATIONS = translations/en_GB.ts translations/ru_RU.ts translations/fr_FR.ts translations/uk_UA.ts From 138135b9e9e871fd46be06cafd6593100be32345 Mon Sep 17 00:00:00 2001 From: SHooZ Date: Tue, 2 May 2017 02:59:24 +0300 Subject: [PATCH 009/163] Add ability to change theme --- MANIFEST.in | 4 ++-- toxygen/main.py | 15 ++++++++++----- toxygen/menu.py | 15 +++++++++++---- toxygen/settings.py | 9 ++++++++- toxygen/styles/{style.qss => dark_style.qss} | 0 toxygen/styles/style.qrc | 2 +- 6 files changed, 32 insertions(+), 13 deletions(-) rename toxygen/styles/{style.qss => dark_style.qss} (100%) diff --git a/MANIFEST.in b/MANIFEST.in index 9bf65b8..a4e17a3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -12,9 +12,9 @@ include toxygen/smileys/starwars/*.png include toxygen/smileys/starwars/config.json include toxygen/smileys/ksk/*.png include toxygen/smileys/ksk/config.json -include toxygen/styles/style.qss +include toxygen/styles/*.qss include toxygen/translations/*.qm include toxygen/libs/libtox.dll include toxygen/libs/libsodium.a include toxygen/libs/libtox64.dll -include toxygen/libs/libsodium64.a \ No newline at end of file +include toxygen/libs/libsodium64.a diff --git a/toxygen/main.py b/toxygen/main.py index c9ab9cf..ea858d4 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -59,11 +59,6 @@ class Toxygen: if platform.system() == 'Linux': QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - # application color scheme - with open(curr_directory() + '/styles/style.qss') as fl: - dark_style = fl.read() - app.setStyleSheet(dark_style) - encrypt_save = toxes.ToxES() if self.path is not None: @@ -185,6 +180,16 @@ class Toxygen: else: settings.set_active_profile() + # application color scheme + for theme in settings.built_in_themes().keys(): + if settings['theme'] == theme: + try: + with open(curr_directory() + settings.built_in_themes()[theme]) as fl: + style = fl.read() + app.setStyleSheet(style) + except IsADirectoryError: + app.setStyleSheet('') # for default style + lang = Settings.supported_languages()[settings['language']] translator = QtCore.QTranslator() translator.load(curr_directory() + '/translations/' + lang) diff --git a/toxygen/menu.py b/toxygen/menu.py index 1fe22cb..8488578 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -581,11 +581,10 @@ class InterfaceSettings(CenteredWidget): self.label.setFont(font) self.themeSelect = QtGui.QComboBox(self) self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30)) - list_of_themes = ['dark'] - self.themeSelect.addItems(list_of_themes) + self.themeSelect.addItems(list(settings.built_in_themes().keys())) theme = settings['theme'] - if theme in list_of_themes: - index = list_of_themes.index(theme) + if theme in settings.built_in_themes().keys(): + index = list(settings.built_in_themes().keys()).index(theme) else: index = 0 self.themeSelect.setCurrentIndex(index) @@ -726,6 +725,14 @@ class InterfaceSettings(CenteredWidget): def closeEvent(self, event): settings = Settings.get_instance() settings['theme'] = str(self.themeSelect.currentText()) + try: + theme = settings['theme'] + app = QtGui.QApplication.instance() + with open(curr_directory() + settings.built_in_themes()[theme]) as fl: + style = fl.read() + app.setStyleSheet(style) + except IsADirectoryError: + app.setStyleSheet('') # for default style settings['smileys'] = self.smileys.isChecked() restart = False if settings['mirror_mode'] != self.mirror_mode.isChecked(): diff --git a/toxygen/settings.py b/toxygen/settings.py index 5fdca64..ca15d3e 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -99,7 +99,7 @@ class Settings(dict, Singleton): Default profile settings """ return { - 'theme': 'default', + 'theme': 'dark', 'ipv6_enabled': True, 'udp_enabled': True, 'proxy_type': 0, @@ -152,6 +152,13 @@ class Settings(dict, Singleton): 'Ukrainian': 'uk_UA' } + @staticmethod + def built_in_themes(): + return { + 'dark': '/styles/dark_style.qss', + 'default': '' + } + def upgrade(self): default = Settings.get_default_settings() for key in default: diff --git a/toxygen/styles/style.qss b/toxygen/styles/dark_style.qss similarity index 100% rename from toxygen/styles/style.qss rename to toxygen/styles/dark_style.qss diff --git a/toxygen/styles/style.qrc b/toxygen/styles/style.qrc index ac14bc5..9759a62 100644 --- a/toxygen/styles/style.qrc +++ b/toxygen/styles/style.qrc @@ -41,6 +41,6 @@ rc/radio_unchecked.png - style.qss + dark_style.qss From 9118e0177541b4b7f63386ea4ff8512f81c6746b Mon Sep 17 00:00:00 2001 From: SHooZ Date: Tue, 2 May 2017 20:59:27 +0300 Subject: [PATCH 010/163] Set dark style as default in load screen --- toxygen/main.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/toxygen/main.py b/toxygen/main.py index ea858d4..8d2cee9 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -59,6 +59,10 @@ class Toxygen: if platform.system() == 'Linux': QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) + with open(curr_directory() + '/styles/dark_style.qss') as fl: + style = fl.read() + app.setStyleSheet(style) + encrypt_save = toxes.ToxES() if self.path is not None: From 89caef69056ef52545bf92da2888609eecf52d6a Mon Sep 17 00:00:00 2001 From: SHooZ Date: Wed, 3 May 2017 18:43:22 +0300 Subject: [PATCH 011/163] Edit default avatar image for light themes compatibility --- toxygen/images/avatar.png | Bin 4013 -> 5848 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/toxygen/images/avatar.png b/toxygen/images/avatar.png index 91d12009d026a8eed973cf9c82e7f951b452842c..83ac75787a06d910f1180218eecfd2125ffa70d4 100755 GIT binary patch delta 5827 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 AqyPW_ literal 4013 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 JS?83{1OWSkY}Wt) From 124decc34ab724528e6ac4d081c19c3b4738a68f Mon Sep 17 00:00:00 2001 From: SHooZ Date: Wed, 3 May 2017 19:42:37 +0300 Subject: [PATCH 012/163] Add padding for search field and contacts nicknames in default theme --- toxygen/main.py | 9 +++------ toxygen/settings.py | 2 +- toxygen/styles/style.qrc | 3 +++ toxygen/styles/style.qss | 4 ++++ 4 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 toxygen/styles/style.qss diff --git a/toxygen/main.py b/toxygen/main.py index 8d2cee9..44e6cbe 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -187,12 +187,9 @@ class Toxygen: # application color scheme for theme in settings.built_in_themes().keys(): if settings['theme'] == theme: - try: - with open(curr_directory() + settings.built_in_themes()[theme]) as fl: - style = fl.read() - app.setStyleSheet(style) - except IsADirectoryError: - app.setStyleSheet('') # for default style + with open(curr_directory() + settings.built_in_themes()[theme]) as fl: + style = fl.read() + app.setStyleSheet(style) lang = Settings.supported_languages()[settings['language']] translator = QtCore.QTranslator() diff --git a/toxygen/settings.py b/toxygen/settings.py index ca15d3e..6edce36 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -156,7 +156,7 @@ class Settings(dict, Singleton): def built_in_themes(): return { 'dark': '/styles/dark_style.qss', - 'default': '' + 'default': '/styles/style.qss' } def upgrade(self): diff --git a/toxygen/styles/style.qrc b/toxygen/styles/style.qrc index 9759a62..7ceed90 100644 --- a/toxygen/styles/style.qrc +++ b/toxygen/styles/style.qrc @@ -43,4 +43,7 @@ dark_style.qss + + style.qss + diff --git a/toxygen/styles/style.qss b/toxygen/styles/style.qss new file mode 100644 index 0000000..11beeb5 --- /dev/null +++ b/toxygen/styles/style.qss @@ -0,0 +1,4 @@ +#contact_name +{ + padding-left: 22px; +} From 8ea1a771868c2d329d8eadd9ec433a9b5c4c6392 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 6 May 2017 19:35:24 +0300 Subject: [PATCH 013/163] version++ --- docs/contributing.md | 4 +++- toxygen/util.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/contributing.md b/docs/contributing.md index dd3e439..4da55c7 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -4,7 +4,7 @@ Help us find all bugs in Toxygen! Please provide following info: - OS - Toxygen version -- Toxygen executable info - .py or precompiled binary, how was it installed in system +- Toxygen executable info - python executable (.py), precompiled binary, from package etc. - Steps to reproduce the bug Want to see new feature in Toxygen? [Ask for it!](https://github.com/toxygen-project/toxygen/issues) @@ -15,6 +15,8 @@ Developer? Feel free to open pull request. Our dev team is small so we glad to g Don't know what to do? Improve UI, fix [issues](https://github.com/toxygen-project/toxygen/issues) or implement features from our TODO list. You can find our TODO's in code, issues list and [here](/README.md). Also you can implement [plugins](/docs/plugins.md) for Toxygen. +Note that we have a lot of branches for different purposes. Master branch is for stable versions (releases) only, so I recommend to open PR's to develop branch. Development of next Toxygen version usually goes there. Other branches used for implementing different tasks such as file transfers improvements or audio calls implementation etc. + # Translations Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist. diff --git a/toxygen/util.py b/toxygen/util.py index f5cff4e..468884b 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -6,7 +6,7 @@ import shutil import sys import re -program_version = '0.2.8' +program_version = '0.2.9' def cached(func): From d0e2f61d0338fc8e05e8695a0361979facf76979 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 11 Jun 2017 15:35:52 +0300 Subject: [PATCH 014/163] merge with pyqt5 branch and video sending --- .travis.yml | 6 +- setup.py | 6 +- toxygen/avwidgets.py | 25 +- toxygen/basecontact.py | 5 +- toxygen/callbacks.py | 12 +- toxygen/calls.py | 28 +- toxygen/contact.py | 5 +- toxygen/file_transfers.py | 10 +- toxygen/items_factory.py | 15 +- toxygen/list_items.py | 78 ++-- toxygen/loginscreen.py | 41 +-- toxygen/main.py | 129 +++---- toxygen/mainscreen.py | 251 +++++++------ toxygen/mainscreen_widgets.py | 167 ++++----- toxygen/menu.py | 489 ++++++++++++-------------- toxygen/notifications.py | 13 +- toxygen/passwordscreen.py | 52 ++- toxygen/plugins/plugin_super_class.py | 13 +- toxygen/profile.py | 67 ++-- toxygen/smileys.py | 5 +- toxygen/styles/style.py | 2 +- toxygen/tox_dns.py | 5 +- toxygen/updater.py | 5 +- toxygen/widgets.py | 57 ++- 24 files changed, 681 insertions(+), 805 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6002d8f..700b449 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,14 @@ language: python python: - - "3.4" + - "3.5" + - "3.6" before_install: - sudo apt-get update - sudo apt-get install -y checkinstall build-essential - sudo apt-get install portaudio19-dev - sudo apt-get install libconfig-dev libvpx-dev check -qq + - sudo apt-get install -y python3-pyqt5 install: - - pip install PySide --no-index --find-links https://parkin.github.io/python-wheelhouse/; - - python ~/virtualenv/python${TRAVIS_PYTHON_VERSION}/bin/pyside_postinstall.py -install - pip install pyaudio before_script: # OPUS diff --git a/setup.py b/setup.py index 7155c37..4794969 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,8 @@ version = program_version + '.0' MODULES = ['numpy'] if system() in ('Windows', 'Darwin'): - MODULES.extend(['PyAudio', 'PySide']) + MODULES.extend['PyAudio', 'PyQt5'] + else: try: import pyaudio @@ -65,7 +66,8 @@ setup(name='Toxygen', include_package_data=True, classifiers=[ 'Programming Language :: Python :: 3 :: Only', - 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', ], entry_points={ 'console_scripts': ['toxygen=toxygen.main:main'], diff --git a/toxygen/avwidgets.py b/toxygen/avwidgets.py index 4ae329d..ff276c4 100644 --- a/toxygen/avwidgets.py +++ b/toxygen/avwidgets.py @@ -1,7 +1,4 @@ -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets import widgets import profile import util @@ -20,7 +17,7 @@ class IncomingCallWidget(widgets.CenteredWidget): super(IncomingCallWidget, self).__init__() self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) self.resize(QtCore.QSize(500, 270)) - self.avatar_label = QtGui.QLabel(self) + self.avatar_label = QtWidgets.QLabel(self) self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64)) self.avatar_label.setScaledContents(False) self.name = widgets.DataLabel(self) @@ -33,11 +30,11 @@ class IncomingCallWidget(widgets.CenteredWidget): self.call_type = widgets.DataLabel(self) self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25)) self.call_type.setFont(font) - self.accept_audio = QtGui.QPushButton(self) + self.accept_audio = QtWidgets.QPushButton(self) self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150)) - self.accept_video = QtGui.QPushButton(self) + self.accept_video = QtWidgets.QPushButton(self) self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150)) - self.decline = QtGui.QPushButton(self) + self.decline = QtWidgets.QPushButton(self) self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150)) pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png') icon = QtGui.QIcon(pixmap) @@ -119,15 +116,14 @@ class AudioMessageRecorder(widgets.CenteredWidget): def __init__(self, friend_number, name): super(AudioMessageRecorder, self).__init__() - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(10, 20, 250, 20)) - text = QtGui.QApplication.translate("MenuWindow", "Send audio message to friend {}", None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate("MenuWindow", "Send audio message to friend {}") self.label.setText(text.format(name)) - self.record = QtGui.QPushButton(self) + self.record = QtWidgets.QPushButton(self) self.record.setGeometry(QtCore.QRect(20, 100, 150, 150)) - self.record.setText(QtGui.QApplication.translate("MenuWindow", "Start recording", None, - QtGui.QApplication.UnicodeUTF8)) + self.record.setText(QtWidgets.QApplication.translate("MenuWindow", "Start recording")) self.record.clicked.connect(self.start_or_stop_recording) self.recording = False self.friend_num = friend_number @@ -135,8 +131,7 @@ class AudioMessageRecorder(widgets.CenteredWidget): def start_or_stop_recording(self): if not self.recording: self.recording = True - self.record.setText(QtGui.QApplication.translate("MenuWindow", "Stop recording", None, - QtGui.QApplication.UnicodeUTF8)) + self.record.setText(QtWidgets.QApplication.translate("MenuWindow", "Stop recording")) else: self.close() diff --git a/toxygen/basecontact.py b/toxygen/basecontact.py index d6366c3..e1243a4 100644 --- a/toxygen/basecontact.py +++ b/toxygen/basecontact.py @@ -1,8 +1,5 @@ from settings import * -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index cc4d216..01a14d4 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -1,7 +1,4 @@ -try: - from PySide import QtCore -except ImportError: - from PyQt4 import QtCore +from PyQt5 import QtCore, QtGui, QtWidgets from notifications import * from settings import Settings from profile import Profile @@ -12,6 +9,8 @@ from plugin_support import PluginLoader import queue import threading import util +import cv2 +import numpy as np # ----------------------------------------------------------------------------------------------------------------- @@ -225,7 +224,7 @@ def tox_file_recv(window, tray): if not window.isActiveWindow(): friend = profile.get_friend_by_number(friend_number) if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: - file_from = QtGui.QApplication.translate("Callback", "File from", None, QtGui.QApplication.UnicodeUTF8) + file_from = QtWidgets.QApplication.translate("Callback", "File from") invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window) if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER']) @@ -327,6 +326,9 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data): pass + #frame = cv2.merge((np.asarray(y), np.asarray(u), np.asarray(v))) + #frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) + #cv2.imshow("frame", frame) # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization diff --git a/toxygen/calls.py b/toxygen/calls.py index 02272be..f2600c9 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -4,6 +4,8 @@ import threading import settings from toxav_enums import * import cv2 +import itertools +import numpy as np # TODO: play sound until outgoing call will be started or cancelled and add timeout @@ -55,6 +57,7 @@ class AV: self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0) self._calls[friend_number] = Call(audio, video) self.start_audio_thread() + self.start_video_thread() def accept_call(self, friend_number, audio_enabled, video_enabled): @@ -133,9 +136,9 @@ class AV: self._video_running = True self._video = cv2.VideoCapture(0) - self._video.set(cv2.CV_CAP_PROP_FPS, 25) - self._video.set(cv2.CV_CAP_PROP_FRAME_WIDTH, 640) - self._video.set(cv2.CV_CAP_PROP_FRAME_HEIGHT, 480) + self._video.set(cv2.CAP_PROP_FPS, 25) + self._video.set(cv2.CAP_PROP_FRAME_WIDTH, 640) + self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) self._video_thread = threading.Thread(target=self.send_video) self._video_thread.start() @@ -206,14 +209,23 @@ class AV: y, u, v = convert_bgr_to_yuv(frame) self._toxav.video_send_frame(friend_num, width, height, y, u, v) except Exception as e: - print(e) + print('1', e) except Exception as e: - print(e) + print('2', e) time.sleep(0.01) def convert_bgr_to_yuv(frame): - print(frame.tostring()) - print(dir(frame)) - return 0, 0, 0 + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) + y = frame[:480,:].tolist() + y = list(itertools.chain.from_iterable(y)) + v = np.zeros((240, 320), dtype=np.int) + v[::2,:] = frame[480:600, :320] + v[1::2,:] = frame[480:600, 320:] + v = list(itertools.chain.from_iterable(v)) + u = np.zeros((240, 320), dtype=np.int) + u[::2,:] = frame[600:, :320] + u[1::2,:] = frame[600:, 320:] + u = list(itertools.chain.from_iterable(u)) + return bytes(y), bytes(v), bytes(u) diff --git a/toxygen/contact.py b/toxygen/contact.py index 1b69b9e..4475e53 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -1,7 +1,4 @@ -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui from history import * import basecontact import util diff --git a/toxygen/file_transfers.py b/toxygen/file_transfers.py index 4a9723a..7e0b193 100644 --- a/toxygen/file_transfers.py +++ b/toxygen/file_transfers.py @@ -4,11 +4,7 @@ from os import remove, rename, chdir from time import time, sleep from tox import Tox import settings -try: - from PySide import QtCore -except ImportError: - from PyQt4 import QtCore - QtCore.Signal = QtCore.pyqtSignal +from PyQt5 import QtCore TOX_FILE_TRANSFER_STATE = { @@ -38,12 +34,12 @@ def is_inline(file_name): class StateSignal(QtCore.QObject): - signal = QtCore.Signal(int, float, int) # state, progress, time in sec + signal = QtCore.pyqtSignal(int, float, int) # state, progress, time in sec class TransferFinishedSignal(QtCore.QObject): - signal = QtCore.Signal(int, int) # friend number, file number + signal = QtCore.pyqtSignal(int, int) # friend number, file number class FileTransfer(QtCore.QObject): diff --git a/toxygen/items_factory.py b/toxygen/items_factory.py index 80986c3..44a00ad 100644 --- a/toxygen/items_factory.py +++ b/toxygen/items_factory.py @@ -1,7 +1,4 @@ -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtWidgets, QtCore from list_items import * @@ -13,7 +10,7 @@ class ItemsFactory: def friend_item(self): item = ContactItem() - elem = QtGui.QListWidgetItem(self._friends) + elem = QtWidgets.QListWidgetItem(self._friends) elem.setSizeHint(QtCore.QSize(250, item.height())) self._friends.addItem(elem) self._friends.setItemWidget(elem, item) @@ -23,7 +20,7 @@ class ItemsFactory: item = MessageItem(text, time, name, sent, message_type, self._messages) if pixmap is not None: item.set_avatar(pixmap) - elem = QtGui.QListWidgetItem() + elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) if append: self._messages.addItem(elem) @@ -33,7 +30,7 @@ class ItemsFactory: return item def inline_item(self, data, append): - elem = QtGui.QListWidgetItem() + elem = QtWidgets.QListWidgetItem() item = InlineImageItem(data, self._messages.width(), elem) elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) if append: @@ -49,7 +46,7 @@ class ItemsFactory: name, time, self._messages.width()) - elem = QtGui.QListWidgetItem() + elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) if append: self._messages.addItem(elem) @@ -61,7 +58,7 @@ class ItemsFactory: def file_transfer_item(self, data, append): data.append(self._messages.width()) item = FileTransferItem(*data) - elem = QtGui.QListWidgetItem() + elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) if append: self._messages.addItem(elem) diff --git a/toxygen/list_items.py b/toxygen/list_items.py index ab8d0b4..9ef4e84 100644 --- a/toxygen/list_items.py +++ b/toxygen/list_items.py @@ -1,9 +1,5 @@ from toxcore_enums_and_consts import * -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui - QtCore.Slot = QtCore.pyqtSlot +from PyQt5 import QtCore, QtGui, QtWidgets import profile from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR from util import curr_directory, convert_time, curr_time @@ -14,7 +10,7 @@ import settings import re -class MessageEdit(QtGui.QTextBrowser): +class MessageEdit(QtWidgets.QTextBrowser): def __init__(self, text, width, message_type, parent=None): super(MessageEdit, self).__init__(parent) @@ -46,7 +42,7 @@ class MessageEdit(QtGui.QTextBrowser): def contextMenuEvent(self, event): menu = create_menu(self.createStandardContextMenu(event.pos())) - quote = menu.addAction(QtGui.QApplication.translate("MainWindow", 'Quote selected text', None, QtGui.QApplication.UnicodeUTF8)) + quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text')) quote.triggered.connect(self.quote_text) text = self.textCursor().selection().toPlainText() if not text: @@ -55,7 +51,7 @@ class MessageEdit(QtGui.QTextBrowser): import plugin_support submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text) if len(submenu): - plug = menu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8)) + plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) plug.addActions(submenu) menu.popup(event.globalPos()) menu.exec_(event.globalPos()) @@ -123,12 +119,12 @@ class MessageEdit(QtGui.QTextBrowser): return text -class MessageItem(QtGui.QWidget): +class MessageItem(QtWidgets.QWidget): """ Message in messages list """ def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None): - QtGui.QWidget.__init__(self, parent) + QtWidgets.QWidget.__init__(self, parent) self.name = DataLabel(self) self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) self.name.setTextFormat(QtCore.Qt.PlainText) @@ -139,7 +135,7 @@ class MessageItem(QtGui.QWidget): self.name.setFont(font) self.name.setText(user) - self.time = QtGui.QLabel(self) + self.time = QtWidgets.QLabel(self) self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) font.setPointSize(10) font.setBold(False) @@ -164,9 +160,9 @@ class MessageItem(QtGui.QWidget): def mouseReleaseEvent(self, event): if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x(): - self.listMenu = QtGui.QMenu() - delete_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Delete message', None, QtGui.QApplication.UnicodeUTF8)) - self.connect(delete_item, QtCore.SIGNAL("triggered()"), self.delete) + self.listMenu = QtWidgets.QMenu() + delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message')) + delete_item.triggered.connect(self.delete) parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position) self.listMenu.show() @@ -216,16 +212,16 @@ class MessageItem(QtGui.QWidget): return text -class ContactItem(QtGui.QWidget): +class ContactItem(QtWidgets.QWidget): """ Contact in friends list """ def __init__(self, parent=None): - QtGui.QWidget.__init__(self, parent) + QtWidgets.QWidget.__init__(self, parent) mode = settings.Settings.get_instance()['compact_mode'] self.setBaseSize(QtCore.QSize(250, 40 if mode else 70)) - self.avatar_label = QtGui.QLabel(self) + self.avatar_label = QtWidgets.QLabel(self) size = 32 if mode else 64 self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size)) self.avatar_label.setScaledContents(False) @@ -248,14 +244,14 @@ class ContactItem(QtGui.QWidget): self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20)) -class StatusCircle(QtGui.QWidget): +class StatusCircle(QtWidgets.QWidget): """ Connection status """ def __init__(self, parent): - QtGui.QWidget.__init__(self, parent) + QtWidgets.QWidget.__init__(self, parent) self.setGeometry(0, 0, 32, 32) - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) self.unread = False @@ -281,12 +277,12 @@ class StatusCircle(QtGui.QWidget): self.label.setPixmap(pixmap) -class UnreadMessagesCount(QtGui.QWidget): +class UnreadMessagesCount(QtWidgets.QWidget): def __init__(self, parent=None): super(UnreadMessagesCount, self).__init__(parent) self.resize(30, 20) - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(0, 0, 30, 20)) self.label.setVisible(False) font = QtGui.QFont() @@ -308,11 +304,11 @@ class UnreadMessagesCount(QtGui.QWidget): self.label.setVisible(False) -class FileTransferItem(QtGui.QListWidget): +class FileTransferItem(QtWidgets.QListWidget): def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): - QtGui.QListWidget.__init__(self, parent) + QtWidgets.QListWidget.__init__(self, parent) self.resize(QtCore.QSize(width, 34)) if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') @@ -332,14 +328,14 @@ class FileTransferItem(QtGui.QListWidget): self.name.setFont(font) self.name.setText(user) - self.time = QtGui.QLabel(self) + self.time = QtWidgets.QLabel(self) self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) font.setPointSize(10) font.setBold(False) self.time.setFont(font) self.time.setText(convert_time(time)) - self.cancel = QtGui.QPushButton(self) + self.cancel = QtWidgets.QPushButton(self) self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png') icon = QtGui.QIcon(pixmap) @@ -349,7 +345,7 @@ class FileTransferItem(QtGui.QListWidget): self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') - self.accept_or_pause = QtGui.QPushButton(self) + self.accept_or_pause = QtWidgets.QPushButton(self) self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: self.accept_or_pause.setVisible(True) @@ -366,7 +362,7 @@ class FileTransferItem(QtGui.QListWidget): self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') - self.pb = QtGui.QProgressBar(self) + self.pb = QtWidgets.QProgressBar(self) self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) self.pb.setValue(0) self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') @@ -387,7 +383,7 @@ class FileTransferItem(QtGui.QListWidget): self.file_name.setText(file_data) self.file_name.setToolTip(file_name) self.saved_name = file_name - self.time_left = QtGui.QLabel(self) + self.time_left = QtWidgets.QLabel(self) self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) font.setPointSize(10) self.time_left.setFont(font) @@ -405,10 +401,10 @@ class FileTransferItem(QtGui.QListWidget): def accept_or_pause_transfer(self, friend_number, file_number, size): if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - directory = QtGui.QFileDialog.getExistingDirectory(self, - QtGui.QApplication.translate("MainWindow", 'Choose folder', None, QtGui.QApplication.UnicodeUTF8), + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), curr_directory(), - QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) self.pb.setVisible(True) if directory: pr = profile.Profile.get_instance() @@ -432,8 +428,7 @@ class FileTransferItem(QtGui.QListWidget): self.accept_or_pause.setIcon(icon) self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) - @QtCore.Slot(int, float, int) - def update(self, state, progress, time): + def update_transfer_state(self, state, progress, time): self.pb.setValue(int(progress * 100)) if time + 1: m, s = divmod(time, 60) @@ -496,14 +491,14 @@ class UnsentFileItem(FileTransferItem): pr.cancel_not_started_transfer(self._time) -class InlineImageItem(QtGui.QScrollArea): +class InlineImageItem(QtWidgets.QScrollArea): def __init__(self, data, width, elem): - QtGui.QScrollArea.__init__(self) + QtWidgets.QScrollArea.__init__(self) self.setFocusPolicy(QtCore.Qt.NoFocus) self._elem = elem - self._image_label = QtGui.QLabel(self) + self._image_label = QtWidgets.QLabel(self) self._image_label.raise_() self.setWidget(self._image_label) self._image_label.setScaledContents(False) @@ -537,12 +532,11 @@ class InlineImageItem(QtGui.QScrollArea): self._full_size = not self._full_size self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) elif event.button() == QtCore.Qt.RightButton: # save inline - directory = QtGui.QFileDialog.getExistingDirectory(self, - QtGui.QApplication.translate("MainWindow", - 'Choose folder', None, - QtGui.QApplication.UnicodeUTF8), + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", + 'Choose folder'), curr_directory(), - QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) if directory: fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') self._pixmap.save(fl, 'PNG') diff --git a/toxygen/loginscreen.py b/toxygen/loginscreen.py index b6d0811..77aa5ba 100644 --- a/toxygen/loginscreen.py +++ b/toxygen/loginscreen.py @@ -1,9 +1,4 @@ -# -*- coding: utf-8 -*- - -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtWidgets, QtCore from widgets import * @@ -31,25 +26,25 @@ class LoginScreen(CenteredWidget): self.resize(400, 200) self.setMinimumSize(QtCore.QSize(400, 200)) self.setMaximumSize(QtCore.QSize(400, 200)) - self.new_profile = QtGui.QPushButton(self) + self.new_profile = QtWidgets.QPushButton(self) self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27)) self.new_profile.clicked.connect(self.create_profile) - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(20, 70, 101, 17)) self.new_name = NickEdit(self) self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31)) - self.load_profile = QtGui.QPushButton(self) + self.load_profile = QtWidgets.QPushButton(self) self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27)) self.load_profile.clicked.connect(self.load_ex_profile) - self.default = QtGui.QCheckBox(self) + self.default = QtWidgets.QCheckBox(self) self.default.setGeometry(QtCore.QRect(220, 110, 131, 22)) - self.groupBox = QtGui.QGroupBox(self) + self.groupBox = QtWidgets.QGroupBox(self) self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151)) - self.comboBox = QtGui.QComboBox(self.groupBox) + self.comboBox = QtWidgets.QComboBox(self.groupBox) self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27)) - self.groupBox_2 = QtGui.QGroupBox(self) + self.groupBox_2 = QtWidgets.QGroupBox(self) self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151)) - self.toxygen = QtGui.QLabel(self) + self.toxygen = QtWidgets.QLabel(self) self.groupBox.raise_() self.groupBox_2.raise_() self.comboBox.raise_() @@ -71,15 +66,15 @@ class LoginScreen(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.new_name.setPlaceholderText(QtGui.QApplication.translate("login", "Profile name", None, QtGui.QApplication.UnicodeUTF8)) - self.setWindowTitle(QtGui.QApplication.translate("login", "Log in", None, QtGui.QApplication.UnicodeUTF8)) - self.new_profile.setText(QtGui.QApplication.translate("login", "Create", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("login", "Profile name:", None, QtGui.QApplication.UnicodeUTF8)) - self.load_profile.setText(QtGui.QApplication.translate("login", "Load profile", None, QtGui.QApplication.UnicodeUTF8)) - self.default.setText(QtGui.QApplication.translate("login", "Use as default", None, QtGui.QApplication.UnicodeUTF8)) - self.groupBox.setTitle(QtGui.QApplication.translate("login", "Load existing profile", None, QtGui.QApplication.UnicodeUTF8)) - self.groupBox_2.setTitle(QtGui.QApplication.translate("login", "Create new profile", None, QtGui.QApplication.UnicodeUTF8)) - self.toxygen.setText(QtGui.QApplication.translate("login", "toxygen", None, QtGui.QApplication.UnicodeUTF8)) + self.new_name.setPlaceholderText(QtWidgets.QApplication.translate("login", "Profile name")) + self.setWindowTitle(QtWidgets.QApplication.translate("login", "Log in")) + self.new_profile.setText(QtWidgets.QApplication.translate("login", "Create")) + self.label.setText(QtWidgets.QApplication.translate("login", "Profile name:")) + self.load_profile.setText(QtWidgets.QApplication.translate("login", "Load profile")) + self.default.setText(QtWidgets.QApplication.translate("login", "Use as default")) + self.groupBox.setTitle(QtWidgets.QApplication.translate("login", "Load existing profile")) + self.groupBox_2.setTitle(QtWidgets.QApplication.translate("login", "Create new profile")) + self.toxygen.setText(QtWidgets.QApplication.translate("login", "toxygen")) def create_profile(self): self.type = 1 diff --git a/toxygen/main.py b/toxygen/main.py index 8c51fd2..b1dffb5 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -2,10 +2,7 @@ import sys from loginscreen import LoginScreen import profile from settings import * -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets from bootstrap import node_generator from mainscreen import MainWindow from callbacks import init_callbacks, stop, start @@ -39,7 +36,7 @@ class Toxygen: tmp = [data] p = PasswordScreen(toxes.ToxES.get_instance(), tmp) p.show() - self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()")) + self.app.lastWindowClosed.connect(self.app.quit) self.app.exec_() if tmp[0] == data: raise SystemExit() @@ -50,7 +47,7 @@ class Toxygen: """ Main function of app. loads login screen if needed and starts main screen """ - app = QtGui.QApplication(sys.argv) + app = QtWidgets.QApplication(sys.argv) app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) self.app = app @@ -92,7 +89,6 @@ class Toxygen: _login = self.Login(profiles) ls.update_on_close(_login.login_screen_close) ls.show() - app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()")) app.exec_() if not _login.t: return @@ -101,40 +97,35 @@ class Toxygen: name = _login.name if _login.name else 'toxygen_user' pr = map(lambda x: x[1], ProfileHelper.find_profiles()) if name in list(pr): - msgBox = QtGui.QMessageBox() + msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtGui.QApplication.translate("MainWindow", "Error", None, QtGui.QApplication.UnicodeUTF8)) - text = (QtGui.QApplication.translate("MainWindow", - 'Profile with this name already exists', - None, QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("MainWindow", "Error")) + text = (QtWidgets.QApplication.translate("MainWindow", + 'Profile with this name already exists')) msgBox.setText(text) msgBox.exec_() return self.tox = profile.tox_factory() self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') self.tox.self_set_status_message(b'Toxing on Toxygen') - reply = QtGui.QMessageBox.question(None, + reply = QtWidgets.QMessageBox.question(None, 'Profile {}'.format(name), - QtGui.QApplication.translate("login", - 'Do you want to set profile password?', - None, - QtGui.QApplication.UnicodeUTF8), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: + QtWidgets.QApplication.translate("login", + 'Do you want to set profile password?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: set_pass = SetProfilePasswordScreen(encrypt_save) set_pass.show() - self.app.connect(self.app, QtCore.SIGNAL("lastWindowClosed()"), self.app, QtCore.SLOT("quit()")) + self.app.lastWindowClosed.connect(self.app.quit) self.app.exec_() - reply = QtGui.QMessageBox.question(None, + reply = QtWidgets.QMessageBox.question(None, 'Profile {}'.format(name), - QtGui.QApplication.translate("login", - 'Do you want to save profile in default folder? If no, profile will be saved in program folder', - None, - QtGui.QApplication.UnicodeUTF8), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: + QtWidgets.QApplication.translate("login", + 'Do you want to save profile in default folder? If no, profile will be saved in program folder'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: path = Settings.get_default_path() else: path = curr_directory() + '/' @@ -143,11 +134,9 @@ class Toxygen: except Exception as ex: print(str(ex)) log('Profile creation exception: ' + str(ex)) - msgBox = QtGui.QMessageBox() - msgBox.setText(QtGui.QApplication.translate("login", - 'Profile saving error! Does Toxygen have permission to write to this directory?', - None, - QtGui.QApplication.UnicodeUTF8)) + msgBox = QtWidgets.QMessageBox() + msgBox.setText(QtWidgets.QApplication.translate("login", + 'Profile saving error! Does Toxygen have permission to write to this directory?')) msgBox.exec_() return path = Settings.get_default_path() @@ -173,12 +162,12 @@ class Toxygen: self.tox = profile.tox_factory(data, settings) if Settings.is_active_profile(path, name): # profile is in use - reply = QtGui.QMessageBox.question(None, + reply = QtWidgets.QMessageBox.question(None, 'Profile {}'.format(name), - QtGui.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?', None, QtGui.QApplication.UnicodeUTF8), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) - if reply != QtGui.QMessageBox.Yes: + QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply != QtWidgets.QMessageBox.Yes: return else: settings.set_active_profile() @@ -190,21 +179,21 @@ class Toxygen: app.translator = translator # tray icon - self.tray = QtGui.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) + self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) self.tray.setObjectName('tray') self.ms = MainWindow(self.tox, self.reset, self.tray) app.aboutToQuit.connect(self.ms.close_window) - class Menu(QtGui.QMenu): + class Menu(QtWidgets.QMenu): def newStatus(self, status): if not Settings.get_instance().locked: profile.Profile.get_instance().set_status(status) - self.aboutToShow() + self.aboutToShowHandler() self.hide() - def aboutToShow(self): + def aboutToShowHandler(self): status = profile.Profile.get_instance().status act = self.act if status is None or Settings.get_instance().locked: @@ -218,24 +207,24 @@ class Toxygen: self.actions()[2].setVisible(not Settings.get_instance().locked) def languageChange(self, *args, **kwargs): - self.actions()[0].setText(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8)) - self.actions()[1].setText(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8)) - self.actions()[2].setText(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8)) - self.act.actions()[0].setText(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8)) - self.act.actions()[1].setText(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8)) - self.act.actions()[2].setText(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8)) + self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) + self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status')) + self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit')) + self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online')) + self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away')) + self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy')) m = Menu() - show = m.addAction(QtGui.QApplication.translate('tray', 'Open Toxygen', None, QtGui.QApplication.UnicodeUTF8)) - sub = m.addMenu(QtGui.QApplication.translate('tray', 'Set status', None, QtGui.QApplication.UnicodeUTF8)) - onl = sub.addAction(QtGui.QApplication.translate('tray', 'Online', None, QtGui.QApplication.UnicodeUTF8)) - away = sub.addAction(QtGui.QApplication.translate('tray', 'Away', None, QtGui.QApplication.UnicodeUTF8)) - busy = sub.addAction(QtGui.QApplication.translate('tray', 'Busy', None, QtGui.QApplication.UnicodeUTF8)) + show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) + sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status')) + onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online')) + away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away')) + busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy')) onl.setCheckable(True) away.setCheckable(True) busy.setCheckable(True) m.act = sub - exit = m.addAction(QtGui.QApplication.translate('tray', 'Exit', None, QtGui.QApplication.UnicodeUTF8)) + exit = m.addAction(QtWidgets.QApplication.translate('tray', 'Exit')) def show_window(): s = Settings.get_instance() @@ -258,7 +247,7 @@ class Toxygen: self.p.show() def tray_activated(reason): - if reason == QtGui.QSystemTrayIcon.DoubleClick: + if reason == QtWidgets.QSystemTrayIcon.DoubleClick: show_window() def close_app(): @@ -266,12 +255,12 @@ class Toxygen: settings.closing = True self.ms.close() - m.connect(show, QtCore.SIGNAL("triggered()"), show_window) - m.connect(exit, QtCore.SIGNAL("triggered()"), close_app) - m.connect(m, QtCore.SIGNAL("aboutToShow()"), lambda: m.aboutToShow()) - sub.connect(onl, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(0)) - sub.connect(away, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(1)) - sub.connect(busy, QtCore.SIGNAL("triggered()"), lambda: m.newStatus(2)) + show.triggered.connect(show_window) + exit.triggered.connect(close_app) + m.aboutToShow.connect(lambda: m.aboutToShowHandler()) + onl.triggered.connect(lambda: m.newStatus(0)) + away.triggered.connect(lambda: m.newStatus(1)) + busy.triggered.connect(lambda: m.newStatus(2)) self.tray.setContextMenu(m) self.tray.show() @@ -287,15 +276,13 @@ class Toxygen: updater.download(version) updating = True else: - reply = QtGui.QMessageBox.question(None, + reply = QtWidgets.QMessageBox.question(None, 'Toxygen', - QtGui.QApplication.translate("login", - 'Update for Toxygen was found. Download and install it?', - None, - QtGui.QApplication.UnicodeUTF8), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: + QtWidgets.QApplication.translate("login", + 'Update for Toxygen was found. Download and install it?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: updater.download(version) updating = True @@ -323,7 +310,7 @@ class Toxygen: if self.uri is not None: self.ms.add_contact(self.uri) - app.connect(app, QtCore.SIGNAL("lastWindowClosed()"), app, QtCore.SLOT("quit()")) + app.lastWindowClosed.connect(app.quit) app.exec_() self.init.stop = True diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index cf52814..9847d01 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -1,7 +1,7 @@ from menu import * from profile import * from list_items import * -from widgets import MultilineEdit, LineEdit, ComboBox +from widgets import MultilineEdit, ComboBox import plugin_support from mainscreen_widgets import * import settings @@ -9,7 +9,7 @@ import platform import toxes -class MainWindow(QtGui.QMainWindow, Singleton): +class MainWindow(QtWidgets.QMainWindow, Singleton): def __init__(self, tox, reset, tray): super().__init__() @@ -23,7 +23,7 @@ class MainWindow(QtGui.QMainWindow, Singleton): self.ws = WelcomeScreen() def setup_menu(self, Form): - box = QtGui.QHBoxLayout() + box = QtWidgets.QHBoxLayout() box.setContentsMargins(0, 0, 0, 0) box.setAlignment(QtCore.Qt.AlignLeft) self.profile_button = MainMenuButton(Form) @@ -36,37 +36,37 @@ class MainWindow(QtGui.QMainWindow, Singleton): box.addWidget(self.about_button) box.setSpacing(0) - self.menuProfile = QtGui.QMenu() + self.menuProfile = QtWidgets.QMenu() self.menuProfile.setObjectName("menuProfile") - self.menuSettings = QtGui.QMenu() + self.menuSettings = QtWidgets.QMenu() self.menuSettings.setObjectName("menuSettings") - self.menuPlugins = QtGui.QMenu() + self.menuPlugins = QtWidgets.QMenu() self.menuPlugins.setObjectName("menuPlugins") - self.menuAbout = QtGui.QMenu() + self.menuAbout = QtWidgets.QMenu() self.menuAbout.setObjectName("menuAbout") - self.actionAdd_friend = QtGui.QAction(Form) + self.actionAdd_friend = QtWidgets.QAction(Form) self.actionAdd_friend.setObjectName("actionAdd_friend") - self.actionprofilesettings = QtGui.QAction(Form) + self.actionprofilesettings = QtWidgets.QAction(Form) self.actionprofilesettings.setObjectName("actionprofilesettings") - self.actionPrivacy_settings = QtGui.QAction(Form) + self.actionPrivacy_settings = QtWidgets.QAction(Form) self.actionPrivacy_settings.setObjectName("actionPrivacy_settings") - self.actionInterface_settings = QtGui.QAction(Form) + self.actionInterface_settings = QtWidgets.QAction(Form) self.actionInterface_settings.setObjectName("actionInterface_settings") - self.actionNotifications = QtGui.QAction(Form) + self.actionNotifications = QtWidgets.QAction(Form) self.actionNotifications.setObjectName("actionNotifications") - self.actionNetwork = QtGui.QAction(Form) + self.actionNetwork = QtWidgets.QAction(Form) self.actionNetwork.setObjectName("actionNetwork") - self.actionAbout_program = QtGui.QAction(Form) + self.actionAbout_program = QtWidgets.QAction(Form) self.actionAbout_program.setObjectName("actionAbout_program") - self.updateSettings = QtGui.QAction(Form) - self.actionSettings = QtGui.QAction(Form) + self.updateSettings = QtWidgets.QAction(Form) + self.actionSettings = QtWidgets.QAction(Form) self.actionSettings.setObjectName("actionSettings") - self.audioSettings = QtGui.QAction(Form) - self.pluginData = QtGui.QAction(Form) - self.importPlugin = QtGui.QAction(Form) - self.reloadPlugins = QtGui.QAction(Form) - self.lockApp = QtGui.QAction(Form) + self.audioSettings = QtWidgets.QAction(Form) + self.pluginData = QtWidgets.QAction(Form) + self.importPlugin = QtWidgets.QAction(Form) + self.reloadPlugins = QtWidgets.QAction(Form) + self.lockApp = QtWidgets.QAction(Form) self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.lockApp) @@ -113,37 +113,37 @@ class MainWindow(QtGui.QMainWindow, Singleton): return super(MainWindow, self).event(event) def retranslateUi(self): - self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8)) - self.plugins_button.setText(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8)) - self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8)) - self.profile_button.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8)) - self.settings_button.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) - self.about_button.setText(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) - self.actionAdd_friend.setText(QtGui.QApplication.translate("MainWindow", "Add contact", None, QtGui.QApplication.UnicodeUTF8)) - self.actionprofilesettings.setText(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8)) - self.actionPrivacy_settings.setText(QtGui.QApplication.translate("MainWindow", "Privacy", None, QtGui.QApplication.UnicodeUTF8)) - self.actionInterface_settings.setText(QtGui.QApplication.translate("MainWindow", "Interface", None, QtGui.QApplication.UnicodeUTF8)) - self.actionNotifications.setText(QtGui.QApplication.translate("MainWindow", "Notifications", None, QtGui.QApplication.UnicodeUTF8)) - self.actionNetwork.setText(QtGui.QApplication.translate("MainWindow", "Network", None, QtGui.QApplication.UnicodeUTF8)) - self.actionAbout_program.setText(QtGui.QApplication.translate("MainWindow", "About program", None, QtGui.QApplication.UnicodeUTF8)) - self.actionSettings.setText(QtGui.QApplication.translate("MainWindow", "Settings", None, QtGui.QApplication.UnicodeUTF8)) - self.audioSettings.setText(QtGui.QApplication.translate("MainWindow", "Audio", None, QtGui.QApplication.UnicodeUTF8)) - self.updateSettings.setText(QtGui.QApplication.translate("MainWindow", "Updates", None, QtGui.QApplication.UnicodeUTF8)) - self.contact_name.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, QtGui.QApplication.UnicodeUTF8)) - self.sendMessageButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Send message", None, QtGui.QApplication.UnicodeUTF8)) - self.callButton.setToolTip(QtGui.QApplication.translate("MainWindow", "Start audio call with friend", None, QtGui.QApplication.UnicodeUTF8)) + self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock")) + self.plugins_button.setText(QtWidgets.QApplication.translate("MainWindow", "Plugins")) + self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins")) + self.profile_button.setText(QtWidgets.QApplication.translate("MainWindow", "Profile")) + self.settings_button.setText(QtWidgets.QApplication.translate("MainWindow", "Settings")) + self.about_button.setText(QtWidgets.QApplication.translate("MainWindow", "About")) + self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact")) + self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile")) + self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy")) + self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface")) + self.actionNotifications.setText(QtWidgets.QApplication.translate("MainWindow", "Notifications")) + self.actionNetwork.setText(QtWidgets.QApplication.translate("MainWindow", "Network")) + self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program")) + self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings")) + self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio")) + self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates")) + self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) + self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message")) + self.callButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Start audio call with friend")) self.online_contacts.clear() - self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "All", None, QtGui.QApplication.UnicodeUTF8)) - self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online", None, QtGui.QApplication.UnicodeUTF8)) - self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first", None, QtGui.QApplication.UnicodeUTF8)) - self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Name", None, QtGui.QApplication.UnicodeUTF8)) - self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online and by name", None, QtGui.QApplication.UnicodeUTF8)) - self.online_contacts.addItem(QtGui.QApplication.translate("MainWindow", "Online first and by name", None, QtGui.QApplication.UnicodeUTF8)) + self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "All")) + self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online")) + self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first")) + self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Name")) + self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online and by name")) + self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first and by name")) ind = Settings.get_instance()['sorting'] d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5} self.online_contacts.setCurrentIndex(d[ind]) - self.importPlugin.setText(QtGui.QApplication.translate("MainWindow", "Import plugin", None, QtGui.QApplication.UnicodeUTF8)) - self.reloadPlugins.setText(QtGui.QApplication.translate("MainWindow", "Reload plugins", None, QtGui.QApplication.UnicodeUTF8)) + self.importPlugin.setText(QtWidgets.QApplication.translate("MainWindow", "Import plugin")) + self.reloadPlugins.setText(QtWidgets.QApplication.translate("MainWindow", "Reload plugins")) def setup_right_bottom(self, Form): Form.resize(650, 60) @@ -155,7 +155,7 @@ class MainWindow(QtGui.QMainWindow, Singleton): font.setFamily(settings.Settings.get_instance()['font']) self.messageEdit.setFont(font) - self.sendMessageButton = QtGui.QPushButton(Form) + self.sendMessageButton = QtWidgets.QPushButton(Form) self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55)) self.sendMessageButton.setObjectName("sendMessageButton") @@ -178,7 +178,7 @@ class MainWindow(QtGui.QMainWindow, Singleton): def setup_left_center_menu(self, Form): Form.resize(270, 25) - self.search_label = QtGui.QLabel(Form) + self.search_label = QtWidgets.QLabel(Form) self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20)) pixmap = QtGui.QPixmap() pixmap.load(curr_directory() + '/images/search.png') @@ -202,7 +202,7 @@ class MainWindow(QtGui.QMainWindow, Singleton): Form.setMinimumSize(QtCore.QSize(270, 75)) Form.setMaximumSize(QtCore.QSize(270, 75)) Form.setBaseSize(QtCore.QSize(270, 75)) - self.avatar_label = Form.avatar_label = QtGui.QLabel(Form) + self.avatar_label = Form.avatar_label = QtWidgets.QLabel(Form) self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64)) self.avatar_label.setScaledContents(False) self.avatar_label.setAlignment(QtCore.Qt.AlignCenter) @@ -230,7 +230,7 @@ class MainWindow(QtGui.QMainWindow, Singleton): def setup_right_top(self, Form): Form.resize(650, 75) - self.account_avatar = QtGui.QLabel(Form) + self.account_avatar = QtWidgets.QLabel(Form) self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64)) self.account_avatar.setScaledContents(False) self.account_name = DataLabel(Form) @@ -249,16 +249,16 @@ class MainWindow(QtGui.QMainWindow, Singleton): font.setBold(False) self.account_status.setFont(font) self.account_status.setObjectName("account_status") - self.callButton = QtGui.QPushButton(Form) + self.callButton = QtWidgets.QPushButton(Form) self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) self.callButton.setObjectName("callButton") self.callButton.clicked.connect(lambda: self.profile.call_click(True)) - self.videocallButton = QtGui.QPushButton(Form) + self.videocallButton = QtWidgets.QPushButton(Form) self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) self.videocallButton.setObjectName("videocallButton") self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True)) self.update_call_state('call') - self.typing = QtGui.QLabel(Form) + self.typing = QtWidgets.QLabel(Form) self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30)) pixmap = QtGui.QPixmap(QtCore.QSize(50, 30)) pixmap.load(curr_directory() + '/images/typing.png') @@ -268,20 +268,19 @@ class MainWindow(QtGui.QMainWindow, Singleton): QtCore.QMetaObject.connectSlotsByName(Form) def setup_left_center(self, widget): - self.friends_list = QtGui.QListWidget(widget) + self.friends_list = QtWidgets.QListWidget(widget) self.friends_list.setObjectName("friends_list") self.friends_list.setGeometry(0, 0, 270, 310) self.friends_list.clicked.connect(self.friend_click) self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.friends_list.connect(self.friends_list, QtCore.SIGNAL("customContextMenuRequested(QPoint)"), - self.friend_right_click) - self.friends_list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) + self.friends_list.customContextMenuRequested.connect(self.friend_right_click) + self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu) def setup_right_center(self, widget): - self.messages = QtGui.QListWidget(widget) + self.messages = QtWidgets.QListWidget(widget) self.messages.setGeometry(0, 0, 620, 310) self.messages.setObjectName("messages") self.messages.setSpacing(1) @@ -295,8 +294,8 @@ class MainWindow(QtGui.QMainWindow, Singleton): self.profile.load_history() self.messages.verticalScrollBar().setValue(1) self.messages.verticalScrollBar().valueChanged.connect(load) - self.messages.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) - self.messages.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection) + self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) def initUI(self, tox): self.setMinimumSize(920, 500) @@ -304,15 +303,15 @@ class MainWindow(QtGui.QMainWindow, Singleton): self.setGeometry(s['x'], s['y'], s['width'], s['height']) self.setWindowTitle('Toxygen') os.chdir(curr_directory() + '/images/') - menu = QtGui.QWidget() - main = QtGui.QWidget() - grid = QtGui.QGridLayout() - search = QtGui.QWidget() - name = QtGui.QWidget() - info = QtGui.QWidget() - main_list = QtGui.QWidget() - messages = QtGui.QWidget() - message_buttons = QtGui.QWidget() + menu = QtWidgets.QWidget() + main = QtWidgets.QWidget() + grid = QtWidgets.QGridLayout() + search = QtWidgets.QWidget() + name = QtWidgets.QWidget() + info = QtWidgets.QWidget() + main_list = QtWidgets.QWidget() + messages = QtWidgets.QWidget() + message_buttons = QtWidgets.QWidget() self.setup_left_center_menu(search) self.setup_left_top(name) self.setup_right_center(messages) @@ -369,9 +368,9 @@ class MainWindow(QtGui.QMainWindow, Singleton): s['width'] = self.width() s['height'] = self.height() s.save() - QtGui.QApplication.closeAllWindows() + QtWidgets.QApplication.closeAllWindows() event.accept() - elif QtGui.QSystemTrayIcon.isSystemTrayAvailable(): + elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): event.ignore() self.hide() @@ -401,13 +400,13 @@ class MainWindow(QtGui.QMainWindow, Singleton): self.profile.update() def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Escape and QtGui.QSystemTrayIcon.isSystemTrayAvailable(): + if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): self.hide() elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems())) indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count()) s = self.profile.export_history(self.profile.active_friend, True, indexes) - clipboard = QtGui.QApplication.clipboard() + clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(s) elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): self.messages.clearSelection() @@ -422,9 +421,9 @@ class MainWindow(QtGui.QMainWindow, Singleton): def about_program(self): import util - msgBox = QtGui.QMessageBox() - msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "About", None, QtGui.QApplication.UnicodeUTF8)) - text = (QtGui.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ', None, QtGui.QApplication.UnicodeUTF8)) + msgBox = QtWidgets.QMessageBox() + msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About")) + text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ')) msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/') msgBox.exec_() @@ -471,22 +470,19 @@ class MainWindow(QtGui.QMainWindow, Singleton): def import_plugin(self): import util - directory = QtGui.QFileDialog.getExistingDirectory(self, - QtGui.QApplication.translate("MainWindow", 'Choose folder with plugin', - None, - QtGui.QApplication.UnicodeUTF8), + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'), util.curr_directory(), - QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) if directory: src = directory + '/' dest = curr_directory() + '/plugins/' util.copy(src, dest) - msgBox = QtGui.QMessageBox() + msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtGui.QApplication.translate("MainWindow", "Restart Toxygen", None, QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen")) msgBox.setText( - QtGui.QApplication.translate("MainWindow", 'Plugin will be loaded after restart', None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart')) msgBox.exec_() def lock_app(self): @@ -494,12 +490,11 @@ class MainWindow(QtGui.QMainWindow, Singleton): Settings.get_instance().locked = True self.hide() else: - msgBox = QtGui.QMessageBox() + msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtGui.QApplication.translate("MainWindow", "Cannot lock app", None, QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("MainWindow", "Cannot lock app")) msgBox.setText( - QtGui.QApplication.translate("MainWindow", 'Error. Profile password is not set.', None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.')) msgBox.exec_() def show_menu(self): @@ -522,8 +517,8 @@ class MainWindow(QtGui.QMainWindow, Singleton): def send_file(self): self.menu.hide() if self.profile.active_friend + 1: - choose = QtGui.QApplication.translate("MainWindow", 'Choose file', None, QtGui.QApplication.UnicodeUTF8) - name = QtGui.QFileDialog.getOpenFileName(self, choose, options=QtGui.QFileDialog.DontUseNativeDialog) + choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file') + name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog) if name[0]: self.profile.send_file(name[0]) @@ -588,44 +583,43 @@ class MainWindow(QtGui.QMainWindow, Singleton): return settings = Settings.get_instance() allowed = friend.tox_id in settings['auto_accept_from_friends'] - auto = QtGui.QApplication.translate("MainWindow", 'Disallow auto accept', None, QtGui.QApplication.UnicodeUTF8) if allowed else QtGui.QApplication.translate("MainWindow", 'Allow auto accept', None, QtGui.QApplication.UnicodeUTF8) + auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept') if item is not None: - self.listMenu = QtGui.QMenu() - set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) + self.listMenu = QtWidgets.QMenu() + set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias')) - history_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Chat history', None, QtGui.QApplication.UnicodeUTF8)) - clear_history_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) - export_to_text_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as text', None, QtGui.QApplication.UnicodeUTF8)) - export_to_html_item = history_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Export as HTML', None, QtGui.QApplication.UnicodeUTF8)) + history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history')) + clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history')) + export_to_text_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as text')) + export_to_html_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as HTML')) - copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8)) - copy_name_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Name', None, QtGui.QApplication.UnicodeUTF8)) - copy_status_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Status message', None, QtGui.QApplication.UnicodeUTF8)) - copy_key_item = copy_menu.addAction(QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8)) + copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy')) + copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name')) + copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message')) + copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key')) auto_accept_item = self.listMenu.addAction(auto) - remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8)) - block_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Block friend', None, QtGui.QApplication.UnicodeUTF8)) - notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8)) + remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend')) + block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend')) + notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes')) plugins_loader = plugin_support.PluginLoader.get_instance() if plugins_loader is not None: submenu = plugins_loader.get_menu(self.listMenu, num) if len(submenu): - plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8)) + plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) plug.addActions(submenu) - self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num)) - self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num)) - self.connect(block_item, QtCore.SIGNAL("triggered()"), lambda: self.block_friend(num)) - self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num)) - self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num)) - self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed)) - self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend)) - self.connect(copy_name_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_name(friend)) - self.connect(copy_status_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_status(friend)) - self.connect(export_to_text_item, QtCore.SIGNAL("triggered()"), lambda: self.export_history(num)) - self.connect(export_to_html_item, QtCore.SIGNAL("triggered()"), - lambda: self.export_history(num, False)) + set_alias_item.triggered.connect(lambda: self.set_alias(num)) + remove_item.triggered.connect(lambda: self.remove_friend(num)) + block_item.triggered.connect(lambda: self.block_friend(num)) + copy_key_item.triggered.connect(lambda: self.copy_friend_key(num)) + clear_history_item.triggered.connect(lambda: self.clear_history(num)) + auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed)) + notes_item.triggered.connect(lambda: self.show_note(friend)) + copy_name_item.triggered.connect(lambda: self.copy_name(friend)) + copy_status_item.triggered.connect(lambda: self.copy_status(friend)) + export_to_text_item.triggered.connect(lambda: self.export_history(num)) + export_to_html_item.triggered.connect(lambda: self.export_history(num, False)) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position + pos) self.listMenu.show() @@ -633,7 +627,7 @@ class MainWindow(QtGui.QMainWindow, Singleton): def show_note(self, friend): s = Settings.get_instance() note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else '' - user = QtGui.QApplication.translate("MainWindow", 'Notes about user', None, QtGui.QApplication.UnicodeUTF8) + user = QtWidgets.QApplication.translate("MainWindow", 'Notes about user') user = '{} {}'.format(user, friend.name) def save_note(text): @@ -647,12 +641,11 @@ class MainWindow(QtGui.QMainWindow, Singleton): def export_history(self, num, as_text=True): s = self.profile.export_history(num, as_text) - directory = QtGui.QFileDialog.getExistingDirectory(None, - QtGui.QApplication.translate("MainWindow", 'Choose folder', - None, - QtGui.QApplication.UnicodeUTF8), + directory = QtWidgets.QFileDialog.getExistingDirectory(None, + QtWidgets.QApplication.translate("MainWindow", + 'Choose folder'), curr_directory(), - QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) if directory: name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html') @@ -671,15 +664,15 @@ class MainWindow(QtGui.QMainWindow, Singleton): def copy_friend_key(self, num): tox_id = self.profile.friend_public_key(num) - clipboard = QtGui.QApplication.clipboard() + clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(tox_id) def copy_name(self, friend): - clipboard = QtGui.QApplication.clipboard() + clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(friend.name) def copy_status(self, friend): - clipboard = QtGui.QApplication.clipboard() + clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(friend.status_message) def clear_history(self, num): diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index 967cd4a..74ae20f 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -1,14 +1,11 @@ -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit from profile import Profile import smileys import util -class MessageArea(QtGui.QPlainTextEdit): +class MessageArea(QtWidgets.QPlainTextEdit): """User types messages here""" def __init__(self, parent, form): @@ -20,7 +17,7 @@ class MessageArea(QtGui.QPlainTextEdit): def keyPressEvent(self, event): if event.matches(QtGui.QKeySequence.Paste): - mimeData = QtGui.QApplication.clipboard().mimeData() + mimeData = QtWidgets.QApplication.clipboard().mimeData() if mimeData.hasUrls(): for url in mimeData.urls(): self.pasteEvent(url.toString()) @@ -67,14 +64,14 @@ class MessageArea(QtGui.QPlainTextEdit): e.ignore() def pasteEvent(self, text=None): - text = text or QtGui.QApplication.clipboard().text() + text = text or QtWidgets.QApplication.clipboard().text() if text.startswith('file://'): self.parent.profile.send_file(text[7:]) else: self.insertPlainText(text) -class ScreenShotWindow(QtGui.QWidget): +class ScreenShotWindow(QtWidgets.QWidget): def __init__(self, parent): super(ScreenShotWindow, self).__init__() @@ -84,6 +81,8 @@ class ScreenShotWindow(QtGui.QWidget): self.showFullScreen() self.setWindowOpacity(0.5) self.rubberband = RubberBand() + self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint) + self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground) def closeEvent(self, *args): if self.parent.isHidden(): @@ -93,7 +92,7 @@ class ScreenShotWindow(QtGui.QWidget): self.origin = event.pos() self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize())) self.rubberband.show() - QtGui.QWidget.mousePressEvent(self, event) + QtWidgets.QWidget.mousePressEvent(self, event) def mouseMoveEvent(self, event): if self.rubberband.isVisible(): @@ -109,11 +108,12 @@ class ScreenShotWindow(QtGui.QWidget): self.rubberband.hide() rect = self.rubberband.geometry() if rect.width() and rect.height(): - p = QtGui.QPixmap.grabWindow(QtGui.QApplication.desktop().winId(), - rect.x() + 4, - rect.y() + 4, - rect.width() - 8, - rect.height() - 8) + screen = QtWidgets.QApplication.primaryScreen() + p = screen.grabWindow(0, + rect.x() + 4, + rect.y() + 4, + rect.width() - 8, + rect.height() - 8) byte_array = QtCore.QByteArray() buffer = QtCore.QBuffer(byte_array) buffer.open(QtCore.QIODevice.WriteOnly) @@ -129,7 +129,7 @@ class ScreenShotWindow(QtGui.QWidget): super(ScreenShotWindow, self).keyPressEvent(event) -class SmileyWindow(QtGui.QWidget): +class SmileyWindow(QtWidgets.QWidget): """ Smiley selection window """ @@ -151,7 +151,7 @@ class SmileyWindow(QtGui.QWidget): self.radio = [] self.parent = parent for i in range(self.page_count): # buttons with smileys - elem = QtGui.QRadioButton(self) + elem = QtWidgets.QRadioButton(self) elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20)) elem.clicked.connect(lambda i=i: self.checked(i)) self.radio.append(elem) @@ -160,7 +160,7 @@ class SmileyWindow(QtGui.QWidget): self.setMinimumSize(width, 200) self.buttons = [] for i in range(self.page_size): # pages - radio buttons - b = QtGui.QPushButton(self) + b = QtWidgets.QPushButton(self) b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20)) b.clicked.connect(lambda i=i: self.clicked(i)) self.buttons.append(b) @@ -190,7 +190,7 @@ class SmileyWindow(QtGui.QWidget): self.close() -class MenuButton(QtGui.QPushButton): +class MenuButton(QtWidgets.QPushButton): def __init__(self, parent, enter): super(MenuButton, self).__init__(parent) @@ -201,7 +201,7 @@ class MenuButton(QtGui.QPushButton): super(MenuButton, self).enterEvent(event) -class DropdownMenu(QtGui.QWidget): +class DropdownMenu(QtWidgets.QWidget): def __init__(self, parent): super(DropdownMenu, self).__init__(parent) @@ -213,20 +213,20 @@ class DropdownMenu(QtGui.QWidget): self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60)) self.screenshotButton.setObjectName("screenshotButton") - self.fileTransferButton = QtGui.QPushButton(self) + self.fileTransferButton = QtWidgets.QPushButton(self) self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60)) self.fileTransferButton.setObjectName("fileTransferButton") - self.audioMessageButton = QtGui.QPushButton(self) + self.audioMessageButton = QtWidgets.QPushButton(self) self.audioMessageButton.setGeometry(QtCore.QRect(120, 60, 60, 60)) - self.smileyButton = QtGui.QPushButton(self) + self.smileyButton = QtWidgets.QPushButton(self) self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60)) - self.videoMessageButton = QtGui.QPushButton(self) + self.videoMessageButton = QtWidgets.QPushButton(self) self.videoMessageButton.setGeometry(QtCore.QRect(120, 0, 60, 60)) - self.stickerButton = QtGui.QPushButton(self) + self.stickerButton = QtWidgets.QPushButton(self) self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png') @@ -254,16 +254,16 @@ class DropdownMenu(QtGui.QWidget): self.stickerButton.setIcon(icon) self.stickerButton.setIconSize(QtCore.QSize(55, 55)) - self.screenshotButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send screenshot", None, QtGui.QApplication.UnicodeUTF8)) - self.fileTransferButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send file", None, QtGui.QApplication.UnicodeUTF8)) - self.audioMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send audio message", None, QtGui.QApplication.UnicodeUTF8)) - self.videoMessageButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send video message", None, QtGui.QApplication.UnicodeUTF8)) - self.smileyButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Add smiley", None, QtGui.QApplication.UnicodeUTF8)) - self.stickerButton.setToolTip(QtGui.QApplication.translate("MenuWindow", "Send sticker", None, QtGui.QApplication.UnicodeUTF8)) + self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot")) + self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file")) + self.audioMessageButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send audio message")) + self.videoMessageButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send video message")) + self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley")) + self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker")) self.fileTransferButton.clicked.connect(parent.send_file) self.screenshotButton.clicked.connect(parent.send_screenshot) - self.connect(self.screenshotButton, QtCore.SIGNAL("rightClicked()"), lambda: parent.send_screenshot(True)) + self.screenshotButton.rightClicked.connect(lambda: parent.send_screenshot(True)) self.smileyButton.clicked.connect(parent.send_smiley) self.stickerButton.clicked.connect(parent.send_sticker) @@ -276,11 +276,11 @@ class DropdownMenu(QtGui.QWidget): return False -class StickerItem(QtGui.QWidget): +class StickerItem(QtWidgets.QWidget): def __init__(self, fl): super(StickerItem, self).__init__() - self._image_label = QtGui.QLabel(self) + self._image_label = QtWidgets.QLabel(self) self.path = fl self.pixmap = QtGui.QPixmap() self.pixmap.load(fl) @@ -290,7 +290,7 @@ class StickerItem(QtGui.QWidget): self._image_label.setPixmap(self.pixmap) -class StickerWindow(QtGui.QWidget): +class StickerWindow(QtWidgets.QWidget): """Sticker selection window""" def __init__(self, parent): @@ -298,16 +298,16 @@ class StickerWindow(QtGui.QWidget): self.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.setMaximumSize(250, 200) self.setMinimumSize(250, 200) - self.list = QtGui.QListWidget(self) + self.list = QtWidgets.QListWidget(self) self.list.setGeometry(QtCore.QRect(0, 0, 250, 200)) self.arr = smileys.sticker_loader() for sticker in self.arr: item = StickerItem(sticker) - elem = QtGui.QListWidgetItem() + elem = QtWidgets.QListWidgetItem() elem.setSizeHint(QtCore.QSize(250, item.height())) self.list.addItem(elem) self.list.setItemWidget(elem, item) - self.list.setVerticalScrollMode(QtGui.QAbstractItemView.ScrollPerPixel) + self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.list.setSpacing(3) self.list.clicked.connect(self.click) self.parent = parent @@ -329,56 +329,44 @@ class WelcomeScreen(CenteredWidget): self.setMinimumSize(250, 200) self.center() self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.text = QtGui.QTextBrowser(self) + self.text = QtWidgets.QTextBrowser(self) self.text.setGeometry(QtCore.QRect(0, 0, 250, 170)) self.text.setOpenExternalLinks(True) - self.checkbox = QtGui.QCheckBox(self) + self.checkbox = QtWidgets.QCheckBox(self) self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30)) - self.checkbox.setText(QtGui.QApplication.translate('WelcomeScreen', "Don't show again", - None, QtGui.QApplication.UnicodeUTF8)) - self.setWindowTitle(QtGui.QApplication.translate('WelcomeScreen', 'Tip of the day', - None, QtGui.QApplication.UnicodeUTF8)) + self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again")) + self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day')) import random num = random.randint(0, 10) if num == 0: - text = QtGui.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.') elif num == 1: - text = QtGui.QApplication.translate('WelcomeScreen', - 'Right click on screenshot button hides app to tray during screenshot.', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Right click on screenshot button hides app to tray during screenshot.') elif num == 2: - text = QtGui.QApplication.translate('WelcomeScreen', - 'You can use Tox over Tor. For more info read this post', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'You can use Tox over Tor. For more info read this post') elif num == 3: - text = QtGui.QApplication.translate('WelcomeScreen', - 'Use Settings -> Interface to customize interface.', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Use Settings -> Interface to customize interface.') elif num == 4: - text = QtGui.QApplication.translate('WelcomeScreen', - 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') elif num == 5: - text = QtGui.QApplication.translate('WelcomeScreen', - 'Since v0.1.3 Toxygen supports plugins. Read more', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Since v0.1.3 Toxygen supports plugins. Read more') elif num in (6, 7): - text = QtGui.QApplication.translate('WelcomeScreen', - 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') elif num == 8: - text = QtGui.QApplication.translate('WelcomeScreen', - 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') elif num == 9: - text = QtGui.QApplication.translate('WelcomeScreen', - 'Use right click on inline image to save it', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Use right click on inline image to save it') else: - text = QtGui.QApplication.translate('WelcomeScreen', - 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.', - None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') self.text.setHtml(text) self.checkbox.stateChanged.connect(self.not_show) QtCore.QTimer.singleShot(1000, self.show) @@ -390,7 +378,7 @@ class WelcomeScreen(CenteredWidget): s.save() -class MainMenuButton(QtGui.QPushButton): +class MainMenuButton(QtWidgets.QPushButton): def __init__(self, *args): super().__init__(*args) @@ -402,16 +390,18 @@ class MainMenuButton(QtGui.QPushButton): super().setText(text) -class ClickableLabel(QtGui.QLabel): +class ClickableLabel(QtWidgets.QLabel): + + clicked = QtCore.pyqtSignal() def __init__(self, *args): super().__init__(*args) def mouseReleaseEvent(self, ev): - self.emit(QtCore.SIGNAL('clicked()')) + self.clicked.emit() -class SearchScreen(QtGui.QWidget): +class SearchScreen(QtWidgets.QWidget): def __init__(self, messages, width, *args): super().__init__(*args) @@ -429,23 +419,23 @@ class SearchScreen(QtGui.QWidget): self.search_button.setScaledContents(False) self.search_button.setAlignment(QtCore.Qt.AlignCenter) self.search_button.setPixmap(pixmap) - self.connect(self.search_button, QtCore.SIGNAL('clicked()'), self.search) + self.search_button.clicked.connect(self.search) font = QtGui.QFont() font.setPointSize(32) font.setBold(True) - self.prev_button = QtGui.QPushButton(self) + self.prev_button = QtWidgets.QPushButton(self) self.prev_button.setGeometry(width - 120, 0, 40, 40) self.prev_button.clicked.connect(self.prev) self.prev_button.setText('\u25B2') - self.next_button = QtGui.QPushButton(self) + self.next_button = QtWidgets.QPushButton(self) self.next_button.setGeometry(width - 80, 0, 40, 40) self.next_button.clicked.connect(self.next) self.next_button.setText('\u25BC') - self.close_button = QtGui.QPushButton(self) + self.close_button = QtWidgets.QPushButton(self) self.close_button.setGeometry(width - 40, 0, 40, 40) self.close_button.clicked.connect(self.close) self.close_button.setText('×') @@ -458,8 +448,7 @@ class SearchScreen(QtGui.QWidget): self.retranslateUi() def retranslateUi(self): - self.search_text.setPlaceholderText(QtGui.QApplication.translate("MainWindow", "Search", None, - QtGui.QApplication.UnicodeUTF8)) + self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) def show(self): super().show() @@ -515,15 +504,11 @@ class SearchScreen(QtGui.QWidget): @staticmethod def not_found(text): - mbox = QtGui.QMessageBox() - mbox_text = QtGui.QApplication.translate("MainWindow", - 'Text "{}" was not found', - None, - QtGui.QApplication.UnicodeUTF8) + mbox = QtWidgets.QMessageBox() + mbox_text = QtWidgets.QApplication.translate("MainWindow", + 'Text "{}" was not found') mbox.setText(mbox_text.format(text)) - mbox.setWindowTitle(QtGui.QApplication.translate("MainWindow", - 'Not found', - None, - QtGui.QApplication.UnicodeUTF8)) + mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", + 'Not found')) mbox.exec_() diff --git a/toxygen/menu.py b/toxygen/menu.py index 1fe22cb..c3958e9 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -1,7 +1,4 @@ -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets from settings import * from profile import Profile from util import curr_directory, copy @@ -23,7 +20,7 @@ class AddContact(CenteredWidget): def initUI(self, tox_id): self.setObjectName('AddContact') self.resize(568, 306) - self.sendRequestButton = QtGui.QPushButton(self) + self.sendRequestButton = QtWidgets.QPushButton(self) self.sendRequestButton.setGeometry(QtCore.QRect(50, 270, 471, 31)) self.sendRequestButton.setMinimumSize(QtCore.QSize(0, 0)) self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0)) @@ -33,7 +30,7 @@ class AddContact(CenteredWidget): self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27)) self.tox_id.setObjectName("lineEdit") self.tox_id.setText(tox_id) - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(50, 10, 80, 20)) self.error_label = DataLabel(self) self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20)) @@ -44,10 +41,10 @@ class AddContact(CenteredWidget): self.error_label.setFont(font) self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }") self.label.setObjectName("label") - self.message_edit = QtGui.QTextEdit(self) + self.message_edit = QtWidgets.QTextEdit(self) self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151)) self.message_edit.setObjectName("textEdit") - self.message = QtGui.QLabel(self) + self.message = QtWidgets.QLabel(self) self.message.setGeometry(QtCore.QRect(50, 70, 101, 31)) self.message.setFont(font) self.message.setObjectName("label_2") @@ -73,11 +70,11 @@ class AddContact(CenteredWidget): self.error_label.setText(send) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate('AddContact', "Add contact", None, QtGui.QApplication.UnicodeUTF8)) - self.sendRequestButton.setText(QtGui.QApplication.translate("Form", "Send request", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate('AddContact', "TOX ID:", None, QtGui.QApplication.UnicodeUTF8)) - self.message.setText(QtGui.QApplication.translate('AddContact', "Message:", None, QtGui.QApplication.UnicodeUTF8)) - self.tox_id.setPlaceholderText(QtGui.QApplication.translate('AddContact', "TOX ID or public key of contact", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate('AddContact', "Add contact")) + self.sendRequestButton.setText(QtWidgets.QApplication.translate("Form", "Send request")) + self.label.setText(QtWidgets.QApplication.translate('AddContact', "TOX ID:")) + self.message.setText(QtWidgets.QApplication.translate('AddContact', "Message:")) + self.tox_id.setPlaceholderText(QtWidgets.QApplication.translate('AddContact', "TOX ID or public key of contact")) class ProfileSettings(CenteredWidget): @@ -95,12 +92,12 @@ class ProfileSettings(CenteredWidget): self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27)) profile = Profile.get_instance() self.nick.setText(profile.name) - self.status = QtGui.QComboBox(self) + self.status = QtWidgets.QComboBox(self) self.status.setGeometry(QtCore.QRect(400, 60, 200, 27)) self.status_message = LineEdit(self) self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27)) self.status_message.setText(profile.status_message) - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(40, 30, 91, 25)) font = QtGui.QFont() font.setFamily(Settings.get_instance()['font']) @@ -108,59 +105,59 @@ class ProfileSettings(CenteredWidget): font.setWeight(75) font.setBold(True) self.label.setFont(font) - self.label_2 = QtGui.QLabel(self) + self.label_2 = QtWidgets.QLabel(self) self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25)) self.label_2.setFont(font) - self.label_3 = QtGui.QLabel(self) + self.label_3 = QtWidgets.QLabel(self) self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25)) self.label_3.setFont(font) - self.tox_id = QtGui.QLabel(self) + self.tox_id = QtWidgets.QLabel(self) self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21)) font.setPointSize(10) self.tox_id.setFont(font) s = profile.tox_id self.tox_id.setText(s) - self.copyId = QtGui.QPushButton(self) + self.copyId = QtWidgets.QPushButton(self) self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30)) self.copyId.clicked.connect(self.copy) - self.export = QtGui.QPushButton(self) + self.export = QtWidgets.QPushButton(self) self.export.setGeometry(QtCore.QRect(230, 250, 180, 30)) self.export.clicked.connect(self.export_profile) - self.new_nospam = QtGui.QPushButton(self) + self.new_nospam = QtWidgets.QPushButton(self) self.new_nospam.setGeometry(QtCore.QRect(420, 250, 180, 30)) self.new_nospam.clicked.connect(self.new_no_spam) - self.copy_pk = QtGui.QPushButton(self) + self.copy_pk = QtWidgets.QPushButton(self) self.copy_pk.setGeometry(QtCore.QRect(40, 300, 180, 30)) self.copy_pk.clicked.connect(self.copy_public_key) - self.new_avatar = QtGui.QPushButton(self) + self.new_avatar = QtWidgets.QPushButton(self) self.new_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30)) - self.delete_avatar = QtGui.QPushButton(self) + self.delete_avatar = QtWidgets.QPushButton(self) self.delete_avatar.setGeometry(QtCore.QRect(420, 300, 180, 30)) self.delete_avatar.clicked.connect(self.reset_avatar) self.new_avatar.clicked.connect(self.set_avatar) - self.profilepass = QtGui.QLabel(self) + self.profilepass = QtWidgets.QLabel(self) self.profilepass.setGeometry(QtCore.QRect(40, 340, 300, 30)) font.setPointSize(18) self.profilepass.setFont(font) self.password = LineEdit(self) self.password.setGeometry(QtCore.QRect(40, 380, 300, 30)) - self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password) - self.leave_blank = QtGui.QLabel(self) + self.password.setEchoMode(QtWidgets.QLineEdit.Password) + self.leave_blank = QtWidgets.QLabel(self) self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30)) self.confirm_password = LineEdit(self) self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30)) - self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password) - self.set_password = QtGui.QPushButton(self) + self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password) + self.set_password = QtWidgets.QPushButton(self) self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30)) self.set_password.clicked.connect(self.new_password) - self.not_match = QtGui.QLabel(self) + self.not_match = QtWidgets.QLabel(self) self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30)) self.not_match.setVisible(False) self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }') - self.warning = QtGui.QLabel(self) + self.warning = QtWidgets.QLabel(self) self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30)) self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') - self.default = QtGui.QPushButton(self) + self.default = QtWidgets.QPushButton(self) self.default.setGeometry(QtCore.QRect(40, 550, 620, 30)) path, name = Settings.get_auto_profile() self.auto = path + name == ProfileHelper.get_path() + Settings.get_instance().name @@ -173,30 +170,30 @@ class ProfileSettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.export.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Export profile", None, QtGui.QApplication.UnicodeUTF8)) - self.setWindowTitle(QtGui.QApplication.translate("ProfileSettingsForm", "Profile settings", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Name:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Status:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_3.setText(QtGui.QApplication.translate("ProfileSettingsForm", "TOX ID:", None, QtGui.QApplication.UnicodeUTF8)) - self.copyId.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Copy TOX ID", None, QtGui.QApplication.UnicodeUTF8)) - self.new_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New avatar", None, QtGui.QApplication.UnicodeUTF8)) - self.delete_avatar.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Reset avatar", None, QtGui.QApplication.UnicodeUTF8)) - self.new_nospam.setText(QtGui.QApplication.translate("ProfileSettingsForm", "New NoSpam", None, QtGui.QApplication.UnicodeUTF8)) - self.profilepass.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Profile password", None, QtGui.QApplication.UnicodeUTF8)) - self.password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)", None, QtGui.QApplication.UnicodeUTF8)) - self.confirm_password.setPlaceholderText(QtGui.QApplication.translate("ProfileSettingsForm", "Confirm password", None, QtGui.QApplication.UnicodeUTF8)) - self.set_password.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Set password", None, QtGui.QApplication.UnicodeUTF8)) - self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None, QtGui.QApplication.UnicodeUTF8)) - self.leave_blank.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password", None, QtGui.QApplication.UnicodeUTF8)) - self.warning.setText(QtGui.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords", None, QtGui.QApplication.UnicodeUTF8)) - self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Online", None, QtGui.QApplication.UnicodeUTF8)) - self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Away", None, QtGui.QApplication.UnicodeUTF8)) - self.status.addItem(QtGui.QApplication.translate("ProfileSettingsForm", "Busy", None, QtGui.QApplication.UnicodeUTF8)) - self.copy_pk.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Copy public key", None, QtGui.QApplication.UnicodeUTF8)) + self.export.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Export profile")) + self.setWindowTitle(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile settings")) + self.label.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Name:")) + self.label_2.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Status:")) + self.label_3.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "TOX ID:")) + self.copyId.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy TOX ID")) + self.new_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New avatar")) + self.delete_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Reset avatar")) + self.new_nospam.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New NoSpam")) + self.profilepass.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile password")) + self.password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)")) + self.confirm_password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Confirm password")) + self.set_password.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Set password")) + self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) + self.leave_blank.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password")) + self.warning.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords")) + self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Online")) + self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Away")) + self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Busy")) + self.copy_pk.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy public key")) if self.auto: - self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None, QtGui.QApplication.UnicodeUTF8)) + self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) else: - self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None, QtGui.QApplication.UnicodeUTF8)) + self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) def auto_profile(self): if self.auto: @@ -205,12 +202,10 @@ class ProfileSettings(CenteredWidget): Settings.set_auto_profile(ProfileHelper.get_path(), Settings.get_instance().name) self.auto = not self.auto if self.auto: - self.default.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Mark as not default profile", None, - QtGui.QApplication.UnicodeUTF8)) + self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) else: self.default.setText( - QtGui.QApplication.translate("ProfileSettingsForm", "Mark as default profile", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) def new_password(self): if self.password.text() == self.confirm_password.text(): @@ -220,16 +215,14 @@ class ProfileSettings(CenteredWidget): self.close() else: self.not_match.setText( - QtGui.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols")) self.not_match.setVisible(True) else: - self.not_match.setText(QtGui.QApplication.translate("ProfileSettingsForm", "Passwords do not match", None, - QtGui.QApplication.UnicodeUTF8)) + self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) self.not_match.setVisible(True) def copy(self): - clipboard = QtGui.QApplication.clipboard() + clipboard = QtWidgets.QApplication.clipboard() profile = Profile.get_instance() clipboard.setText(profile.tox_id) pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') @@ -238,7 +231,7 @@ class ProfileSettings(CenteredWidget): self.copyId.setIconSize(QtCore.QSize(10, 10)) def copy_public_key(self): - clipboard = QtGui.QApplication.clipboard() + clipboard = QtWidgets.QApplication.clipboard() profile = Profile.get_instance() clipboard.setText(profile.tox_id[:64]) pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') @@ -253,9 +246,9 @@ class ProfileSettings(CenteredWidget): Profile.get_instance().reset_avatar() def set_avatar(self): - choose = QtGui.QApplication.translate("ProfileSettingsForm", "Choose avatar", None, QtGui.QApplication.UnicodeUTF8) - name = QtGui.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', - options=QtGui.QFileDialog.DontUseNativeDialog) + choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar") + name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', + QtGui.QComboBoxQtWidgets.QFileDialog.DontUseNativeDialog) if name[0]: bitmap = QtGui.QPixmap(name[0]) bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio, @@ -268,25 +261,21 @@ class ProfileSettings(CenteredWidget): Profile.get_instance().set_avatar(bytes(byte_array.data())) def export_profile(self): - directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog, + directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog, dir=curr_directory()) + '/' if directory != '/': - reply = QtGui.QMessageBox.question(None, - QtGui.QApplication.translate("ProfileSettingsForm", - 'Use new path', - None, - QtGui.QApplication.UnicodeUTF8), - QtGui.QApplication.translate("ProfileSettingsForm", - 'Do you want to move your profile to this location?', - None, - QtGui.QApplication.UnicodeUTF8), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) + reply = QtWidgets.QMessageBox.question(None, + QtWidgets.QApplication.translate("ProfileSettingsForm", + 'Use new path'), + QtWidgets.QApplication.translate("ProfileSettingsForm", + 'Do you want to move your profile to this location?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) settings = Settings.get_instance() settings.export(directory) profile = Profile.get_instance() profile.export_db(directory) - ProfileHelper.get_instance().export_profile(directory, reply == QtGui.QMessageBox.Yes) + ProfileHelper.get_instance().export_profile(directory, reply == QtWidgets.QMessageBox.Yes) def closeEvent(self, event): profile = Profile.get_instance() @@ -309,15 +298,15 @@ class NetworkSettings(CenteredWidget): self.setMinimumSize(QtCore.QSize(300, 330)) self.setMaximumSize(QtCore.QSize(300, 330)) self.setBaseSize(QtCore.QSize(300, 330)) - self.ipv = QtGui.QCheckBox(self) + self.ipv = QtWidgets.QCheckBox(self) self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22)) self.ipv.setObjectName("ipv") - self.udp = QtGui.QCheckBox(self) + self.udp = QtWidgets.QCheckBox(self) self.udp.setGeometry(QtCore.QRect(150, 10, 97, 22)) self.udp.setObjectName("udp") - self.proxy = QtGui.QCheckBox(self) + self.proxy = QtWidgets.QCheckBox(self) self.proxy.setGeometry(QtCore.QRect(20, 40, 97, 22)) - self.http = QtGui.QCheckBox(self) + self.http = QtWidgets.QCheckBox(self) self.http.setGeometry(QtCore.QRect(20, 70, 97, 22)) self.proxy.setObjectName("proxy") self.proxyip = LineEdit(self) @@ -326,11 +315,11 @@ class NetworkSettings(CenteredWidget): self.proxyport = LineEdit(self) self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27)) self.proxyport.setObjectName("proxyport") - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(40, 100, 66, 17)) - self.label_2 = QtGui.QLabel(self) + self.label_2 = QtWidgets.QLabel(self) self.label_2.setGeometry(QtCore.QRect(40, 165, 66, 17)) - self.reconnect = QtGui.QPushButton(self) + self.reconnect = QtWidgets.QPushButton(self) self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30)) self.reconnect.clicked.connect(self.restart_core) settings = Settings.get_instance() @@ -340,7 +329,7 @@ class NetworkSettings(CenteredWidget): self.proxyip.setText(settings['proxy_host']) self.proxyport.setText(str(settings['proxy_port'])) self.http.setChecked(settings['proxy_type'] == 1) - self.warning = QtGui.QLabel(self) + self.warning = QtWidgets.QLabel(self) self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60)) self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') self.retranslateUi() @@ -349,16 +338,15 @@ class NetworkSettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate("NetworkSettings", "Network settings", None, QtGui.QApplication.UnicodeUTF8)) - self.ipv.setText(QtGui.QApplication.translate("Form", "IPv6", None, QtGui.QApplication.UnicodeUTF8)) - self.udp.setText(QtGui.QApplication.translate("Form", "UDP", None, QtGui.QApplication.UnicodeUTF8)) - self.proxy.setText(QtGui.QApplication.translate("Form", "Proxy", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("Form", "IP:", None, QtGui.QApplication.UnicodeUTF8)) - self.label_2.setText(QtGui.QApplication.translate("Form", "Port:", None, QtGui.QApplication.UnicodeUTF8)) - self.reconnect.setText(QtGui.QApplication.translate("NetworkSettings", "Restart TOX core", None, QtGui.QApplication.UnicodeUTF8)) - self.http.setText(QtGui.QApplication.translate("Form", "HTTP", None, QtGui.QApplication.UnicodeUTF8)) - self.warning.setText(QtGui.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak", - None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate("NetworkSettings", "Network settings")) + self.ipv.setText(QtWidgets.QApplication.translate("Form", "IPv6")) + self.udp.setText(QtWidgets.QApplication.translate("Form", "UDP")) + self.proxy.setText(QtWidgets.QApplication.translate("Form", "Proxy")) + self.label.setText(QtWidgets.QApplication.translate("Form", "IP:")) + self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:")) + self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core")) + self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP")) + self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) def activate(self): bl = self.proxy.isChecked() @@ -395,23 +383,23 @@ class PrivacySettings(CenteredWidget): self.resize(370, 600) self.setMinimumSize(QtCore.QSize(370, 600)) self.setMaximumSize(QtCore.QSize(370, 600)) - self.saveHistory = QtGui.QCheckBox(self) + self.saveHistory = QtWidgets.QCheckBox(self) self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22)) - self.saveUnsentOnly = QtGui.QCheckBox(self) + self.saveUnsentOnly = QtWidgets.QCheckBox(self) self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22)) - self.fileautoaccept = QtGui.QCheckBox(self) + self.fileautoaccept = QtWidgets.QCheckBox(self) self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22)) - self.typingNotifications = QtGui.QCheckBox(self) + self.typingNotifications = QtWidgets.QCheckBox(self) self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30)) - self.inlines = QtGui.QCheckBox(self) + self.inlines = QtWidgets.QCheckBox(self) self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30)) - self.auto_path = QtGui.QLabel(self) + self.auto_path = QtWidgets.QLabel(self) self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30)) - self.path = QtGui.QPlainTextEdit(self) + self.path = QtWidgets.QPlainTextEdit(self) self.path.setGeometry(QtCore.QRect(10, 265, 350, 45)) - self.change_path = QtGui.QPushButton(self) + self.change_path = QtWidgets.QPushButton(self) self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30)) settings = Settings.get_instance() self.typingNotifications.setChecked(settings['typing_notifications']) @@ -423,37 +411,37 @@ class PrivacySettings(CenteredWidget): self.saveHistory.stateChanged.connect(self.update) self.path.setPlainText(settings['auto_accept_path'] or curr_directory()) self.change_path.clicked.connect(self.new_path) - self.block_user_label = QtGui.QLabel(self) + self.block_user_label = QtWidgets.QLabel(self) self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30)) - self.block_id = QtGui.QPlainTextEdit(self) + self.block_id = QtWidgets.QPlainTextEdit(self) self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30)) - self.block = QtGui.QPushButton(self) + self.block = QtWidgets.QPushButton(self) self.block.setGeometry(QtCore.QRect(10, 430, 350, 30)) self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close()) - self.blocked_users_label = QtGui.QLabel(self) + self.blocked_users_label = QtWidgets.QLabel(self) self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30)) - self.comboBox = QtGui.QComboBox(self) + self.comboBox = QtWidgets.QComboBox(self) self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30)) self.comboBox.addItems(settings['blocked']) - self.unblock = QtGui.QPushButton(self) + self.unblock = QtWidgets.QPushButton(self) self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30)) self.unblock.clicked.connect(lambda: self.unblock_user()) self.retranslateUi() QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate("privacySettings", "Privacy settings", None, QtGui.QApplication.UnicodeUTF8)) - self.saveHistory.setText(QtGui.QApplication.translate("privacySettings", "Save chat history", None, QtGui.QApplication.UnicodeUTF8)) - self.fileautoaccept.setText(QtGui.QApplication.translate("privacySettings", "Allow file auto accept", None, QtGui.QApplication.UnicodeUTF8)) - self.typingNotifications.setText(QtGui.QApplication.translate("privacySettings", "Send typing notifications", None, QtGui.QApplication.UnicodeUTF8)) - self.auto_path.setText(QtGui.QApplication.translate("privacySettings", "Auto accept default path:", None, QtGui.QApplication.UnicodeUTF8)) - self.change_path.setText(QtGui.QApplication.translate("privacySettings", "Change", None, QtGui.QApplication.UnicodeUTF8)) - self.inlines.setText(QtGui.QApplication.translate("privacySettings", "Allow inlines", None, QtGui.QApplication.UnicodeUTF8)) - self.block_user_label.setText(QtGui.QApplication.translate("privacySettings", "Block by public key:", None, QtGui.QApplication.UnicodeUTF8)) - self.blocked_users_label.setText(QtGui.QApplication.translate("privacySettings", "Blocked users:", None, QtGui.QApplication.UnicodeUTF8)) - self.unblock.setText(QtGui.QApplication.translate("privacySettings", "Unblock", None, QtGui.QApplication.UnicodeUTF8)) - self.block.setText(QtGui.QApplication.translate("privacySettings", "Block user", None, QtGui.QApplication.UnicodeUTF8)) - self.saveUnsentOnly.setText(QtGui.QApplication.translate("privacySettings", "Save unsent messages only", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate("privacySettings", "Privacy settings")) + self.saveHistory.setText(QtWidgets.QApplication.translate("privacySettings", "Save chat history")) + self.fileautoaccept.setText(QtWidgets.QApplication.translate("privacySettings", "Allow file auto accept")) + self.typingNotifications.setText(QtWidgets.QApplication.translate("privacySettings", "Send typing notifications")) + self.auto_path.setText(QtWidgets.QApplication.translate("privacySettings", "Auto accept default path:")) + self.change_path.setText(QtWidgets.QApplication.translate("privacySettings", "Change")) + self.inlines.setText(QtWidgets.QApplication.translate("privacySettings", "Allow inlines")) + self.block_user_label.setText(QtWidgets.QApplication.translate("privacySettings", "Block by public key:")) + self.blocked_users_label.setText(QtWidgets.QApplication.translate("privacySettings", "Blocked users:")) + self.unblock.setText(QtWidgets.QApplication.translate("privacySettings", "Unblock")) + self.block.setText(QtWidgets.QApplication.translate("privacySettings", "Block user")) + self.saveUnsentOnly.setText(QtWidgets.QApplication.translate("privacySettings", "Save unsent messages only")) def update(self, new_state): self.saveUnsentOnly.setEnabled(new_state) @@ -463,10 +451,10 @@ class PrivacySettings(CenteredWidget): def unblock_user(self): if not self.comboBox.count(): return - title = QtGui.QApplication.translate("privacySettings", "Add to friend list", None, QtGui.QApplication.UnicodeUTF8) - info = QtGui.QApplication.translate("privacySettings", "Do you want to add this user to friend list?", None, QtGui.QApplication.UnicodeUTF8) - reply = QtGui.QMessageBox.question(None, title, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) - Profile.get_instance().unblock_user(self.comboBox.currentText(), reply == QtGui.QMessageBox.Yes) + title = QtWidgets.QApplication.translate("privacySettings", "Add to friend list") + info = QtWidgets.QApplication.translate("privacySettings", "Do you want to add this user to friend list?") + reply = QtWidgets.QMessageBox.question(None, title, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) + Profile.get_instance().unblock_user(self.comboBox.currentText(), reply == QtWidgets.QMessageBox.Yes) self.close() def closeEvent(self, event): @@ -475,31 +463,27 @@ class PrivacySettings(CenteredWidget): settings['allow_auto_accept'] = self.fileautoaccept.isChecked() if settings['save_history'] and not self.saveHistory.isChecked(): # clear history - reply = QtGui.QMessageBox.question(None, - QtGui.QApplication.translate("privacySettings", - 'Chat history', - None, QtGui.QApplication.UnicodeUTF8), - QtGui.QApplication.translate("privacySettings", - 'History will be cleaned! Continue?', - None, QtGui.QApplication.UnicodeUTF8), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: + reply = QtWidgets.QMessageBox.question(None, + QtWidgets.QApplication.translate("privacySettings", + 'Chat history'), + QtWidgets.QApplication.translate("privacySettings", + 'History will be cleaned! Continue?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: Profile.get_instance().clear_history() settings['save_history'] = self.saveHistory.isChecked() else: settings['save_history'] = self.saveHistory.isChecked() if self.saveUnsentOnly.isChecked() and not settings['save_unsent_only']: - reply = QtGui.QMessageBox.question(None, - QtGui.QApplication.translate("privacySettings", - 'Chat history', - None, QtGui.QApplication.UnicodeUTF8), - QtGui.QApplication.translate("privacySettings", - 'History will be cleaned! Continue?', - None, QtGui.QApplication.UnicodeUTF8), - QtGui.QMessageBox.Yes, - QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: + reply = QtWidgets.QMessageBox.question(None, + QtWidgets.QApplication.translate("privacySettings", + 'Chat history'), + QtWidgets.QApplication.translate("privacySettings", + 'History will be cleaned! Continue?'), + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: Profile.get_instance().clear_history(None, True) settings['save_unsent_only'] = self.saveUnsentOnly.isChecked() else: @@ -509,7 +493,7 @@ class PrivacySettings(CenteredWidget): settings.save() def new_path(self): - directory = QtGui.QFileDialog.getExistingDirectory(options=QtGui.QFileDialog.DontUseNativeDialog) + '/' + directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog) + '/' if directory != '/': self.path.setPlainText(directory) @@ -527,11 +511,11 @@ class NotificationsSettings(CenteredWidget): self.resize(350, 180) self.setMinimumSize(QtCore.QSize(350, 180)) self.setMaximumSize(QtCore.QSize(350, 180)) - self.enableNotifications = QtGui.QCheckBox(self) + self.enableNotifications = QtWidgets.QCheckBox(self) self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18)) - self.callsSound = QtGui.QCheckBox(self) + self.callsSound = QtWidgets.QCheckBox(self) self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18)) - self.soundNotifications = QtGui.QCheckBox(self) + self.soundNotifications = QtWidgets.QCheckBox(self) self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18)) font = QtGui.QFont() s = Settings.get_instance() @@ -547,10 +531,10 @@ class NotificationsSettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate("notificationsForm", "Notification settings", None, QtGui.QApplication.UnicodeUTF8)) - self.enableNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable notifications", None, QtGui.QApplication.UnicodeUTF8)) - self.callsSound.setText(QtGui.QApplication.translate("notificationsForm", "Enable call\'s sound", None, QtGui.QApplication.UnicodeUTF8)) - self.soundNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable sound notifications", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate("notificationsForm", "Notification settings")) + self.enableNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable notifications")) + self.callsSound.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable call\'s sound")) + self.soundNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable sound notifications")) def closeEvent(self, *args, **kwargs): settings = Settings.get_instance() @@ -571,7 +555,7 @@ class InterfaceSettings(CenteredWidget): self.setObjectName("interfaceForm") self.setMinimumSize(QtCore.QSize(400, 650)) self.setMaximumSize(QtCore.QSize(400, 650)) - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(30, 10, 370, 20)) settings = Settings.get_instance() font = QtGui.QFont() @@ -579,7 +563,7 @@ class InterfaceSettings(CenteredWidget): font.setBold(True) font.setFamily(settings['font']) self.label.setFont(font) - self.themeSelect = QtGui.QComboBox(self) + self.themeSelect = QtWidgets.QComboBox(self) self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30)) list_of_themes = ['dark'] self.themeSelect.addItems(list_of_themes) @@ -589,26 +573,26 @@ class InterfaceSettings(CenteredWidget): else: index = 0 self.themeSelect.setCurrentIndex(index) - self.lang_choose = QtGui.QComboBox(self) + self.lang_choose = QtWidgets.QComboBox(self) self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30)) supported = sorted(Settings.supported_languages().keys(), reverse=True) for key in supported: self.lang_choose.insertItem(0, key) if settings['language'] == key: self.lang_choose.setCurrentIndex(0) - self.lang = QtGui.QLabel(self) + self.lang = QtWidgets.QLabel(self) self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20)) self.lang.setFont(font) - self.mirror_mode = QtGui.QCheckBox(self) + self.mirror_mode = QtWidgets.QCheckBox(self) self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20)) self.mirror_mode.setChecked(settings['mirror_mode']) - self.smileys = QtGui.QCheckBox(self) + self.smileys = QtWidgets.QCheckBox(self) self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20)) self.smileys.setChecked(settings['smileys']) - self.smiley_pack_label = QtGui.QLabel(self) + self.smiley_pack_label = QtWidgets.QLabel(self) self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20)) self.smiley_pack_label.setFont(font) - self.smiley_pack = QtGui.QComboBox(self) + self.smiley_pack = QtWidgets.QComboBox(self) self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30)) sm = smileys.SmileyLoader.get_instance() self.smiley_pack.addItems(sm.get_packs_list()) @@ -617,39 +601,39 @@ class InterfaceSettings(CenteredWidget): except: ind = sm.get_packs_list().index('default') self.smiley_pack.setCurrentIndex(ind) - self.messages_font_size_label = QtGui.QLabel(self) + self.messages_font_size_label = QtWidgets.QLabel(self) self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20)) self.messages_font_size_label.setFont(font) - self.messages_font_size = QtGui.QComboBox(self) + self.messages_font_size = QtWidgets.QComboBox(self) self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30)) self.messages_font_size.addItems([str(x) for x in range(10, 19)]) self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10) - self.unread = QtGui.QPushButton(self) + self.unread = QtWidgets.QPushButton(self) self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30)) self.unread.clicked.connect(self.select_color) - self.compact_mode = QtGui.QCheckBox(self) + self.compact_mode = QtWidgets.QCheckBox(self) self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20)) self.compact_mode.setChecked(settings['compact_mode']) - self.close_to_tray = QtGui.QCheckBox(self) + self.close_to_tray = QtWidgets.QCheckBox(self) self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20)) self.close_to_tray.setChecked(settings['close_to_tray']) - self.show_avatars = QtGui.QCheckBox(self) + self.show_avatars = QtWidgets.QCheckBox(self) self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20)) self.show_avatars.setChecked(settings['show_avatars']) - self.choose_font = QtGui.QPushButton(self) + self.choose_font = QtWidgets.QPushButton(self) self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30)) self.choose_font.clicked.connect(self.new_font) - self.import_smileys = QtGui.QPushButton(self) + self.import_smileys = QtWidgets.QPushButton(self) self.import_smileys.setGeometry(QtCore.QRect(30, 550, 340, 30)) self.import_smileys.clicked.connect(self.import_sm) - self.import_stickers = QtGui.QPushButton(self) + self.import_stickers = QtWidgets.QPushButton(self) self.import_stickers.setGeometry(QtCore.QRect(30, 590, 340, 30)) self.import_stickers.clicked.connect(self.import_st) @@ -657,29 +641,27 @@ class InterfaceSettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.show_avatars.setText(QtGui.QApplication.translate("interfaceForm", "Show avatars in chat", None, QtGui.QApplication.UnicodeUTF8)) - self.setWindowTitle(QtGui.QApplication.translate("interfaceForm", "Interface settings", None, QtGui.QApplication.UnicodeUTF8)) - self.label.setText(QtGui.QApplication.translate("interfaceForm", "Theme:", None, QtGui.QApplication.UnicodeUTF8)) - self.lang.setText(QtGui.QApplication.translate("interfaceForm", "Language:", None, QtGui.QApplication.UnicodeUTF8)) - self.smileys.setText(QtGui.QApplication.translate("interfaceForm", "Smileys", None, QtGui.QApplication.UnicodeUTF8)) - self.smiley_pack_label.setText(QtGui.QApplication.translate("interfaceForm", "Smiley pack:", None, QtGui.QApplication.UnicodeUTF8)) - self.mirror_mode.setText(QtGui.QApplication.translate("interfaceForm", "Mirror mode", None, QtGui.QApplication.UnicodeUTF8)) - self.messages_font_size_label.setText(QtGui.QApplication.translate("interfaceForm", "Messages font size:", None, QtGui.QApplication.UnicodeUTF8)) - self.unread.setText(QtGui.QApplication.translate("interfaceForm", "Select unread messages notification color", None, QtGui.QApplication.UnicodeUTF8)) - self.compact_mode.setText(QtGui.QApplication.translate("interfaceForm", "Compact contact list", None, QtGui.QApplication.UnicodeUTF8)) - self.import_smileys.setText(QtGui.QApplication.translate("interfaceForm", "Import smiley pack", None, QtGui.QApplication.UnicodeUTF8)) - self.import_stickers.setText(QtGui.QApplication.translate("interfaceForm", "Import sticker pack", None, QtGui.QApplication.UnicodeUTF8)) - self.close_to_tray.setText(QtGui.QApplication.translate("interfaceForm", "Close to tray", None, QtGui.QApplication.UnicodeUTF8)) - self.choose_font.setText(QtGui.QApplication.translate("interfaceForm", "Select font", None, QtGui.QApplication.UnicodeUTF8)) + self.show_avatars.setText(QtWidgets.QApplication.translate("interfaceForm", "Show avatars in chat")) + self.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", "Interface settings")) + self.label.setText(QtWidgets.QApplication.translate("interfaceForm", "Theme:")) + self.lang.setText(QtWidgets.QApplication.translate("interfaceForm", "Language:")) + self.smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Smileys")) + self.smiley_pack_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Smiley pack:")) + self.mirror_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Mirror mode")) + self.messages_font_size_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Messages font size:")) + self.unread.setText(QtWidgets.QApplication.translate("interfaceForm", "Select unread messages notification color")) + self.compact_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Compact contact list")) + self.import_smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Import smiley pack")) + self.import_stickers.setText(QtWidgets.QApplication.translate("interfaceForm", "Import sticker pack")) + self.close_to_tray.setText(QtWidgets.QApplication.translate("interfaceForm", "Close to tray")) + self.choose_font.setText(QtWidgets.QApplication.translate("interfaceForm", "Select font")) def import_st(self): - directory = QtGui.QFileDialog.getExistingDirectory(self, - QtGui.QApplication.translate("MainWindow", - 'Choose folder with sticker pack', - None, - QtGui.QApplication.UnicodeUTF8), + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", + 'Choose folder with sticker pack'), curr_directory(), - QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) if directory: src = directory + '/' @@ -687,13 +669,11 @@ class InterfaceSettings(CenteredWidget): copy(src, dest) def import_sm(self): - directory = QtGui.QFileDialog.getExistingDirectory(self, - QtGui.QApplication.translate("MainWindow", - 'Choose folder with smiley pack', - None, - QtGui.QApplication.UnicodeUTF8), + directory = QtWidgets.QFileDialog.getExistingDirectory(self, + QtWidgets.QApplication.translate("MainWindow", + 'Choose folder with smiley pack'), curr_directory(), - QtGui.QFileDialog.ShowDirsOnly | QtGui.QFileDialog.DontUseNativeDialog) + QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) if directory: src = directory + '/' @@ -702,21 +682,19 @@ class InterfaceSettings(CenteredWidget): def new_font(self): settings = Settings.get_instance() - font, ok = QtGui.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self) + font, ok = QtWidgets.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self) if ok: settings['font'] = font.family() settings.save() - msgBox = QtGui.QMessageBox() - text = QtGui.QApplication.translate("interfaceForm", 'Restart app to apply settings', None, - QtGui.QApplication.UnicodeUTF8) - msgBox.setWindowTitle(QtGui.QApplication.translate("interfaceForm", 'Restart required', None, - QtGui.QApplication.UnicodeUTF8)) + msgBox = QtWidgets.QMessageBox() + text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings') + msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required')) msgBox.setText(text) msgBox.exec_() def select_color(self): settings = Settings.get_instance() - col = QtGui.QColorDialog.getColor(settings['unread_color']) + col = QtWidgets.QColorDialog.getColor(QtGui.QColor(settings['unread_color'])) if col.isValid(): name = col.name() @@ -745,7 +723,7 @@ class InterfaceSettings(CenteredWidget): settings['language'] = language text = self.lang_choose.currentText() path = Settings.supported_languages()[text] - app = QtGui.QApplication.instance() + app = QtWidgets.QApplication.instance() app.removeTranslator(app.translator) app.translator.load(curr_directory() + '/translations/' + path) app.installTranslator(app.translator) @@ -753,11 +731,9 @@ class InterfaceSettings(CenteredWidget): Profile.get_instance().update() settings.save() if restart: - msgBox = QtGui.QMessageBox() - text = QtGui.QApplication.translate("interfaceForm", 'Restart app to apply settings', None, - QtGui.QApplication.UnicodeUTF8) - msgBox.setWindowTitle(QtGui.QApplication.translate("interfaceForm", 'Restart required', None, - QtGui.QApplication.UnicodeUTF8)) + msgBox = QtWidgets.QMessageBox() + text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings') + msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required')) msgBox.setText(text) msgBox.exec_() @@ -778,9 +754,9 @@ class AudioSettings(CenteredWidget): self.resize(400, 150) self.setMinimumSize(QtCore.QSize(400, 150)) self.setMaximumSize(QtCore.QSize(400, 150)) - self.in_label = QtGui.QLabel(self) + self.in_label = QtWidgets.QLabel(self) self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - self.out_label = QtGui.QLabel(self) + self.out_label = QtWidgets.QLabel(self) self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20)) settings = Settings.get_instance() font = QtGui.QFont() @@ -789,9 +765,9 @@ class AudioSettings(CenteredWidget): font.setFamily(settings['font']) self.in_label.setFont(font) self.out_label.setFont(font) - self.input = QtGui.QComboBox(self) + self.input = QtWidgets.QComboBox(self) self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.output = QtGui.QComboBox(self) + self.output = QtWidgets.QComboBox(self) self.output.setGeometry(QtCore.QRect(25, 90, 350, 30)) p = pyaudio.PyAudio() self.in_indexes, self.out_indexes = [], [] @@ -808,9 +784,9 @@ class AudioSettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate("audioSettingsForm", "Audio settings", None, QtGui.QApplication.UnicodeUTF8)) - self.in_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Input device:", None, QtGui.QApplication.UnicodeUTF8)) - self.out_label.setText(QtGui.QApplication.translate("audioSettingsForm", "Output device:", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate("audioSettingsForm", "Audio settings")) + self.in_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Input device:")) + self.out_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Output device:")) def closeEvent(self, event): settings = Settings.get_instance() @@ -834,15 +810,15 @@ class PluginsSettings(CenteredWidget): self.resize(400, 210) self.setMinimumSize(QtCore.QSize(400, 210)) self.setMaximumSize(QtCore.QSize(400, 210)) - self.comboBox = QtGui.QComboBox(self) + self.comboBox = QtWidgets.QComboBox(self) self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30)) - self.label = QtGui.QLabel(self) + self.label = QtWidgets.QLabel(self) self.label.setGeometry(QtCore.QRect(30, 40, 340, 90)) self.label.setWordWrap(True) - self.button = QtGui.QPushButton(self) + self.button = QtWidgets.QPushButton(self) self.button.setGeometry(QtCore.QRect(30, 130, 340, 30)) self.button.clicked.connect(self.button_click) - self.open = QtGui.QPushButton(self) + self.open = QtWidgets.QPushButton(self) self.open.setGeometry(QtCore.QRect(30, 170, 340, 30)) self.open.clicked.connect(self.open_plugin) self.pl_loader = plugin_support.PluginLoader.get_instance() @@ -851,8 +827,8 @@ class PluginsSettings(CenteredWidget): self.show_data() def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate('PluginsForm', "Plugins", None, QtGui.QApplication.UnicodeUTF8)) - self.open.setText(QtGui.QApplication.translate('PluginsForm', "Open selected plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate('PluginsForm', "Plugins")) + self.open.setText(QtWidgets.QApplication.translate('PluginsForm', "Open selected plugin")) def open_plugin(self): ind = self.comboBox.currentIndex() @@ -862,11 +838,9 @@ class PluginsSettings(CenteredWidget): self.window = window self.window.show() else: - msgBox = QtGui.QMessageBox() - text = QtGui.QApplication.translate("PluginsForm", 'No GUI found for this plugin', None, - QtGui.QApplication.UnicodeUTF8) - msgBox.setWindowTitle(QtGui.QApplication.translate("PluginsForm", 'Error', None, - QtGui.QApplication.UnicodeUTF8)) + msgBox = QtWidgets.QMessageBox() + text = QtWidgets.QApplication.translate("PluginsForm", 'No GUI found for this plugin') + msgBox.setWindowTitle(QtWidgets.QApplication.translate("PluginsForm", 'Error')) msgBox.setText(text) msgBox.exec_() @@ -880,16 +854,16 @@ class PluginsSettings(CenteredWidget): ind = self.comboBox.currentIndex() if len(self.data): plugin = self.data[ind] - descr = plugin[2] or QtGui.QApplication.translate("PluginsForm", "No description available", None, QtGui.QApplication.UnicodeUTF8) + descr = plugin[2] or QtWidgets.QApplication.translate("PluginsForm", "No description available") self.label.setText(descr) if plugin[1]: - self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin")) else: - self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin")) else: self.open.setVisible(False) self.button.setVisible(False) - self.label.setText(QtGui.QApplication.translate("PluginsForm", "No plugins found", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtWidgets.QApplication.translate("PluginsForm", "No plugins found")) def button_click(self): ind = self.comboBox.currentIndex() @@ -897,9 +871,9 @@ class PluginsSettings(CenteredWidget): self.pl_loader.toggle_plugin(plugin[-1]) plugin[1] = not plugin[1] if plugin[1]: - self.button.setText(QtGui.QApplication.translate("PluginsForm", "Disable plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin")) else: - self.button.setText(QtGui.QApplication.translate("PluginsForm", "Enable plugin", None, QtGui.QApplication.UnicodeUTF8)) + self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin")) class UpdateSettings(CenteredWidget): @@ -917,7 +891,7 @@ class UpdateSettings(CenteredWidget): self.resize(400, 150) self.setMinimumSize(QtCore.QSize(400, 120)) self.setMaximumSize(QtCore.QSize(400, 120)) - self.in_label = QtGui.QLabel(self) + self.in_label = QtWidgets.QLabel(self) self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) settings = Settings.get_instance() font = QtGui.QFont() @@ -925,9 +899,9 @@ class UpdateSettings(CenteredWidget): font.setBold(True) font.setFamily(settings['font']) self.in_label.setFont(font) - self.autoupdate = QtGui.QComboBox(self) + self.autoupdate = QtWidgets.QComboBox(self) self.autoupdate.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.button = QtGui.QPushButton(self) + self.button = QtWidgets.QPushButton(self) self.button.setGeometry(QtCore.QRect(25, 70, 350, 30)) self.button.setEnabled(settings['update']) self.button.clicked.connect(self.update_client) @@ -937,12 +911,12 @@ class UpdateSettings(CenteredWidget): QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate("updateSettingsForm", "Update settings", None, QtGui.QApplication.UnicodeUTF8)) - self.in_label.setText(QtGui.QApplication.translate("updateSettingsForm", "Select update mode:", None, QtGui.QApplication.UnicodeUTF8)) - self.button.setText(QtGui.QApplication.translate("updateSettingsForm", "Update Toxygen", None, QtGui.QApplication.UnicodeUTF8)) - self.autoupdate.addItem(QtGui.QApplication.translate("updateSettingsForm", "Disabled", None, QtGui.QApplication.UnicodeUTF8)) - self.autoupdate.addItem(QtGui.QApplication.translate("updateSettingsForm", "Manual", None, QtGui.QApplication.UnicodeUTF8)) - self.autoupdate.addItem(QtGui.QApplication.translate("updateSettingsForm", "Auto", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate("updateSettingsForm", "Update settings")) + self.in_label.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Select update mode:")) + self.button.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Update Toxygen")) + self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Disabled")) + self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Manual")) + self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Auto")) def closeEvent(self, event): settings = Settings.get_instance() @@ -951,34 +925,29 @@ class UpdateSettings(CenteredWidget): def update_client(self): if not updater.connection_available(): - msgBox = QtGui.QMessageBox() + msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtGui.QApplication.translate("updateSettingsForm", "Error", None, - QtGui.QApplication.UnicodeUTF8)) - text = (QtGui.QApplication.translate("updateSettingsForm", 'Problems with internet connection', None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("updateSettingsForm", "Error")) + text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Problems with internet connection')) msgBox.setText(text) msgBox.exec_() return if not updater.updater_available(): - msgBox = QtGui.QMessageBox() + msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtGui.QApplication.translate("updateSettingsForm", "Error", None, - QtGui.QApplication.UnicodeUTF8)) - text = (QtGui.QApplication.translate("updateSettingsForm", 'Updater not found', None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("updateSettingsForm", "Error")) + text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Updater not found')) msgBox.setText(text) msgBox.exec_() return version = updater.check_for_updates() if version is not None: updater.download(version) - QtGui.QApplication.closeAllWindows() + QtWidgets.QApplication.closeAllWindows() else: - msgBox = QtGui.QMessageBox() + msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle( - QtGui.QApplication.translate("updateSettingsForm", "No updates found", None, QtGui.QApplication.UnicodeUTF8)) - text = (QtGui.QApplication.translate("updateSettingsForm", 'Toxygen is up to date', None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("updateSettingsForm", "No updates found")) + text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Toxygen is up to date')) msgBox.setText(text) msgBox.exec_() diff --git a/toxygen/notifications.py b/toxygen/notifications.py index 20082f6..26a29ec 100644 --- a/toxygen/notifications.py +++ b/toxygen/notifications.py @@ -1,7 +1,4 @@ -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtWidgets from util import curr_directory import wave import pyaudio @@ -23,16 +20,16 @@ def tray_notification(title, text, tray, window): :param tray: ref to tray icon :param window: main window """ - if QtGui.QSystemTrayIcon.isSystemTrayAvailable(): + if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): if len(text) > 30: text = text[:27] + '...' - tray.showMessage(title, text, QtGui.QSystemTrayIcon.NoIcon, 3000) - QtGui.QApplication.alert(window, 0) + tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000) + QtWidgets.QApplication.alert(window, 0) def message_clicked(): window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) window.activateWindow() - tray.connect(tray, QtCore.SIGNAL("messageClicked()"), message_clicked) + tray.messageClicked.connect(message_clicked) class AudioFile: diff --git a/toxygen/passwordscreen.py b/toxygen/passwordscreen.py index dcd9d05..ca721e5 100644 --- a/toxygen/passwordscreen.py +++ b/toxygen/passwordscreen.py @@ -1,8 +1,5 @@ from widgets import CenteredWidget, LineEdit -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtWidgets class PasswordArea(LineEdit): @@ -10,7 +7,7 @@ class PasswordArea(LineEdit): def __init__(self, parent): super(PasswordArea, self).__init__(parent) self.parent = parent - self.setEchoMode(QtGui.QLineEdit.EchoMode.Password) + self.setEchoMode(QtWidgets.QLineEdit.Password) def keyPressEvent(self, event): if event.key() == QtCore.Qt.Key_Return: @@ -31,18 +28,18 @@ class PasswordScreenBase(CenteredWidget): self.setMinimumSize(QtCore.QSize(360, 170)) self.setMaximumSize(QtCore.QSize(360, 170)) - self.enter_pass = QtGui.QLabel(self) + self.enter_pass = QtWidgets.QLabel(self) self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30)) self.password = PasswordArea(self) self.password.setGeometry(QtCore.QRect(30, 50, 300, 30)) - self.button = QtGui.QPushButton(self) + self.button = QtWidgets.QPushButton(self) self.button.setGeometry(QtCore.QRect(30, 90, 300, 30)) self.button.setText('OK') self.button.clicked.connect(self.button_click) - self.warning = QtGui.QLabel(self) + self.warning = QtWidgets.QLabel(self) self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30)) self.warning.setStyleSheet('QLabel { color: #F70D1A; }') self.warning.setVisible(False) @@ -61,9 +58,9 @@ class PasswordScreenBase(CenteredWidget): super(PasswordScreenBase, self).keyPressEvent(event) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate("pass", "Enter password", None, QtGui.QApplication.UnicodeUTF8)) - self.enter_pass.setText(QtGui.QApplication.translate("pass", "Password:", None, QtGui.QApplication.UnicodeUTF8)) - self.warning.setText(QtGui.QApplication.translate("pass", "Incorrect password", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate("pass", "Enter password")) + self.enter_pass.setText(QtWidgets.QApplication.translate("pass", "Password:")) + self.warning.setText(QtWidgets.QApplication.translate("pass", "Incorrect password")) class PasswordScreen(PasswordScreenBase): @@ -116,37 +113,32 @@ class SetProfilePasswordScreen(CenteredWidget): self.setMaximumSize(QtCore.QSize(700, 200)) self.password = LineEdit(self) self.password.setGeometry(QtCore.QRect(40, 10, 300, 30)) - self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password) + self.password.setEchoMode(QtWidgets.QLineEdit.Password) self.confirm_password = LineEdit(self) self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30)) - self.confirm_password.setEchoMode(QtGui.QLineEdit.EchoMode.Password) - self.set_password = QtGui.QPushButton(self) + self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password) + self.set_password = QtWidgets.QPushButton(self) self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30)) self.set_password.clicked.connect(self.new_password) - self.not_match = QtGui.QLabel(self) + self.not_match = QtWidgets.QLabel(self) self.not_match.setGeometry(QtCore.QRect(350, 50, 300, 30)) self.not_match.setVisible(False) self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }') - self.warning = QtGui.QLabel(self) + self.warning = QtWidgets.QLabel(self) self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30)) self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate("PasswordScreen", "Profile password", None, - QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtWidgets.QApplication.translate("PasswordScreen", "Profile password")) self.password.setPlaceholderText( - QtGui.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)")) self.confirm_password.setPlaceholderText( - QtGui.QApplication.translate("PasswordScreen", "Confirm password", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("PasswordScreen", "Confirm password")) self.set_password.setText( - QtGui.QApplication.translate("PasswordScreen", "Set password", None, QtGui.QApplication.UnicodeUTF8)) - self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("PasswordScreen", "Set password")) + self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match")) self.warning.setText( - QtGui.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords")) def new_password(self): if self.password.text() == self.confirm_password.text(): @@ -155,10 +147,8 @@ class SetProfilePasswordScreen(CenteredWidget): self.close() else: self.not_match.setText( - QtGui.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols")) self.not_match.setVisible(True) else: - self.not_match.setText(QtGui.QApplication.translate("PasswordScreen", "Passwords do not match", None, - QtGui.QApplication.UnicodeUTF8)) + self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match")) self.not_match.setVisible(True) diff --git a/toxygen/plugins/plugin_super_class.py b/toxygen/plugins/plugin_super_class.py index 8192b78..c857c56 100644 --- a/toxygen/plugins/plugin_super_class.py +++ b/toxygen/plugins/plugin_super_class.py @@ -1,8 +1,5 @@ import os -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtWidgets MAX_SHORT_NAME_LENGTH = 5 @@ -137,10 +134,10 @@ class PluginSuperClass: :param command: string with command """ if command == 'help': - msgbox = QtGui.QMessageBox() - title = QtGui.QApplication.translate("PluginWindow", "List of commands for plugin {}", None, QtGui.QApplication.UnicodeUTF8) + msgbox = QtWidgets.QMessageBox() + title = QtWidgets.QApplication.translate("PluginWindow", "List of commands for plugin {}") msgbox.setWindowTitle(title.format(self._name)) - msgbox.setText(QtGui.QApplication.translate("PluginWindow", "No commands available", None, QtGui.QApplication.UnicodeUTF8)) + msgbox.setText(QtWidgets.QApplication.translate("PluginWindow", "No commands available")) msgbox.exec_() # ----------------------------------------------------------------------------------------------------------------- @@ -151,7 +148,7 @@ class PluginSuperClass: """ This method loads translations for GUI """ - app = QtGui.QApplication.instance() + app = QtWidgets.QApplication.instance() langs = self._settings.supported_languages() curr_lang = self._settings['language'] if curr_lang in langs: diff --git a/toxygen/profile.py b/toxygen/profile.py index e89bf2b..dd123ed 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1,8 +1,5 @@ from list_items import * -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtGui, QtWidgets from friend import * from settings import * from toxcore_enums_and_consts import * @@ -97,8 +94,7 @@ class Profile(basecontact.BaseContact, Singleton): tmp = self.name super(Profile, self).set_name(value.encode('utf-8')) self._tox.self_set_name(self._name.encode('utf-8')) - message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, - QtGui.QApplication.UnicodeUTF8) + message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') message = message.format(tmp, value) for friend in self._contacts: friend.append_message(InfoMessage(message, time.time())) @@ -246,7 +242,7 @@ class Profile(basecontact.BaseContact, Singleton): if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer try: ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update) + ft.set_state_changed_handler(item.update_transfer_state) ft.signal() except: print('Incoming not started transfer - no info found') @@ -312,7 +308,7 @@ class Profile(basecontact.BaseContact, Singleton): friend.set_name(name) name = str(name, 'utf-8') if friend.name == name and tmp != name: - message = QtGui.QApplication.translate("MainWindow", 'User {} is now known as {}', None, QtGui.QApplication.UnicodeUTF8) + message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') message = message.format(tmp, name) friend.append_message(InfoMessage(message, time.time())) friend.actions = True @@ -566,7 +562,7 @@ class Profile(basecontact.BaseContact, Singleton): if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer try: ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update) + ft.set_state_changed_handler(item.update_transfer_state) ft.signal() except: print('Incoming not started transfer - no info found') @@ -663,17 +659,15 @@ class Profile(basecontact.BaseContact, Singleton): """ friend = self._contacts[num] name = friend.name - dialog = QtGui.QApplication.translate('MainWindow', - "Enter new alias for friend {} or leave empty to use friend's name:", - None, QtGui.QApplication.UnicodeUTF8) + dialog = QtWidgets.QApplication.translate('MainWindow', + "Enter new alias for friend {} or leave empty to use friend's name:") dialog = dialog.format(name) - title = QtGui.QApplication.translate('MainWindow', - 'Set alias', - None, QtGui.QApplication.UnicodeUTF8) + title = QtWidgets.QApplication.translate('MainWindow', + 'Set alias') text, ok = QtGui.QInputDialog.getText(None, title, dialog, - QtGui.QLineEdit.Normal, + QtWidgets.QLineEdit.Normal, name) if ok: settings = Settings.get_instance() @@ -798,9 +792,9 @@ class Profile(basecontact.BaseContact, Singleton): raise Exception('TOX DNS lookup failed') if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key self.add_friend(tox_id) - msgBox = QtGui.QMessageBox() - msgBox.setWindowTitle(QtGui.QApplication.translate("MainWindow", "Friend added", None, QtGui.QApplication.UnicodeUTF8)) - text = (QtGui.QApplication.translate("MainWindow", 'Friend added without sending friend request', None, QtGui.QApplication.UnicodeUTF8)) + msgBox = QtWidgets.QMessageBox() + msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added")) + text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request')) msgBox.setText(text) msgBox.exec_() else: @@ -826,11 +820,11 @@ class Profile(basecontact.BaseContact, Singleton): :param message: message """ try: - text = QtGui.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}', None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') info = text.format(tox_id, message) - fr_req = QtGui.QApplication.translate('MainWindow', 'Friend request', None, QtGui.QApplication.UnicodeUTF8) - reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) - if reply == QtGui.QMessageBox.Yes: # accepted + fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request') + reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: # accepted self.add_friend(tox_id) data = self._tox.get_savedata() ProfileHelper.get_instance().save_profile(data) @@ -942,7 +936,7 @@ class Profile(basecontact.BaseContact, Singleton): if friend_number == self.get_active_number(): item = self.create_file_transfer_item(tm) if accepted: - self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update) + self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state) self._messages.scrollToBottom() else: friend.actions = True @@ -1031,7 +1025,7 @@ class Profile(basecontact.BaseContact, Singleton): self._file_transfers[(friend_number, file_number)] = rt self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) if item is not None: - rt.set_state_changed_handler(item.update) + rt.set_state_changed_handler(item.update_transfer_state) self.get_friend_by_number(friend_number).update_transfer_data(file_number, TOX_FILE_TRANSFER_STATE['RUNNING']) @@ -1070,7 +1064,7 @@ class Profile(basecontact.BaseContact, Singleton): st.get_file_number()) item = self.create_file_transfer_item(tm) friend.append_message(tm) - st.set_state_changed_handler(item.update) + st.set_state_changed_handler(item.update_transfer_state) self._messages.scrollToBottom() def send_file(self, path, number=None, is_resend=False, file_id=None): @@ -1103,7 +1097,7 @@ class Profile(basecontact.BaseContact, Singleton): st.get_file_number()) if friend_number == self.get_active_number(): item = self.create_file_transfer_item(tm) - st.set_state_changed_handler(item.update) + st.set_state_changed_handler(item.update_transfer_state) self._messages.scrollToBottom() self._contacts[friend_number].append_message(tm) @@ -1119,7 +1113,6 @@ class Profile(basecontact.BaseContact, Singleton): """ self._file_transfers[(friend_number, file_number)].send_chunk(position, size) - @QtCore.Slot(int, int) def transfer_finished(self, friend_number, file_number): transfer = self._file_transfers[(friend_number, file_number)] t = type(transfer) @@ -1136,7 +1129,7 @@ class Profile(basecontact.BaseContact, Singleton): if friend_number == self.get_active_number(): count = self._messages.count() if count + i + 1 >= 0: - elem = QtGui.QListWidgetItem() + elem = QtWidgets.QListWidgetItem() item = InlineImageItem(transfer.get_data(), self._messages.width(), elem) elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) self._messages.insertItem(count + i + 1, elem) @@ -1206,11 +1199,9 @@ class Profile(basecontact.BaseContact, Singleton): self._call(num, audio, video) self._screen.active_call() if video: - text = QtGui.QApplication.translate("incoming_call", "Outgoing video call", None, - QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call") else: - text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None, - QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call") self.get_curr_friend().append_message(InfoMessage(text, time.time())) self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self._messages.scrollToBottom() @@ -1225,11 +1216,9 @@ class Profile(basecontact.BaseContact, Singleton): return friend = self.get_friend_by_number(friend_number) if video: - text = QtGui.QApplication.translate("incoming_call", "Incoming video call", None, - QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call") else: - text = QtGui.QApplication.translate("incoming_call", "Incoming audio call", None, - QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call") friend.append_message(InfoMessage(text, time.time())) self._incoming_calls.add(friend_number) if friend_number == self.get_active_number(): @@ -1258,9 +1247,9 @@ class Profile(basecontact.BaseContact, Singleton): """ if friend_number in self._incoming_calls: self._incoming_calls.remove(friend_number) - text = QtGui.QApplication.translate("incoming_call", "Call declined", None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate("incoming_call", "Call declined") else: - text = QtGui.QApplication.translate("incoming_call", "Call finished", None, QtGui.QApplication.UnicodeUTF8) + text = QtWidgets.QApplication.translate("incoming_call", "Call finished") self._screen.call_finished() self._call.finish_call(friend_number, by_friend) # finish or decline call if hasattr(self, '_call_widget'): diff --git a/toxygen/smileys.py b/toxygen/smileys.py index 6031ac4..52cb603 100644 --- a/toxygen/smileys.py +++ b/toxygen/smileys.py @@ -2,10 +2,7 @@ import util import json import os from collections import OrderedDict -try: - from PySide import QtCore -except ImportError: - from PyQt4 import QtCore +from PyQt5 import QtCore class SmileyLoader(util.Singleton): diff --git a/toxygen/styles/style.py b/toxygen/styles/style.py index 61352b0..6e05c3e 100644 --- a/toxygen/styles/style.py +++ b/toxygen/styles/style.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- try: - from PySide import QtCore + from PyQt5 import QtCore except ImportError: from PyQt4 import QtCore diff --git a/toxygen/tox_dns.py b/toxygen/tox_dns.py index ec8582f..76614a4 100644 --- a/toxygen/tox_dns.py +++ b/toxygen/tox_dns.py @@ -2,10 +2,7 @@ import json import urllib.request from util import log import settings -try: - from PySide import QtNetwork, QtCore -except: - from PyQt4 import QtNetwork, QtCore +from PyQt5 import QtNetwork, QtCore def tox_dns(email): diff --git a/toxygen/updater.py b/toxygen/updater.py index e7cd1c2..ba5d1c9 100644 --- a/toxygen/updater.py +++ b/toxygen/updater.py @@ -3,10 +3,7 @@ import os import settings import platform import urllib -try: - from PySide import QtNetwork, QtCore -except ImportError: - from PyQt4 import QtNetwork, QtCore +from PyQt5 import QtNetwork, QtCore import subprocess diff --git a/toxygen/widgets.py b/toxygen/widgets.py index f83e02c..3894782 100644 --- a/toxygen/widgets.py +++ b/toxygen/widgets.py @@ -1,10 +1,7 @@ -try: - from PySide import QtCore, QtGui -except ImportError: - from PyQt4 import QtCore, QtGui +from PyQt5 import QtCore, QtGui, QtWidgets -class DataLabel(QtGui.QLabel): +class DataLabel(QtWidgets.QLabel): """ Label with elided text """ @@ -15,14 +12,14 @@ class DataLabel(QtGui.QLabel): super().setText(text) -class ComboBox(QtGui.QComboBox): +class ComboBox(QtWidgets.QComboBox): def __init__(self, *args): super().__init__(*args) - self.view().setSizePolicy(QtGui.QSizePolicy.MinimumExpanding, QtGui.QSizePolicy.Expanding) + self.view().setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) -class CenteredWidget(QtGui.QWidget): +class CenteredWidget(QtWidgets.QWidget): def __init__(self): super(CenteredWidget, self).__init__() @@ -30,12 +27,12 @@ class CenteredWidget(QtGui.QWidget): def center(self): qr = self.frameGeometry() - cp = QtGui.QDesktopWidget().availableGeometry().center() + cp = QtWidgets.QDesktopWidget().availableGeometry().center() qr.moveCenter(cp) self.move(qr.topLeft()) -class LineEdit(QtGui.QLineEdit): +class LineEdit(QtWidgets.QLineEdit): def __init__(self, parent=None): super(LineEdit, self).__init__(parent) @@ -46,25 +43,27 @@ class LineEdit(QtGui.QLineEdit): del menu -class QRightClickButton(QtGui.QPushButton): +class QRightClickButton(QtWidgets.QPushButton): """ Button with right click support """ + rightClicked = QtCore.pyqtSignal() + def __init__(self, parent): super(QRightClickButton, self).__init__(parent) def mousePressEvent(self, event): if event.button() == QtCore.Qt.RightButton: - self.emit(QtCore.SIGNAL("rightClicked()")) + self.rightClicked.emit() else: super(QRightClickButton, self).mousePressEvent(event) -class RubberBand(QtGui.QRubberBand): +class RubberBand(QtWidgets.QRubberBand): def __init__(self): - super(RubberBand, self).__init__(QtGui.QRubberBand.Rectangle, None) + super(RubberBand, self).__init__(QtWidgets.QRubberBand.Rectangle, None) self.setPalette(QtGui.QPalette(QtCore.Qt.transparent)) self.pen = QtGui.QPen(QtCore.Qt.blue, 4) self.pen.setStyle(QtCore.Qt.SolidLine) @@ -86,29 +85,21 @@ def create_menu(menu): text = action.text() if 'Link Location' in text: text = text.replace('Copy &Link Location', - QtGui.QApplication.translate("MainWindow", "Copy link location", None, - QtGui.QApplication.UnicodeUTF8)) + QtWidgets.QApplication.translate("MainWindow", "Copy link location")) elif '&Copy' in text: - text = text.replace('&Copy', QtGui.QApplication.translate("MainWindow", "Copy", None, - QtGui.QApplication.UnicodeUTF8)) + text = text.replace('&Copy', QtWidgets.QApplication.translate("MainWindow", "Copy")) elif 'All' in text: - text = text.replace('Select All', QtGui.QApplication.translate("MainWindow", "Select all", None, - QtGui.QApplication.UnicodeUTF8)) + text = text.replace('Select All', QtWidgets.QApplication.translate("MainWindow", "Select all")) elif 'Delete' in text: - text = text.replace('Delete', QtGui.QApplication.translate("MainWindow", "Delete", None, - QtGui.QApplication.UnicodeUTF8)) + text = text.replace('Delete', QtWidgets.QApplication.translate("MainWindow", "Delete")) elif '&Paste' in text: - text = text.replace('&Paste', QtGui.QApplication.translate("MainWindow", "Paste", None, - QtGui.QApplication.UnicodeUTF8)) + text = text.replace('&Paste', QtWidgets.QApplication.translate("MainWindow", "Paste")) elif 'Cu&t' in text: - text = text.replace('Cu&t', QtGui.QApplication.translate("MainWindow", "Cut", None, - QtGui.QApplication.UnicodeUTF8)) + text = text.replace('Cu&t', QtWidgets.QApplication.translate("MainWindow", "Cut")) elif '&Undo' in text: - text = text.replace('&Undo', QtGui.QApplication.translate("MainWindow", "Undo", None, - QtGui.QApplication.UnicodeUTF8)) + text = text.replace('&Undo', QtWidgets.QApplication.translate("MainWindow", "Undo")) elif '&Redo' in text: - text = text.replace('&Redo', QtGui.QApplication.translate("MainWindow", "Redo", None, - QtGui.QApplication.UnicodeUTF8)) + text = text.replace('&Redo', QtWidgets.QApplication.translate("MainWindow", "Redo")) else: menu.removeAction(action) continue @@ -124,12 +115,12 @@ class MultilineEdit(CenteredWidget): self.setMinimumSize(QtCore.QSize(350, 200)) self.setMaximumSize(QtCore.QSize(350, 200)) self.setWindowTitle(title) - self.edit = QtGui.QTextEdit(self) + self.edit = QtWidgets.QTextEdit(self) self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150)) self.edit.setText(text) - self.button = QtGui.QPushButton(self) + self.button = QtWidgets.QPushButton(self) self.button.setGeometry(QtCore.QRect(0, 150, 350, 50)) - self.button.setText(QtGui.QApplication.translate("MainWindow", "Save", None, QtGui.QApplication.UnicodeUTF8)) + self.button.setText(QtWidgets.QApplication.translate("MainWindow", "Save")) self.button.clicked.connect(self.button_click) self.center() self.save = save From a20a00130dbcb3fb403b49dfd83bbaff70c18107 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 11 Jun 2017 22:58:11 +0300 Subject: [PATCH 015/163] email notifications disabled --- .travis.yml | 2 ++ toxygen/history.py | 3 --- toxygen/loginscreen.py | 2 -- toxygen/main.py | 2 -- toxygen/tox.py | 1 - toxygen/util.py | 3 +-- 6 files changed, 3 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6002d8f..e79850e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: python python: - "3.4" +notifications: + email: false before_install: - sudo apt-get update - sudo apt-get install -y checkinstall build-essential diff --git a/toxygen/history.py b/toxygen/history.py index fe7d3ee..586981a 100644 --- a/toxygen/history.py +++ b/toxygen/history.py @@ -1,6 +1,3 @@ -# -*- coding: utf-8 -*- - - from sqlite3 import connect import settings from os import chdir diff --git a/toxygen/loginscreen.py b/toxygen/loginscreen.py index b6d0811..fbaa4e3 100644 --- a/toxygen/loginscreen.py +++ b/toxygen/loginscreen.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - try: from PySide import QtCore, QtGui except ImportError: diff --git a/toxygen/main.py b/toxygen/main.py index 44e6cbe..0f6932e 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import sys from loginscreen import LoginScreen import profile diff --git a/toxygen/tox.py b/toxygen/tox.py index 862badd..0545653 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- from ctypes import c_char_p, Structure, c_bool, byref, 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 toxcore_enums_and_consts import * diff --git a/toxygen/util.py b/toxygen/util.py index 468884b..07e78b3 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -1,5 +1,3 @@ -# -*- coding: utf-8 -*- - import os import time import shutil @@ -80,6 +78,7 @@ def append_slash(s): return s +@cached def is_64_bit(): return sys.maxsize > 2 ** 32 From a2273e8c2765439a0d1943553474fc6ded8c030c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 00:26:21 +0300 Subject: [PATCH 016/163] video sending and playing, temporary hardcoded size --- toxygen/callbacks.py | 28 +++++++++++++++++++++++----- toxygen/calls.py | 17 ++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 01a14d4..f3cd71a 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -12,7 +12,6 @@ import util import cv2 import numpy as np - # ----------------------------------------------------------------------------------------------------------------- # Threads # ----------------------------------------------------------------------------------------------------------------- @@ -325,10 +324,29 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data): - pass - #frame = cv2.merge((np.asarray(y), np.asarray(u), np.asarray(v))) - #frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) - #cv2.imshow("frame", frame) + try: + Y = abs(max(width, abs(ystride))) + U = abs(max(width//2, abs(ustride))) + V = abs(max(width//2, abs(vstride))) + y = np.asarray(y[:Y * height], dtype=np.uint8).reshape(height, Y) + u = np.asarray(u[:U * height // 2], dtype=np.uint8).reshape(height // 2, U) + v = np.asarray(v[:V * height // 2], dtype=np.uint8).reshape(height // 2, V) + frame = np.zeros((int(height * 1.5), width), dtype=np.uint8) + + frame[:height,:] = y[:,:width] + #tmp, tmp2 = u[::2,:width], frame[height:height * 5 // 4, :width // 2] + #print(tmp.shape, tmp2.shape + frame[height:height * 5 // 4, :width // 2] = u[:140:2,:width // 2] + frame[height:height * 5 // 4, width // 2:] = u[1:140:2,:width // 2] + + frame[height * 5 // 4 + 1:, :width // 2] = v[:140:2,:width // 2] + frame[height * 5 // 4 + 1:, width // 2:] = v[1:140:2,:width // 2] + + frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) + + invoke_in_main_thread(cv2.imshow, str(friend_number), frame) + except Exception as ex: + print(ex) # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization diff --git a/toxygen/calls.py b/toxygen/calls.py index f2600c9..a771470 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -218,14 +218,17 @@ class AV: def convert_bgr_to_yuv(frame): frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) + y = frame[:480,:].tolist() y = list(itertools.chain.from_iterable(y)) - v = np.zeros((240, 320), dtype=np.int) - v[::2,:] = frame[480:600, :320] - v[1::2,:] = frame[480:600, 320:] - v = list(itertools.chain.from_iterable(v)) + u = np.zeros((240, 320), dtype=np.int) - u[::2,:] = frame[600:, :320] - u[1::2,:] = frame[600:, 320:] + u[::2,:] = frame[480:600, :320] + u[1::2,:] = frame[480:600, 320:] u = list(itertools.chain.from_iterable(u)) - return bytes(y), bytes(v), bytes(u) + + v = np.zeros((240, 320), dtype=np.int) + v[::2,:] = frame[600:, :320] + v[1::2,:] = frame[600:, 320:] + v = list(itertools.chain.from_iterable(v)) + return bytes(y), bytes(u), bytes(v) From c60808a7dace52ba998ad6274a9d254ac0f81bdf Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 00:36:45 +0300 Subject: [PATCH 017/163] cleanup and few todo's --- setup.py | 10 ++++------ toxygen/callbacks.py | 30 ++++++++++++++---------------- toxygen/calls.py | 26 ++++++++++++++------------ 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/setup.py b/setup.py index 4794969..b738a61 100644 --- a/setup.py +++ b/setup.py @@ -11,19 +11,17 @@ version = program_version + '.0' MODULES = ['numpy'] if system() in ('Windows', 'Darwin'): - MODULES.extend['PyAudio', 'PyQt5'] - + MODULES.extend(['PyAudio', 'PyQt5']) else: try: import pyaudio except ImportError: - MODULES.append('PyAudio') + MODULES.append('PyAudio') # TODO: ? +DEP_LINKS = [] if system() == 'Windows': - DEPS_LINKS = [] # TODO: add opencv.whl -else: - DEPS_LINKS = [] + DEP_LINKS = [] # TODO: add opencv.whl class InstallScript(install): diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index f3cd71a..a2ee895 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -325,23 +325,21 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data): try: - Y = abs(max(width, abs(ystride))) - U = abs(max(width//2, abs(ustride))) - V = abs(max(width//2, abs(vstride))) - y = np.asarray(y[:Y * height], dtype=np.uint8).reshape(height, Y) - u = np.asarray(u[:U * height // 2], dtype=np.uint8).reshape(height // 2, U) - v = np.asarray(v[:V * height // 2], dtype=np.uint8).reshape(height // 2, V) + y_size = abs(max(width, abs(ystride))) + u_size = abs(max(width//2, abs(ustride))) + v_size = abs(max(width//2, abs(vstride))) + y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size) + u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size) + v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size) frame = np.zeros((int(height * 1.5), width), dtype=np.uint8) - - frame[:height,:] = y[:,:width] - #tmp, tmp2 = u[::2,:width], frame[height:height * 5 // 4, :width // 2] - #print(tmp.shape, tmp2.shape - frame[height:height * 5 // 4, :width // 2] = u[:140:2,:width // 2] - frame[height:height * 5 // 4, width // 2:] = u[1:140:2,:width // 2] - - frame[height * 5 // 4 + 1:, :width // 2] = v[:140:2,:width // 2] - frame[height * 5 // 4 + 1:, width // 2:] = v[1:140:2,:width // 2] - + + frame[:height, :] = y[:, :width] + frame[height:height * 5 // 4, :width // 2] = u[:140:2, :width // 2] # TODO: remove hardcoded values + frame[height:height * 5 // 4, width // 2:] = u[1:140:2, :width // 2] + + frame[height * 5 // 4 + 1:, :width // 2] = v[:140:2, :width // 2] + frame[height * 5 // 4 + 1:, width // 2:] = v[1:140:2, :width // 2] + frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) invoke_in_main_thread(cv2.imshow, str(friend_number), frame) diff --git a/toxygen/calls.py b/toxygen/calls.py index a771470..5c6ead4 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -7,6 +7,7 @@ import cv2 import itertools import numpy as np # TODO: play sound until outgoing call will be started or cancelled and add timeout +# TODO: rewrite logic class Call: @@ -205,30 +206,31 @@ class AV: height, width, channels = frame.shape for friend_num in self._calls: if self._calls[friend_num].video: - try: # TODO: bgr => yuv + try: y, u, v = convert_bgr_to_yuv(frame) self._toxav.video_send_frame(friend_num, width, height, y, u, v) except Exception as e: - print('1', e) + print(e) except Exception as e: - print('2', e) + print(e) time.sleep(0.01) -def convert_bgr_to_yuv(frame): +def convert_bgr_to_yuv(frame): # TODO: remove hardcoded values and add docs frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) - - y = frame[:480,:].tolist() + + y = frame[:480, :].tolist() y = list(itertools.chain.from_iterable(y)) - + u = np.zeros((240, 320), dtype=np.int) - u[::2,:] = frame[480:600, :320] - u[1::2,:] = frame[480:600, 320:] + u[::2, :] = frame[480:600, :320] + u[1::2, :] = frame[480:600, 320:] u = list(itertools.chain.from_iterable(u)) - + v = np.zeros((240, 320), dtype=np.int) - v[::2,:] = frame[600:, :320] - v[1::2,:] = frame[600:, 320:] + v[::2, :] = frame[600:, :320] + v[1::2, :] = frame[600:, 320:] v = list(itertools.chain.from_iterable(v)) + return bytes(y), bytes(u), bytes(v) From 464fba23c53f0c0fbad5fe819b4eb8a5170e54ee Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 21:14:05 +0300 Subject: [PATCH 018/163] incoming video yuv => bgr fix --- toxygen/callbacks.py | 44 ++++++++++++++++++++++++++++++++++++-------- toxygen/calls.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index a2ee895..f97e980 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -324,21 +324,50 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data): + """ + Creates yuv frame from y, u, v and shows it using OpenCV + For yuv => bgr we need this YUV420 frame: + + width + ------------------------- + | | + | Y | height + | | + ------------------------- + | | | + | U even | U odd | height // 4 + | | | + ------------------------- + | | | + | V even | V odd | height // 4 + | | | + ------------------------- + + width // 2 width // 2 + + It can be created from initial y, u, v using slices + For more info see callback_video_receive_frame docs + """ try: y_size = abs(max(width, abs(ystride))) - u_size = abs(max(width//2, abs(ustride))) - v_size = abs(max(width//2, abs(vstride))) + u_size = abs(max(width // 2, abs(ustride))) + v_size = abs(max(width // 2, abs(vstride))) + y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size) u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size) v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size) + + width -= width % 4 + height -= height % 4 + frame = np.zeros((int(height * 1.5), width), dtype=np.uint8) - frame[:height, :] = y[:, :width] - frame[height:height * 5 // 4, :width // 2] = u[:140:2, :width // 2] # TODO: remove hardcoded values - frame[height:height * 5 // 4, width // 2:] = u[1:140:2, :width // 2] + frame[:height, :] = y[:height, :width] + frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2] + frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2] - frame[height * 5 // 4 + 1:, :width // 2] = v[:140:2, :width // 2] - frame[height * 5 // 4 + 1:, width // 2:] = v[1:140:2, :width // 2] + frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2] + frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2] frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) @@ -382,4 +411,3 @@ def init_callbacks(tox, window, tray): tox.callback_friend_lossless_packet(lossless_packet, 0) tox.callback_friend_lossy_packet(lossy_packet, 0) - diff --git a/toxygen/calls.py b/toxygen/calls.py index 5c6ead4..cf8b0e1 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -217,7 +217,37 @@ class AV: time.sleep(0.01) -def convert_bgr_to_yuv(frame): # TODO: remove hardcoded values and add docs +def convert_bgr_to_yuv(frame): # TODO: remove hardcoded values + """ + :param frame: input bgr frame + :return y, u, v: y, u, v values of frame + + How this function works: + OpenCV creates YUV420 frame from BGR + This frame has following structure and size: + width, height - dim of input frame + width, height * 1.5 - dim of output frame + + width + ------------------------- + | | + | Y | height + | | + ------------------------- + | | | + | U even | U odd | height // 4 + | | | + ------------------------- + | | | + | V even | V odd | height // 4 + | | | + ------------------------- + + width // 2 width // 2 + + Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable() + Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes + """ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) y = frame[:480, :].tolist() From 6d705deb557e9e75bbae2e46513c27fa74424b4e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 21:40:54 +0300 Subject: [PATCH 019/163] calls.py rewriting --- toxygen/calls.py | 172 +++++++++++++++++++++++++++++------------------ 1 file changed, 107 insertions(+), 65 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index cf8b0e1..cde8133 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -7,15 +7,47 @@ import cv2 import itertools import numpy as np # TODO: play sound until outgoing call will be started or cancelled and add timeout -# TODO: rewrite logic class Call: - def __init__(self, audio=False, video=False): - self.audio = audio - self.video = video - # TODO: add widget for call + def __init__(self, out_audio, out_video, in_audio=False, in_video=False): + self._in_audio = in_audio + self._in_video = in_video + self._out_audio = out_audio + self._out_video = out_video + + def get_in_audio(self): + return self._in_audio + + def set_in_audio(self, value): + self._in_audio = value + + in_audio = property(get_in_audio, set_in_audio) + + def get_out_audio(self): + return self._out_audio + + def set_out_audio(self, value): + self._out_audio = value + + out_audio = property(get_out_audio, set_out_audio) + + def get_in_video(self): + return self._in_video + + def set_in_video(self, value): + self._in_video = value + + in_video = property(get_in_video, set_in_video) + + def get_out_video(self): + return self._out_video + + def set_out_video(self, value): + self._in_video = value + + out_video = property(get_out_video, set_out_video) class AV: @@ -41,6 +73,9 @@ class AV: self._video_thread = None self._video_running = False + self._video_width = 640 + self._video_height = 480 + def stop(self): self._running = False self.stop_audio_thread() @@ -57,15 +92,15 @@ class AV: """Call friend with specified number""" self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0) self._calls[friend_number] = Call(audio, video) - self.start_audio_thread() - self.start_video_thread() def accept_call(self, friend_number, audio_enabled, video_enabled): - if self._running: self._calls[friend_number] = Call(audio_enabled, video_enabled) self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0) - self.start_audio_thread() + if audio_enabled: + self.start_audio_thread() + if video_enabled: + self.start_video_thread() def finish_call(self, friend_number, by_friend=False): @@ -73,20 +108,25 @@ class AV: self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL']) if friend_number in self._calls: del self._calls[friend_number] - if not len(self._calls): + if not len(list(filter(lambda c: c.out_audio, self._calls))): self.stop_audio_thread() + if not len(list(filter(lambda c: c.out_video, self._calls))): + self.stop_video_thread() def toxav_call_state_cb(self, friend_number, state): """ New call state """ - pass # TODO: ignore? - # if self._running: - # - # if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_A']: - # self._calls[friend_number].audio = True - # if state & TOXAV_FRIEND_CALL_STATE['ACCEPTING_V']: - # self._calls[friend_number].video = True + call = self._calls[friend_number] + + call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] + call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] + + if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio: + self.start_audio_thread() + + if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video: + self.start_video_thread() # ----------------------------------------------------------------------------------------------------------------- # Threads @@ -136,10 +176,13 @@ class AV: self._video_running = True + self._video_width = 640 # TODO: use settings + self._video_height = 480 + self._video = cv2.VideoCapture(0) self._video.set(cv2.CAP_PROP_FPS, 25) - self._video.set(cv2.CAP_PROP_FRAME_WIDTH, 640) - self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, 480) + self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width) + self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) self._video_thread = threading.Thread(target=self.send_video) self._video_thread.start() @@ -170,9 +213,6 @@ class AV: output=True) self._out_stream.write(samples) - def video_chunk(self): - pass - # ----------------------------------------------------------------------------------------------------------------- # AV sending # ----------------------------------------------------------------------------------------------------------------- @@ -187,7 +227,7 @@ class AV: pcm = self._audio_stream.read(self._audio_sample_count) if pcm: for friend_num in self._calls: - if self._calls[friend_num].audio: + if self._calls[friend_num].out_audio: try: self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count, self._audio_channels, self._audio_rate) @@ -199,15 +239,18 @@ class AV: time.sleep(0.01) def send_video(self): + """ + This method sends video to friends + """ while self._video_running: try: result, frame = self._video.read() if result: height, width, channels = frame.shape for friend_num in self._calls: - if self._calls[friend_num].video: + if self._calls[friend_num].out_video: try: - y, u, v = convert_bgr_to_yuv(frame) + y, u, v = self.convert_bgr_to_yuv(frame) self._toxav.video_send_frame(friend_num, width, height, y, u, v) except Exception as e: print(e) @@ -216,51 +259,50 @@ class AV: time.sleep(0.01) + def convert_bgr_to_yuv(self, frame): + """ + :param frame: input bgr frame + :return y, u, v: y, u, v values of frame -def convert_bgr_to_yuv(frame): # TODO: remove hardcoded values - """ - :param frame: input bgr frame - :return y, u, v: y, u, v values of frame + How this function works: + OpenCV creates YUV420 frame from BGR + This frame has following structure and size: + width, height - dim of input frame + width, height * 1.5 - dim of output frame - How this function works: - OpenCV creates YUV420 frame from BGR - This frame has following structure and size: - width, height - dim of input frame - width, height * 1.5 - dim of output frame + width + ------------------------- + | | + | Y | height + | | + ------------------------- + | | | + | U even | U odd | height // 4 + | | | + ------------------------- + | | | + | V even | V odd | height // 4 + | | | + ------------------------- - width - ------------------------- - | | - | Y | height - | | - ------------------------- - | | | - | U even | U odd | height // 4 - | | | - ------------------------- - | | | - | V even | V odd | height // 4 - | | | - ------------------------- + width // 2 width // 2 - width // 2 width // 2 + Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable() + Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes + """ + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) - Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable() - Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes - """ - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) + y = frame[:self._video_height, :].tolist() + y = list(itertools.chain.from_iterable(y)) - y = frame[:480, :].tolist() - y = list(itertools.chain.from_iterable(y)) + u = np.zeros((self._video_width // 2, self._video_height // 2), dtype=np.int) + u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_height // 2] + u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_height // 2:] + u = list(itertools.chain.from_iterable(u)) - u = np.zeros((240, 320), dtype=np.int) - u[::2, :] = frame[480:600, :320] - u[1::2, :] = frame[480:600, 320:] - u = list(itertools.chain.from_iterable(u)) + v = np.zeros((self._video_width // 2, self._video_height // 2), dtype=np.int) + v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_height // 2] + v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_height // 2:] + v = list(itertools.chain.from_iterable(v)) - v = np.zeros((240, 320), dtype=np.int) - v[::2, :] = frame[600:, :320] - v[1::2, :] = frame[600:, 320:] - v = list(itertools.chain.from_iterable(v)) - - return bytes(y), bytes(u), bytes(v) + return bytes(y), bytes(u), bytes(v) From d1e90c6aefb021b1851f7bc7231974a7ff30103f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 21:50:00 +0300 Subject: [PATCH 020/163] fixed bug with video sending --- toxygen/calls.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index cde8133..f7fa9cc 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -292,17 +292,17 @@ class AV: """ frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) - y = frame[:self._video_height, :].tolist() + y = frame[:self._video_height, :] y = list(itertools.chain.from_iterable(y)) - u = np.zeros((self._video_width // 2, self._video_height // 2), dtype=np.int) - u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_height // 2] - u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_height // 2:] + u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int) + u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2] + u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:] u = list(itertools.chain.from_iterable(u)) - v = np.zeros((self._video_width // 2, self._video_height // 2), dtype=np.int) - v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_height // 2] - v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_height // 2:] + v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int) + v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2] + v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:] v = list(itertools.chain.from_iterable(v)) return bytes(y), bytes(u), bytes(v) From 769119c79515d3be386f469724629fd02d31d277 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 22:32:32 +0300 Subject: [PATCH 021/163] video settings (untested) --- toxygen/menu.py | 46 +++++++++++++++++++++++++++++++++++++++++++++ toxygen/settings.py | 1 + 2 files changed, 47 insertions(+) diff --git a/toxygen/menu.py b/toxygen/menu.py index c3958e9..3e9696f 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -795,6 +795,52 @@ class AudioSettings(CenteredWidget): settings.save() +class VideoSettings(CenteredWidget): + """ + Audio calls settings form + """ + + def __init__(self): + super().__init__() + self.initUI() + self.retranslateUi() + self.center() + + def initUI(self): + self.setObjectName("videoSettingsForm") + self.resize(400, 100) + self.setMinimumSize(QtCore.QSize(400, 100)) + self.setMaximumSize(QtCore.QSize(400, 100)) + self.in_label = QtWidgets.QLabel(self) + self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) + settings = Settings.get_instance() + font = QtGui.QFont() + font.setPointSize(16) + font.setBold(True) + font.setFamily(settings['font']) + self.in_label.setFont(font) + self.input = QtWidgets.QComboBox(self) + self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) + import cv2 + self.devices = [] + for i in range(15): + v = cv2.VideoCapture(i) + if v.isOpened(): + del v + self.devices.append(i) + self.input.addItem('Device #' + str(i)) + self.input.setCurrentIndex(self.in_indexes.index(settings.video['device'])) + + def retranslateUi(self): + self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings")) + self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:")) + + def closeEvent(self, event): + settings = Settings.get_instance() + settings.video['device'] = self.devices[self.input.currentIndex()] + settings.save() + + class PluginsSettings(CenteredWidget): """ Plugins settings form diff --git a/toxygen/settings.py b/toxygen/settings.py index 9604250..c007d3a 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -47,6 +47,7 @@ class Settings(dict, Singleton): self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1, 'output': p.get_default_output_device_info()['index'] if output_devices else -1, 'enabled': input_devices and output_devices} + self.video = {'device': 0} @staticmethod def get_auto_profile(): From ec6c04a7df8858c32b96de5fd3668f07dcb25760 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 22:37:01 +0300 Subject: [PATCH 022/163] video settings to main menu --- toxygen/mainscreen.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 9847d01..1c1e113 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -63,6 +63,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.actionSettings = QtWidgets.QAction(Form) self.actionSettings.setObjectName("actionSettings") self.audioSettings = QtWidgets.QAction(Form) + self.videoSettings = QtWidgets.QAction(Form) self.pluginData = QtWidgets.QAction(Form) self.importPlugin = QtWidgets.QAction(Form) self.reloadPlugins = QtWidgets.QAction(Form) @@ -75,6 +76,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.menuSettings.addAction(self.actionNotifications) self.menuSettings.addAction(self.actionNetwork) self.menuSettings.addAction(self.audioSettings) + self.menuSettings.addAction(self.videoSettings) self.menuSettings.addAction(self.updateSettings) self.menuPlugins.addAction(self.pluginData) self.menuPlugins.addAction(self.importPlugin) @@ -94,6 +96,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.actionInterface_settings.triggered.connect(self.interface_settings) self.actionNotifications.triggered.connect(self.notification_settings) self.audioSettings.triggered.connect(self.audio_settings) + self.videoSettings.triggered.connect(self.video_settings) self.updateSettings.triggered.connect(self.update_settings) self.pluginData.triggered.connect(self.plugins_menu) self.lockApp.triggered.connect(self.lock_app) @@ -459,6 +462,10 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.audio_s = AudioSettings() self.audio_s.show() + def video_settings(self): + self.video_s = VideoSettings() + self.video_s.show() + def update_settings(self): self.update_s = UpdateSettings() self.update_s.show() From 8bc4613407189748989c15cca5d8f3a970caf3ab Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 13 Jun 2017 22:42:05 +0300 Subject: [PATCH 023/163] device selection in settings --- toxygen/calls.py | 2 +- toxygen/mainscreen.py | 1 + toxygen/menu.py | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index f7fa9cc..5898862 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -179,7 +179,7 @@ class AV: self._video_width = 640 # TODO: use settings self._video_height = 480 - self._video = cv2.VideoCapture(0) + self._video = cv2.VideoCapture(settings.Settings.get_instance().video['device']) self._video.set(cv2.CAP_PROP_FPS, 25) self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width) self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 1c1e113..84d266c 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -131,6 +131,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program")) self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings")) self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio")) + self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video")) self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates")) self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message")) diff --git a/toxygen/menu.py b/toxygen/menu.py index 3e9696f..77fd1ea 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -823,13 +823,13 @@ class VideoSettings(CenteredWidget): self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) import cv2 self.devices = [] - for i in range(15): + for i in range(10): v = cv2.VideoCapture(i) if v.isOpened(): del v self.devices.append(i) self.input.addItem('Device #' + str(i)) - self.input.setCurrentIndex(self.in_indexes.index(settings.video['device'])) + self.input.setCurrentIndex(self.devices.index(settings.video['device'])) def retranslateUi(self): self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings")) From 0a378c168215b5b43ed9342c79f379689a1a0d9d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 15 Jun 2017 00:25:16 +0300 Subject: [PATCH 024/163] ui fixes for video --- toxygen/avwidgets.py | 3 +-- toxygen/profile.py | 3 +++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/toxygen/avwidgets.py b/toxygen/avwidgets.py index ff276c4..84f2174 100644 --- a/toxygen/avwidgets.py +++ b/toxygen/avwidgets.py @@ -7,7 +7,6 @@ import wave import settings from util import curr_directory -# TODO: widget for video # TODO: improve IncomingCallWidget @@ -56,7 +55,7 @@ class IncomingCallWidget(widgets.CenteredWidget): self.call_type.setText(text) pr = profile.Profile.get_instance() self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop()) - self.accept_video.clicked.connect(lambda: pr.accept_call(friend_number, True, True)) + self.accept_video.clicked.connect(lambda: pr.accept_call(friend_number, True, True) or self.stop()) self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop()) class SoundPlay(QtCore.QThread): diff --git a/toxygen/profile.py b/toxygen/profile.py index dd123ed..b479cb1 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -14,6 +14,8 @@ import avwidgets import plugin_support import basecontact import items_factory +import cv2 +import threading class Profile(basecontact.BaseContact, Singleton): @@ -1255,6 +1257,7 @@ class Profile(basecontact.BaseContact, Singleton): if hasattr(self, '_call_widget'): self._call_widget[friend_number].close() del self._call_widget[friend_number] + threading.Timer(3.0, lambda: cv2.destroyWindow(str(friend_number))).start() friend = self.get_friend_by_number(friend_number) friend.append_message(InfoMessage(text, time.time())) if friend_number == self.get_active_number(): From 9031a4a3e366b51e4086fc991c9739c3fbbca0a6 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 15 Jun 2017 21:34:43 +0300 Subject: [PATCH 025/163] incoming call widget update --- setup.py | 6 +++--- toxygen/avwidgets.py | 35 +++++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/setup.py b/setup.py index b738a61..82526c1 100644 --- a/setup.py +++ b/setup.py @@ -8,15 +8,15 @@ import sys version = program_version + '.0' -MODULES = ['numpy'] +MODULES = ['numpy', 'PyQt5'] if system() in ('Windows', 'Darwin'): - MODULES.extend(['PyAudio', 'PyQt5']) + MODULES.append('PyAudio') else: try: import pyaudio except ImportError: - MODULES.append('PyAudio') # TODO: ? + MODULES.append('PyAudio') DEP_LINKS = [] diff --git a/toxygen/avwidgets.py b/toxygen/avwidgets.py index 84f2174..9280f2a 100644 --- a/toxygen/avwidgets.py +++ b/toxygen/avwidgets.py @@ -7,8 +7,6 @@ import wave import settings from util import curr_directory -# TODO: improve IncomingCallWidget - class IncomingCallWidget(widgets.CenteredWidget): @@ -21,6 +19,7 @@ class IncomingCallWidget(widgets.CenteredWidget): self.avatar_label.setScaledContents(False) self.name = widgets.DataLabel(self) self.name.setGeometry(QtCore.QRect(90, 20, 300, 25)) + self._friend_number = friend_number font = QtGui.QFont() font.setFamily(settings.Settings.get_instance()['font']) font.setPointSize(16) @@ -53,10 +52,10 @@ class IncomingCallWidget(widgets.CenteredWidget): self.setWindowTitle(text) self.name.setText(name) self.call_type.setText(text) - pr = profile.Profile.get_instance() - self.accept_audio.clicked.connect(lambda: pr.accept_call(friend_number, True, False) or self.stop()) - self.accept_video.clicked.connect(lambda: pr.accept_call(friend_number, True, True) or self.stop()) - self.decline.clicked.connect(lambda: pr.stop_call(friend_number, False) or self.stop()) + self._processing = False + self.accept_audio.clicked.connect(self.accept_call_with_audio) + self.accept_video.clicked.connect(self.accept_call_with_video) + self.decline.clicked.connect(self.decline_call) class SoundPlay(QtCore.QThread): @@ -107,6 +106,30 @@ class IncomingCallWidget(widgets.CenteredWidget): self.thread.wait() self.close() + def accept_call_with_audio(self): + if self._processing: + return + self._processing = True + pr = profile.Profile.get_instance() + pr.accept_call(self._friend_number, True, False) + self.stop() + + def accept_call_with_video(self): + if self._processing: + return + self._processing = True + pr = profile.Profile.get_instance() + pr.accept_call(self._friend_number, True, True) + self.stop() + + def decline_call(self): + if self._processing: + return + self._processing = True + pr = profile.Profile.get_instance() + pr.stop_call(self._friend_number, False) + self.stop() + def set_pixmap(self, pixmap): self.avatar_label.setPixmap(pixmap) From 361f1f0e29938ef61611506cfbd40c12f0b9bfe6 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 16 Jun 2017 18:47:00 +0300 Subject: [PATCH 026/163] call timeout --- toxygen/calls.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index 5898862..0ad8fac 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -6,7 +6,7 @@ from toxav_enums import * import cv2 import itertools import numpy as np -# TODO: play sound until outgoing call will be started or cancelled and add timeout +# TODO: play sound until outgoing call will be started or cancelled class Call: @@ -16,6 +16,19 @@ class Call: self._in_video = in_video self._out_audio = out_audio self._out_video = out_video + self._is_active = False + + def get_is_active(self): + return self._is_active + + def set_is_active(self, value): + self._is_active = value + + is_active = property(get_is_active, set_is_active) + + # ----------------------------------------------------------------------------------------------------------------- + # Audio + # ----------------------------------------------------------------------------------------------------------------- def get_in_audio(self): return self._in_audio @@ -33,6 +46,10 @@ class Call: out_audio = property(get_out_audio, set_out_audio) + # ----------------------------------------------------------------------------------------------------------------- + # Video + # ----------------------------------------------------------------------------------------------------------------- + def get_in_video(self): return self._in_video @@ -92,6 +109,7 @@ class AV: """Call friend with specified number""" self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0) self._calls[friend_number] = Call(audio, video) + threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start() def accept_call(self, friend_number, audio_enabled, video_enabled): if self._running: @@ -103,7 +121,6 @@ class AV: self.start_video_thread() def finish_call(self, friend_number, by_friend=False): - if not by_friend: self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL']) if friend_number in self._calls: @@ -113,11 +130,18 @@ class AV: if not len(list(filter(lambda c: c.out_video, self._calls))): self.stop_video_thread() + def finish_not_started_call(self, friend_number): + if friend_number in self: + call = self._calls[friend_number] + if not call.is_active: + self.finish_call(friend_number) + def toxav_call_state_cb(self, friend_number, state): """ New call state """ call = self._calls[friend_number] + call.is_active = True call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] From 54a2da46706c70ab321c8a9507e9a1e58bbc5b30 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 18 Jun 2017 00:30:08 +0300 Subject: [PATCH 027/163] video setting - selector --- toxygen/menu.py | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/toxygen/menu.py b/toxygen/menu.py index 77fd1ea..ea323d6 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -823,13 +823,25 @@ class VideoSettings(CenteredWidget): self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) import cv2 self.devices = [] + self.frame_max_sizes = [] for i in range(10): v = cv2.VideoCapture(i) if v.isOpened(): + v.set(cv2.CAP_PROP_FRAME_WIDTH, 10000) + v.set(cv2.CAP_PROP_FRAME_HEIGHT, 10000) + + width = int(v.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(v.get(cv2.CAP_PROP_FRAME_HEIGHT)) del v self.devices.append(i) + self.frame_max_sizes.append((width, height)) self.input.addItem('Device #' + str(i)) - self.input.setCurrentIndex(self.devices.index(settings.video['device'])) + self.size = QtWidgets.QComboBox(self) + self.size.setGeometry(QtCore.QRect(60, 30, 350, 30)) + self.input.currentIndexChanged.connect(self.selectionChanged) + index = self.devices.index(settings.video['device']) + if index + 1: + self.input.setCurrentIndex(index) def retranslateUi(self): self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings")) @@ -840,6 +852,22 @@ class VideoSettings(CenteredWidget): settings.video['device'] = self.devices[self.input.currentIndex()] settings.save() + def selectionChanged(self): + width, height = self.frame_max_sizes[self.input.currentIndex()] + self.size.clear() + dims = [ + (320, 240), + (640, 360), + (640, 480), + (720, 480), + (1280, 720), + (1920, 1080), + (2560, 1440) + ] + for w, h in dims: + if w <= width and h <= height: + self.size.addItem(str(w) + ' * ' + str(h)) + class PluginsSettings(CenteredWidget): """ From df5a1a901a326d58fcf587c9f3b4da48eb9faabb Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 18 Jun 2017 00:50:42 +0300 Subject: [PATCH 028/163] video settings --- toxygen/calls.py | 8 ++++---- toxygen/menu.py | 19 +++++++++++-------- toxygen/settings.py | 2 +- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index 0ad8fac..a651a26 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -199,11 +199,11 @@ class AV: return self._video_running = True + s = settings.Settings.get_instance() + self._video_width = s.video['width'] + self._video_height = s.video['height'] - self._video_width = 640 # TODO: use settings - self._video_height = 480 - - self._video = cv2.VideoCapture(settings.Settings.get_instance().video['device']) + self._video = cv2.VideoCapture(s.video['device']) self._video.set(cv2.CAP_PROP_FPS, 25) self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width) self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) diff --git a/toxygen/menu.py b/toxygen/menu.py index ea323d6..4bb5258 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -808,9 +808,9 @@ class VideoSettings(CenteredWidget): def initUI(self): self.setObjectName("videoSettingsForm") - self.resize(400, 100) - self.setMinimumSize(QtCore.QSize(400, 100)) - self.setMaximumSize(QtCore.QSize(400, 100)) + self.resize(400, 120) + self.setMinimumSize(QtCore.QSize(400, 120)) + self.setMaximumSize(QtCore.QSize(400, 120)) self.in_label = QtWidgets.QLabel(self) self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) settings = Settings.get_instance() @@ -819,8 +819,11 @@ class VideoSettings(CenteredWidget): font.setBold(True) font.setFamily(settings['font']) self.in_label.setFont(font) + self.video_size = QtWidgets.QComboBox(self) + self.video_size.setGeometry(QtCore.QRect(25, 70, 350, 30)) self.input = QtWidgets.QComboBox(self) self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) + self.input.currentIndexChanged.connect(self.selectionChanged) import cv2 self.devices = [] self.frame_max_sizes = [] @@ -836,9 +839,6 @@ class VideoSettings(CenteredWidget): self.devices.append(i) self.frame_max_sizes.append((width, height)) self.input.addItem('Device #' + str(i)) - self.size = QtWidgets.QComboBox(self) - self.size.setGeometry(QtCore.QRect(60, 30, 350, 30)) - self.input.currentIndexChanged.connect(self.selectionChanged) index = self.devices.index(settings.video['device']) if index + 1: self.input.setCurrentIndex(index) @@ -850,11 +850,14 @@ class VideoSettings(CenteredWidget): def closeEvent(self, event): settings = Settings.get_instance() settings.video['device'] = self.devices[self.input.currentIndex()] + text = self.video_size.currentText() + settings.video['width'] = int(text.split(' ')[0]) + settings.video['height'] = int(text.split(' ')[-1]) settings.save() def selectionChanged(self): width, height = self.frame_max_sizes[self.input.currentIndex()] - self.size.clear() + self.video_size.clear() dims = [ (320, 240), (640, 360), @@ -866,7 +869,7 @@ class VideoSettings(CenteredWidget): ] for w, h in dims: if w <= width and h <= height: - self.size.addItem(str(w) + ' * ' + str(h)) + self.video_size.addItem(str(w) + ' * ' + str(h)) class PluginsSettings(CenteredWidget): diff --git a/toxygen/settings.py b/toxygen/settings.py index c007d3a..9a1c439 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -47,7 +47,7 @@ class Settings(dict, Singleton): self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1, 'output': p.get_default_output_device_info()['index'] if output_devices else -1, 'enabled': input_devices and output_devices} - self.video = {'device': 0} + self.video = {'device': 0, 'width': 640, 'height': 480} @staticmethod def get_auto_profile(): From 49fc253c19b65ceab08775e20fc50fc9b139330f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 18 Jun 2017 12:43:11 +0300 Subject: [PATCH 029/163] video call icons --- toxygen/images/{videocall.png => call_video.png} | Bin toxygen/images/finish_call_video.png | Bin 0 -> 3112 bytes toxygen/images/incoming_call_video.png | Bin 0 -> 3112 bytes toxygen/list_items.py | 10 ++++------ toxygen/mainscreen.py | 3 +-- toxygen/util.py | 2 +- 6 files changed, 6 insertions(+), 9 deletions(-) rename toxygen/images/{videocall.png => call_video.png} (100%) create mode 100755 toxygen/images/finish_call_video.png create mode 100755 toxygen/images/incoming_call_video.png diff --git a/toxygen/images/videocall.png b/toxygen/images/call_video.png similarity index 100% rename from toxygen/images/videocall.png rename to toxygen/images/call_video.png diff --git a/toxygen/images/finish_call_video.png b/toxygen/images/finish_call_video.png new file mode 100755 index 0000000000000000000000000000000000000000..846510638a42a6c5a02f2af943fb9c9debbd8d45 GIT binary patch literal 3112 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 literal 0 HcmV?d00001 diff --git a/toxygen/images/incoming_call_video.png b/toxygen/images/incoming_call_video.png new file mode 100755 index 0000000000000000000000000000000000000000..4fe4c98de12608600d4d98cf8d5978e32523d79c GIT binary patch literal 3112 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 Date: Sun, 18 Jun 2017 13:05:26 +0300 Subject: [PATCH 030/163] icons fixes --- toxygen/mainscreen.py | 8 +++++--- toxygen/profile.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 669ec65..834ae32 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -567,13 +567,15 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def call_finished(self): self.update_call_state('call') - def update_call_state(self, fl): + def update_call_state(self, state): os.chdir(curr_directory() + '/images/') - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(fl)) + + pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(state)) icon = QtGui.QIcon(pixmap) self.callButton.setIcon(icon) self.callButton.setIconSize(QtCore.QSize(50, 50)) - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png') + + pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png'.format(state)) icon = QtGui.QIcon(pixmap) self.videocallButton.setIcon(icon) self.videocallButton.setIconSize(QtCore.QSize(35, 35)) diff --git a/toxygen/profile.py b/toxygen/profile.py index b479cb1..32d9abd 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1257,7 +1257,7 @@ class Profile(basecontact.BaseContact, Singleton): if hasattr(self, '_call_widget'): self._call_widget[friend_number].close() del self._call_widget[friend_number] - threading.Timer(3.0, lambda: cv2.destroyWindow(str(friend_number))).start() + threading.Timer(2.0, lambda: cv2.destroyWindow(str(friend_number))).start() friend = self.get_friend_by_number(friend_number) friend.append_message(InfoMessage(text, time.time())) if friend_number == self.get_active_number(): From 6cbacef95b671cb662848ba1bbb44778d77e3427 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 18 Jun 2017 17:48:32 +0300 Subject: [PATCH 031/163] travis.yml fix --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 700b449..4c5c585 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,9 +7,11 @@ before_install: - sudo apt-get install -y checkinstall build-essential - sudo apt-get install portaudio19-dev - sudo apt-get install libconfig-dev libvpx-dev check -qq - - sudo apt-get install -y python3-pyqt5 install: + - pip install sip + - pip install pyqt5 - pip install pyaudio + - pip install opencv-python before_script: # OPUS - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz From 4d4fd21fe973cae278c16e10ae269edd20585a0f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 20 Jun 2017 19:41:08 +0300 Subject: [PATCH 032/163] removed audio/video messages functionality. tox dns fixed --- toxygen/avwidgets.py | 26 -------------------------- toxygen/images/audio_message.png | Bin 4295 -> 0 bytes toxygen/images/video_message.png | Bin 3671 -> 0 bytes toxygen/mainscreen_widgets.py | 25 ++++++------------------- toxygen/tox_dns.py | 3 ++- 5 files changed, 8 insertions(+), 46 deletions(-) delete mode 100755 toxygen/images/audio_message.png delete mode 100755 toxygen/images/video_message.png diff --git a/toxygen/avwidgets.py b/toxygen/avwidgets.py index 9280f2a..8c81387 100644 --- a/toxygen/avwidgets.py +++ b/toxygen/avwidgets.py @@ -132,29 +132,3 @@ class IncomingCallWidget(widgets.CenteredWidget): def set_pixmap(self, pixmap): self.avatar_label.setPixmap(pixmap) - - -class AudioMessageRecorder(widgets.CenteredWidget): - - def __init__(self, friend_number, name): - super(AudioMessageRecorder, self).__init__() - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(10, 20, 250, 20)) - text = QtWidgets.QApplication.translate("MenuWindow", "Send audio message to friend {}") - self.label.setText(text.format(name)) - self.record = QtWidgets.QPushButton(self) - self.record.setGeometry(QtCore.QRect(20, 100, 150, 150)) - - self.record.setText(QtWidgets.QApplication.translate("MenuWindow", "Start recording")) - self.record.clicked.connect(self.start_or_stop_recording) - self.recording = False - self.friend_num = friend_number - - def start_or_stop_recording(self): - if not self.recording: - self.recording = True - self.record.setText(QtWidgets.QApplication.translate("MenuWindow", "Stop recording")) - else: - self.close() - - diff --git a/toxygen/images/audio_message.png b/toxygen/images/audio_message.png deleted file mode 100755 index 22ba2a053911650a480dce7444960f1902cfa7b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4295 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 LS3j3^P63~M9S&0}C-;4JWnEM{Qf76xHPhFNnY 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 diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index 74ae20f..ed0d94e 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -207,8 +207,8 @@ class DropdownMenu(QtWidgets.QWidget): super(DropdownMenu, self).__init__(parent) self.installEventFilter(self) self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - self.setMaximumSize(180, 120) - self.setMinimumSize(180, 120) + self.setMaximumSize(120, 120) + self.setMinimumSize(120, 120) self.screenshotButton = QRightClickButton(self) self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60)) self.screenshotButton.setObjectName("screenshotButton") @@ -217,15 +217,9 @@ class DropdownMenu(QtWidgets.QWidget): self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60)) self.fileTransferButton.setObjectName("fileTransferButton") - self.audioMessageButton = QtWidgets.QPushButton(self) - self.audioMessageButton.setGeometry(QtCore.QRect(120, 60, 60, 60)) - self.smileyButton = QtWidgets.QPushButton(self) self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60)) - self.videoMessageButton = QtWidgets.QPushButton(self) - self.videoMessageButton.setGeometry(QtCore.QRect(120, 0, 60, 60)) - self.stickerButton = QtWidgets.QPushButton(self) self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) @@ -233,22 +227,17 @@ class DropdownMenu(QtWidgets.QWidget): icon = QtGui.QIcon(pixmap) self.fileTransferButton.setIcon(icon) self.fileTransferButton.setIconSize(QtCore.QSize(50, 50)) + pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png') icon = QtGui.QIcon(pixmap) self.screenshotButton.setIcon(icon) self.screenshotButton.setIconSize(QtCore.QSize(50, 60)) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/audio_message.png') - icon = QtGui.QIcon(pixmap) - self.audioMessageButton.setIcon(icon) - self.audioMessageButton.setIconSize(QtCore.QSize(50, 50)) + pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png') icon = QtGui.QIcon(pixmap) self.smileyButton.setIcon(icon) self.smileyButton.setIconSize(QtCore.QSize(50, 50)) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/video_message.png') - icon = QtGui.QIcon(pixmap) - self.videoMessageButton.setIcon(icon) - self.videoMessageButton.setIconSize(QtCore.QSize(55, 55)) + pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png') icon = QtGui.QIcon(pixmap) self.stickerButton.setIcon(icon) @@ -256,8 +245,6 @@ class DropdownMenu(QtWidgets.QWidget): self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot")) self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file")) - self.audioMessageButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send audio message")) - self.videoMessageButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send video message")) self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley")) self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker")) @@ -270,7 +257,7 @@ class DropdownMenu(QtWidgets.QWidget): def leaveEvent(self, event): self.close() - def eventFilter(self, object, event): + def eventFilter(self, obj, event): if event.type() == QtCore.QEvent.WindowDeactivate: self.close() return False diff --git a/toxygen/tox_dns.py b/toxygen/tox_dns.py index 76614a4..26b9619 100644 --- a/toxygen/tox_dns.py +++ b/toxygen/tox_dns.py @@ -30,7 +30,8 @@ def tox_dns(email): netman.setProxy(proxy) for url in urls: try: - request = QtNetwork.QNetworkRequest(url) + request = QtNetwork.QNetworkRequest() + request.setUrl(QtCore.QUrl(url)) request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json") reply = netman.post(request, bytes(json.dumps(data), 'utf-8')) From 1b4c211c1d2fd388578c485a87809648617ad6ac Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 20 Jun 2017 20:10:17 +0300 Subject: [PATCH 033/163] version info updated --- toxygen/main.py | 30 +++++++++++++++--------------- toxygen/mainscreen_widgets.py | 23 +++++++++++++---------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/toxygen/main.py b/toxygen/main.py index b7838ef..0074052 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -100,17 +100,17 @@ class Toxygen: msgBox.setWindowTitle( QtWidgets.QApplication.translate("MainWindow", "Error")) text = (QtWidgets.QApplication.translate("MainWindow", - 'Profile with this name already exists')) + 'Profile with this name already exists')) msgBox.setText(text) msgBox.exec_() return self.tox = profile.tox_factory() self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') - self.tox.self_set_status_message(b'Toxing on Toxygen') + self.tox.self_set_status_message(b'Toxing on T03') reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to set profile password?'), + 'Profile {}'.format(name), + QtWidgets.QApplication.translate("login", + 'Do you want to set profile password?'), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: @@ -119,10 +119,10 @@ class Toxygen: self.app.lastWindowClosed.connect(self.app.quit) self.app.exec_() reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to save profile in default folder? If no, profile will be saved in program folder'), - QtWidgets.QMessageBox.Yes, + 'Profile {}'.format(name), + QtWidgets.QApplication.translate("login", + 'Do you want to save profile in default folder? If no, profile will be saved in program folder'), + QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: path = Settings.get_default_path() @@ -135,7 +135,7 @@ class Toxygen: log('Profile creation exception: ' + str(ex)) msgBox = QtWidgets.QMessageBox() msgBox.setText(QtWidgets.QApplication.translate("login", - 'Profile saving error! Does Toxygen have permission to write to this directory?')) + 'Profile saving error! Does Toxygen have permission to write to this directory?')) msgBox.exec_() return path = Settings.get_default_path() @@ -162,8 +162,8 @@ class Toxygen: if Settings.is_active_profile(path, name): # profile is in use reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), + 'Profile {}'.format(name), + QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply != QtWidgets.QMessageBox.Yes: @@ -283,9 +283,9 @@ class Toxygen: updating = True else: reply = QtWidgets.QMessageBox.question(None, - 'Toxygen', - QtWidgets.QApplication.translate("login", - 'Update for Toxygen was found. Download and install it?'), + 'Toxygen', + QtWidgets.QApplication.translate("login", + 'Update for Toxygen was found. Download and install it?'), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) if reply == QtWidgets.QMessageBox.Yes: diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index ed0d94e..1de7b54 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -329,31 +329,34 @@ class WelcomeScreen(CenteredWidget): text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.') elif num == 1: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Right click on screenshot button hides app to tray during screenshot.') + 'Right click on screenshot button hides app to tray during screenshot.') elif num == 2: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'You can use Tox over Tor. For more info read this post') + 'You can use Tox over Tor. For more info read this post') elif num == 3: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use Settings -> Interface to customize interface.') + 'Use Settings -> Interface to customize interface.') elif num == 4: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') + 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') elif num == 5: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Since v0.1.3 Toxygen supports plugins. Read more') - elif num in (6, 7): + 'Since v0.1.3 Toxygen supports plugins. Read more') + elif num == 6: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') + 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') + elif num == 7: + text = QtWidgets.QApplication.translate('WelcomeScreen', + 'New in Toxygen 0.3.0:
Video calls
Python3.6 support
Migration to PyQt5') elif num == 8: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') + 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') elif num == 9: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use right click on inline image to save it') + 'Use right click on inline image to save it') else: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') + 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') self.text.setHtml(text) self.checkbox.stateChanged.connect(self.not_show) QtCore.QTimer.singleShot(1000, self.show) From 1b6b8e043a6581eaaa2f628f58c131caf7d0d479 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 20 Jun 2017 21:31:23 +0300 Subject: [PATCH 034/163] back to menu --- toxygen/mainscreen.py | 86 +++++++++++++++++++------------------------ 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 834ae32..27b884f 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -22,52 +22,47 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): if settings.Settings.get_instance()['show_welcome_screen']: self.ws = WelcomeScreen() - def setup_menu(self, Form): - box = QtWidgets.QHBoxLayout() - box.setContentsMargins(0, 0, 0, 0) - box.setAlignment(QtCore.Qt.AlignLeft) - self.profile_button = MainMenuButton(Form) - box.addWidget(self.profile_button) - self.settings_button = MainMenuButton(Form) - box.addWidget(self.settings_button) - self.plugins_button = MainMenuButton(Form) - box.addWidget(self.plugins_button) - self.about_button = MainMenuButton(Form) - box.addWidget(self.about_button) - box.setSpacing(0) + def setup_menu(self, window): + self.menubar = QtWidgets.QMenuBar(window) + self.menubar.setObjectName("menubar") + self.menubar.setNativeMenuBar(False) + self.menubar.setMinimumSize(self.width(), 25) + self.menubar.setMaximumSize(self.width(), 25) + self.menubar.setBaseSize(self.width(), 25) + self.menuProfile = QtWidgets.QMenu(self.menubar) - self.menuProfile = QtWidgets.QMenu() + self.menuProfile = QtWidgets.QMenu(self.menubar) self.menuProfile.setObjectName("menuProfile") - self.menuSettings = QtWidgets.QMenu() + self.menuSettings = QtWidgets.QMenu(self.menubar) self.menuSettings.setObjectName("menuSettings") - self.menuPlugins = QtWidgets.QMenu() + self.menuPlugins = QtWidgets.QMenu(self.menubar) self.menuPlugins.setObjectName("menuPlugins") - self.menuAbout = QtWidgets.QMenu() + self.menuAbout = QtWidgets.QMenu(self.menubar) self.menuAbout.setObjectName("menuAbout") - self.actionAdd_friend = QtWidgets.QAction(Form) + self.actionAdd_friend = QtWidgets.QAction(window) self.actionAdd_friend.setObjectName("actionAdd_friend") - self.actionprofilesettings = QtWidgets.QAction(Form) + self.actionprofilesettings = QtWidgets.QAction(window) self.actionprofilesettings.setObjectName("actionprofilesettings") - self.actionPrivacy_settings = QtWidgets.QAction(Form) + self.actionPrivacy_settings = QtWidgets.QAction(window) self.actionPrivacy_settings.setObjectName("actionPrivacy_settings") - self.actionInterface_settings = QtWidgets.QAction(Form) + self.actionInterface_settings = QtWidgets.QAction(window) self.actionInterface_settings.setObjectName("actionInterface_settings") - self.actionNotifications = QtWidgets.QAction(Form) + self.actionNotifications = QtWidgets.QAction(window) self.actionNotifications.setObjectName("actionNotifications") - self.actionNetwork = QtWidgets.QAction(Form) + self.actionNetwork = QtWidgets.QAction(window) self.actionNetwork.setObjectName("actionNetwork") - self.actionAbout_program = QtWidgets.QAction(Form) + self.actionAbout_program = QtWidgets.QAction(window) self.actionAbout_program.setObjectName("actionAbout_program") - self.updateSettings = QtWidgets.QAction(Form) - self.actionSettings = QtWidgets.QAction(Form) + self.updateSettings = QtWidgets.QAction(window) + self.actionSettings = QtWidgets.QAction(window) self.actionSettings.setObjectName("actionSettings") - self.audioSettings = QtWidgets.QAction(Form) - self.videoSettings = QtWidgets.QAction(Form) - self.pluginData = QtWidgets.QAction(Form) - self.importPlugin = QtWidgets.QAction(Form) - self.reloadPlugins = QtWidgets.QAction(Form) - self.lockApp = QtWidgets.QAction(Form) + self.audioSettings = QtWidgets.QAction(window) + self.videoSettings = QtWidgets.QAction(window) + self.pluginData = QtWidgets.QAction(window) + self.importPlugin = QtWidgets.QAction(window) + self.reloadPlugins = QtWidgets.QAction(window) + self.lockApp = QtWidgets.QAction(window) self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.lockApp) @@ -83,10 +78,10 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.menuPlugins.addAction(self.reloadPlugins) self.menuAbout.addAction(self.actionAbout_program) - self.profile_button.setMenu(self.menuProfile) - self.settings_button.setMenu(self.menuSettings) - self.plugins_button.setMenu(self.menuPlugins) - self.about_button.setMenu(self.menuAbout) + self.menubar.addAction(self.menuProfile.menuAction()) + self.menubar.addAction(self.menuSettings.menuAction()) + self.menubar.addAction(self.menuPlugins.menuAction()) + self.menubar.addAction(self.menuAbout.menuAction()) self.actionAbout_program.triggered.connect(self.about_program) self.actionNetwork.triggered.connect(self.network_settings) @@ -103,9 +98,6 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.importPlugin.triggered.connect(self.import_plugin) self.reloadPlugins.triggered.connect(self.reload_plugins) - Form.setLayout(box) - QtCore.QMetaObject.connectSlotsByName(Form) - def languageChange(self, *args, **kwargs): self.retranslateUi() @@ -117,11 +109,11 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def retranslateUi(self): self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock")) - self.plugins_button.setText(QtWidgets.QApplication.translate("MainWindow", "Plugins")) + self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins")) self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins")) - self.profile_button.setText(QtWidgets.QApplication.translate("MainWindow", "Profile")) - self.settings_button.setText(QtWidgets.QApplication.translate("MainWindow", "Settings")) - self.about_button.setText(QtWidgets.QApplication.translate("MainWindow", "About")) + self.menuProfile.setTitle(QtWidgets.QApplication.translate("MainWindow", "Profile")) + self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings")) + self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About")) self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact")) self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile")) self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy")) @@ -383,12 +375,8 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): self.close() def resizeEvent(self, *args, **kwargs): - if platform.system() == 'Windows': - self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) - self.friends_list.setGeometry(0, 0, 270, self.height() - 125) - else: - self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 159) - self.friends_list.setGeometry(0, 0, 270, self.height() - 129) + self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) + self.friends_list.setGeometry(0, 0, 270, self.height() - 125) self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50)) self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50)) From 142255ccc80d44857b08b83c87628c7dd2ff4527 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 20 Jun 2017 22:34:24 +0300 Subject: [PATCH 035/163] translations update. docs partial update --- README.md | 2 +- docs/contributing.md | 2 +- docs/plugin_api.md | 4 +- docs/plugins.md | 2 +- toxygen/translations/en_GB.ts | 502 ++++++++------- toxygen/translations/fr_FR.ts | 502 ++++++++------- toxygen/translations/ru_RU.qm | Bin 24591 -> 24613 bytes toxygen/translations/ru_RU.ts | 482 +++++++------- toxygen/translations/uk_UA.ts | 1109 +++++++++++++++++++++++++++++---- 9 files changed, 1733 insertions(+), 872 deletions(-) diff --git a/README.md b/README.md index d2159e9..5a586b0 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu - [x] 1v1 messages - [x] File transfers - [x] Audio calls +- [x] Video calls - [x] Plugins support - [x] Chat history - [x] Emoticons @@ -42,7 +43,6 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu - [x] Changing nospam - [x] File resuming - [x] Read receipts -- [ ] Video calls - [ ] Desktop sharing - [ ] Group chats diff --git a/docs/contributing.md b/docs/contributing.md index 4da55c7..93292aa 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -19,4 +19,4 @@ Note that we have a lot of branches for different purposes. Master branch is for # Translations -Help us translate Toxygen! Translation can be created using pyside-lupdate (``pyside-lupdate toxygen.pro``) and QT Linguist. +Help us translate Toxygen! Translation can be created using pylupdate (``pylupdate5 toxygen.pro``) and QT Linguist. diff --git a/docs/plugin_api.md b/docs/plugin_api.md index 480cb33..d549e68 100644 --- a/docs/plugin_api.md +++ b/docs/plugin_api.md @@ -1,6 +1,6 @@ # Plugins API -In Toxygen plugin is single python (supported Python 3.0 - 3.4) module (.py file) and directory with data associated with it. +In Toxygen plugin is single python (supported Python 3.4 - 3.6) module (.py file) and directory with data associated with it. Every module must contain one class derived from PluginSuperClass defined in [plugin_super_class.py](/src/plugins/plugin_super_class.py). Instance of this class will be created by PluginLoader class (defined in [plugin_support.py](/src/plugin_support.py) ). This class can enable/disable plugins and send data to it. Every plugin has its own full name and unique short name (1-5 symbols). Main app can get it using special methods. @@ -45,7 +45,7 @@ Import statement will not work in case you import module that wasn't previously About GUI: -It's strictly recommended to support both PySide and PyQt4 in GUI. Plugin can have no GUI at all. +GUI is available via PyQt5. Plugin can have no GUI at all. Exceptions: diff --git a/docs/plugins.md b/docs/plugins.md index ef6e5dd..dc8c38d 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -1,6 +1,6 @@ # Plugins -Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 module (.py file) and directory with plugin's data which provide some additional functionality. +Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality. # How to write plugin diff --git a/toxygen/translations/en_GB.ts b/toxygen/translations/en_GB.ts index 0a9871d..7c49649 100644 --- a/toxygen/translations/en_GB.ts +++ b/toxygen/translations/en_GB.ts @@ -1,24 +1,24 @@ - + AddContact - + Add contact Add contact - + TOX ID: TOX ID: - + Message: Message: - + TOX ID or public key of contact @@ -26,7 +26,7 @@ Callback - + File from @@ -34,32 +34,32 @@ Form - + Send request Send request - + IPv6 IPv6 - + UDP UDP - + Proxy Proxy - + IP: IP: - + Port: Port: @@ -69,12 +69,12 @@ Online contacts - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -84,98 +84,98 @@ can produce IP leak MainWindow - + Profile - + Settings - + About - + Add contact - + Privacy - + Interface - + Notifications - + Network - + About program - + User {} wants to add you to contact list. Message: {} - + Friend request - + Choose file Choose file - + Disallow auto accept - + Allow auto accept - + Set alias - + Clear history - + Remove friend - + Enter new alias for friend {} or leave empty to use friend's name: Enter new alias for friend {} or leave empty to use friend's name: - + Audio Audio @@ -185,24 +185,24 @@ can produce IP leak Find contact - + Friend added Friend added - + Toxygen is Tox client written on Python. Version: Toxygen is Tox client written on Python. Version: - + Friend added without sending friend request Friend added without sending friend request - + Choose folder Choose folder @@ -217,280 +217,260 @@ Version: Send file - + Send message Send message - + Start audio call with friend Start audio call with friend - + Plugins - + List of plugins - + Search - + All - + Online - + Notes - + Notes about user - + Copy link location - + Copy - + Select all - + Delete - + Paste - + Cut - + Undo - + Redo - + Save - + User {} is now known as {} - + Delete message - + Lock - + Cannot lock app - + Error. Profile password is not set. - + Name - + Status message - + Public key - + Error - + Profile with this name already exists - + Choose folder with sticker pack - + Choose folder with smiley pack - + Import plugin - + Choose folder with plugin - + Restart Toxygen - + Plugin will be loaded after restart - + Quote selected text - + Chat history - + Export as text - + Export as HTML - + Updates - + Online first - + Online and by name - + Online first and by name - + Block friend - + Not found - + Text "{}" was not found - + Reload plugins + + + Video + + MenuWindow - - Send audio message to friend {} - - - - - Start recording - - - - - Stop recording - - - - + Send screenshot Send screenshot - + Send file Send file - - Send audio message - - - - - Send video message - - - - + Add smiley - + Send sticker @@ -498,12 +478,12 @@ Version: NetworkSettings - + Network settings Network settings - + Restart TOX core Restart Tox core @@ -511,37 +491,37 @@ Version: PasswordScreen - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + There is no way to recover lost passwords - + Password must be at least 8 symbols @@ -549,12 +529,12 @@ Version: PluginWindow - + List of commands for plugin {} - + No commands available @@ -562,42 +542,42 @@ Version: PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + No description available - + Disable plugin - + Enable plugin - + No plugins found - + Error @@ -605,132 +585,132 @@ Version: ProfileSettingsForm - + Export profile - + Profile settings - + Name: - + Status: - + TOX ID: - + Copy TOX ID - + New avatar - + Reset avatar - + New NoSpam New NoSpam - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + Leaving blank will reset current password - + There is no way to recover lost passwords - + Password must be at least 8 symbols - + Choose avatar - + Online - + Away - + Busy - + Mark as not default profile - + Mark as default profile - + Copy public key - + Use new path - + Do you want to move your profile to this location? @@ -738,80 +718,85 @@ Version: WelcomeScreen - + Don't show again - + Tip of the day - + Press Esc if you want hide app to tray. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. - - Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a> - - - - + Right click on screenshot button hides app to tray during screenshot. - + Use Settings -> Interface to customize interface. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu - + Use right click on inline image to save it + + + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> + + + + + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 + + audioSettingsForm - + Audio settings Audio settings - + Input device: Input device: - + Output device: Output device: @@ -819,32 +804,32 @@ Version: incoming_call - + Incoming video call Incoming video call - + Incoming audio call Incoming audio call - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -852,82 +837,82 @@ Version: interfaceForm - + Interface settings - + Theme: - + Language: - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Restart app to apply settings - + Restart required - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Show avatars in chat - + Close to tray - + Select font @@ -935,72 +920,72 @@ Version: login - + Log in - + Create - + Profile name: - + Load profile - + Use as default - + Load existing profile - + Create new profile - + toxygen - + Profile name - + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? - + Do you want to set profile password? - + Do you want to save profile in default folder? If no, profile will be saved in program folder - + Profile saving error! Does Toxygen have permission to write to this directory? - + Update for Toxygen was found. Download and install it? @@ -1008,22 +993,22 @@ Version: notificationsForm - + Notification settings - + Enable notifications - + Enable call's sound - + Enable sound notifications @@ -1031,17 +1016,17 @@ Version: pass - + Enter password - + Password: - + Incorrect password @@ -1049,72 +1034,72 @@ Version: privacySettings - + Privacy settings - + Save chat history - + Allow file auto accept - + Send typing notifications - + Auto accept default path: - + Change - + Allow inlines - + Chat history - + History will be cleaned! Continue? - + Blocked users: Blocked users: - + Unblock Unblock - + Block user Block user - + Add to friend list Add to friend list - + Do you want to add this user to friend list? Do you want to add this user to friend list? @@ -1124,12 +1109,12 @@ Version: Block by TOX ID: - + Block by public key: - + Save unsent messages only @@ -1137,32 +1122,32 @@ Version: tray - + Open Toxygen - + Exit - + Set status - + Online - + Away - + Busy @@ -1170,59 +1155,72 @@ Version: updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date + + videoSettingsForm + + + Video settings + + + + + Device: + + + diff --git a/toxygen/translations/fr_FR.ts b/toxygen/translations/fr_FR.ts index 16f37e7..4eacf6a 100644 --- a/toxygen/translations/fr_FR.ts +++ b/toxygen/translations/fr_FR.ts @@ -1,24 +1,24 @@ - + AddContact - + Add contact Rajouter un contact - + TOX ID: ID TOX : - + Message: Message : - + TOX ID or public key of contact @@ -26,7 +26,7 @@ Callback - + File from @@ -34,32 +34,32 @@ Form - + Send request Envoyer une demande - + IPv6 IPv6 - + UDP UDP - + Proxy Proxy - + IP: IP : - + Port: Port : @@ -69,12 +69,12 @@ Contacts connectés - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -84,58 +84,58 @@ can produce IP leak MainWindow - + Profile Profile - + Settings Paramêtres - + About À Propos - + Add contact Rajouter un contact - + Privacy Confidentialité - + Interface Interface - + Notifications Notifications - + Network Réseau - + About program À propos du programme - + User {} wants to add you to contact list. Message: {} L'Utilisateur {} veut vout rajouter à sa liste de contacts. Message : {} - + Friend request Demande d'amis @@ -145,27 +145,27 @@ can produce IP leak Toxygen est un client Tox écris en Python 2.7. Version : - + Choose file Choisir un fichier - + Disallow auto accept Désactiver l'auto-réception - + Allow auto accept Activer l'auto-réception - + Set alias Définir un alias - + Clear history Vider l'historique @@ -175,17 +175,17 @@ can produce IP leak Copier la clé publique - + Remove friend Retirer un ami - + Enter new alias for friend {} or leave empty to use friend's name: Entrez un nouvel alias pour l'ami {} ou laissez vide pour garder son nom de base : - + Audio Audio @@ -195,24 +195,24 @@ can produce IP leak Trouver le contact - + Friend added Ami rajouté - + Toxygen is Tox client written on Python. Version: Toxygen est un client Tox écrit en Python. Version : - + Friend added without sending friend request Ami rajouté sans avoir envoyé de demande - + Choose folder Choisir le dossier @@ -227,280 +227,260 @@ Version : Envoyer le fichier - + Send message Envoyer le message - + Start audio call with friend Lancer un appel audio avec un ami - + Plugins - + List of plugins - + Search - + All - + Online - + Notes - + Notes about user - + Copy link location - + Copy - + Select all - + Delete - + Paste - + Cut - + Undo - + Redo - + Save - + User {} is now known as {} - + Delete message - + Lock - + Cannot lock app - + Error. Profile password is not set. - + Name - + Status message - + Public key - + Error - + Profile with this name already exists - + Choose folder with sticker pack - + Choose folder with smiley pack - + Import plugin - + Choose folder with plugin - + Restart Toxygen - + Plugin will be loaded after restart - + Quote selected text - + Chat history Historique de chat - + Export as text - + Export as HTML - + Updates - + Online first - + Online and by name - + Online first and by name - + Block friend - + Not found - + Text "{}" was not found - + Reload plugins + + + Video + + MenuWindow - - Send audio message to friend {} - - - - - Start recording - - - - - Stop recording - - - - + Send screenshot Envoyer une capture d'écran - + Send file Envoyer le fichier - - Send audio message - - - - - Send video message - - - - + Add smiley - + Send sticker @@ -508,12 +488,12 @@ Version : NetworkSettings - + Network settings Paramètres réseaux - + Restart TOX core Relancer le noyau TOX @@ -521,37 +501,37 @@ Version : PasswordScreen - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + There is no way to recover lost passwords - + Password must be at least 8 symbols @@ -559,12 +539,12 @@ Version : PluginWindow - + List of commands for plugin {} - + No commands available @@ -572,42 +552,42 @@ Version : PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + No description available - + Disable plugin - + Enable plugin - + No plugins found - + Error @@ -615,132 +595,132 @@ Version : ProfileSettingsForm - + Export profile Exporter le profile - + Profile settings Paramêtres du profil - + Name: Nom : - + Status: Status : - + TOX ID: ID TOX : - + Copy TOX ID Copier l'ID TOX - + New avatar Nouvel avatar - + Reset avatar Réinitialiser l'avatar - + New NoSpam Nouveau NoSpam - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + Leaving blank will reset current password - + There is no way to recover lost passwords - + Password must be at least 8 symbols - + Choose avatar - + Online - + Away - + Busy - + Mark as not default profile - + Mark as default profile - + Copy public key Copier la clé publique - + Use new path - + Do you want to move your profile to this location? @@ -748,80 +728,85 @@ Version : WelcomeScreen - + Don't show again - + Tip of the day - + Press Esc if you want hide app to tray. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. - - Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a> - - - - + Right click on screenshot button hides app to tray during screenshot. - + Use Settings -> Interface to customize interface. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu - + Use right click on inline image to save it + + + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> + + + + + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 + + audioSettingsForm - + Audio settings Paramètres audio - + Input device: Péripherique d'entrée : - + Output device: Péripherique de sortie : @@ -829,32 +814,32 @@ Version : incoming_call - + Incoming video call Appel vidéo entrant - + Incoming audio call Appel audio entrant - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -862,82 +847,82 @@ Version : interfaceForm - + Interface settings Paramêtres de l'interface - + Theme: Thème : - + Language: Langue : - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Restart app to apply settings - + Restart required - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Show avatars in chat - + Close to tray - + Select font @@ -945,42 +930,42 @@ Version : login - + Log in Se connecter - + Create Créer - + Profile name: Nom du profil : - + Load profile Charger le profil - + Use as default Utiliser par défaut - + Load existing profile Charger un profil existant - + Create new profile Créer un nouveau profil - + toxygen toxygen @@ -990,32 +975,32 @@ Version : Il semble qu'une autre instance de Toxygen utilise ce profil ! Continuer ? - + Profile name - + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? - + Do you want to set profile password? - + Do you want to save profile in default folder? If no, profile will be saved in program folder - + Profile saving error! Does Toxygen have permission to write to this directory? - + Update for Toxygen was found. Download and install it? @@ -1023,22 +1008,22 @@ Version : notificationsForm - + Notification settings Paramêtres de notification - + Enable notifications Activer les notifications - + Enable call's sound Activer les sons d'appel - + Enable sound notifications Activer les sons de notifications @@ -1046,17 +1031,17 @@ Version : pass - + Enter password - + Password: - + Incorrect password @@ -1064,72 +1049,72 @@ Version : privacySettings - + Privacy settings Paramêtres de confidentialité - + Save chat history Sauvegarder l'historique de chat - + Allow file auto accept Autoriser les fichier automatiquement - + Send typing notifications Notifier la frappe - + Auto accept default path: Chemin d'accès des fichiers acceptés automatiquement : - + Change Modifier - + Allow inlines Activer l'auto-réception - + Chat history Historique de chat - + History will be cleaned! Continue? L'Historique va être nettoyé ! Confirmer ? - + Blocked users: Utilisateurs bloqués : - + Unblock Débloquer - + Block user Bloquer l'utilisateur - + Add to friend list Ajouter à la liste des amis - + Do you want to add this user to friend list? Voulez vous rajouter cet utilisateur à votre liste d'amis ? @@ -1139,12 +1124,12 @@ Version : Bloquer l'ID TOX : - + Block by public key: - + Save unsent messages only @@ -1152,32 +1137,32 @@ Version : tray - + Open Toxygen Ouvrir Toxygen - + Exit Quitter - + Set status - + Online - + Away - + Busy @@ -1185,59 +1170,72 @@ Version : updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date + + videoSettingsForm + + + Video settings + + + + + Device: + + + diff --git a/toxygen/translations/ru_RU.qm b/toxygen/translations/ru_RU.qm index 25507b2e47b2914bc7459f6e28e25b44990467c4..4f36c6d4374c338edd267f81499026da7a2b9d10 100644 GIT binary patch delta 1996 zcmeA_z_|1P;{*{#fr+AOMj?wCSb94d7?iCcbZ{X9%bb-A49fKoI(Qia%d$!a1`clq zmh&tO48dn7=Gt?{FmT%5V_*o8pSaabNYRnOXJ!%uL#QT0NOKnhgZl1?e-+bXe(h&q zkXp+yeHse`L&yt;qh7@f41vB3r)_u`7;H)yPDdPIU|@}BxPI+60|R?ABje^@3=Hl^ z85Q++GBDWoGe-6(F)%nTVvJ>1W?*peVJy0_g@M6mDP!5Ja|{gJp^W>ulo=SbrcJJ8 z6tAym@?JcJfq|8eDfnp$1A~$uQ`xjw1_nlJru|`G7#MhFG9B0z%D~|IhUt3!VFm^n zex?svkqivN%a}gbIx{eMb1*ZAJz`+c`oJvqZ8HNy#B^rOtU3k;rA5p>+mjd=oKG=F z)U`4&D1Km0YMRHuV7Q98^|L1fLy#ErDV`||3`w5Xm_J-O%D|wj$oz}Rj)8%riG|tS zl7T^13PNk_V+ryrVPFt^#F94sDFcJF3QOkxuM7;%B`jro=Q1!zUSw$(|IfhS%Fi{!ac;OsWJmPx!ml~qK&gn_|FhgH#NCj$ffe^%3J{}>qD zOjs?VYZw?p6Iq=SEEyP7cC!Y)ddt9|{(&{sz?FePpbA23v_R| z>@5H0F)--OWas++nSsHofn7k_l!3v$bMjthNqx(oPZ=2WKC)XsU}j(tDuU3;Z0tT3 z%nS@cOW7;SUokNF*|Rr`lrS*(a!>xtEM6bTzI?_?1_r6y?CZrBGcbfCv2V`W&%ofx z#(t^7gn_~O6#I34J_ZI82lf{aoEaG8wIQ?)9|w!J9Rq{H3J#w6Ees46rW}oHA2Trc zHge3g7G_}ZTE;Q|(*p(u2{umVxTy>bs<${{G2>BIS~IEsP6e%9o-EaLUiTn@Uy3=B@Uxg3Q!7#QRXAhhN^F7JId3=Fmg zT=9~u3=FQbxl(p?GcYhs;c6BUVPLR*#MNe&!oa|Dk88I5E(QjxOI$1VSTQhYPvKf; z_ltqSrqD_(HkP{5-|L5D~)lcw-3zgYau^zO{=P7?MQVxJ~rW zF)%26UIR*wVG47Hnehds2AGxO({A6HYy$qrK_j51)!^6NJdY^mM zx7`d3cB{D82IMg?m}hZsGZJNB5LnH9vF;rMgJKbnr|D@1hG4D9Z&}6bTX|$VG z@Z#CcSj@m6wwLErgAD_NjX2K(sm}}y0v0@9OP4S(gn!^=TdKmq;Qf+Uh3h;6g9i(* zUe|gChJf|FrVRHO7z`HjnjL(?z#w-ILTfwmhWxt1z`&%-8*^2efq}1|cW&A(1_s|N zyvO797#PAGd2d>7Wnicmbme_5W6Hpwc9{3?Bozh*hE_hA87T}5B6s<;`nNJL$i(yM zOWH9o7_jhp{Q^1cBH!dC`xqFE9`RkO)?#3A>g6|nzKMZB`Ut;^^Ct!dwkZC{G!_O1 zmN@>Z$D0@!l(+JCCT(G0;GD|8^2QVf1}|6sle*dr40e9}7hddV&|pwi;lJqqgn>cq z8~>v?6$S?B4E|>(tPBjCZv21K-ZL-+uxBzbM7*4QUr}PS7yC>pDJ~Wv77G>=7E=~G z4h9AW1~vu;2G+35l+^smiu_`epDMafW>l<{TCR|prx21~QJJ2ar(mFGtY=_llT>5}_MJjMLR|u#KEHPzgnEcsVWb{li*gx3TI41h*o)|}7-7U2AH-M1 NPz?wpCVz;R2LJ+}6Wss+ delta 1985 zcmZ2_fU*Ao;{*{#iHV|WMxjy+EWMo!3@Q&GbnpuXmN_dK7*tgtbciei%d$!a1`clq zmh&tO3?VKPbL}}VGjQ78V_*oGHgT(&kkT^-pP5Mv4514cLYli67&H?m{#C4>K8=Ne zAvB)hs8=xqL*NmH(>6Q|3^uPBPDdPIU|@}BxPI+60|R?ABje^@3=AGNjEed@85r!? z7$bX>7#N%+8DrU%85kT6GZx+0!oc7w!&o-!90LP)DC0gZWd;TvYsTZodJGJr%1m6A zPZ$^k{xK;ig)lH!e`E4qJcWUQm9L&D_-P6QgYsvlvT3ml42;%H`@_C4F!0P|I3aQP1_s$JOdqr&85o3@F@3IeW?=BHXZqI{&cGlm%giA5h=D<8Dl^w*5I>b! zGpmk)LB*ZfXL}L@gNq|`L|rQbgYs17q^5Zc3`RE0t)D#^7=n74Pw`A)VDL)xW&UvG zCKT-#VCUz^3i;6H(7UdvSm2LC@SD|RepU~t|$xt2-1{tl~%dIb%(nc7@WIVkJ*1_ zVDPeUV7+%Zg@HkhoAqP*R0amc&ukozr!X)$Rrb#RpRtmGL251gdhx{!3?Vn!H)riff*85n$jaLlw8W?=A^<(U8J0Rw{s8z*zz zR0an1dQO&&UlQz!0Rwc{yql1B1UF=gq0Q3=EP!oX?7* z7#JJ`xs(`>FfcGMKxpPST=o)A7#JKcaXIJ)GcY&@aXAWcFfhnph0xl~T;BU^7#M7q zbHz)tGSoA;32~+D=w@JGn!?pABErC67s=ITmcqcmbB}Ab{VoOuYfr8fd#o53bS=5o zPWZ~eAhU;So!u`62A?-v2h2Sg7|iOp4!XuMFqmB8Ix+ny0|Q?u*W-;P3=G1rx%t*E zW?&F$<2KPh$H1U8n>%YlECYjL4R`+Ea|{e#z1$^J{9G6qEHk*L82n^lV7&~X1I)M= z|KVX^5WUa6>f3Gx275*BwE=ky4CW8Hw;72tFbJ&XzF7B;fk9cE$J6vQ14Hn_$!}T3 z>wojCtk}iCz~IHRo3WUIL2NJ2sRkPc2Ae*f2U4FI7z8YMzLqXwV2DWPWm~Gkz~Ga> ztHO1jfx)AiSFdY514E!1uPMVl1_nc8UbBNw7#I|qA+*kO-jH8c7#Nszd1J0BGcfS= z^Uh7X#lYa_&3imvkAWe42k%YGtqctHg08%;WlR|uG%|SqPEuiDU})u&nUTW4Aaa*a ztA8s4gB%N=zN8%kgTXXDuV0`D@Zg)gWFG^AaS7k0YApr^XBNIU0Y@1aq`vVRKi|Z_ zAXCWi;{1t$fh~$ZGL40SfhCT=>hUH92GuD3&ZI3244hN>SKa^>MSJ*9>S{9>FxVaC zzwlx|1B2R0{)_HU7#PI9@jr@FVPKFE<$q?v%D}+s#{W0%Jp)5PT_yuV#Ocji>>H&f zOL2=#mQ^xkG??r!D{5xI;>=>gV!>j^;>cphV#i|0;>lvlV#{K|V#)zZVr&cy3_`)F zc_|8sr74;D3c0Dp#fj;Y8 AddContact - + Add contact Добавить контакт - + TOX ID: TOX ID: - + Message: Сообщение: - + TOX ID or public key of contact TOX ID или публичный ключ контакта @@ -27,7 +27,7 @@ Callback - + File from Файл от @@ -35,32 +35,32 @@ Form - + Send request Отправить запрос - + IPv6 IPv6 - + UDP UDP - + Proxy Прокси - + IP: IP: - + Port: Порт: @@ -70,12 +70,12 @@ Контакты в сети - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -87,84 +87,84 @@ can produce IP leak MainWindow - + Profile Профиль - + Settings Настройки - + About О программе - + Add contact Добавить контакт - + Privacy Приватность - + Interface Интерфейс - + Notifications Уведомления - + Network Сеть - + About program О программе - + User {} wants to add you to contact list. Message: {} Пользователь {} хочет добавить Вас в список контактов. Сообщение: {} - + Friend request Запрос на добавление в друзья - + Choose file Выберите файл - + Disallow auto accept Запретить автоматическое получение файлов - + Allow auto accept Разрешить автоматическое получение файлов - + Set alias Изменить псевдоним - + Clear history Очистить историю @@ -174,17 +174,17 @@ can produce IP leak Копировать публичный ключ - + Remove friend Удалить друга - + Enter new alias for friend {} or leave empty to use friend's name: Введите новый псевдоним для друга {} или оставьте пустым для использования его имени: - + Audio Аудио @@ -194,23 +194,23 @@ can produce IP leak Найти контакт - + Friend added Друг добавлен - + Toxygen is Tox client written on Python. Version: Toxygen - клиент для мессенджера Tox, написанный на Python. Версия: - + Friend added without sending friend request Друг добавлен без отправки запроса на добавление в друзья - + Choose folder Выбрать папку @@ -225,280 +225,285 @@ Version: Отправить файл - + Send message Отправить сообщение - + Start audio call with friend Начать аудиозвонок с другом - + Plugins Плагины - + List of plugins Список плагинов - + Search Поиск - + All Все - + Online Онлайн - + Notes Заметки - + Notes about user Заметки о пользователе - + Copy link location Копировать адрес ссылки - + Copy Копировать - + Select all Выделить всё - + Delete Удалить - + Paste Вставить - + Cut Вырезать - + Undo Отменить - + Redo Повторить - + Save Сохранить - + User {} is now known as {} Пользователь {} сейчас известен как {} - + Delete message Удалить сообщение - + Lock Заблокировать - + Cannot lock app Невозможно заблокировать приложение - + Error. Profile password is not set. Ошибка. Пароль профиля не установлен. - + Name Имя - + Status message Статус - + Public key Публичный ключ - + Error Ошибка - + Profile with this name already exists Профиль с данным именем уже существует - + Choose folder with sticker pack Выберите папку в паком стикеров - + Choose folder with smiley pack Выберите папку с паком смайлов - + Import plugin Импортировать плагин - + Choose folder with plugin Выберите папку с плагином - + Restart Toxygen Перезапустите Toxygen - + Plugin will be loaded after restart Плагин будет загружен после перезапуска - + Quote selected text Цитировать выбранный текст - + Chat history История чата - + Export as text Экспортировать как текст - + Export as HTML Экспортировать как HTML - + Updates Обновления - + Online first Сначала онлайн - + Online and by name Онлайн и по имени - + Online first and by name Сначала онлайн и по имени - + Block friend Заблокировать друга - + Not found Не найдено - + Text "{}" was not found Текст "{}" не был найден - + Reload plugins Перезагрузить плагины + + + Video + Видео + MenuWindow Send audio message to friend {} - Отправить аудиосообщение другу + Отправить аудиосообщение другу Start recording - Начать запись + Начать запись Stop recording - Остановить запись + Остановить запись - + Send screenshot Отправить снимок экрана - + Send file Отправить файл Send audio message - Отправить аудиосообщение + Отправить аудиосообщение Send video message - Отправить видеосообщение + Отправить видеосообщение - + Add smiley Добавить смайлик - + Send sticker Отправить стикер @@ -506,12 +511,12 @@ Version: NetworkSettings - + Network settings Настройки сети - + Restart TOX core Перезапустить ядро TOX @@ -519,37 +524,37 @@ Version: PasswordScreen - + Profile password Пароль профиля - + Password (at least 8 symbols) Пароль (минимум 8 символов) - + Confirm password Подтверждение пароля - + Set password Изменить пароль - + Passwords do not match Пароли не совпадают - + There is no way to recover lost passwords Восстановление забытых паролей не поддерживается - + Password must be at least 8 symbols Пароль должен быть длиной не менее 8 символов @@ -557,12 +562,12 @@ Version: PluginWindow - + List of commands for plugin {} Список команд для плагина {} - + No commands available Команды не найдены @@ -570,42 +575,42 @@ Version: PluginsForm - + Plugins Плагины - + Open selected plugin Открыть выбранный плагин - + No GUI found for this plugin GUI для данного плагина не найден - + No description available Описание недоступно - + Disable plugin Отключить плагин - + Enable plugin Включить плагин - + No plugins found Плагины не найдены - + Error Ошибка @@ -613,32 +618,32 @@ Version: ProfileSettingsForm - + Export profile Экспорт профиля - + Profile settings Настройки профиля - + Name: Имя: - + Status: Статус: - + TOX ID: TOX ID: - + Copy TOX ID Копировать TOX ID @@ -648,102 +653,102 @@ Version: Язык: - + New avatar Новый аватар - + Reset avatar Сбросить аватар - + New NoSpam Новый NoSpam - + Profile password Пароль профиля - + Password (at least 8 symbols) Пароль (минимум 8 символов) - + Confirm password Подтверждение пароля - + Set password Изменить пароль - + Passwords do not match Пароли не совпадают - + Leaving blank will reset current password Пустое поле сбросит текущий пароль - + There is no way to recover lost passwords Восстановление забытых паролей не поддерживается - + Password must be at least 8 symbols Пароль должен быть длиной не менее 8 символов - + Choose avatar Выбрать аватар - + Online Онлайн - + Away Нет на месте - + Busy Занят - + Mark as not default profile Отключить автозагрузку профиля - + Mark as default profile Сделать профилем по умолчанию - + Copy public key Копировать публичный ключ - + Use new path Использовать новый путь - + Do you want to move your profile to this location? Вы хотите переместить ваш профиль в эту папку? @@ -751,17 +756,17 @@ Version: WelcomeScreen - + Don't show again Не показывать снова - + Tip of the day Подсказка дня - + Press Esc if you want hide app to tray. Нажатие Esc сворачивает приложение в трей. @@ -771,7 +776,7 @@ Version: Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> Вы можете использовать Tox через Tor. Дополнительная информация <a href="https://wiki.tox.chat/users/tox_over_tor_tot">тут</a> @@ -781,14 +786,14 @@ Version: Используйте Настройки -> Интерфейс для настройки интерфейса - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. Установите пароль профиля: Профиль -> Настройки. Пароль позволяет шифровать историю переписки и настройки. Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Read more</a> - С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Узнать больше.</a> + С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/xveduk/toxygen/blob/master/docs/plugins.md">Узнать больше.</a> @@ -806,22 +811,22 @@ Version: Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam - + Right click on screenshot button hides app to tray during screenshot. Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота. - + Use Settings -> Interface to customize interface. Используйте Настройки -> Интерфейс для настройки интерфейса. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. Toxygen поддерживает псевдооффлайн сообщения и файл трансферы. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam. @@ -831,12 +836,12 @@ Version: Новое в Toxygen 0.2.3:<br>Соответствие TCS<br>Импорт плагинов, смайлов и стикеров<br>Исправления ошибок - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu Чтобы удалить отдельное сообщение в чате сделайте правый клик на спиннер или время сообщения и выберите "Удалить" в меню - + Use right click on inline image to save it Правый клик на инлайн изображении позволит сохранить его @@ -850,21 +855,31 @@ Version: New in Toxygen v0.2.6:<br>Updater<br>Better contact sorting<br>Plugins improvements Новое в Toxygen v0.2.6:<br>Поддержка обновлений<br>Улучшенная сортировка контактов<br>Улучшения в работе плагинов + + + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> + С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Узнать больше.</a> + + + + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 + Новое в Toxygen 0.3.0:<br>Видеозвонки<br>Поддержка Python3.6<br>Миграция на PyQt5 + audioSettingsForm - + Audio settings Настройки аудио - + Input device: Устройство ввода: - + Output device: Устройство вывода: @@ -872,32 +887,32 @@ Version: incoming_call - + Incoming video call Входящий видеозвонок - + Incoming audio call Входящий аудиозвонок - + Outgoing video call Исходящий видеозвонок - + Outgoing audio call Исходящий аудиозвонок - + Call declined Звонок отменен - + Call finished Звонок завершен @@ -905,82 +920,82 @@ Version: interfaceForm - + Interface settings Настройки интерфейса - + Theme: Тема: - + Language: Язык: - + Smileys Смайлики - + Smiley pack: Набор смайликов: - + Mirror mode Зеркальный режим - + Messages font size: Размер шрифта сообщений: - + Restart app to apply settings Для применения настроек необходимо перезапустить приложение - + Restart required Требуется перезапуск - + Select unread messages notification color Цвет уведомления о сообщении - + Compact contact list Компактный список контактов - + Import smiley pack Импортировать смайлы - + Import sticker pack Импортировать стикеры - + Show avatars in chat Показывать аватары в чате - + Close to tray Сворачивать в трей - + Select font Выбрать шрифт @@ -988,42 +1003,42 @@ Version: login - + Log in Вход - + Create Создать - + Profile name: Имя профиля: - + Load profile Загрузить профиль - + Use as default По умолчанию - + Load existing profile Загрузить профиль - + Create new profile Создать новый профиль - + toxygen toxygen @@ -1033,32 +1048,32 @@ Version: Похоже, что этот профиль используется другим экземпляром Toxygen! Продолжить? - + Profile name Имя профиля - + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? Этот профиль используется другим экземпляром Toxygen или не был правильно закрыт. Продолжить? - + Do you want to set profile password? Хотите ли вы установить пароль профиля? - + Do you want to save profile in default folder? If no, profile will be saved in program folder Вы хотите сохранить профиль в папку по умолчанию? Если нет, профиль будет сохранен в папке с программой - + Profile saving error! Does Toxygen have permission to write to this directory? Ошибка сохранения профиля! Toxygen имеет разрешение на запись в данную папку? - + Update for Toxygen was found. Download and install it? Обновление для Toxygen было найдено. Загрузить и установить его? @@ -1066,22 +1081,22 @@ Version: notificationsForm - + Notification settings Настройки уведомлений - + Enable notifications Включить уведомления - + Enable call's sound Включить звук звонка - + Enable sound notifications Включить звуковые уведомления @@ -1090,17 +1105,17 @@ Version: pass - + Enter password Введите пароль - + Password: Пароль: - + Incorrect password Неверный пароль @@ -1108,72 +1123,72 @@ Version: privacySettings - + Privacy settings Настройки приватности - + Save chat history Сохранять историю переписки - + Allow file auto accept Разрешить автополучение файлов - + Send typing notifications Посылать уведомления о наборе текста - + Auto accept default path: Путь автоприема файлов: - + Change Изменить - + Allow inlines Разрешать инлайны - + Chat history История чата - + History will be cleaned! Continue? История переписки будет очищена! Продолжить? - + Blocked users: Заблокированные пользователи: - + Unblock Разблокировать - + Block user Заблокировать пользователя - + Add to friend list Добавить в список друзей - + Do you want to add this user to friend list? Добавить этого пользователя в список друзей? @@ -1183,12 +1198,12 @@ Version: Блокировать по TOX ID: - + Block by public key: Блокировать по публичному ключу: - + Save unsent messages only Сохранять только неотправленные сообщения @@ -1196,32 +1211,32 @@ Version: tray - + Open Toxygen Открыть Toxygen - + Exit Выход - + Set status Изменить статус - + Online Онлайн - + Away Нет на месте - + Busy Занят @@ -1229,59 +1244,72 @@ Version: updateSettingsForm - + Update settings Обновить настройки - + Select update mode: Выбрать режим обновлений: - + Update Toxygen Обновить Toxygen - + Disabled Отключены - + Manual Вручную - + Auto Автоматически - + Error Ошибка - + Problems with internet connection Проблемы с соединением - + Updater not found Апдейтер не был найден - + No updates found Обновления не найдены - + Toxygen is up to date Toxygen уже обновлен + + videoSettingsForm + + + Video settings + Настройки видео + + + + Device: + Устройство: + + diff --git a/toxygen/translations/uk_UA.ts b/toxygen/translations/uk_UA.ts index 1f6a47e..9e70b4b 100644 --- a/toxygen/translations/uk_UA.ts +++ b/toxygen/translations/uk_UA.ts @@ -1,400 +1,1237 @@ - - + + + AddContact + + + TOX ID: + TOX ID: + + + + Add contact + Додати контакт + + + + Message: + Повідомлення: + + + + TOX ID or public key of contact + + + + + Callback + + + File from + + + Form + IP: IP: + UDP UDP + HTTP HTTP + IPv6 IPv6 + Port: Порт: + Proxy Проксі + Online contacts - Контактів онлайн + Контактів онлайн + Send request Відправити запит - - - tray - Exit - Вихід - - - Open Toxygen - Відкрити Toxygen + + WARNING: +using proxy with enabled UDP +can produce IP leak + MainWindow + About program Про проґраму + Friend request Запит дружби + About Про + Audio Звук + Friend added Друга додано + Send file - Надіслати файл + Надіслати файл + User {} wants to add you to contact list. Message: {} Користувач {} хоче додати вас до списку контактів. Повідомлення {} + Network Мережа + Clear history Очистити журнал + Copy public key - Копіювати публічний ключ + Копіювати публічний ключ + Send message Надіслати повідомлення + Set alias Встановити скорочення + Privacy Приватність + Profile Профіль + Toxygen is Tox client written on Python. Version: Toxygen — це клієнт Tox написаний на Python. Версія: + Choose file Обрати файл + Enter new alias for friend {} or leave empty to use friend's name: Введіть нове скорочення для друга {} або залишіть порожнім, щоб використовувати його псевдо: + Add contact Додати контакт + Friend added without sending friend request Друга додано без надсилання запиту дружби + Interface Зовнішній вигляд + Settings Налаштування + Notifications Сповіщення + Remove friend Вилучити друга + Find contact - Знайти контакт + Знайти контакт + Choose folder Обрати теку + Allow auto accept Дозволити автоприймання + Disallow auto accept Заборонити автоприймання + Start audio call with friend Почати звуковий дзвінок + Send screenshot - Надіслати знімок екрану - - - - ProfileSettingsForm - - Name: - Псевдо: + Надіслати знімок екрану - Profile settings - Налаштування профілю + + Error + - Reset avatar - Скинути аватар + + Profile with this name already exists + - New NoSpam - Новий NoSpam + + User {} is now known as {} + - Copy TOX ID - Копіювати TOX ID + + Choose folder with sticker pack + - New avatar - Новий аватар + + Choose folder with smiley pack + - Export profile - Експортувати профіль + + Quote selected text + - TOX ID: - TOX ID: + + Plugins + - Status: - Статус: - - - - privacySettings - - Privacy settings - Налаштування приватності + + Delete message + - Add to friend list - Додати до списку друзів + + Lock + - Block by TOX ID: - Блокувати по TOX ID: + + List of plugins + - Blocked users: - Блоковані користувачі: + + Video + - Change - Змінити + + Updates + - Send typing notifications - Надсилати сповіщення про те, що я друкую + + Search + - Allow file auto accept - Дозволити автоприймання файлів + + All + - Allow inlines - Дозволити інлайни + + Online + - Save chat history - Зберігати журнал бесіди + + Online first + - Block user - Блокувати користувача + + Name + + + Online and by name + + + + + Online first and by name + + + + + Import plugin + + + + + Reload plugins + + + + + Choose folder with plugin + + + + + Restart Toxygen + + + + + Plugin will be loaded after restart + + + + + Cannot lock app + + + + + Error. Profile password is not set. + + + + Chat history - Журнал бесіди + Журнал бесіди - Unblock - Розблокувати + + Export as text + - History will be cleaned! Continue? - Журнал буде очищено! Продовжити? + + Export as HTML + - Auto accept default path: - Шлях за замовчуванням для автоприймання: + + Copy + - Do you want to add this user to friend list? - Ви хочете додати цього користувача у список друзів? + + Status message + + + + + Public key + + + + + Block friend + + + + + Notes + + + + + Notes about user + + + + + Copy link location + + + + + Select all + + + + + Delete + + + + + Paste + + + + + Cut + + + + + Undo + + + + + Redo + + + + + Save + + + + + Text "{}" was not found + + + + + Not found + - incoming_call + MenuWindow - Incoming video call - Вхідний відеодзвінок + + Send screenshot + Надіслати знімок екрану - Incoming audio call - Вхідний аудіодзвінок - - - - login - - Profile name: - Псевдо профілю: + + Send file + Надіслати файл - Load profile - Завантажити профіль + + Add smiley + - Use as default - За замовчуванням - - - Create new profile - Створити новий профіль - - - Create - Створити - - - Log in - Увійти - - - Load existing profile - Завантажити існуючий - - - toxygen - toxygen - - - Looks like other instance of Toxygen uses this profile! Continue? - Схоже, що інша копія Toxygenʼу використовує цей профіль! Продовжити? + + Send sticker + NetworkSettings + Network settings Налаштування мережі + Restart TOX core Перезапустити ядро Tox - notificationsForm + PasswordScreen - Enable sound notifications - Увімкнути звукові сповіщення + + Profile password + - Enable notifications - Увімкнути сповіщення + + Password (at least 8 symbols) + - Notification settings - Налаштування сповіщень + + Confirm password + - Enable call's sound - Увімкнути звук дзвінка + + Set password + + + + + Passwords do not match + + + + + There is no way to recover lost passwords + + + + + Password must be at least 8 symbols + - interfaceForm + PluginWindow - Language: - Мова: + + List of commands for plugin {} + - Theme: - Тема: + + No commands available + + + + + PluginsForm + + + Plugins + - Interface settings - Налаштування зовнішнього вигляду + + Open selected plugin + + + + + No GUI found for this plugin + + + + + Error + + + + + No description available + + + + + Disable plugin + + + + + Enable plugin + + + + + No plugins found + + + + + ProfileSettingsForm + + + Name: + Псевдо: + + + + Profile settings + Налаштування профілю + + + + Reset avatar + Скинути аватар + + + + New NoSpam + Новий NoSpam + + + + Copy TOX ID + Копіювати TOX ID + + + + New avatar + Новий аватар + + + + Export profile + Експортувати профіль + + + + TOX ID: + TOX ID: + + + + Status: + Статус: + + + + Profile password + + + + + Password (at least 8 symbols) + + + + + Confirm password + + + + + Set password + + + + + Passwords do not match + + + + + Leaving blank will reset current password + + + + + There is no way to recover lost passwords + + + + + Online + + + + + Away + + + + + Busy + + + + + Copy public key + Копіювати публічний ключ + + + + Mark as not default profile + + + + + Mark as default profile + + + + + Password must be at least 8 symbols + + + + + Choose avatar + + + + + Use new path + + + + + Do you want to move your profile to this location? + + + + + WelcomeScreen + + + Don't show again + + + + + Tip of the day + + + + + Press Esc if you want hide app to tray. + + + + + Right click on screenshot button hides app to tray during screenshot. + + + + + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> + + + + + Use Settings -> Interface to customize interface. + + + + + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. + + + + + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> + + + + + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. + + + + + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 + + + + + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu + + + + + Use right click on inline image to save it + + + + + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. + audioSettingsForm + Output device: Пристрій виводу: + Audio settings Налаштування звуку + Input device: Пристрій вводу: - AddContact + incoming_call - TOX ID: - TOX ID: + + Incoming video call + Вхідний відеодзвінок - Add contact - Додати контакт + + Incoming audio call + Вхідний аудіодзвінок - Message: - Повідомлення: + + Outgoing video call + + + + + Outgoing audio call + + + + + Call declined + + + + + Call finished + + + + + interfaceForm + + + Language: + Мова: + + + + Theme: + Тема: + + + + Interface settings + Налаштування зовнішнього вигляду + + + + Show avatars in chat + + + + + Smileys + + + + + Smiley pack: + + + + + Mirror mode + + + + + Messages font size: + + + + + Select unread messages notification color + + + + + Compact contact list + + + + + Import smiley pack + + + + + Import sticker pack + + + + + Close to tray + + + + + Select font + + + + + Restart app to apply settings + + + + + Restart required + + + + + login + + + Profile name: + Псевдо профілю: + + + + Load profile + Завантажити профіль + + + + Use as default + За замовчуванням + + + + Create new profile + Створити новий профіль + + + + Create + Створити + + + + Log in + Увійти + + + + Load existing profile + Завантажити існуючий + + + + toxygen + toxygen + + + + Looks like other instance of Toxygen uses this profile! Continue? + Схоже, що інша копія Toxygenʼу використовує цей профіль! Продовжити? + + + + Do you want to set profile password? + + + + + Do you want to save profile in default folder? If no, profile will be saved in program folder + + + + + Profile saving error! Does Toxygen have permission to write to this directory? + + + + + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? + + + + + Update for Toxygen was found. Download and install it? + + + + + Profile name + + + + + notificationsForm + + + Enable sound notifications + Увімкнути звукові сповіщення + + + + Enable notifications + Увімкнути сповіщення + + + + Notification settings + Налаштування сповіщень + + + + Enable call's sound + Увімкнути звук дзвінка + + + + pass + + + Enter password + + + + + Password: + + + + + Incorrect password + + + + + privacySettings + + + Privacy settings + Налаштування приватності + + + + Add to friend list + Додати до списку друзів + + + + Block by TOX ID: + Блокувати по TOX ID: + + + + Blocked users: + Блоковані користувачі: + + + + Change + Змінити + + + + Send typing notifications + Надсилати сповіщення про те, що я друкую + + + + Allow file auto accept + Дозволити автоприймання файлів + + + + Allow inlines + Дозволити інлайни + + + + Save chat history + Зберігати журнал бесіди + + + + Block user + Блокувати користувача + + + + Chat history + Журнал бесіди + + + + Unblock + Розблокувати + + + + History will be cleaned! Continue? + Журнал буде очищено! Продовжити? + + + + Auto accept default path: + Шлях за замовчуванням для автоприймання: + + + + Do you want to add this user to friend list? + Ви хочете додати цього користувача у список друзів? + + + + Block by public key: + + + + + Save unsent messages only + + + + + tray + + + Exit + Вихід + + + + Open Toxygen + Відкрити Toxygen + + + + Set status + + + + + Online + + + + + Away + + + + + Busy + + + + + updateSettingsForm + + + Update settings + + + + + Select update mode: + + + + + Update Toxygen + + + + + Disabled + + + + + Manual + + + + + Auto + + + + + Error + + + + + Problems with internet connection + + + + + Updater not found + + + + + No updates found + + + + + Toxygen is up to date + + + + + videoSettingsForm + + + Video settings + + + + + Device: + From adf6cefd1fdeb9d0d4cf4b49306a6f0920ed596a Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 20 Jun 2017 22:55:48 +0300 Subject: [PATCH 036/163] pyqt5 fixes - menu and smileys --- toxygen/mainscreen_widgets.py | 4 ++-- toxygen/menu.py | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index 1de7b54..7632af0 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -153,7 +153,7 @@ class SmileyWindow(QtWidgets.QWidget): for i in range(self.page_count): # buttons with smileys elem = QtWidgets.QRadioButton(self) elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20)) - elem.clicked.connect(lambda i=i: self.checked(i)) + elem.clicked.connect(lambda c, t=i: self.checked(t)) self.radio.append(elem) width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10) self.setMaximumSize(width, 200) @@ -162,7 +162,7 @@ class SmileyWindow(QtWidgets.QWidget): for i in range(self.page_size): # pages - radio buttons b = QtWidgets.QPushButton(self) b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20)) - b.clicked.connect(lambda i=i: self.clicked(i)) + b.clicked.connect(lambda c, t=i: self.clicked(t)) self.buttons.append(b) self.checked(0) diff --git a/toxygen/menu.py b/toxygen/menu.py index cabd03f..beea221 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -705,7 +705,7 @@ class InterfaceSettings(CenteredWidget): settings['theme'] = str(self.themeSelect.currentText()) try: theme = settings['theme'] - app = QtGui.QApplication.instance() + app = QtWidgets.QApplication.instance() with open(curr_directory() + settings.built_in_themes()[theme]) as fl: style = fl.read() app.setStyleSheet(style) @@ -846,9 +846,11 @@ class VideoSettings(CenteredWidget): self.devices.append(i) self.frame_max_sizes.append((width, height)) self.input.addItem('Device #' + str(i)) - index = self.devices.index(settings.video['device']) - if index + 1: + try: + index = self.devices.index(settings.video['device']) self.input.setCurrentIndex(index) + except: + print('Video devices error!') def retranslateUi(self): self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings")) From 5932d8cb847e7327100b1845e5e880c8008f5d52 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 24 Jun 2017 15:44:55 +0300 Subject: [PATCH 037/163] installation docs update --- docs/install.md | 69 +++++++++++++++++++------------------------------ setup.py | 17 ++++-------- 2 files changed, 31 insertions(+), 55 deletions(-) diff --git a/docs/install.md b/docs/install.md index 808eb93..589d559 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,13 +1,13 @@ # How to install Toxygen -## Use precompiled binary: +## Use precompiled binary (recommended for users): [Check our releases page](https://github.com/toxygen-project/toxygen/releases) ## Using pip3 ### Windows -``pip3.4 install toxygen`` +``pip install toxygen`` Run app using ``toxygen`` command. @@ -16,19 +16,11 @@ Run app using ``toxygen`` command. 1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/) 2. Install PortAudio: ``sudo apt-get install portaudio19-dev`` -3. Install PySide: ``sudo apt-get install python3-pyside`` -4. Install toxygen: -``sudo pip3.4 install toxygen`` -5. Run toxygen using ``toxygen`` command. - -### OS X - -1. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system -2. Install PortAudio: -``brew install portaudio`` -3. Install toxygen: -``pip3.4 install toxygen`` -4. Run toxygen using ``toxygen`` command. +3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5`` +4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) +5. Install toxygen: +``sudo pip3 install toxygen`` +6. Run toxygen using ``toxygen`` command. ## Packages @@ -40,15 +32,18 @@ Debian/Ubuntu: [tox.chat](https://tox.chat/download.html#gnulinux) ### Windows -1. [Download and install latest Python 3.4](https://www.python.org/downloads/windows/) -2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-windows-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download) -3. Install PyAudio: ``pip3.4 install pyaudio`` -4. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip) -5. Unpack archive -6. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\ -7. Run \toxygen\main.py. +Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly recommended to use 64-bit Python. -Optional: install toxygen using setup.py: ``python3.4 setup.py install`` +1. [Download and install latest Python 3 64-bit](https://www.python.org/downloads/windows/) +2. Install PyQt5: ``pip install pyqt5`` +3. Install PyAudio: ``pip install pyaudio`` +4. Download [numpy](http://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy) and [OpenCV](http://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv). Install it using ``pip install ``. +5. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) +6. Unpack archive +7. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\ +8. Run \toxygen\main.py. + +Optional: install toxygen using setup.py: ``python setup.py install`` [libtox.dll for 32-bit Python](https://build.tox.chat/view/libtoxcore/job/libtoxcore_build_windows_x86_shared_release/lastSuccessfulBuild/artifact/libtoxcore_build_windows_x86_shared_release.zip) @@ -62,27 +57,15 @@ Optional: install toxygen using setup.py: ``python3.4 setup.py install`` 1. Install latest Python3: ``sudo apt-get install python3`` -2. Install PySide: ``sudo apt-get install python3-pyside`` or install [PyQt4](https://riverbankcomputing.com/software/pyqt/download) (``sudo apt-get install python3-pyqt4``). +2. Install PyQt5: ``sudo apt-get install python3-pyqt5`` or ``sudo pip3 install pyqt5`` 3. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system (install in /usr/lib/) 4. Install PyAudio: -``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``pip3 install pyaudio``) -5. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip) -6. Unpack archive -7. Run app: -``python3.4 main.py`` - -Optional: install toxygen using setup.py: ``python3.4 setup.py install`` - -### OS X - -1. [Download and install latest Python 3.4](https://www.python.org/downloads/mac-osx/) -2. [Install PySide](https://pypi.python.org/pypi/PySide/1.2.4#installing-pyside-on-a-mac-os-x-system) (recommended) or [PyQt4](https://riverbankcomputing.com/software/pyqt/download) -3. Install PortAudio: -``brew install portaudio`` -4. Install PyAudio: ``pip3 install pyaudio`` -5. Install [toxcore](https://github.com/irungentoo/toxcore/blob/master/INSTALL.md) with toxav support in your system -6. [Download toxygen](https://github.com/xveduk/toxygen/archive/master.zip) -7. Unpack archive -8. Run \toxygen\main.py. +``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``) +5. Install NumPy: ``sudo pip3 install numpy`` +6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) +7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) +8. Unpack archive +9. Run app: +``python3 main.py`` Optional: install toxygen using setup.py: ``python3 setup.py install`` diff --git a/setup.py b/setup.py index 82526c1..f069665 100644 --- a/setup.py +++ b/setup.py @@ -8,20 +8,13 @@ import sys version = program_version + '.0' -MODULES = ['numpy', 'PyQt5'] - -if system() in ('Windows', 'Darwin'): - MODULES.append('PyAudio') -else: - try: - import pyaudio - except ImportError: - MODULES.append('PyAudio') - +MODULES = ['PyQt5', 'PyAudio'] DEP_LINKS = [] -if system() == 'Windows': - DEP_LINKS = [] # TODO: add opencv.whl +if system() != 'Windows': + MODULES.append('numpy') +else: + DEP_LINKS = [] # TODO: add opencv wheel and numpy wheel class InstallScript(install): From 4b85401adfb02795e09e4d1b8a8d4f01989fbda3 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 29 Jun 2017 22:14:52 +0300 Subject: [PATCH 038/163] os x removed, minor updates --- .gitignore | 3 ++- .travis.yml | 2 +- README.md | 4 +--- docs/os.png | Bin 28689 -> 0 bytes 4 files changed, 4 insertions(+), 5 deletions(-) delete mode 100755 docs/os.png diff --git a/.gitignore b/.gitignore index e8841f6..78c26f5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ tests/tests tests/libs tests/.cache tests/__pycache__ +tests/avatars toxygen/libs .idea *~ @@ -23,4 +24,4 @@ toxygen/__pycache__ html Toxygen.egg-info *.tox - +.cache diff --git a/.travis.yml b/.travis.yml index 76d89d2..81e9a02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ install: - pip install pyaudio - pip install opencv-python before_script: -# OPUS +# Opus - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz - tar xzf opus-1.0.3.tar.gz - cd opus-1.0.3 diff --git a/README.md b/README.md index 5a586b0..e9dbc93 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,7 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu ### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) - [Updater](https://github.com/toxygen-project/toxygen_updater) -### Supported OS: - -![Linux, Windows and OS X](/docs/os.png) +### Supported OS: Linux and Windows ### Features: diff --git a/docs/os.png b/docs/os.png deleted file mode 100755 index 16207df1bdbc772319039f65bbc7e63df045019a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28689 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 From 6e1b8a9f17617051590f4d17ca6689b0ccf3572e Mon Sep 17 00:00:00 2001 From: limalayla Date: Mon, 3 Jul 2017 00:51:43 +0200 Subject: [PATCH 039/163] French translation up to v0.3.0 --- toxygen/translations/fr_FR.ts | 354 +++++++++++++++++----------------- 1 file changed, 179 insertions(+), 175 deletions(-) diff --git a/toxygen/translations/fr_FR.ts b/toxygen/translations/fr_FR.ts index 4eacf6a..69a7b38 100644 --- a/toxygen/translations/fr_FR.ts +++ b/toxygen/translations/fr_FR.ts @@ -1,16 +1,17 @@ - + + AddContact Add contact - Rajouter un contact + Ajouter un contact TOX ID: - ID TOX : + ID Tox : @@ -20,7 +21,7 @@ TOX ID or public key of contact - + ID Tox ou clé publique de contact @@ -28,7 +29,7 @@ File from - + Fichier de @@ -78,7 +79,9 @@ WARNING: using proxy with enabled UDP can produce IP leak - + ATTENTION : +Utiliser un proxy avec UDP +peut entrainer une fuite d'IP @@ -86,12 +89,12 @@ can produce IP leak Profile - Profile + Profil Settings - Paramêtres + Paramètres @@ -101,7 +104,7 @@ can produce IP leak Add contact - Rajouter un contact + Ajouter un contact @@ -126,18 +129,18 @@ can produce IP leak About program - À propos du programme + À propos de toxygen User {} wants to add you to contact list. Message: {} - L'Utilisateur {} veut vout rajouter à sa liste de contacts. Message : {} + L'Utilisateur {} veut vous ajouter à sa liste de contacts. Message : {} Friend request - Demande d'amis + Demande de contact @@ -147,7 +150,7 @@ can produce IP leak Choose file - Choisir un fichier + Sélectionner un fichier @@ -177,12 +180,12 @@ can produce IP leak Remove friend - Retirer un ami + Retirer ce contact Enter new alias for friend {} or leave empty to use friend's name: - Entrez un nouvel alias pour l'ami {} ou laissez vide pour garder son nom de base : + Entrez un nouvel alias pour le contact {} ou laissez vide pour garder son nom de base : @@ -197,7 +200,7 @@ can produce IP leak Friend added - Ami rajouté + Contact ajouté @@ -209,12 +212,13 @@ Version : Friend added without sending friend request - Ami rajouté sans avoir envoyé de demande + Contact ajouté sans envoi de demande Choose folder - Choisir le dossier + un ? le ? + Sélectionner un dossier @@ -234,232 +238,232 @@ Version : Start audio call with friend - Lancer un appel audio avec un ami + Démarrer un appel audio avec un ami Plugins - + Plugins List of plugins - + Liste de plugins Search - + Chercher All - + Tous Online - + En ligne Notes - + Notes Notes about user - + Notes sur l'utilisateur Copy link location - + Copier l'emplacement du lien Copy - + Copier Select all - + Tout sélectionner Delete - + Supprimer Paste - + Coller Cut - + Couper Undo - + Annuler Redo - + Refaire Save - + Sauvegarder User {} is now known as {} - + L'utilisateur {} s'appelle désormais {} Delete message - + Supprimer ce message Lock - + Verrouiller Cannot lock app - + Impossible de verrouiller l'application Error. Profile password is not set. - + Erreur. Le profil n'a pas de mot de passe. Name - + Nom Status message - + Status Public key - + Clé publique Error - + Erreur Profile with this name already exists - + Un profil ayant ce nom existe déjà Choose folder with sticker pack - + Sélectionner le dossier contenant le pack de stickers Choose folder with smiley pack - + Sélectionner le dossier contenant le pack de smileys Import plugin - + Importer un plugin Choose folder with plugin - + Sélectionner un dossier avec des plugins Restart Toxygen - + Redémarrer Toxyger Plugin will be loaded after restart - + Le plugin sera chargé après le redémarrage Quote selected text - + Citer le texte sélectionné Chat history - Historique de chat + Historique de la conversation Export as text - + Exporter comme texte Export as HTML - + Exporter comme HTML Updates - + Mises à jour Online first - + En ligne d'abord Online and by name - + En ligne et par nom Online first and by name - + En ligne d'abord puis par nom Block friend - + Bloquer le contact Not found - + Non trouvé Text "{}" was not found - + Le texte "{}" n'a pas été trouvé Reload plugins - + Recharger les plugins Video - + Vidéo @@ -467,22 +471,22 @@ Version : Send screenshot - Envoyer une capture d'écran + Envoyer une capture d'écran Send file - Envoyer le fichier + Envoyer un fichier Add smiley - + Ajouter un smiley Send sticker - + Ajouter un sticker @@ -495,7 +499,7 @@ Version : Restart TOX core - Relancer le noyau TOX + Relancer le noyau Tox @@ -503,37 +507,37 @@ Version : Profile password - + Mot de passe du profil Password (at least 8 symbols) - + Mot de passe (8 symboles minimum) Confirm password - + Confirmation Set password - + Enregistrer le mot de passe Passwords do not match - + Les mots de passes sont différents There is no way to recover lost passwords - + Il est impossible de récuperer un mot de passe perdu Password must be at least 8 symbols - + Un mot de passe doit faire 8 symboles minimum @@ -541,12 +545,12 @@ Version : List of commands for plugin {} - + Liste de commandes du plugin {} No commands available - + Pas de commandes disponibles @@ -554,42 +558,42 @@ Version : Plugins - + Plugins Open selected plugin - + Ouvrir le plugin sélectionné No GUI found for this plugin - + Pas d'interface pour ce plugin No description available - + Pas de description Disable plugin - + Désactiver le plugin Enable plugin - + Activer le plugin No plugins found - + Pas de plugin trouvé Error - + Erreur @@ -597,12 +601,12 @@ Version : Export profile - Exporter le profile + Exporter le profil Profile settings - Paramêtres du profil + Paramètres du profil @@ -622,7 +626,7 @@ Version : Copy TOX ID - Copier l'ID TOX + Copier l'ID Tox @@ -642,87 +646,87 @@ Version : Profile password - + Mot de passe du profil Password (at least 8 symbols) - + Mot de passe (8 symboles minimum) Confirm password - + Confirmation Set password - + Sauvegarder le mot de passe Passwords do not match - + Les mots de passe sont différents Leaving blank will reset current password - + Laisser vide réinitialisera le mot de passe actuel There is no way to recover lost passwords - + Il est impossible de récupérer un mot de passe perdu Password must be at least 8 symbols - + Le mot de passe doit faire 8 symboles minimum Choose avatar - + Choisir l'avatar Online - + En ligne Away - + Absent Busy - + Occupé Mark as not default profile - + Ne plus en faire le profil par défaut Mark as default profile - + En faire le profil par défaut Copy public key - Copier la clé publique + Copier la clé publique Use new path - + Utiliser un nouveau chemin Do you want to move your profile to this location? - + Déplacer le profil dans ce dossier ? @@ -730,67 +734,67 @@ Version : Don't show again - + Ne plus montrer Tip of the day - + Astuce du jou Press Esc if you want hide app to tray. - + Appuyez sur échap pour réduire l'application. You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> - + Vous pouvez utiliser Tox avec Tor. Pour plus d'informations, voir <a href="https://wiki.tox.chat/users/tox_over_tor_tot">cet article</a> Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. - + Vous pouvez mettre un mot de passe dans Profil -> Paramètres -> Mot de passe pour que toxygen encrypte votre historique et vos paramètres. Right click on screenshot button hides app to tray during screenshot. - + Faire un clic droit sur le bouton de capture d'écran réduit l'application avant de capturer l'écran. Use Settings -> Interface to customize interface. - + Vous pouvez customizer votre interface dans Paramètres -> Interface. Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - + Toxygen permet d'envoyer des messages et fichiers en différé. Envoyez des messages ou fichiers à un contact hors ligne et il le recevra plus tard. Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. - + Vous pouvez empecher le spam dans les demandes de contact avec Profil -> Paramètres -> Nouveau NoSpam. Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu - + Pour supprimer un seul message dans une conversation, faites un clic droit sur l'heure du message et sélectionnez "Supprimer ce message" dans le menu Use right click on inline image to save it - + Pour sauvegarder une image intégrée, faites un clic droit dessus Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> - + Depuis la version 0.1.3 Toxygen supporte les plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">En savoir plus</a> New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 - + Nouveau dans Toxygen 0.3.0 : <br>Appels vidéo<br>Support de Python3.6<br>Migration vers PyQt5 @@ -826,22 +830,22 @@ Version : Outgoing video call - + Appel vidéo sortant Outgoing audio call - + Appel audio sortant Call declined - + Appel refusé Call finished - + Appel terminé @@ -849,7 +853,7 @@ Version : Interface settings - Paramêtres de l'interface + Paramètres de l'interface @@ -864,67 +868,67 @@ Version : Smileys - + Smileys Smiley pack: - + Pack de smileys : Mirror mode - + Mode miroir Messages font size: - + Taille des messages : Restart app to apply settings - + Redémarrer toxygen pour appliquer les paramètres Restart required - + Redémarrage nécessaire Select unread messages notification color - + Sélectionner la couleur des messages non-lus Compact contact list - + Liste de contacts compacte Import smiley pack - + Importer un pack de smileys Import sticker pack - + Importer un pack de stickers Show avatars in chat - + Montrer les avatars dans la conversation Close to tray - + Réduire Select font - + Sélectionner la police @@ -977,32 +981,32 @@ Version : Profile name - + Nom de profil Other instance of Toxygen uses this profile or profile was not properly closed. Continue? - + Ce profil semble être utilisé par une autre instance de toxygen ou avoir été incorrectement fermé . Continuer ? Do you want to set profile password? - + Souhaitez vous protéger le profil par un mot de passe ? Do you want to save profile in default folder? If no, profile will be saved in program folder - + Souhaitez vous conserver le profil dans le dossier par défaut ? Si non, il sera conservé dans le dossier du programme Profile saving error! Does Toxygen have permission to write to this directory? - + Un problème est survenu lors de la sauvegarde du profil ! Toxygen as t'il le droit d'écrire dans ce dossier ? Update for Toxygen was found. Download and install it? - + Une mise à jour est disponible. La télécharger et l'installer ? @@ -1010,7 +1014,7 @@ Version : Notification settings - Paramêtres de notification + Paramètres de notification @@ -1033,17 +1037,17 @@ Version : Enter password - + Entrer le mot de passe Password: - + Mot de passe : Incorrect password - + Mot de passe incorrect @@ -1051,12 +1055,12 @@ Version : Privacy settings - Paramêtres de confidentialité + Paramètres de confidentialité Save chat history - Sauvegarder l'historique de chat + Sauvegarder l'historique de conversation @@ -1066,12 +1070,12 @@ Version : Send typing notifications - Notifier la frappe + Informer de la frappe Auto accept default path: - Chemin d'accès des fichiers acceptés automatiquement : + Chemin par défaut des fichiers acceptés automatiquement : @@ -1081,17 +1085,17 @@ Version : Allow inlines - Activer l'auto-réception + Activer l'affichage integré Chat history - Historique de chat + Historique de conversation History will be cleaned! Continue? - L'Historique va être nettoyé ! Confirmer ? + L'Historique va être vidé ! Confirmer ? @@ -1111,12 +1115,12 @@ Version : Add to friend list - Ajouter à la liste des amis + Ajouter à la liste de contacts Do you want to add this user to friend list? - Voulez vous rajouter cet utilisateur à votre liste d'amis ? + Voulez vous aajouter cet utilisateur à votre liste de contacts ? @@ -1126,12 +1130,12 @@ Version : Block by public key: - + Bloquer par clé publique : Save unsent messages only - + Sauvegarder les messages non envoyés uniquement @@ -1149,22 +1153,22 @@ Version : Set status - + Changer le status Online - + En ligne Away - + Absent Busy - + Occupé @@ -1172,57 +1176,57 @@ Version : Update settings - + Paramètres de mise à jour Select update mode: - + Sélectionner le mode de mise à jour : Update Toxygen - + Mettre à jour toxygen Disabled - + Désactivé Manual - + Manuel Auto - + Automatique Error - + Erreur Problems with internet connection - + Il y à des problèmes avec votre connexion internet Updater not found - + Updater non trouvé No updates found - + Pas de mises à jour trouvés Toxygen is up to date - + Toxygen est à jour @@ -1230,12 +1234,12 @@ Version : Video settings - + Paramètres vidéo Device: - + Périphérique : From 8d0426f77595834cc1e3da96e50f2f1c1f1ce3af Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 3 Jul 2017 13:01:07 +0300 Subject: [PATCH 040/163] .qm for french translation --- toxygen/translations/fr_FR.qm | Bin 8601 -> 26088 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/toxygen/translations/fr_FR.qm b/toxygen/translations/fr_FR.qm index e7ab53176cd68af31bd7955eb3031e54faf6bd82..33b0cbc8375ab2771dac73d4f01cbd41a6c05407 100644 GIT binary patch literal 26088 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 From c21e39b15845a1ad36bd8e4675fcaa7027021df3 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 3 Jul 2017 21:36:11 +0300 Subject: [PATCH 041/163] bug fixes for updates --- toxygen/updater.py | 8 ++++++-- toxygen/util.py | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/toxygen/updater.py b/toxygen/updater.py index ba5d1c9..762892a 100644 --- a/toxygen/updater.py +++ b/toxygen/updater.py @@ -57,7 +57,10 @@ def get_url(version): def get_params(url, version): if is_from_sources(): - return ['python3', 'toxygen_updater.py', url, version] + if platform.system() == 'Windows': + return ['python', 'toxygen_updater.py', url, version] + else: + return ['python3', 'toxygen_updater.py', url, version] elif platform.system() == 'Windows': return [util.curr_directory() + '/toxygen_updater.exe', url, version] else: @@ -87,7 +90,8 @@ def send_request(version): netman.setProxy(proxy) url = test_url(version) try: - request = QtNetwork.QNetworkRequest(url) + request = QtNetwork.QNetworkRequest() + request.setUrl(QtCore.QUrl(url)) reply = netman.get(request) while not reply.isFinished(): QtCore.QThread.msleep(1) diff --git a/toxygen/util.py b/toxygen/util.py index df33998..2183f4a 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -22,8 +22,11 @@ def cached(func): def log(data): - with open(curr_directory() + '/logs.log', 'a') as fl: - fl.write(str(data) + '\n') + try: + with open(curr_directory() + '/logs.log', 'a') as fl: + fl.write(str(data) + '\n') + except: + pass @cached From d06982b38a2a0cbb1e0d1306fa74a1161a89f242 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 6 Jul 2017 21:32:35 +0300 Subject: [PATCH 042/163] updates for pip3 --- docs/install.md | 11 ++++++----- setup.py | 18 +++++------------- toxygen/main.py | 23 ++++------------------- 3 files changed, 15 insertions(+), 37 deletions(-) diff --git a/docs/install.md b/docs/install.md index 589d559..79c07f4 100644 --- a/docs/install.md +++ b/docs/install.md @@ -37,11 +37,12 @@ Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly r 1. [Download and install latest Python 3 64-bit](https://www.python.org/downloads/windows/) 2. Install PyQt5: ``pip install pyqt5`` 3. Install PyAudio: ``pip install pyaudio`` -4. Download [numpy](http://www.lfd.uci.edu/~gohlke/pythonlibs/#numpy) and [OpenCV](http://www.lfd.uci.edu/~gohlke/pythonlibs/#opencv). Install it using ``pip install ``. -5. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) -6. Unpack archive -7. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\ -8. Run \toxygen\main.py. +4. Install numpy: ``pip install numpy`` +5. Install OpenCV: ``pip install opencv-python`` +6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) +7. Unpack archive +8. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\ +9. Run \toxygen\main.py. Optional: install toxygen using setup.py: ``python setup.py install`` diff --git a/setup.py b/setup.py index f069665..3ca0f3f 100644 --- a/setup.py +++ b/setup.py @@ -8,13 +8,10 @@ import sys version = program_version + '.0' -MODULES = ['PyQt5', 'PyAudio'] -DEP_LINKS = [] +MODULES = ['PyQt5', 'PyAudio', 'numpy'] -if system() != 'Windows': - MODULES.append('numpy') -else: - DEP_LINKS = [] # TODO: add opencv wheel and numpy wheel +if system() == 'Windows': + MODULES.append('opencv-python') class InstallScript(install): @@ -23,9 +20,7 @@ class InstallScript(install): def run(self): install.run(self) try: - if system() == 'Windows': - call(["toxygen", "--configure"]) - else: + if system() != 'Windows': call(["toxygen", "--clean"]) except: try: @@ -35,9 +30,7 @@ class InstallScript(install): if path[-1] not in ('/', '\\'): path += '/' path += 'bin/toxygen' - if system() == 'Windows': - call([path, "--configure"]) - else: + if system() != 'Windows': call([path, "--clean"]) except: pass @@ -53,7 +46,6 @@ setup(name='Toxygen', license='GPL3', packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'], install_requires=MODULES, - dependency_links=DEP_LINKS, include_package_data=True, classifiers=[ 'Programming Language :: Python :: 3 :: Only', diff --git a/toxygen/main.py b/toxygen/main.py index 0074052..ad78407 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -457,22 +457,10 @@ def clean(): def configure(): """Removes unused libs""" d = curr_directory() + '/libs/' - is_64bits = is_64_bit() - if not is_64bits: - if os.path.exists(d + 'libtox64.dll'): - os.remove(d + 'libtox64.dll') - if os.path.exists(d + 'libsodium64.a'): - os.remove(d + 'libsodium64.a') - else: - if os.path.exists(d + 'libtox.dll'): - os.remove(d + 'libtox.dll') - if os.path.exists(d + 'libsodium.a'): - os.remove(d + 'libsodium.a') - try: - os.rename(d + 'libtox64.dll', d + 'libtox.dll') - os.rename(d + 'libsodium64.a', d + 'libsodium.a') - except: - pass + if os.path.exists(d + 'libtox.dll'): + os.remove(d + 'libtox.dll') + if os.path.exists(d + 'libsodium.a'): + os.remove(d + 'libsodium.a') def reset(): @@ -490,9 +478,6 @@ def main(): elif arg == '--help': print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset') return - elif arg == '--configure': - configure() - return elif arg == '--clean': clean() return From 262714d3eef233ae90acdabb0196ebe09e12da8d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 6 Jul 2017 21:39:15 +0300 Subject: [PATCH 043/163] fix and cleanup --- setup.py | 3 +-- toxygen/main.py | 9 --------- toxygen/menu.py | 15 +++++++++------ 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/setup.py b/setup.py index 3ca0f3f..4623952 100644 --- a/setup.py +++ b/setup.py @@ -57,5 +57,4 @@ setup(name='Toxygen', }, cmdclass={ 'install': InstallScript, - }, - ) + }) diff --git a/toxygen/main.py b/toxygen/main.py index ad78407..04bad7f 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -454,15 +454,6 @@ def clean(): remove(d) -def configure(): - """Removes unused libs""" - d = curr_directory() + '/libs/' - if os.path.exists(d + 'libtox.dll'): - os.remove(d + 'libtox.dll') - if os.path.exists(d + 'libsodium.a'): - os.remove(d + 'libsodium.a') - - def reset(): Settings.reset_auto_profile() diff --git a/toxygen/menu.py b/toxygen/menu.py index beea221..bd4e98c 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -857,12 +857,15 @@ class VideoSettings(CenteredWidget): self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:")) def closeEvent(self, event): - settings = Settings.get_instance() - settings.video['device'] = self.devices[self.input.currentIndex()] - text = self.video_size.currentText() - settings.video['width'] = int(text.split(' ')[0]) - settings.video['height'] = int(text.split(' ')[-1]) - settings.save() + try: + settings = Settings.get_instance() + settings.video['device'] = self.devices[self.input.currentIndex()] + text = self.video_size.currentText() + settings.video['width'] = int(text.split(' ')[0]) + settings.video['height'] = int(text.split(' ')[-1]) + settings.save() + except Exception as ex: + print('Saving video settings error: ' + str(ex)) def selectionChanged(self): width, height = self.frame_max_sizes[self.input.currentIndex()] From 000a4c7920377d84e1ca433492981bc1b19d86a7 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 6 Jul 2017 22:16:12 +0300 Subject: [PATCH 044/163] travis.yml update --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index 81e9a02..cfabadd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,12 +2,16 @@ language: python python: - "3.5" - "3.6" +os: + - linux +dist: trusty notifications: email: false before_install: - sudo apt-get update - sudo apt-get install -y checkinstall build-essential - sudo apt-get install portaudio19-dev + - sudo apt-get install libsecret-1-dev - sudo apt-get install libconfig-dev libvpx-dev check -qq install: - pip install sip From fbe0b1f819b67f4166cabd8e448d4a11232fda5c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 7 Jul 2017 20:30:04 +0300 Subject: [PATCH 045/163] installation updates --- MANIFEST.in | 2 -- docs/install.md | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index a4e17a3..dba3ada 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,5 +16,3 @@ include toxygen/styles/*.qss include toxygen/translations/*.qm include toxygen/libs/libtox.dll include toxygen/libs/libsodium.a -include toxygen/libs/libtox64.dll -include toxygen/libs/libsodium64.a diff --git a/docs/install.md b/docs/install.md index 79c07f4..c9e12cc 100644 --- a/docs/install.md +++ b/docs/install.md @@ -38,7 +38,7 @@ Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly r 2. Install PyQt5: ``pip install pyqt5`` 3. Install PyAudio: ``pip install pyaudio`` 4. Install numpy: ``pip install numpy`` -5. Install OpenCV: ``pip install opencv-python`` +5. Install OpenCV: ``pip install opencv-python`` or via ``sudo apt-get install python3-opencv`` 6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) 7. Unpack archive 8. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\ @@ -63,7 +63,7 @@ Optional: install toxygen using setup.py: ``python setup.py install`` 4. Install PyAudio: ``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``) 5. Install NumPy: ``sudo pip3 install numpy`` -6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) +6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo apt-get install python3-opencv`` 7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) 8. Unpack archive 9. Run app: From b6f512349557668db3a561f4655942fb4ddfed04 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 9 Jul 2017 13:17:51 +0300 Subject: [PATCH 046/163] setup.py fix for packages --- setup.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 4623952..adcd58c 100644 --- a/setup.py +++ b/setup.py @@ -8,10 +8,23 @@ import sys version = program_version + '.0' -MODULES = ['PyQt5', 'PyAudio', 'numpy'] if system() == 'Windows': - MODULES.append('opencv-python') + MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python'] +else: + MODULES = [] + try: + import pyaudio + except ImportError: + MODULES.append('PyAudio') + try: + import PyQt5 + except ImportError: + MODULES.append('PyQt5') + try: + import numpy + except ImportError: + MODULES.append('numpy') class InstallScript(install): From 335d646c4240734c37d71384a3ddf14c086eeebe Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 9 Jul 2017 17:22:37 +0300 Subject: [PATCH 047/163] avatars fix --- toxygen/menu.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/toxygen/menu.py b/toxygen/menu.py index bd4e98c..c0ad290 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -248,11 +248,11 @@ class ProfileSettings(CenteredWidget): def set_avatar(self): choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar") name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', - QtGui.QComboBoxQtWidgets.QFileDialog.DontUseNativeDialog) + options=QtWidgets.QFileDialog.DontUseNativeDialog) if name[0]: bitmap = QtGui.QPixmap(name[0]) - bitmap.scaled(QtCore.QSize(128, 128), aspectMode=QtCore.Qt.KeepAspectRatio, - mode=QtCore.Qt.SmoothTransformation) + bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio, + transformMode=QtCore.Qt.SmoothTransformation) byte_array = QtCore.QByteArray() buffer = QtCore.QBuffer(byte_array) @@ -262,13 +262,13 @@ class ProfileSettings(CenteredWidget): def export_profile(self): directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog, - dir=curr_directory()) + '/' + dir=curr_directory()) + '/' if directory != '/': reply = QtWidgets.QMessageBox.question(None, QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Use new path'), + 'Use new path'), QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Do you want to move your profile to this location?'), + 'Do you want to move your profile to this location?'), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) settings = Settings.get_instance() From 1f4e81af35bef15e24d8ed289792ff554c84385f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 9 Jul 2017 17:37:05 +0300 Subject: [PATCH 048/163] export fix. version++ --- toxygen/menu.py | 4 ++-- toxygen/util.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/toxygen/menu.py b/toxygen/menu.py index c0ad290..4b2419a 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -261,8 +261,8 @@ class ProfileSettings(CenteredWidget): Profile.get_instance().set_avatar(bytes(byte_array.data())) def export_profile(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog, - dir=curr_directory()) + '/' + directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(), + QtWidgets.QFileDialog.DontUseNativeDialog) + '/' if directory != '/': reply = QtWidgets.QMessageBox.question(None, QtWidgets.QApplication.translate("ProfileSettingsForm", diff --git a/toxygen/util.py b/toxygen/util.py index 2183f4a..e8e96c5 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.3.0' +program_version = '0.3.1' def cached(func): From 300b28bdfabc3e588bd39de19614d47b9d7f37e2 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 10 Jul 2017 18:23:20 +0300 Subject: [PATCH 049/163] set alias fix --- toxygen/profile.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index c9502e5..1d744db 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -663,15 +663,15 @@ class Profile(basecontact.BaseContact, Singleton): friend = self._contacts[num] name = friend.name dialog = QtWidgets.QApplication.translate('MainWindow', - "Enter new alias for friend {} or leave empty to use friend's name:") + "Enter new alias for friend {} or leave empty to use friend's name:") dialog = dialog.format(name) title = QtWidgets.QApplication.translate('MainWindow', - 'Set alias') - text, ok = QtGui.QInputDialog.getText(None, - title, - dialog, - QtWidgets.QLineEdit.Normal, - name) + 'Set alias') + text, ok = QtWidgets.QInputDialog.getText(None, + title, + dialog, + QtWidgets.QLineEdit.Normal, + name) if ok: settings = Settings.get_instance() aliases = settings['friends_aliases'] From 1e1772e3067f8e61c176c03857355ad984e6202d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 12 Jul 2017 21:18:21 +0300 Subject: [PATCH 050/163] screen sharing initial commit --- toxygen/calls.py | 13 +++++++++---- toxygen/menu.py | 8 ++++++-- toxygen/screen_sharing.py | 22 ++++++++++++++++++++++ toxygen/settings.py | 2 +- 4 files changed, 38 insertions(+), 7 deletions(-) create mode 100644 toxygen/screen_sharing.py diff --git a/toxygen/calls.py b/toxygen/calls.py index 6d6bbef..a16a79d 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -6,6 +6,7 @@ from toxav_enums import * import cv2 import itertools import numpy as np +import screen_sharing # TODO: play sound until outgoing call will be started or cancelled @@ -203,10 +204,14 @@ class AV: self._video_width = s.video['width'] self._video_height = s.video['height'] - self._video = cv2.VideoCapture(s.video['device']) - self._video.set(cv2.CAP_PROP_FPS, 25) - self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width) - self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) + if s.video['device'] == -1: + self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'], + s.video['width'], s.video['height']) + else: + self._video = cv2.VideoCapture(s.video['device']) + self._video.set(cv2.CAP_PROP_FPS, 25) + self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width) + self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) self._video_thread = threading.Thread(target=self.send_video) self._video_thread.start() diff --git a/toxygen/menu.py b/toxygen/menu.py index 4b2419a..547d721 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -832,8 +832,12 @@ class VideoSettings(CenteredWidget): self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) self.input.currentIndexChanged.connect(self.selectionChanged) import cv2 - self.devices = [] - self.frame_max_sizes = [] + self.devices = [-1] + screen = QtWidgets.QApplication.primaryScreen() + size = screen.size() + self.frame_max_sizes = [(size.width(), size.height())] + desktop = QtWidgets.QApplication.translate("videoSettingsForm", "Desktop") + self.input.addItem(desktop) for i in range(10): v = cv2.VideoCapture(i) if v.isOpened(): diff --git a/toxygen/screen_sharing.py b/toxygen/screen_sharing.py new file mode 100644 index 0000000..265658c --- /dev/null +++ b/toxygen/screen_sharing.py @@ -0,0 +1,22 @@ +import numpy as np +from PyQt5 import QtWidgets + + +class DesktopGrabber: + + def __init__(self, x, y, width, height): + self._x = x + self._y = y + self._width = width + self._height = height + self._width -= width % 4 + self._height -= height % 4 + self._screen = QtWidgets.QApplication.primaryScreen() + + def read(self): + pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height) + image = pixmap.toImage() + s = image.bits().asstring(self._width * self._height * 4) + arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4)) + + return True, arr diff --git a/toxygen/settings.py b/toxygen/settings.py index 0718b17..5457ba6 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -47,7 +47,7 @@ class Settings(dict, Singleton): self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1, 'output': p.get_default_output_device_info()['index'] if output_devices else -1, 'enabled': input_devices and output_devices} - self.video = {'device': 0, 'width': 640, 'height': 480} + self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0} @staticmethod def get_auto_profile(): From 2ff41313f88f8f55ab2e7e0bad7da17d33144aeb Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 12 Jul 2017 21:36:19 +0300 Subject: [PATCH 051/163] default profile bug fix. install.md fix --- docs/install.md | 6 +++--- toxygen/settings.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/install.md b/docs/install.md index c9e12cc..3f29b79 100644 --- a/docs/install.md +++ b/docs/install.md @@ -17,7 +17,7 @@ Run app using ``toxygen`` command. 2. Install PortAudio: ``sudo apt-get install portaudio19-dev`` 3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5`` -4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) +4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo apt-get install python3-opencv`` 5. Install toxygen: ``sudo pip3 install toxygen`` 6. Run toxygen using ``toxygen`` command. @@ -38,10 +38,10 @@ Note: 32-bit Python isn't supported due to bug with videocalls. It is strictly r 2. Install PyQt5: ``pip install pyqt5`` 3. Install PyAudio: ``pip install pyaudio`` 4. Install numpy: ``pip install numpy`` -5. Install OpenCV: ``pip install opencv-python`` or via ``sudo apt-get install python3-opencv`` +5. Install OpenCV: ``pip install opencv-python`` 6. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) 7. Unpack archive -8. Download latest libtox.dll build, download latest libsodium.a build, put it into \src\libs\ +8. Download latest libtox.dll build, download latest libsodium.a build, put it into \toxygen\libs\ 9. Run \toxygen\main.py. Optional: install toxygen using setup.py: ``python setup.py install`` diff --git a/toxygen/settings.py b/toxygen/settings.py index 5457ba6..b0d0502 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -57,7 +57,10 @@ class Settings(dict, Singleton): data = fl.read() auto = json.loads(data) if 'path' in auto and 'name' in auto: - return str(auto['path']), str(auto['name']) + path = str(auto['path']) + name = str(auto['name']) + if os.path.isfile(append_slash(path) + name + '.tox'): + return path, name return '', '' @staticmethod From ace663804e21c4b3b33e2b9dd2d24f4b137b73f7 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 13 Jul 2017 21:02:42 +0300 Subject: [PATCH 052/163] screen sharing - area selection --- toxygen/mainscreen_widgets.py | 37 ++--------------------------------- toxygen/menu.py | 36 +++++++++++++++++++++++++++++++++- toxygen/widgets.py | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 36 deletions(-) diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index 7632af0..b6d4b0c 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -1,5 +1,5 @@ from PyQt5 import QtCore, QtGui, QtWidgets -from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit +from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit from profile import Profile import smileys import util @@ -71,38 +71,12 @@ class MessageArea(QtWidgets.QPlainTextEdit): self.insertPlainText(text) -class ScreenShotWindow(QtWidgets.QWidget): - - def __init__(self, parent): - super(ScreenShotWindow, self).__init__() - self.parent = parent - self.setMouseTracking(True) - self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) - self.showFullScreen() - self.setWindowOpacity(0.5) - self.rubberband = RubberBand() - self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint) - self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground) +class ScreenShotWindow(RubberBandWindow): def closeEvent(self, *args): if self.parent.isHidden(): self.parent.show() - def mousePressEvent(self, event): - self.origin = event.pos() - self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize())) - self.rubberband.show() - QtWidgets.QWidget.mousePressEvent(self, event) - - def mouseMoveEvent(self, event): - if self.rubberband.isVisible(): - self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized()) - left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height())) - right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height())) - top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y()) - bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height()) - self.setMask(left + right + top + bottom) - def mouseReleaseEvent(self, event): if self.rubberband.isVisible(): self.rubberband.hide() @@ -121,13 +95,6 @@ class ScreenShotWindow(QtWidgets.QWidget): Profile.get_instance().send_screenshot(bytes(byte_array.data())) self.close() - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Escape: - self.rubberband.setHidden(True) - self.close() - else: - super(ScreenShotWindow, self).keyPressEvent(event) - class SmileyWindow(QtWidgets.QWidget): """ diff --git a/toxygen/menu.py b/toxygen/menu.py index 547d721..b361154 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -2,7 +2,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from settings import * from profile import Profile from util import curr_directory, copy -from widgets import CenteredWidget, DataLabel, LineEdit +from widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow import pyaudio import toxes import plugin_support @@ -802,6 +802,18 @@ class AudioSettings(CenteredWidget): settings.save() +class DesktopAreaSelectionWindow(RubberBandWindow): + + def mouseReleaseEvent(self, event): + if self.rubberband.isVisible(): + self.rubberband.hide() + rect = self.rubberband.geometry() + width, height = rect.width(), rect.height() + if width >= 8 and height >= 8: + self.parent.save(width, height) + self.close() + + class VideoSettings(CenteredWidget): """ Audio calls settings form @@ -812,6 +824,7 @@ class VideoSettings(CenteredWidget): self.initUI() self.retranslateUi() self.center() + self.desktopAreaSelection = None def initUI(self): self.setObjectName("videoSettingsForm") @@ -831,6 +844,9 @@ class VideoSettings(CenteredWidget): self.input = QtWidgets.QComboBox(self) self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) self.input.currentIndexChanged.connect(self.selectionChanged) + self.button = QtWidgets.QPushButton(self) + self.button.clicked.connect(self.button_clicked) + self.button.setGeometry(QtCore.QRect(25, 70, 350, 30)) import cv2 self.devices = [-1] screen = QtWidgets.QApplication.primaryScreen() @@ -859,6 +875,10 @@ class VideoSettings(CenteredWidget): def retranslateUi(self): self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings")) self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:")) + self.button.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Select region")) + + def button_clicked(self): + self.desktopAreaSelection = DesktopAreaSelectionWindow(self) def closeEvent(self, event): try: @@ -871,7 +891,21 @@ class VideoSettings(CenteredWidget): except Exception as ex: print('Saving video settings error: ' + str(ex)) + def save(self, width, height): + self.desktopAreaSelection = None + settings = Settings.get_instance() + settings.video['device'] = -1 + settings.video['width'] = width + settings.video['height'] = height + settings.save() + def selectionChanged(self): + if self.input.currentIndex() == 0: + self.button.setVisible(True) + self.video_size.setVisible(False) + else: + self.button.setVisible(False) + self.video_size.setVisible(True) width, height = self.frame_max_sizes[self.input.currentIndex()] self.video_size.clear() dims = [ diff --git a/toxygen/widgets.py b/toxygen/widgets.py index 2cc97cf..b63deb0 100644 --- a/toxygen/widgets.py +++ b/toxygen/widgets.py @@ -77,6 +77,42 @@ class RubberBand(QtWidgets.QRubberBand): self.painter.end() +class RubberBandWindow(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__() + self.parent = parent + self.setMouseTracking(True) + self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) + self.showFullScreen() + self.setWindowOpacity(0.5) + self.rubberband = RubberBand() + self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint) + self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + def mousePressEvent(self, event): + self.origin = event.pos() + self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize())) + self.rubberband.show() + QtWidgets.QWidget.mousePressEvent(self, event) + + def mouseMoveEvent(self, event): + if self.rubberband.isVisible(): + self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized()) + left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height())) + right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height())) + top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y()) + bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height()) + self.setMask(left + right + top + bottom) + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Escape: + self.rubberband.setHidden(True) + self.close() + else: + super().keyPressEvent(event) + + def create_menu(menu): """ :return translated menu From 7505b06ddfdbfe8d1fdfd6500e618cd9178d80ba Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 13 Jul 2017 21:19:13 +0300 Subject: [PATCH 053/163] translations update --- toxygen/translations/en_GB.ts | 464 ++++++++++++++++----------------- toxygen/translations/fr_FR.ts | 466 +++++++++++++++++----------------- toxygen/translations/ru_RU.qm | Bin 24613 -> 24781 bytes toxygen/translations/ru_RU.ts | 462 ++++++++++++++++----------------- toxygen/translations/uk_UA.ts | 464 ++++++++++++++++----------------- 5 files changed, 947 insertions(+), 909 deletions(-) diff --git a/toxygen/translations/en_GB.ts b/toxygen/translations/en_GB.ts index 7c49649..588d0ff 100644 --- a/toxygen/translations/en_GB.ts +++ b/toxygen/translations/en_GB.ts @@ -1,24 +1,24 @@ - + AddContact - + Add contact Add contact - + TOX ID: TOX ID: - + Message: Message: - + TOX ID or public key of contact @@ -26,7 +26,7 @@ Callback - + File from @@ -34,32 +34,32 @@ Form - + Send request Send request - + IPv6 IPv6 - + UDP UDP - + Proxy Proxy - + IP: IP: - + Port: Port: @@ -69,12 +69,12 @@ Online contacts - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -84,98 +84,98 @@ can produce IP leak MainWindow - + Profile - + Settings - + About - + Add contact - + Privacy - + Interface - + Notifications - + Network - + About program - + User {} wants to add you to contact list. Message: {} - + Friend request - + Choose file Choose file - + Disallow auto accept - + Allow auto accept - + Set alias - + Clear history - + Remove friend - + Enter new alias for friend {} or leave empty to use friend's name: Enter new alias for friend {} or leave empty to use friend's name: - + Audio Audio @@ -185,24 +185,24 @@ can produce IP leak Find contact - + Friend added Friend added - + Toxygen is Tox client written on Python. Version: Toxygen is Tox client written on Python. Version: - + Friend added without sending friend request Friend added without sending friend request - + Choose folder Choose folder @@ -217,237 +217,237 @@ Version: Send file - + Send message Send message - + Start audio call with friend Start audio call with friend - + Plugins - + List of plugins - + Search - + All - + Online - + Notes - + Notes about user - + Copy link location - + Copy - + Select all - + Delete - + Paste - + Cut - + Undo - + Redo - + Save - + User {} is now known as {} - + Delete message - + Lock - + Cannot lock app - + Error. Profile password is not set. - + Name - + Status message - + Public key - + Error - + Profile with this name already exists - + Choose folder with sticker pack - + Choose folder with smiley pack - + Import plugin - + Choose folder with plugin - + Restart Toxygen - + Plugin will be loaded after restart - + Quote selected text - + Chat history - + Export as text - + Export as HTML - + Updates - + Online first - + Online and by name - + Online first and by name - + Block friend - + Not found - + Text "{}" was not found - + Reload plugins - + Video @@ -455,22 +455,22 @@ Version: MenuWindow - + Send screenshot Send screenshot - + Send file Send file - + Add smiley - + Send sticker @@ -478,12 +478,12 @@ Version: NetworkSettings - + Network settings Network settings - + Restart TOX core Restart Tox core @@ -491,37 +491,37 @@ Version: PasswordScreen - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + There is no way to recover lost passwords - + Password must be at least 8 symbols @@ -529,12 +529,12 @@ Version: PluginWindow - + List of commands for plugin {} - + No commands available @@ -542,42 +542,42 @@ Version: PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + No description available - + Disable plugin - + Enable plugin - + No plugins found - + Error @@ -585,132 +585,132 @@ Version: ProfileSettingsForm - + Export profile - + Profile settings - + Name: - + Status: - + TOX ID: - + Copy TOX ID - + New avatar - + Reset avatar - + New NoSpam New NoSpam - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + Leaving blank will reset current password - + There is no way to recover lost passwords - + Password must be at least 8 symbols - + Choose avatar - + Online - + Away - + Busy - + Mark as not default profile - + Mark as default profile - + Copy public key - + Use new path - + Do you want to move your profile to this location? @@ -718,67 +718,67 @@ Version: WelcomeScreen - + Don't show again - + Tip of the day - + Press Esc if you want hide app to tray. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. - + Right click on screenshot button hides app to tray during screenshot. - + Use Settings -> Interface to customize interface. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu - + Use right click on inline image to save it - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 @@ -786,17 +786,17 @@ Version: audioSettingsForm - + Audio settings Audio settings - + Input device: Input device: - + Output device: Output device: @@ -804,32 +804,32 @@ Version: incoming_call - + Incoming video call Incoming video call - + Incoming audio call Incoming audio call - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -837,82 +837,82 @@ Version: interfaceForm - + Interface settings - + Theme: - + Language: - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Restart app to apply settings - + Restart required - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Show avatars in chat - + Close to tray - + Select font @@ -920,72 +920,72 @@ Version: login - + Log in - + Create - + Profile name: - + Load profile - + Use as default - + Load existing profile - + Create new profile - + toxygen - + Profile name - + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? - + Do you want to set profile password? - + Do you want to save profile in default folder? If no, profile will be saved in program folder - + Profile saving error! Does Toxygen have permission to write to this directory? - + Update for Toxygen was found. Download and install it? @@ -993,22 +993,22 @@ Version: notificationsForm - + Notification settings - + Enable notifications - + Enable call's sound - + Enable sound notifications @@ -1016,17 +1016,17 @@ Version: pass - + Enter password - + Password: - + Incorrect password @@ -1034,72 +1034,72 @@ Version: privacySettings - + Privacy settings - + Save chat history - + Allow file auto accept - + Send typing notifications - + Auto accept default path: - + Change - + Allow inlines - + Chat history - + History will be cleaned! Continue? - + Blocked users: Blocked users: - + Unblock Unblock - + Block user Block user - + Add to friend list Add to friend list - + Do you want to add this user to friend list? Do you want to add this user to friend list? @@ -1109,12 +1109,12 @@ Version: Block by TOX ID: - + Block by public key: - + Save unsent messages only @@ -1122,32 +1122,32 @@ Version: tray - + Open Toxygen - + Exit - + Set status - + Online - + Away - + Busy @@ -1155,57 +1155,57 @@ Version: updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date @@ -1213,14 +1213,24 @@ Version: videoSettingsForm - + Video settings - + Device: + + + Desktop + + + + + Select region + + diff --git a/toxygen/translations/fr_FR.ts b/toxygen/translations/fr_FR.ts index 69a7b38..b389de0 100644 --- a/toxygen/translations/fr_FR.ts +++ b/toxygen/translations/fr_FR.ts @@ -1,25 +1,24 @@ - - + AddContact - + Add contact Ajouter un contact - + TOX ID: ID Tox : - + Message: Message : - + TOX ID or public key of contact ID Tox ou clé publique de contact @@ -27,7 +26,7 @@ Callback - + File from Fichier de @@ -35,32 +34,32 @@ Form - + Send request Envoyer une demande - + IPv6 IPv6 - + UDP UDP - + Proxy Proxy - + IP: IP : - + Port: Port : @@ -70,12 +69,12 @@ Contacts connectés - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -87,58 +86,58 @@ peut entrainer une fuite d'IP MainWindow - + Profile Profil - + Settings Paramètres - + About À Propos - + Add contact Ajouter un contact - + Privacy Confidentialité - + Interface Interface - + Notifications Notifications - + Network Réseau - + About program À propos de toxygen - + User {} wants to add you to contact list. Message: {} L'Utilisateur {} veut vous ajouter à sa liste de contacts. Message : {} - + Friend request Demande de contact @@ -148,27 +147,27 @@ peut entrainer une fuite d'IP Toxygen est un client Tox écris en Python 2.7. Version : - + Choose file Sélectionner un fichier - + Disallow auto accept Désactiver l'auto-réception - + Allow auto accept Activer l'auto-réception - + Set alias Définir un alias - + Clear history Vider l'historique @@ -178,17 +177,17 @@ peut entrainer une fuite d'IP Copier la clé publique - + Remove friend Retirer ce contact - + Enter new alias for friend {} or leave empty to use friend's name: Entrez un nouvel alias pour le contact {} ou laissez vide pour garder son nom de base : - + Audio Audio @@ -198,26 +197,25 @@ peut entrainer une fuite d'IP Trouver le contact - + Friend added Contact ajouté - + Toxygen is Tox client written on Python. Version: Toxygen est un client Tox écrit en Python. Version : - + Friend added without sending friend request Contact ajouté sans envoi de demande - + Choose folder - un ? le ? Sélectionner un dossier @@ -231,237 +229,237 @@ Version : Envoyer le fichier - + Send message Envoyer le message - + Start audio call with friend Démarrer un appel audio avec un ami - + Plugins Plugins - + List of plugins Liste de plugins - + Search Chercher - + All Tous - + Online En ligne - + Notes Notes - + Notes about user Notes sur l'utilisateur - + Copy link location Copier l'emplacement du lien - + Copy Copier - + Select all Tout sélectionner - + Delete Supprimer - + Paste Coller - + Cut Couper - + Undo Annuler - + Redo Refaire - + Save Sauvegarder - + User {} is now known as {} L'utilisateur {} s'appelle désormais {} - + Delete message Supprimer ce message - + Lock Verrouiller - + Cannot lock app Impossible de verrouiller l'application - + Error. Profile password is not set. Erreur. Le profil n'a pas de mot de passe. - + Name Nom - + Status message Status - + Public key Clé publique - + Error Erreur - + Profile with this name already exists Un profil ayant ce nom existe déjà - + Choose folder with sticker pack Sélectionner le dossier contenant le pack de stickers - + Choose folder with smiley pack Sélectionner le dossier contenant le pack de smileys - + Import plugin Importer un plugin - + Choose folder with plugin Sélectionner un dossier avec des plugins - + Restart Toxygen Redémarrer Toxyger - + Plugin will be loaded after restart Le plugin sera chargé après le redémarrage - + Quote selected text Citer le texte sélectionné - + Chat history Historique de la conversation - + Export as text Exporter comme texte - + Export as HTML Exporter comme HTML - + Updates Mises à jour - + Online first En ligne d'abord - + Online and by name En ligne et par nom - + Online first and by name En ligne d'abord puis par nom - + Block friend Bloquer le contact - + Not found Non trouvé - + Text "{}" was not found Le texte "{}" n'a pas été trouvé - + Reload plugins Recharger les plugins - + Video Vidéo @@ -469,22 +467,22 @@ Version : MenuWindow - + Send screenshot Envoyer une capture d'écran - + Send file Envoyer un fichier - + Add smiley Ajouter un smiley - + Send sticker Ajouter un sticker @@ -492,12 +490,12 @@ Version : NetworkSettings - + Network settings Paramètres réseaux - + Restart TOX core Relancer le noyau Tox @@ -505,37 +503,37 @@ Version : PasswordScreen - + Profile password Mot de passe du profil - + Password (at least 8 symbols) Mot de passe (8 symboles minimum) - + Confirm password Confirmation - + Set password Enregistrer le mot de passe - + Passwords do not match Les mots de passes sont différents - + There is no way to recover lost passwords Il est impossible de récuperer un mot de passe perdu - + Password must be at least 8 symbols Un mot de passe doit faire 8 symboles minimum @@ -543,12 +541,12 @@ Version : PluginWindow - + List of commands for plugin {} Liste de commandes du plugin {} - + No commands available Pas de commandes disponibles @@ -556,42 +554,42 @@ Version : PluginsForm - + Plugins Plugins - + Open selected plugin Ouvrir le plugin sélectionné - + No GUI found for this plugin Pas d'interface pour ce plugin - + No description available Pas de description - + Disable plugin Désactiver le plugin - + Enable plugin Activer le plugin - + No plugins found Pas de plugin trouvé - + Error Erreur @@ -599,132 +597,132 @@ Version : ProfileSettingsForm - + Export profile Exporter le profil - + Profile settings Paramètres du profil - + Name: Nom : - + Status: Status : - + TOX ID: ID TOX : - + Copy TOX ID Copier l'ID Tox - + New avatar Nouvel avatar - + Reset avatar Réinitialiser l'avatar - + New NoSpam Nouveau NoSpam - + Profile password Mot de passe du profil - + Password (at least 8 symbols) Mot de passe (8 symboles minimum) - + Confirm password Confirmation - + Set password Sauvegarder le mot de passe - + Passwords do not match Les mots de passe sont différents - + Leaving blank will reset current password Laisser vide réinitialisera le mot de passe actuel - + There is no way to recover lost passwords Il est impossible de récupérer un mot de passe perdu - + Password must be at least 8 symbols Le mot de passe doit faire 8 symboles minimum - + Choose avatar Choisir l'avatar - + Online En ligne - + Away Absent - + Busy Occupé - + Mark as not default profile Ne plus en faire le profil par défaut - + Mark as default profile En faire le profil par défaut - + Copy public key Copier la clé publique - + Use new path Utiliser un nouveau chemin - + Do you want to move your profile to this location? Déplacer le profil dans ce dossier ? @@ -732,67 +730,67 @@ Version : WelcomeScreen - + Don't show again Ne plus montrer - + Tip of the day Astuce du jou - + Press Esc if you want hide app to tray. Appuyez sur échap pour réduire l'application. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> Vous pouvez utiliser Tox avec Tor. Pour plus d'informations, voir <a href="https://wiki.tox.chat/users/tox_over_tor_tot">cet article</a> - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. Vous pouvez mettre un mot de passe dans Profil -> Paramètres -> Mot de passe pour que toxygen encrypte votre historique et vos paramètres. - + Right click on screenshot button hides app to tray during screenshot. Faire un clic droit sur le bouton de capture d'écran réduit l'application avant de capturer l'écran. - + Use Settings -> Interface to customize interface. Vous pouvez customizer votre interface dans Paramètres -> Interface. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. Toxygen permet d'envoyer des messages et fichiers en différé. Envoyez des messages ou fichiers à un contact hors ligne et il le recevra plus tard. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. Vous pouvez empecher le spam dans les demandes de contact avec Profil -> Paramètres -> Nouveau NoSpam. - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu Pour supprimer un seul message dans une conversation, faites un clic droit sur l'heure du message et sélectionnez "Supprimer ce message" dans le menu - + Use right click on inline image to save it Pour sauvegarder une image intégrée, faites un clic droit dessus - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> Depuis la version 0.1.3 Toxygen supporte les plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">En savoir plus</a> - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 Nouveau dans Toxygen 0.3.0 : <br>Appels vidéo<br>Support de Python3.6<br>Migration vers PyQt5 @@ -800,17 +798,17 @@ Version : audioSettingsForm - + Audio settings Paramètres audio - + Input device: Péripherique d'entrée : - + Output device: Péripherique de sortie : @@ -818,32 +816,32 @@ Version : incoming_call - + Incoming video call Appel vidéo entrant - + Incoming audio call Appel audio entrant - + Outgoing video call Appel vidéo sortant - + Outgoing audio call Appel audio sortant - + Call declined Appel refusé - + Call finished Appel terminé @@ -851,82 +849,82 @@ Version : interfaceForm - + Interface settings Paramètres de l'interface - + Theme: Thème : - + Language: Langue : - + Smileys Smileys - + Smiley pack: Pack de smileys : - + Mirror mode Mode miroir - + Messages font size: Taille des messages : - + Restart app to apply settings Redémarrer toxygen pour appliquer les paramètres - + Restart required Redémarrage nécessaire - + Select unread messages notification color Sélectionner la couleur des messages non-lus - + Compact contact list Liste de contacts compacte - + Import smiley pack Importer un pack de smileys - + Import sticker pack Importer un pack de stickers - + Show avatars in chat Montrer les avatars dans la conversation - + Close to tray Réduire - + Select font Sélectionner la police @@ -934,42 +932,42 @@ Version : login - + Log in Se connecter - + Create Créer - + Profile name: Nom du profil : - + Load profile Charger le profil - + Use as default Utiliser par défaut - + Load existing profile Charger un profil existant - + Create new profile Créer un nouveau profil - + toxygen toxygen @@ -979,32 +977,32 @@ Version : Il semble qu'une autre instance de Toxygen utilise ce profil ! Continuer ? - + Profile name Nom de profil - + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? Ce profil semble être utilisé par une autre instance de toxygen ou avoir été incorrectement fermé . Continuer ? - + Do you want to set profile password? Souhaitez vous protéger le profil par un mot de passe ? - + Do you want to save profile in default folder? If no, profile will be saved in program folder Souhaitez vous conserver le profil dans le dossier par défaut ? Si non, il sera conservé dans le dossier du programme - + Profile saving error! Does Toxygen have permission to write to this directory? Un problème est survenu lors de la sauvegarde du profil ! Toxygen as t'il le droit d'écrire dans ce dossier ? - + Update for Toxygen was found. Download and install it? Une mise à jour est disponible. La télécharger et l'installer ? @@ -1012,22 +1010,22 @@ Version : notificationsForm - + Notification settings Paramètres de notification - + Enable notifications Activer les notifications - + Enable call's sound Activer les sons d'appel - + Enable sound notifications Activer les sons de notifications @@ -1035,17 +1033,17 @@ Version : pass - + Enter password Entrer le mot de passe - + Password: Mot de passe : - + Incorrect password Mot de passe incorrect @@ -1053,72 +1051,72 @@ Version : privacySettings - + Privacy settings Paramètres de confidentialité - + Save chat history Sauvegarder l'historique de conversation - + Allow file auto accept Autoriser les fichier automatiquement - + Send typing notifications Informer de la frappe - + Auto accept default path: Chemin par défaut des fichiers acceptés automatiquement : - + Change Modifier - + Allow inlines Activer l'affichage integré - + Chat history Historique de conversation - + History will be cleaned! Continue? L'Historique va être vidé ! Confirmer ? - + Blocked users: Utilisateurs bloqués : - + Unblock Débloquer - + Block user Bloquer l'utilisateur - + Add to friend list Ajouter à la liste de contacts - + Do you want to add this user to friend list? Voulez vous aajouter cet utilisateur à votre liste de contacts ? @@ -1128,12 +1126,12 @@ Version : Bloquer l'ID TOX : - + Block by public key: Bloquer par clé publique : - + Save unsent messages only Sauvegarder les messages non envoyés uniquement @@ -1141,32 +1139,32 @@ Version : tray - + Open Toxygen Ouvrir Toxygen - + Exit Quitter - + Set status Changer le status - + Online En ligne - + Away Absent - + Busy Occupé @@ -1174,57 +1172,57 @@ Version : updateSettingsForm - + Update settings Paramètres de mise à jour - + Select update mode: Sélectionner le mode de mise à jour : - + Update Toxygen Mettre à jour toxygen - + Disabled Désactivé - + Manual Manuel - + Auto Automatique - + Error Erreur - + Problems with internet connection Il y à des problèmes avec votre connexion internet - + Updater not found Updater non trouvé - + No updates found Pas de mises à jour trouvés - + Toxygen is up to date Toxygen est à jour @@ -1232,14 +1230,24 @@ Version : videoSettingsForm - + Video settings Paramètres vidéo - + Device: Périphérique : + + + Desktop + + + + + Select region + + diff --git a/toxygen/translations/ru_RU.qm b/toxygen/translations/ru_RU.qm index 4f36c6d4374c338edd267f81499026da7a2b9d10..fddbe6bc379ad9dd4a54d73e1eed9650710413d7 100644 GIT binary patch delta 175 zcmZ2_fbr}>#t9;f3L8bQF)~H!Z+^?z$;^LBTY-TgB82PA&r=Kx5i>W}vKlh7d#2?v zFhty$yq2w+DY9|17JEd5mIR9eivf!viyezQiv^1%g93{qixZ0-i!}!W0|Nsa0|NuQ zOKNd;Nq)iPRq@i3o8p}<6j+2{tw0tXT}entk9}d4p4P TQj<#*ic-@v^YbRJimwI$=?E$O delta 55 zcmX?mka6h&#t9;f0vkoIF)~F=-~5)blbQX@&r=Kx5h0sfSq&K{?_;ZCig>x%iajD? M^7VM<$+-y$09n=*n*aa+ diff --git a/toxygen/translations/ru_RU.ts b/toxygen/translations/ru_RU.ts index 430ca10..a7f9dba 100644 --- a/toxygen/translations/ru_RU.ts +++ b/toxygen/translations/ru_RU.ts @@ -4,22 +4,22 @@ AddContact - + Add contact Добавить контакт - + TOX ID: TOX ID: - + Message: Сообщение: - + TOX ID or public key of contact TOX ID или публичный ключ контакта @@ -27,7 +27,7 @@ Callback - + File from Файл от @@ -35,32 +35,32 @@ Form - + Send request Отправить запрос - + IPv6 IPv6 - + UDP UDP - + Proxy Прокси - + IP: IP: - + Port: Порт: @@ -70,12 +70,12 @@ Контакты в сети - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -87,84 +87,84 @@ can produce IP leak MainWindow - + Profile Профиль - + Settings Настройки - + About О программе - + Add contact Добавить контакт - + Privacy Приватность - + Interface Интерфейс - + Notifications Уведомления - + Network Сеть - + About program О программе - + User {} wants to add you to contact list. Message: {} Пользователь {} хочет добавить Вас в список контактов. Сообщение: {} - + Friend request Запрос на добавление в друзья - + Choose file Выберите файл - + Disallow auto accept Запретить автоматическое получение файлов - + Allow auto accept Разрешить автоматическое получение файлов - + Set alias Изменить псевдоним - + Clear history Очистить историю @@ -174,17 +174,17 @@ can produce IP leak Копировать публичный ключ - + Remove friend Удалить друга - + Enter new alias for friend {} or leave empty to use friend's name: Введите новый псевдоним для друга {} или оставьте пустым для использования его имени: - + Audio Аудио @@ -194,23 +194,23 @@ can produce IP leak Найти контакт - + Friend added Друг добавлен - + Toxygen is Tox client written on Python. Version: Toxygen - клиент для мессенджера Tox, написанный на Python. Версия: - + Friend added without sending friend request Друг добавлен без отправки запроса на добавление в друзья - + Choose folder Выбрать папку @@ -225,237 +225,237 @@ Version: Отправить файл - + Send message Отправить сообщение - + Start audio call with friend Начать аудиозвонок с другом - + Plugins Плагины - + List of plugins Список плагинов - + Search Поиск - + All Все - + Online Онлайн - + Notes Заметки - + Notes about user Заметки о пользователе - + Copy link location Копировать адрес ссылки - + Copy Копировать - + Select all Выделить всё - + Delete Удалить - + Paste Вставить - + Cut Вырезать - + Undo Отменить - + Redo Повторить - + Save Сохранить - + User {} is now known as {} Пользователь {} сейчас известен как {} - + Delete message Удалить сообщение - + Lock Заблокировать - + Cannot lock app Невозможно заблокировать приложение - + Error. Profile password is not set. Ошибка. Пароль профиля не установлен. - + Name Имя - + Status message Статус - + Public key Публичный ключ - + Error Ошибка - + Profile with this name already exists Профиль с данным именем уже существует - + Choose folder with sticker pack Выберите папку в паком стикеров - + Choose folder with smiley pack Выберите папку с паком смайлов - + Import plugin Импортировать плагин - + Choose folder with plugin Выберите папку с плагином - + Restart Toxygen Перезапустите Toxygen - + Plugin will be loaded after restart Плагин будет загружен после перезапуска - + Quote selected text Цитировать выбранный текст - + Chat history История чата - + Export as text Экспортировать как текст - + Export as HTML Экспортировать как HTML - + Updates Обновления - + Online first Сначала онлайн - + Online and by name Онлайн и по имени - + Online first and by name Сначала онлайн и по имени - + Block friend Заблокировать друга - + Not found Не найдено - + Text "{}" was not found Текст "{}" не был найден - + Reload plugins Перезагрузить плагины - + Video Видео @@ -478,12 +478,12 @@ Version: Остановить запись - + Send screenshot Отправить снимок экрана - + Send file Отправить файл @@ -498,12 +498,12 @@ Version: Отправить видеосообщение - + Add smiley Добавить смайлик - + Send sticker Отправить стикер @@ -511,12 +511,12 @@ Version: NetworkSettings - + Network settings Настройки сети - + Restart TOX core Перезапустить ядро TOX @@ -524,37 +524,37 @@ Version: PasswordScreen - + Profile password Пароль профиля - + Password (at least 8 symbols) Пароль (минимум 8 символов) - + Confirm password Подтверждение пароля - + Set password Изменить пароль - + Passwords do not match Пароли не совпадают - + There is no way to recover lost passwords Восстановление забытых паролей не поддерживается - + Password must be at least 8 symbols Пароль должен быть длиной не менее 8 символов @@ -562,12 +562,12 @@ Version: PluginWindow - + List of commands for plugin {} Список команд для плагина {} - + No commands available Команды не найдены @@ -575,42 +575,42 @@ Version: PluginsForm - + Plugins Плагины - + Open selected plugin Открыть выбранный плагин - + No GUI found for this plugin GUI для данного плагина не найден - + No description available Описание недоступно - + Disable plugin Отключить плагин - + Enable plugin Включить плагин - + No plugins found Плагины не найдены - + Error Ошибка @@ -618,32 +618,32 @@ Version: ProfileSettingsForm - + Export profile Экспорт профиля - + Profile settings Настройки профиля - + Name: Имя: - + Status: Статус: - + TOX ID: TOX ID: - + Copy TOX ID Копировать TOX ID @@ -653,102 +653,102 @@ Version: Язык: - + New avatar Новый аватар - + Reset avatar Сбросить аватар - + New NoSpam Новый NoSpam - + Profile password Пароль профиля - + Password (at least 8 symbols) Пароль (минимум 8 символов) - + Confirm password Подтверждение пароля - + Set password Изменить пароль - + Passwords do not match Пароли не совпадают - + Leaving blank will reset current password Пустое поле сбросит текущий пароль - + There is no way to recover lost passwords Восстановление забытых паролей не поддерживается - + Password must be at least 8 symbols Пароль должен быть длиной не менее 8 символов - + Choose avatar Выбрать аватар - + Online Онлайн - + Away Нет на месте - + Busy Занят - + Mark as not default profile Отключить автозагрузку профиля - + Mark as default profile Сделать профилем по умолчанию - + Copy public key Копировать публичный ключ - + Use new path Использовать новый путь - + Do you want to move your profile to this location? Вы хотите переместить ваш профиль в эту папку? @@ -756,17 +756,17 @@ Version: WelcomeScreen - + Don't show again Не показывать снова - + Tip of the day Подсказка дня - + Press Esc if you want hide app to tray. Нажатие Esc сворачивает приложение в трей. @@ -776,7 +776,7 @@ Version: Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> Вы можете использовать Tox через Tor. Дополнительная информация <a href="https://wiki.tox.chat/users/tox_over_tor_tot">тут</a> @@ -786,7 +786,7 @@ Version: Используйте Настройки -> Интерфейс для настройки интерфейса - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. Установите пароль профиля: Профиль -> Настройки. Пароль позволяет шифровать историю переписки и настройки. @@ -811,22 +811,22 @@ Version: Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam - + Right click on screenshot button hides app to tray during screenshot. Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота. - + Use Settings -> Interface to customize interface. Используйте Настройки -> Интерфейс для настройки интерфейса. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. Toxygen поддерживает псевдооффлайн сообщения и файл трансферы. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam. @@ -836,12 +836,12 @@ Version: Новое в Toxygen 0.2.3:<br>Соответствие TCS<br>Импорт плагинов, смайлов и стикеров<br>Исправления ошибок - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu Чтобы удалить отдельное сообщение в чате сделайте правый клик на спиннер или время сообщения и выберите "Удалить" в меню - + Use right click on inline image to save it Правый клик на инлайн изображении позволит сохранить его @@ -856,12 +856,12 @@ Version: Новое в Toxygen v0.2.6:<br>Поддержка обновлений<br>Улучшенная сортировка контактов<br>Улучшения в работе плагинов - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Узнать больше.</a> - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 Новое в Toxygen 0.3.0:<br>Видеозвонки<br>Поддержка Python3.6<br>Миграция на PyQt5 @@ -869,17 +869,17 @@ Version: audioSettingsForm - + Audio settings Настройки аудио - + Input device: Устройство ввода: - + Output device: Устройство вывода: @@ -887,32 +887,32 @@ Version: incoming_call - + Incoming video call Входящий видеозвонок - + Incoming audio call Входящий аудиозвонок - + Outgoing video call Исходящий видеозвонок - + Outgoing audio call Исходящий аудиозвонок - + Call declined Звонок отменен - + Call finished Звонок завершен @@ -920,82 +920,82 @@ Version: interfaceForm - + Interface settings Настройки интерфейса - + Theme: Тема: - + Language: Язык: - + Smileys Смайлики - + Smiley pack: Набор смайликов: - + Mirror mode Зеркальный режим - + Messages font size: Размер шрифта сообщений: - + Restart app to apply settings Для применения настроек необходимо перезапустить приложение - + Restart required Требуется перезапуск - + Select unread messages notification color Цвет уведомления о сообщении - + Compact contact list Компактный список контактов - + Import smiley pack Импортировать смайлы - + Import sticker pack Импортировать стикеры - + Show avatars in chat Показывать аватары в чате - + Close to tray Сворачивать в трей - + Select font Выбрать шрифт @@ -1003,42 +1003,42 @@ Version: login - + Log in Вход - + Create Создать - + Profile name: Имя профиля: - + Load profile Загрузить профиль - + Use as default По умолчанию - + Load existing profile Загрузить профиль - + Create new profile Создать новый профиль - + toxygen toxygen @@ -1048,32 +1048,32 @@ Version: Похоже, что этот профиль используется другим экземпляром Toxygen! Продолжить? - + Profile name Имя профиля - + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? Этот профиль используется другим экземпляром Toxygen или не был правильно закрыт. Продолжить? - + Do you want to set profile password? Хотите ли вы установить пароль профиля? - + Do you want to save profile in default folder? If no, profile will be saved in program folder Вы хотите сохранить профиль в папку по умолчанию? Если нет, профиль будет сохранен в папке с программой - + Profile saving error! Does Toxygen have permission to write to this directory? Ошибка сохранения профиля! Toxygen имеет разрешение на запись в данную папку? - + Update for Toxygen was found. Download and install it? Обновление для Toxygen было найдено. Загрузить и установить его? @@ -1081,22 +1081,22 @@ Version: notificationsForm - + Notification settings Настройки уведомлений - + Enable notifications Включить уведомления - + Enable call's sound Включить звук звонка - + Enable sound notifications Включить звуковые уведомления @@ -1105,17 +1105,17 @@ Version: pass - + Enter password Введите пароль - + Password: Пароль: - + Incorrect password Неверный пароль @@ -1123,72 +1123,72 @@ Version: privacySettings - + Privacy settings Настройки приватности - + Save chat history Сохранять историю переписки - + Allow file auto accept Разрешить автополучение файлов - + Send typing notifications Посылать уведомления о наборе текста - + Auto accept default path: Путь автоприема файлов: - + Change Изменить - + Allow inlines Разрешать инлайны - + Chat history История чата - + History will be cleaned! Continue? История переписки будет очищена! Продолжить? - + Blocked users: Заблокированные пользователи: - + Unblock Разблокировать - + Block user Заблокировать пользователя - + Add to friend list Добавить в список друзей - + Do you want to add this user to friend list? Добавить этого пользователя в список друзей? @@ -1198,12 +1198,12 @@ Version: Блокировать по TOX ID: - + Block by public key: Блокировать по публичному ключу: - + Save unsent messages only Сохранять только неотправленные сообщения @@ -1211,32 +1211,32 @@ Version: tray - + Open Toxygen Открыть Toxygen - + Exit Выход - + Set status Изменить статус - + Online Онлайн - + Away Нет на месте - + Busy Занят @@ -1244,57 +1244,57 @@ Version: updateSettingsForm - + Update settings Обновить настройки - + Select update mode: Выбрать режим обновлений: - + Update Toxygen Обновить Toxygen - + Disabled Отключены - + Manual Вручную - + Auto Автоматически - + Error Ошибка - + Problems with internet connection Проблемы с соединением - + Updater not found Апдейтер не был найден - + No updates found Обновления не найдены - + Toxygen is up to date Toxygen уже обновлен @@ -1302,14 +1302,24 @@ Version: videoSettingsForm - + Video settings Настройки видео - + Device: Устройство: + + + Desktop + Рабочий стол + + + + Select region + Выберите область + diff --git a/toxygen/translations/uk_UA.ts b/toxygen/translations/uk_UA.ts index 9e70b4b..5a7760a 100644 --- a/toxygen/translations/uk_UA.ts +++ b/toxygen/translations/uk_UA.ts @@ -1,24 +1,24 @@ - + AddContact - + TOX ID: TOX ID: - + Add contact Додати контакт - + Message: Повідомлення: - + TOX ID or public key of contact @@ -26,7 +26,7 @@ Callback - + File from @@ -34,32 +34,32 @@ Form - + IP: IP: - + UDP UDP - + HTTP HTTP - + IPv6 IPv6 - + Port: Порт: - + Proxy Проксі @@ -69,12 +69,12 @@ Контактів онлайн - + Send request Відправити запит - + WARNING: using proxy with enabled UDP can produce IP leak @@ -84,27 +84,27 @@ can produce IP leak MainWindow - + About program Про проґраму - + Friend request Запит дружби - + About Про - + Audio Звук - + Friend added Друга додано @@ -114,19 +114,19 @@ can produce IP leak Надіслати файл - + User {} wants to add you to contact list. Message: {} Користувач {} хоче додати вас до списку контактів. Повідомлення {} - + Network Мережа - + Clear history Очистити журнал @@ -136,69 +136,69 @@ can produce IP leak Копіювати публічний ключ - + Send message Надіслати повідомлення - + Set alias Встановити скорочення - + Privacy Приватність - + Profile Профіль - + Toxygen is Tox client written on Python. Version: Toxygen — це клієнт Tox написаний на Python. Версія: - + Choose file Обрати файл - + Enter new alias for friend {} or leave empty to use friend's name: Введіть нове скорочення для друга {} або залишіть порожнім, щоб використовувати його псевдо: - + Add contact Додати контакт - + Friend added without sending friend request Друга додано без надсилання запиту дружби - + Interface Зовнішній вигляд - + Settings Налаштування - + Notifications Сповіщення - + Remove friend Вилучити друга @@ -208,22 +208,22 @@ Version: Знайти контакт - + Choose folder Обрати теку - + Allow auto accept Дозволити автоприймання - + Disallow auto accept Заборонити автоприймання - + Start audio call with friend Почати звуковий дзвінок @@ -233,227 +233,227 @@ Version: Надіслати знімок екрану - + Error - + Profile with this name already exists - + User {} is now known as {} - + Choose folder with sticker pack - + Choose folder with smiley pack - + Quote selected text - + Plugins - + Delete message - + Lock - + List of plugins - + Video - + Updates - + Search - + All - + Online - + Online first - + Name - + Online and by name - + Online first and by name - + Import plugin - + Reload plugins - + Choose folder with plugin - + Restart Toxygen - + Plugin will be loaded after restart - + Cannot lock app - + Error. Profile password is not set. - + Chat history Журнал бесіди - + Export as text - + Export as HTML - + Copy - + Status message - + Public key - + Block friend - + Notes - + Notes about user - + Copy link location - + Select all - + Delete - + Paste - + Cut - + Undo - + Redo - + Save - + Text "{}" was not found - + Not found @@ -461,22 +461,22 @@ Version: MenuWindow - + Send screenshot Надіслати знімок екрану - + Send file Надіслати файл - + Add smiley - + Send sticker @@ -484,12 +484,12 @@ Version: NetworkSettings - + Network settings Налаштування мережі - + Restart TOX core Перезапустити ядро Tox @@ -497,37 +497,37 @@ Version: PasswordScreen - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + There is no way to recover lost passwords - + Password must be at least 8 symbols @@ -535,12 +535,12 @@ Version: PluginWindow - + List of commands for plugin {} - + No commands available @@ -548,42 +548,42 @@ Version: PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + Error - + No description available - + Disable plugin - + Enable plugin - + No plugins found @@ -591,132 +591,132 @@ Version: ProfileSettingsForm - + Name: Псевдо: - + Profile settings Налаштування профілю - + Reset avatar Скинути аватар - + New NoSpam Новий NoSpam - + Copy TOX ID Копіювати TOX ID - + New avatar Новий аватар - + Export profile Експортувати профіль - + TOX ID: TOX ID: - + Status: Статус: - + Profile password - + Password (at least 8 symbols) - + Confirm password - + Set password - + Passwords do not match - + Leaving blank will reset current password - + There is no way to recover lost passwords - + Online - + Away - + Busy - + Copy public key Копіювати публічний ключ - + Mark as not default profile - + Mark as default profile - + Password must be at least 8 symbols - + Choose avatar - + Use new path - + Do you want to move your profile to this location? @@ -724,67 +724,67 @@ Version: WelcomeScreen - + Don't show again - + Tip of the day - + Press Esc if you want hide app to tray. - + Right click on screenshot button hides app to tray during screenshot. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> - + Use Settings -> Interface to customize interface. - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu - + Use right click on inline image to save it - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. @@ -792,17 +792,17 @@ Version: audioSettingsForm - + Output device: Пристрій виводу: - + Audio settings Налаштування звуку - + Input device: Пристрій вводу: @@ -810,32 +810,32 @@ Version: incoming_call - + Incoming video call Вхідний відеодзвінок - + Incoming audio call Вхідний аудіодзвінок - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -843,82 +843,82 @@ Version: interfaceForm - + Language: Мова: - + Theme: Тема: - + Interface settings Налаштування зовнішнього вигляду - + Show avatars in chat - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Close to tray - + Select font - + Restart app to apply settings - + Restart required @@ -926,42 +926,42 @@ Version: login - + Profile name: Псевдо профілю: - + Load profile Завантажити профіль - + Use as default За замовчуванням - + Create new profile Створити новий профіль - + Create Створити - + Log in Увійти - + Load existing profile Завантажити існуючий - + toxygen toxygen @@ -971,32 +971,32 @@ Version: Схоже, що інша копія Toxygenʼу використовує цей профіль! Продовжити? - + Do you want to set profile password? - + Do you want to save profile in default folder? If no, profile will be saved in program folder - + Profile saving error! Does Toxygen have permission to write to this directory? - + Other instance of Toxygen uses this profile or profile was not properly closed. Continue? - + Update for Toxygen was found. Download and install it? - + Profile name @@ -1004,22 +1004,22 @@ Version: notificationsForm - + Enable sound notifications Увімкнути звукові сповіщення - + Enable notifications Увімкнути сповіщення - + Notification settings Налаштування сповіщень - + Enable call's sound Увімкнути звук дзвінка @@ -1027,17 +1027,17 @@ Version: pass - + Enter password - + Password: - + Incorrect password @@ -1045,12 +1045,12 @@ Version: privacySettings - + Privacy settings Налаштування приватності - + Add to friend list Додати до списку друзів @@ -1060,72 +1060,72 @@ Version: Блокувати по TOX ID: - + Blocked users: Блоковані користувачі: - + Change Змінити - + Send typing notifications Надсилати сповіщення про те, що я друкую - + Allow file auto accept Дозволити автоприймання файлів - + Allow inlines Дозволити інлайни - + Save chat history Зберігати журнал бесіди - + Block user Блокувати користувача - + Chat history Журнал бесіди - + Unblock Розблокувати - + History will be cleaned! Continue? Журнал буде очищено! Продовжити? - + Auto accept default path: Шлях за замовчуванням для автоприймання: - + Do you want to add this user to friend list? Ви хочете додати цього користувача у список друзів? - + Block by public key: - + Save unsent messages only @@ -1133,32 +1133,32 @@ Version: tray - + Exit Вихід - + Open Toxygen Відкрити Toxygen - + Set status - + Online - + Away - + Busy @@ -1166,57 +1166,57 @@ Version: updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date @@ -1224,14 +1224,24 @@ Version: videoSettingsForm - + Video settings - + Device: + + + Desktop + + + + + Select region + + From c755b4a52a1cc9b3db5d12822f24afc71cb84914 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 14 Jul 2017 21:21:53 +0300 Subject: [PATCH 054/163] light theme fix --- toxygen/styles/style.qss | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/toxygen/styles/style.qss b/toxygen/styles/style.qss index 11beeb5..26fbaf2 100644 --- a/toxygen/styles/style.qss +++ b/toxygen/styles/style.qss @@ -2,3 +2,28 @@ { padding-left: 22px; } + +MessageEdit +{ + border: none; +} + +MessageEdit::focus +{ + border: none; +} + +MessageItem::focus +{ + border: none; +} + +MessageEdit:hover +{ + border: none; +} + +MessageEdit +{ + background-color: transparent; +} From 4854b6151dab7b17e6cd6b5857c3c829e15d5a19 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 14 Jul 2017 21:37:50 +0300 Subject: [PATCH 055/163] desktop sharing - area selection fix --- toxygen/menu.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/toxygen/menu.py b/toxygen/menu.py index b361154..3707706 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -810,7 +810,7 @@ class DesktopAreaSelectionWindow(RubberBandWindow): rect = self.rubberband.geometry() width, height = rect.width(), rect.height() if width >= 8 and height >= 8: - self.parent.save(width, height) + self.parent.save(rect.x(), rect.y(), width, height) self.close() @@ -891,12 +891,14 @@ class VideoSettings(CenteredWidget): except Exception as ex: print('Saving video settings error: ' + str(ex)) - def save(self, width, height): + def save(self, x, y, width, height): self.desktopAreaSelection = None settings = Settings.get_instance() settings.video['device'] = -1 settings.video['width'] = width settings.video['height'] = height + settings.video['x'] = x + settings.video['y'] = y settings.save() def selectionChanged(self): From f4d806f5fcc5cef7a7ffdefc357de0d53920e047 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 15 Jul 2017 12:28:19 +0300 Subject: [PATCH 056/163] readme update --- README.md | 57 +++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index e9dbc93..6ded3c9 100644 --- a/README.md +++ b/README.md @@ -14,35 +14,34 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu ### Features: -- [x] 1v1 messages -- [x] File transfers -- [x] Audio calls -- [x] Video calls -- [x] Plugins support -- [x] Chat history -- [x] Emoticons -- [x] Stickers -- [x] Screenshots -- [x] Name lookups (toxme.io support) -- [x] Save file encryption -- [x] Profile import and export -- [x] Faux offline messaging -- [x] Faux offline file transfers -- [x] Inline images -- [x] Message splitting -- [x] Proxy support -- [x] Avatars -- [x] Multiprofile -- [x] Multilingual -- [x] Sound notifications -- [x] Contact aliases -- [x] Contact blocking -- [x] Typing notifications -- [x] Changing nospam -- [x] File resuming -- [x] Read receipts -- [ ] Desktop sharing -- [ ] Group chats +- 1v1 messages +- File transfers +- Audio calls +- Video calls +- Plugins support +- Desktop sharing +- Chat history +- Emoticons +- Stickers +- Screenshots +- Name lookups (toxme.io support) +- Save file encryption +- Profile import and export +- Faux offline messaging +- Faux offline file transfers +- Inline images +- Message splitting +- Proxy support +- Avatars +- Multiprofile +- Multilingual +- Sound notifications +- Contact aliases +- Contact blocking +- Typing notifications +- Changing nospam +- File resuming +- Read receipts ### Downloads [Releases](https://github.com/toxygen-project/toxygen/releases) From 1bbd9a629ccfaaec97d1bce080faeee3f617a730 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 15 Jul 2017 23:11:49 +0300 Subject: [PATCH 057/163] video calls fix --- toxygen/callbacks.py | 1 - toxygen/menu.py | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index f97e980..eaf5346 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -346,7 +346,6 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u width // 2 width // 2 It can be created from initial y, u, v using slices - For more info see callback_video_receive_frame docs """ try: y_size = abs(max(width, abs(ystride))) diff --git a/toxygen/menu.py b/toxygen/menu.py index 3707706..6724b75 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -881,6 +881,8 @@ class VideoSettings(CenteredWidget): self.desktopAreaSelection = DesktopAreaSelectionWindow(self) def closeEvent(self, event): + if self.input.currentIndex() == 0: + return try: settings = Settings.get_instance() settings.video['device'] = self.devices[self.input.currentIndex()] From 87392ea95a36f313002650a97326d7089d99fced Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 16 Jul 2017 20:02:33 +0300 Subject: [PATCH 058/163] wrapper for old gc (gen) --- toxygen/tox.py | 93 ++++++++++++++++++++++++++++- toxygen/toxcore_enums_and_consts.py | 6 ++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/toxygen/tox.py b/toxygen/tox.py index 0545653..f36ea37 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1,5 +1,4 @@ -from ctypes import c_char_p, Structure, c_bool, byref, 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 ctypes import * from toxcore_enums_and_consts import * from toxav import ToxAV from libtox import LibToxCore @@ -1509,3 +1508,93 @@ class Tox: return result elif tox_err_get_port == TOX_ERR_GET_PORT['NOT_BOUND']: raise RuntimeError('The instance was not bound to any port.') + + # ----------------------------------------------------------------------------------------------------------------- + # Group chats + # ----------------------------------------------------------------------------------------------------------------- + + def del_groupchat(self, groupnumber): + result = Tox.libtoxcore.tox_del_groupchat(self._tox_pointer, c_int(groupnumber), None) + return result + + def group_peername(self, groupnumber, peernumber): + buffer = create_string_buffer(TOX_MAX_NAME_LENGTH) + result = Tox.libtoxcore.tox_group_peername(self._tox_pointer, c_int(groupnumber), c_int(peernumber), + buffer, None) + return buffer[:] + + def invite_friend(self, friendnumber, groupnumber): + result = Tox.libtoxcore.tox_invite_friend(self._tox_pointer, c_int(friendnumber), + c_int(groupnumber), None) + return result + + def join_groupchat(self, friendnumber, data, length): + result = Tox.libtoxcore.tox_join_groupchat(self._tox_pointer, + c_int(friendnumber), c_char_p(data), c_uint16(length), None) + return result + + def group_message_send(self, groupnumber, message): + result = Tox.libtoxcore.tox_group_message_send(self._tox_pointer, c_int(groupnumber), c_char_p(message), + c_uint16(len(message)), None) + return result + + def group_action_send(self, groupnumber, action): + result = Tox.libtoxcore.tox_group_action_send(self._tox_pointer, + c_int(groupnumber), c_char_p(action), + c_uint16(len(action)), None) + return result + + def group_set_title(self, groupnumber, title): + result = Tox.libtoxcore.tox_group_set_title(self._tox_pointer, c_int(groupnumber), + c_char_p(title), c_uint8(len(title)), None) + return result + + def group_get_title(self, groupnumber): + buffer = create_string_buffer(TOX_MAX_NAME_LENGTH) + result = Tox.libtoxcore.tox_group_get_title(self._tox_pointer, + c_int(groupnumber), buffer, + c_uint32(TOX_MAX_NAME_LENGTH), None) + return buffer[:] + + def group_number_peers(self, groupnumber): + result = Tox.libtoxcore.tox_group_number_peers(self._tox_pointer, c_int(groupnumber), None) + return result + + # def group_get_names(self): + # result = Tox.libtoxcore.tox_group_get_names(self._tox_pointer, c_int(groupnumber), + # c_char_p(names), None, c_uint16(length), error) + # return result + + def add_av_groupchat(self): + result = Tox.libtoxcore.tox_add_av_groupchat(self._tox_pointer, None, None, None) + return result + + def join_av_groupchat(self, friendnumber, data, length): + result = Tox.libtoxcore.tox_join_av_groupchat(self._tox_pointer, c_int(friendnumber), + c_char_p(data), c_uint16(length), None, None, None) + return result + + def callback_group_invite(self, callback, user_data=None): + c_callback = CFUNCTYPE(None, c_void_p, c_int32, c_uint8, POINTER(c_uint8), c_uint16, c_void_p) + self.group_invite_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data) + + def callback_group_message(self, callback, user_data=None): + c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p) + self.group_message_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data) + + def callback_group_action(self, callback, user_data=None): + c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p) + self.group_action_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_action(self._tox_pointer, self.group_action_cb, user_data) + + def callback_group_title(self, callback, user_data=None): + c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint8, c_void_p) + self.group_title_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_title(self._tox_pointer, self.group_title_cb, user_data) + + def callback_group_namelist_change(self, callback, user_data=None): + c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_uint8, c_void_p) + self.group_namelist_change_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_namelist_change(self._tox_pointer, self.group_namelist_change_cb, user_data) diff --git a/toxygen/toxcore_enums_and_consts.py b/toxygen/toxcore_enums_and_consts.py index 4d52837..fc8941b 100644 --- a/toxygen/toxcore_enums_and_consts.py +++ b/toxygen/toxcore_enums_and_consts.py @@ -188,6 +188,12 @@ TOX_ERR_GET_PORT = { 'NOT_BOUND': 1, } +TOX_CHAT_CHANGE = { + 'PEER_ADD': 0, + 'PEER_DEL': 1, + 'PEER_NAME': 2 +} + TOX_PUBLIC_KEY_SIZE = 32 TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6 From 9c129e925be6845fe817887f0514eb6d30b5f012 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 16 Jul 2017 22:51:20 +0300 Subject: [PATCH 059/163] base gc class, callbacks part1 --- toxygen/callbacks.py | 29 +++++++++++++++++++++++++++++ toxygen/group_chat.py | 19 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 toxygen/group_chat.py diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index eaf5346..31b50bd 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -374,6 +374,29 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u except Exception as ex: print(ex) +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - groups +# ----------------------------------------------------------------------------------------------------------------- + + +def group_invite(tox, friend_number, gc_type, data, length, user_data): + invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type, data[:length]) + + +def group_message(tox, group_number, peer_number, message, length, user_data): + invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, + peer_number, TOX_MESSAGE_TYPE['NORMAL'], message[:length]) + + +def group_action(tox, group_number, peer_number, message, length, user_data): + invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, + peer_number, TOX_MESSAGE_TYPE['ACTION'], message[:length]) + + +def group_title(tox, group_number, peer_number, title, length, user_data): + invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number, + title[:length]) + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization # ----------------------------------------------------------------------------------------------------------------- @@ -410,3 +433,9 @@ def init_callbacks(tox, window, tray): tox.callback_friend_lossless_packet(lossless_packet, 0) tox.callback_friend_lossy_packet(lossy_packet, 0) + + tox.callback_group_invite(group_invite) + tox.callback_group_message(group_message) + tox.callback_group_action(group_action) + tox.callback_group_title(group_title) + tox.callback_group_namelist_change(group_namelist_change) diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py new file mode 100644 index 0000000..acf84e8 --- /dev/null +++ b/toxygen/group_chat.py @@ -0,0 +1,19 @@ +import basecontact + + +class GroupChat(basecontact.BaseContact): + + def __init__(self, name, status_message, widget, tox, group_number): + super().__init__(name, status_message, widget, None) + self._number = group_number + self._tox = tox + + def set_name(self, name): + self._tox.group_set_title(self._number, name) + super().set_name(name) + + def send_message(self, message): + self._tox.group_message_send(self._number, message.encode('utf-8')) + + def new_title(self, title): + self._name = title From aae71d081f96a93e52f22ab7a63e06244166dd30 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 17 Jul 2017 01:11:09 +0300 Subject: [PATCH 060/163] base backend for gc --- toxygen/callbacks.py | 4 ++ toxygen/group_chat.py | 9 ++-- toxygen/messages.py | 14 +++++- toxygen/profile.py | 67 ++++++++++++++++++++++++++++- toxygen/tox.py | 8 ++-- toxygen/toxcore_enums_and_consts.py | 5 +++ 6 files changed, 96 insertions(+), 11 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 31b50bd..7028f97 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -397,6 +397,10 @@ def group_title(tox, group_number, peer_number, title, length, user_data): invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number, title[:length]) + +def group_namelist_change(tox, group_number, peer_number, change, user_data): + invoke_in_main_thread(Profile.get_instance().update_gc, group_number) + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py index acf84e8..563d494 100644 --- a/toxygen/group_chat.py +++ b/toxygen/group_chat.py @@ -1,11 +1,10 @@ -import basecontact +import contact -class GroupChat(basecontact.BaseContact): +class GroupChat(contact.Contact): def __init__(self, name, status_message, widget, tox, group_number): - super().__init__(name, status_message, widget, None) - self._number = group_number + super().__init__(None, group_number, name, status_message, widget, None) self._tox = tox def set_name(self, name): @@ -16,4 +15,4 @@ class GroupChat(basecontact.BaseContact): self._tox.group_message_send(self._number, message.encode('utf-8')) def new_title(self, title): - self._name = title + super().set_name(title) diff --git a/toxygen/messages.py b/toxygen/messages.py index 87a1cc2..8d9f4a3 100644 --- a/toxygen/messages.py +++ b/toxygen/messages.py @@ -5,7 +5,9 @@ MESSAGE_TYPE = { 'ACTION': 1, 'FILE_TRANSFER': 2, 'INLINE': 3, - 'INFO_MESSAGE': 4 + 'INFO_MESSAGE': 4, + 'GC_TEXT': 5, + 'GC_ACTION': 6 } @@ -39,6 +41,16 @@ class TextMessage(Message): return self._message, self._owner, self._time, self._type +class GroupChatMessage(TextMessage): + + def __init__(self, message, owner, time, message_type, name): + super().__init__(message, owner, time, message_type) + self._user_name = name + + def get_data(self): + return self._message, self._owner, self._time, self._type, self._user_name + + class TransferMessage(Message): """ Message with info about file transfer diff --git a/toxygen/profile.py b/toxygen/profile.py index 1d744db..a0870d8 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -16,6 +16,7 @@ import basecontact import items_factory import cv2 import threading +from group_chat import * class Profile(basecontact.BaseContact, Singleton): @@ -177,7 +178,7 @@ class Profile(basecontact.BaseContact, Singleton): # ----------------------------------------------------------------------------------------------------------------- def get_friend_by_number(self, num): - return list(filter(lambda x: x.number == num, self._contacts))[0] + return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] def get_friend(self, num): if num < 0 or num >= len(self._contacts): @@ -636,6 +637,16 @@ class Profile(basecontact.BaseContact, Singleton): return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, append, pixmap) + def create_gc_message_item(self, text, time, owner, name, message_type, append=True): + pixmap = None + if self._show_avatars: + if owner == MESSAGE_OWNER['FRIEND']: + pixmap = self.get_curr_friend().get_pixmap() + else: + pixmap = self.get_pixmap() + return self._factory.message_item(text, time, name, True, + message_type, append, pixmap) + def create_file_transfer_item(self, tm, append=True): data = list(tm.get_data()) data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name @@ -1265,6 +1276,60 @@ class Profile(basecontact.BaseContact, Singleton): self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self._messages.scrollToBottom() + # ----------------------------------------------------------------------------------------------------------------- + # GC support + # ----------------------------------------------------------------------------------------------------------------- + + def is_active_a_friend(self): + return type(self.get_curr_friend()) is Friend + + def get_group_by_number(self, number): + groups = filter(lambda x: type(x) is GroupChat and x.number == number, self._contacts) + return list(groups)[0] + + def add_gc(self, number): + widget = self.create_friend_item() + gc = GroupChat('', '', widget, self._tox, number) + self._contacts.append(gc) + + def create_group_chat(self): + number = self._tox.add_av_groupchat() + self.add_gc(number) + + def group_invite(self, friend_number, gc_type, data): + text = QtWidgets.QApplication.translate('MainWindow', 'User {} invites you to group chat. Accept?') + title = QtWidgets.QApplication.translate('MainWindow', 'Group chat invite') + reply = QtWidgets.QMessageBox.question(None, title, text, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) + if reply == QtWidgets.QMessageBox.Yes: # accepted + if gc_type == TOX_GROUPCHAT_TYPE['TEXT']: + number = self._tox.join_groupchat(friend_number, data) + else: + number = self._tox.join_av_groupchat(friend_number, data) + self.add_gc(number) + + def new_gc_message(self, group_number, peer_number, message_type, message): + name = self._tox.group_peername(group_number, peer_number) + if group_number == self.get_active_number() and not self.is_active_a_friend(): # add message to list + t = time.time() + self.create_gc_message_item(message, t, MESSAGE_OWNER['FRIEND'], name, message_type) + self._messages.scrollToBottom() + self.get_curr_friend().append_message( + GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type. name)) + else: + gc = self.get_group_by_number(group_number) + gc.inc_messages() + gc.append_message( + GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type, name)) + if not gc.visibility: + self.update_filtration() + + def new_gc_title(self, group_number, title): + gc = self.get_group_by_number(group_number) + gc.new_title(title) + + def update_gc(self, group_number): + pass + def tox_factory(data=None, settings=None): """ diff --git a/toxygen/tox.py b/toxygen/tox.py index f36ea37..0d06f5b 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1528,9 +1528,9 @@ class Tox: c_int(groupnumber), None) return result - def join_groupchat(self, friendnumber, data, length): + def join_groupchat(self, friendnumber, data): result = Tox.libtoxcore.tox_join_groupchat(self._tox_pointer, - c_int(friendnumber), c_char_p(data), c_uint16(length), None) + c_int(friendnumber), c_char_p(data), c_uint16(len(data)), None) return result def group_message_send(self, groupnumber, message): @@ -1569,9 +1569,9 @@ class Tox: result = Tox.libtoxcore.tox_add_av_groupchat(self._tox_pointer, None, None, None) return result - def join_av_groupchat(self, friendnumber, data, length): + def join_av_groupchat(self, friendnumber, data): result = Tox.libtoxcore.tox_join_av_groupchat(self._tox_pointer, c_int(friendnumber), - c_char_p(data), c_uint16(length), None, None, None) + c_char_p(data), c_uint16(len(data)), None, None, None) return result def callback_group_invite(self, callback, user_data=None): diff --git a/toxygen/toxcore_enums_and_consts.py b/toxygen/toxcore_enums_and_consts.py index fc8941b..a17d93e 100644 --- a/toxygen/toxcore_enums_and_consts.py +++ b/toxygen/toxcore_enums_and_consts.py @@ -194,6 +194,11 @@ TOX_CHAT_CHANGE = { 'PEER_NAME': 2 } +TOX_GROUPCHAT_TYPE = { + 'TEXT': 0, + 'AV': 1 +} + TOX_PUBLIC_KEY_SIZE = 32 TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6 From 8e6d37e23c0e500410da28c4b2d057839aca52b2 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 17 Jul 2017 21:53:35 +0300 Subject: [PATCH 061/163] minimal working functionality --- toxygen/callbacks.py | 7 ++++--- toxygen/group_chat.py | 15 ++++++++++++++ toxygen/images/group.png | Bin 0 -> 4142 bytes toxygen/mainscreen.py | 7 +++++++ toxygen/profile.py | 43 ++++++++++++++++++++++++++++++--------- toxygen/tox.py | 29 ++++++++++++++++++-------- 6 files changed, 79 insertions(+), 22 deletions(-) create mode 100644 toxygen/images/group.png diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 7028f97..6cc88c0 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -380,17 +380,18 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u def group_invite(tox, friend_number, gc_type, data, length, user_data): - invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type, data[:length]) + invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type, + bytes(data[:length])) def group_message(tox, group_number, peer_number, message, length, user_data): invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['NORMAL'], message[:length]) + peer_number, TOX_MESSAGE_TYPE['NORMAL'], str(message, 'utf-8')) def group_action(tox, group_number, peer_number, message, length, user_data): invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['ACTION'], message[:length]) + peer_number, TOX_MESSAGE_TYPE['ACTION'], str(message, 'utf-8')) def group_title(tox, group_number, peer_number, title, length, user_data): diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py index 563d494..fe009b7 100644 --- a/toxygen/group_chat.py +++ b/toxygen/group_chat.py @@ -1,4 +1,7 @@ import contact +import util +from PyQt5 import QtGui, QtCore +import toxcore_enums_and_consts as cnst class GroupChat(contact.Contact): @@ -6,6 +9,7 @@ class GroupChat(contact.Contact): def __init__(self, name, status_message, widget, tox, group_number): super().__init__(None, group_number, name, status_message, widget, None) self._tox = tox + self._status = cnst.TOX_USER_STATUS['NONE'] def set_name(self, name): self._tox.group_set_title(self._number, name) @@ -16,3 +20,14 @@ class GroupChat(contact.Contact): def new_title(self, title): super().set_name(title) + + def load_avatar(self): + path = util.curr_directory() + '/images/group.png' + width = self._widget.avatar_label.width() + pixmap = QtGui.QPixmap(path) + self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation)) + self._widget.avatar_label.repaint() + + def remove_invalid_unsent_files(self): + pass diff --git a/toxygen/images/group.png b/toxygen/images/group.png new file mode 100644 index 0000000000000000000000000000000000000000..22adab0a66b1b939ae0c9b6fee5d0781c41b91c0 GIT binary patch literal 4142 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 Date: Mon, 17 Jul 2017 22:15:29 +0300 Subject: [PATCH 062/163] chat menu --- toxygen/mainscreen.py | 50 +++++++++++++++++++++++++++---------------- toxygen/profile.py | 36 ++++++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 19 deletions(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 3643b39..ac753d5 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -590,7 +590,9 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept') if item is not None: self.listMenu = QtWidgets.QMenu() - set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias')) + is_friend = type(friend) is Friend + if is_friend: + set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias')) history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history')) clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history')) @@ -600,26 +602,32 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy')) copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name')) copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message')) - copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key')) + if is_friend: + copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key')) - auto_accept_item = self.listMenu.addAction(auto) - remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend')) - block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend')) - notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes')) + auto_accept_item = self.listMenu.addAction(auto) + remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend')) + block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend')) + notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes')) - plugins_loader = plugin_support.PluginLoader.get_instance() - if plugins_loader is not None: - submenu = plugins_loader.get_menu(self.listMenu, num) - if len(submenu): - plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) - plug.addActions(submenu) - set_alias_item.triggered.connect(lambda: self.set_alias(num)) - remove_item.triggered.connect(lambda: self.remove_friend(num)) - block_item.triggered.connect(lambda: self.block_friend(num)) - copy_key_item.triggered.connect(lambda: self.copy_friend_key(num)) + plugins_loader = plugin_support.PluginLoader.get_instance() + if plugins_loader is not None: + submenu = plugins_loader.get_menu(self.listMenu, num) + if len(submenu): + plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) + plug.addActions(submenu) + set_alias_item.triggered.connect(lambda: self.set_alias(num)) + copy_key_item.triggered.connect(lambda: self.copy_friend_key(num)) + remove_item.triggered.connect(lambda: self.remove_friend(num)) + block_item.triggered.connect(lambda: self.block_friend(num)) + auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed)) + notes_item.triggered.connect(lambda: self.show_note(friend)) + else: + leave_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Leave chat')) + set_title_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set title')) + leave_item.triggered.connect(lambda: self.leave_gc(num)) + set_title_item.triggered.connect(lambda: self.set_title(num)) clear_history_item.triggered.connect(lambda: self.clear_history(num)) - auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed)) - notes_item.triggered.connect(lambda: self.show_note(friend)) copy_name_item.triggered.connect(lambda: self.copy_name(friend)) copy_status_item.triggered.connect(lambda: self.copy_status(friend)) export_to_text_item.triggered.connect(lambda: self.export_history(num)) @@ -682,6 +690,12 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def clear_history(self, num): self.profile.clear_history(num) + def leave_gc(self, num): + self.profile.leave_gc(num) + + def set_title(self, num): + self.profile.set_title(num) + def auto_accept(self, num, value): settings = Settings.get_instance() tox_id = self.profile.friend_public_key(num) diff --git a/toxygen/profile.py b/toxygen/profile.py index 49139c6..09627a8 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -883,7 +883,7 @@ class Profile(basecontact.BaseContact, Singleton): QtCore.QTimer.singleShot(50000, self.reconnect) def close(self): - for friend in self._contacts: + for friend in map(lambda x: type(x) is Friend, self._contacts): self.friend_exit(friend.number) for i in range(len(self._contacts)): del self._contacts[0] @@ -1305,6 +1305,17 @@ class Profile(basecontact.BaseContact, Singleton): number = self._tox.add_av_groupchat() self.add_gc(number) + def leave_gc(self, num): + gc = self._contacts[num] + self._tox.del_groupchat(gc.number) + del self._contacts[num] + self._screen.friends_list.takeItem(num) + if num == self._active_friend: # active friend was deleted + if not len(self._contacts): # last friend was deleted + self.set_active(-1) + else: + self.set_active(0) + def group_invite(self, friend_number, gc_type, data): text = QtWidgets.QApplication.translate('MainWindow', 'User {} invites you to group chat. Accept?') title = QtWidgets.QApplication.translate('MainWindow', 'Group chat invite') @@ -1353,6 +1364,29 @@ class Profile(basecontact.BaseContact, Singleton): self._tox.group_message_send(group_number, text.encode('utf-8')) self._screen.messageEdit.clear() + def set_title(self, num): + """ + Set new title for gc + """ + gc = self._contacts[num] + name = gc.name + dialog = QtWidgets.QApplication.translate('MainWindow', + "Enter new title for group {}:") + dialog = dialog.format(name) + title = QtWidgets.QApplication.translate('MainWindow', + 'Set title') + text, ok = QtWidgets.QInputDialog.getText(None, + title, + dialog, + QtWidgets.QLineEdit.Normal, + name) + if ok: + text = text.encode('utf-8') + self._tox.group_set_title(gc.number, text) + self.new_gc_title(gc.number, text) + if num == self.get_active_number() and not self.is_active_a_friend(): + self.update() + def tox_factory(data=None, settings=None): """ From db519e260813cb77fcb86ed37836e7cc3d053ead Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 17 Jul 2017 22:27:52 +0300 Subject: [PATCH 063/163] bug fix and version++ --- toxygen/profile.py | 8 +++++--- toxygen/util.py | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index 09627a8..14e7ca0 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -883,7 +883,7 @@ class Profile(basecontact.BaseContact, Singleton): QtCore.QTimer.singleShot(50000, self.reconnect) def close(self): - for friend in map(lambda x: type(x) is Friend, self._contacts): + for friend in filter(lambda x: type(x) is Friend, self._contacts): self.friend_exit(friend.number) for i in range(len(self._contacts)): del self._contacts[0] @@ -1348,12 +1348,16 @@ class Profile(basecontact.BaseContact, Singleton): def new_gc_title(self, group_number, title): gc = self.get_group_by_number(group_number) gc.new_title(title) + if not self.is_active_a_friend() and self.get_active_number() == group_number: + self.update() def update_gc(self, group_number): count = self._tox.group_number_peers(group_number) gc = self.get_group_by_number(group_number) text = QtWidgets.QApplication.translate('MainWindow', '{} users in chat') gc.status_message = text.format(str(count)).encode('utf-8') + if not self.is_active_a_friend() and self.get_active_number() == group_number: + self.update() def send_gc_message(self, text): group_number = self.get_active_number() @@ -1384,8 +1388,6 @@ class Profile(basecontact.BaseContact, Singleton): text = text.encode('utf-8') self._tox.group_set_title(gc.number, text) self.new_gc_title(gc.number, text) - if num == self.get_active_number() and not self.is_active_a_friend(): - self.update() def tox_factory(data=None, settings=None): diff --git a/toxygen/util.py b/toxygen/util.py index e8e96c5..8881380 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.3.1' +program_version = '0.3.2' def cached(func): From 65167de1fed4e547d83a9439d9d8d15975aa10e8 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 18 Jul 2017 21:36:14 +0300 Subject: [PATCH 064/163] group notifications and bug fixes --- toxygen/callbacks.py | 36 ++++++++++++++++++++++++++++-------- toxygen/friend.py | 7 +++++++ toxygen/group_chat.py | 13 +++++++++++-- toxygen/mainscreen.py | 20 +++++++++++++++----- toxygen/menu.py | 14 ++++++++++---- toxygen/profile.py | 31 ++++++++++++++++++++++--------- toxygen/settings.py | 3 ++- toxygen/tox.py | 18 ++++-------------- 8 files changed, 99 insertions(+), 43 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 6cc88c0..94e954e 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -384,14 +384,34 @@ def group_invite(tox, friend_number, gc_type, data, length, user_data): bytes(data[:length])) -def group_message(tox, group_number, peer_number, message, length, user_data): - invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['NORMAL'], str(message, 'utf-8')) +def show_gc_notification(window, tray, message, group_number): + profile = Profile.get_instance() + settings = Settings.get_instance() + chat = profile.get_group_by_number(group_number) + if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']): + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: + invoke_in_main_thread(tray_notification, chat.name, message, tray, window) + if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['MESSAGE']) + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) -def group_action(tox, group_number, peer_number, message, length, user_data): - invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['ACTION'], str(message, 'utf-8')) +def group_message(window, tray): + def wrapped(tox, group_number, peer_number, message, length, user_data): + message = str(message[:length], 'utf-8') + invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, + peer_number, TOX_MESSAGE_TYPE['NORMAL'], message) + show_gc_notification(window, tray, message, group_number) + return wrapped + + +def group_action(window, tray): + def wrapped(tox, group_number, peer_number, message, length, user_data): + message = str(message[:length], 'utf-8') + invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, + peer_number, TOX_MESSAGE_TYPE['ACTION'], message) + show_gc_notification(window, tray, message, group_number) + return wrapped def group_title(tox, group_number, peer_number, title, length, user_data): @@ -440,7 +460,7 @@ def init_callbacks(tox, window, tray): tox.callback_friend_lossy_packet(lossy_packet, 0) tox.callback_group_invite(group_invite) - tox.callback_group_message(group_message) - tox.callback_group_action(group_action) + tox.callback_group_message(group_message(window, tray)) + tox.callback_group_action(group_action(window, tray)) tox.callback_group_title(group_title) tox.callback_group_namelist_change(group_namelist_change) diff --git a/toxygen/friend.py b/toxygen/friend.py index f560e5c..d912708 100644 --- a/toxygen/friend.py +++ b/toxygen/friend.py @@ -66,3 +66,10 @@ class Friend(contact.Contact): if self._receipts: self._receipts -= 1 self.mark_as_sent() + + # ----------------------------------------------------------------------------------------------------------------- + # Full status + # ----------------------------------------------------------------------------------------------------------------- + + def get_full_status(self): + return self._status_message diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py index fe009b7..9b505ce 100644 --- a/toxygen/group_chat.py +++ b/toxygen/group_chat.py @@ -1,7 +1,7 @@ import contact import util from PyQt5 import QtGui, QtCore -import toxcore_enums_and_consts as cnst +import toxcore_enums_and_consts as constants class GroupChat(contact.Contact): @@ -9,7 +9,7 @@ class GroupChat(contact.Contact): def __init__(self, name, status_message, widget, tox, group_number): super().__init__(None, group_number, name, status_message, widget, None) self._tox = tox - self._status = cnst.TOX_USER_STATUS['NONE'] + self.set_status(constants.TOX_USER_STATUS['NONE']) def set_name(self, name): self._tox.group_set_title(self._number, name) @@ -31,3 +31,12 @@ class GroupChat(contact.Contact): def remove_invalid_unsent_files(self): pass + + def get_full_status(self): + peers_count = self._tox.group_number_peers(self._number) + names = [] + for i in range(peers_count): + name = self._tox.group_peername(self._number, i) + names.append(name) + names = sorted(names, key=lambda n: n.lower()) + return '\n'.join(names) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index ac753d5..c76f19b 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -5,7 +5,6 @@ from widgets import MultilineEdit, ComboBox import plugin_support from mainscreen_widgets import * import settings -import platform import toxes @@ -519,7 +518,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def send_file(self): self.menu.hide() - if self.profile.active_friend + 1: + if self.profile.active_friend + 1and self.profile.is_active_a_friend(): choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file') name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog) if name[0]: @@ -527,7 +526,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def send_screenshot(self, hide=False): self.menu.hide() - if self.profile.active_friend + 1: + if self.profile.active_friend + 1 and self.profile.is_active_a_friend(): self.sw = ScreenShotWindow(self) self.sw.show() if hide: @@ -545,7 +544,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def send_sticker(self): self.menu.hide() - if self.profile.active_friend + 1: + if self.profile.active_friend + 1 and self.profile.is_active_a_friend(): self.sticker = StickerWindow(self) self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(), self.y() + self.height() - 200, @@ -593,6 +592,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): is_friend = type(friend) is Friend if is_friend: set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias')) + set_alias_item.triggered.connect(lambda: self.set_alias(num)) history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history')) clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history')) @@ -610,13 +610,20 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend')) notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes')) + chats = self.profile.get_group_chats() + if len(chats) and self.profile.is_active_online(): + invite_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Invite to group chat')) + for i in range(len(chats)): + name, number = chats[i] + item = invite_menu.addAction(name) + item.triggered.connect(lambda: self.invite_friend_to_gc(num, number)) + plugins_loader = plugin_support.PluginLoader.get_instance() if plugins_loader is not None: submenu = plugins_loader.get_menu(self.listMenu, num) if len(submenu): plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) plug.addActions(submenu) - set_alias_item.triggered.connect(lambda: self.set_alias(num)) copy_key_item.triggered.connect(lambda: self.copy_friend_key(num)) remove_item.triggered.connect(lambda: self.remove_friend(num)) block_item.triggered.connect(lambda: self.block_friend(num)) @@ -705,6 +712,9 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): settings['auto_accept_from_friends'].remove(tox_id) settings.save() + def invite_friend_to_gc(self, friend_number, group_number): + self.profile.invite_friend(friend_number, group_number) + # ----------------------------------------------------------------------------------------------------------------- # Functions which called when user click somewhere else # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/menu.py b/toxygen/menu.py index 6724b75..37f7bb6 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -508,15 +508,17 @@ class NotificationsSettings(CenteredWidget): def initUI(self): self.setObjectName("notificationsForm") - self.resize(350, 180) - self.setMinimumSize(QtCore.QSize(350, 180)) - self.setMaximumSize(QtCore.QSize(350, 180)) + self.resize(350, 210) + self.setMinimumSize(QtCore.QSize(350, 210)) + self.setMaximumSize(QtCore.QSize(350, 210)) self.enableNotifications = QtWidgets.QCheckBox(self) self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18)) self.callsSound = QtWidgets.QCheckBox(self) - self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18)) + self.callsSound.setGeometry(QtCore.QRect(10, 170, 340, 18)) self.soundNotifications = QtWidgets.QCheckBox(self) self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18)) + self.groupNotifications = QtWidgets.QCheckBox(self) + self.groupNotifications.setGeometry(QtCore.QRect(10, 120, 340, 18)) font = QtGui.QFont() s = Settings.get_instance() font.setFamily(s['font']) @@ -524,8 +526,10 @@ class NotificationsSettings(CenteredWidget): self.callsSound.setFont(font) self.soundNotifications.setFont(font) self.enableNotifications.setFont(font) + self.groupNotifications.setFont(font) self.enableNotifications.setChecked(s['notifications']) self.soundNotifications.setChecked(s['sound_notifications']) + self.groupNotifications.setChecked(s['group_notifications']) self.callsSound.setChecked(s['calls_sound']) self.retranslateUi() QtCore.QMetaObject.connectSlotsByName(self) @@ -533,6 +537,7 @@ class NotificationsSettings(CenteredWidget): def retranslateUi(self): self.setWindowTitle(QtWidgets.QApplication.translate("notificationsForm", "Notification settings")) self.enableNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable notifications")) + self.groupNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Notify about all messages in groups")) self.callsSound.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable call\'s sound")) self.soundNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable sound notifications")) @@ -540,6 +545,7 @@ class NotificationsSettings(CenteredWidget): settings = Settings.get_instance() settings['notifications'] = self.enableNotifications.isChecked() settings['sound_notifications'] = self.soundNotifications.isChecked() + settings['group_notifications'] = self.groupNotifications.isChecked() settings['calls_sound'] = self.callsSound.isChecked() settings.save() diff --git a/toxygen/profile.py b/toxygen/profile.py index 14e7ca0..63cf09b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -205,6 +205,7 @@ class Profile(basecontact.BaseContact, Singleton): if value == -1: # all friends were deleted self._screen.account_name.setText('') self._screen.account_status.setText('') + self._screen.account_status.setToolTip('') self._active_friend = -1 self._screen.account_avatar.setHidden(True) self._messages.clear() @@ -274,6 +275,7 @@ class Profile(basecontact.BaseContact, Singleton): self._screen.account_name.setText(friend.name) self._screen.account_status.setText(friend.status_message) + self._screen.account_status.setToolTip(friend.get_full_status()) if friend.tox_id is None: avatar_path = curr_directory() + '/images/group.png' else: @@ -354,7 +356,7 @@ class Profile(basecontact.BaseContact, Singleton): elif data[1] == friend_number and not data[2]: self.send_file(data[0], friend_number, True, key) del self._paused_file_transfers[key] - if friend_number == self.get_active_number(): + if friend_number == self.get_active_number() and self.is_active_a_friend(): self.update() except Exception as ex: print('Exception in file sending: ' + str(ex)) @@ -396,7 +398,7 @@ class Profile(basecontact.BaseContact, Singleton): """ Display incoming typing notification """ - if friend_number == self.get_active_number(): + if friend_number == self.get_active_number() and self.is_active_a_friend(): self._screen.typing.setVisible(typing) # ----------------------------------------------------------------------------------------------------------------- @@ -452,7 +454,7 @@ class Profile(basecontact.BaseContact, Singleton): :param message_type: message type - plain text or action message (/me) :param message: text of message """ - if friend_num == self.get_active_number(): # add message to list + if friend_num == self.get_active_number()and self.is_active_a_friend(): # add message to list t = time.time() self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) self._messages.scrollToBottom() @@ -712,7 +714,7 @@ class Profile(basecontact.BaseContact, Singleton): except: pass settings.save() - if num == self.get_active_number(): + if num == self.get_active_number() and self.is_active_a_friend(): self.update() def friend_public_key(self, num): @@ -956,7 +958,7 @@ class Profile(basecontact.BaseContact, Singleton): friend_number, file_number) accepted = False - if friend_number == self.get_active_number(): + if friend_number == self.get_active_number() and self.is_active_a_friend(): item = self.create_file_transfer_item(tm) if accepted: self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state) @@ -987,7 +989,7 @@ class Profile(basecontact.BaseContact, Singleton): else: if not already_cancelled: self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) - if friend_number == self.get_active_number(): + if friend_number == self.get_active_number() and self.is_active_a_friend(): tmp = self._messages.count() + i if tmp >= 0: self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'], @@ -1141,7 +1143,7 @@ class Profile(basecontact.BaseContact, Singleton): t = type(transfer) if t is ReceiveAvatar: self.get_friend_by_number(friend_number).load_avatar() - if friend_number == self.get_active_number(): + if friend_number == self.get_active_number() and self.is_active_a_friend(): self.set_active(None) elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image print('inline') @@ -1149,7 +1151,7 @@ class Profile(basecontact.BaseContact, Singleton): i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, TOX_FILE_TRANSFER_STATE['FINISHED'], inline) - if friend_number == self.get_active_number(): + if friend_number == self.get_active_number() and self.is_active_a_friend(): count = self._messages.count() if count + i + 1 >= 0: elem = QtWidgets.QListWidgetItem() @@ -1191,7 +1193,7 @@ class Profile(basecontact.BaseContact, Singleton): ra.set_transfer_finished_handler(self.transfer_finished) else: self.get_friend_by_number(friend_number).load_avatar() - if self.get_active_number() == friend_number: + if self.get_active_number() == friend_number and self.is_active_a_friend(): self.set_active(None) def reset_avatar(self): @@ -1216,6 +1218,8 @@ class Profile(basecontact.BaseContact, Singleton): def call_click(self, audio=True, video=False): """User clicked audio button in main window""" num = self.get_active_number() + if not self.is_active_a_friend(): + return if num not in self._call and self.is_active_online(): # start call if not Settings.get_instance().audio['enabled']: return @@ -1389,6 +1393,15 @@ class Profile(basecontact.BaseContact, Singleton): self._tox.group_set_title(gc.number, text) self.new_gc_title(gc.number, text) + def get_group_chats(self): + chats = filter(lambda x: type(x) is GroupChat, self._contacts) + chats = map(lambda c: (c.name, c.number), chats) + return list(chats) + + def invite_friend(self, friend_num, group_number): + friend = self._contacts[friend_num] + self._tox.invite_friend(friend.number, group_number) + def tox_factory(data=None, settings=None): """ diff --git a/toxygen/settings.py b/toxygen/settings.py index b0d0502..21c8bec 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -144,7 +144,8 @@ class Settings(dict, Singleton): 'show_welcome_screen': True, 'close_to_tray': False, 'font': 'Times New Roman', - 'update': 1 + 'update': 1, + 'group_notifications': True } @staticmethod diff --git a/toxygen/tox.py b/toxygen/tox.py index c7760bc..ef4e44c 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1565,24 +1565,14 @@ class Tox: result = Tox.libtoxcore.tox_group_number_peers(self._tox_pointer, c_int(groupnumber), None) return result - def group_get_names(self, groupnumber): - peers_count = self.group_number_peers(groupnumber) - arr = (c_char_p * peers_count)() - for i in range(peers_count): - arr[i] = create_string_buffer(TOX_MAX_NAME_LENGTH) - result = Tox.libtoxcore.tox_group_get_names(self._tox_pointer, c_int(groupnumber), - arr, None, c_uint16(peers_count), None) - arr = map(lambda x: str(x, 'utf-8'), arr) - return list(arr) - def add_av_groupchat(self): - result = self.AV.libtoxav.tox_add_av_groupchat(self._tox_pointer, None, None, None) + result = self.AV.libtoxav.toxav_add_av_groupchat(self._tox_pointer, None, None) return result def join_av_groupchat(self, friendnumber, data): - result = self.AV.libtoxav.tox_join_av_groupchat(self._tox_pointer, c_int(friendnumber), - c_char_p(data), c_uint16(len(data)), - None, None, None) + result = self.AV.libtoxav.toxav_join_av_groupchat(self._tox_pointer, c_int32(friendnumber), + c_char_p(data), c_uint16(len(data)), + None, None) return result def callback_group_invite(self, callback, user_data=None): From 1ea919bdc2975df598dc85de73a9ddb294b6559d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 18 Jul 2017 23:36:40 +0300 Subject: [PATCH 065/163] tab && bug fix --- toxygen/group_chat.py | 6 +++++- toxygen/mainscreen_widgets.py | 4 ++++ toxygen/profile.py | 20 +++++++++++++++++--- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py index 9b505ce..0ee4b35 100644 --- a/toxygen/group_chat.py +++ b/toxygen/group_chat.py @@ -32,11 +32,15 @@ class GroupChat(contact.Contact): def remove_invalid_unsent_files(self): pass - def get_full_status(self): + def get_names(self): peers_count = self._tox.group_number_peers(self._number) names = [] for i in range(peers_count): name = self._tox.group_peername(self._number, i) names.append(name) names = sorted(names, key=lambda n: n.lower()) + return names + + def get_full_status(self): + names = self.get_names() return '\n'.join(names) diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index b6d4b0c..700f16b 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -34,6 +34,10 @@ class MessageArea(QtWidgets.QPlainTextEdit): self.parent.send_message() elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText(): self.appendPlainText(Profile.get_instance().get_last_message()) + elif event.key() == QtCore.Qt.Key_Tab and not self.parent.profile.is_active_a_friend(): + text = self.toPlainText() + pos = self.textCursor().position() + self.insertPlainText(Profile.get_instance().get_gc_peer_name(text[:pos])) else: self.parent.profile.send_typing(True) if self.timer.isActive(): diff --git a/toxygen/profile.py b/toxygen/profile.py index 63cf09b..c54220b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -17,6 +17,7 @@ import items_factory import cv2 import threading from group_chat import * +import re class Profile(basecontact.BaseContact, Singleton): @@ -130,6 +131,7 @@ class Profile(basecontact.BaseContact, Singleton): filter_str = filter_str.lower() settings = Settings.get_instance() number = self.get_active_number() + is_friend = self.is_active_a_friend() if sorting > 1: if sorting & 2: self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) @@ -165,7 +167,7 @@ class Profile(basecontact.BaseContact, Singleton): self._sorting, self._filter_string = sorting, filter_str settings['sorting'] = self._sorting settings.save() - self.set_active_by_number(number) + self.set_active_by_number_and_type(number, is_friend) def update_filtration(self): """ @@ -291,9 +293,10 @@ class Profile(basecontact.BaseContact, Singleton): log('Error in set active: ' + str(ex)) raise - def set_active_by_number(self, number): + def set_active_by_number_and_type(self, number, is_friend): for i in range(len(self._contacts)): - if self._contacts[i].number == number: + c = self._contacts[i] + if c.number == number and (type(c) is Friend == is_friend): self._active_friend = i break @@ -1402,6 +1405,17 @@ class Profile(basecontact.BaseContact, Singleton): friend = self._contacts[friend_num] self._tox.invite_friend(friend.number, group_number) + def get_gc_peer_name(self, text): + gc = self.get_curr_friend() + if type(gc) is not GroupChat: + return '\t' + names = gc.get_names() + name = re.split("\s+", text)[-1] + suggested_names = list(filter(lambda x: x.startswith(name), names)) + if not len(suggested_names): + return '\t' + return suggested_names[0][len(name):] + def tox_factory(data=None, settings=None): """ From d5d1e616ba2508965e629f3bf91dc9d08f02607c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 19 Jul 2017 00:14:41 +0300 Subject: [PATCH 066/163] translations update and bug fix --- toxygen/profile.py | 2 +- toxygen/translations/en_GB.ts | 297 +++++++++++++++++++--------------- toxygen/translations/fr_FR.ts | 297 +++++++++++++++++++--------------- toxygen/translations/ru_RU.qm | Bin 24781 -> 25864 bytes toxygen/translations/ru_RU.ts | 297 +++++++++++++++++++--------------- toxygen/translations/uk_UA.ts | 297 +++++++++++++++++++--------------- 6 files changed, 685 insertions(+), 505 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index c54220b..d8bf085 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1414,7 +1414,7 @@ class Profile(basecontact.BaseContact, Singleton): suggested_names = list(filter(lambda x: x.startswith(name), names)) if not len(suggested_names): return '\t' - return suggested_names[0][len(name):] + return suggested_names[0][len(name):] + ': ' def tox_factory(data=None, settings=None): diff --git a/toxygen/translations/en_GB.ts b/toxygen/translations/en_GB.ts index 588d0ff..fa975db 100644 --- a/toxygen/translations/en_GB.ts +++ b/toxygen/translations/en_GB.ts @@ -84,98 +84,98 @@ can produce IP leak MainWindow - + Profile - + Settings - + About - + Add contact - + Privacy - + Interface - + Notifications - + Network - + About program - + User {} wants to add you to contact list. Message: {} - + Friend request - + Choose file Choose file - + Disallow auto accept - + Allow auto accept - + Set alias - + Clear history - + Remove friend - + Enter new alias for friend {} or leave empty to use friend's name: Enter new alias for friend {} or leave empty to use friend's name: - + Audio Audio @@ -185,24 +185,24 @@ can produce IP leak Find contact - + Friend added Friend added - + Toxygen is Tox client written on Python. Version: Toxygen is Tox client written on Python. Version: - + Friend added without sending friend request Friend added without sending friend request - + Choose folder Choose folder @@ -217,47 +217,47 @@ Version: Send file - + Send message Send message - + Start audio call with friend Start audio call with friend - + Plugins - + List of plugins - + Search - + All - + Online - + Notes - + Notes about user @@ -307,7 +307,7 @@ Version: - + User {} is now known as {} @@ -317,32 +317,32 @@ Version: - + Lock - + Cannot lock app - + Error. Profile password is not set. - + Name - + Status message - + Public key @@ -357,32 +357,32 @@ Version: - + Choose folder with sticker pack - + Choose folder with smiley pack - + Import plugin - + Choose folder with plugin - + Restart Toxygen - + Plugin will be loaded after restart @@ -392,85 +392,125 @@ Version: - + Chat history - + Export as text - + Export as HTML - + Updates - + Online first - + Online and by name - + Online first and by name - + Block friend - + Not found - + Text "{}" was not found - + Reload plugins - + Video + + + User {} invites you to group chat. Accept? + + + + + Group chat invite + + + + + {} users in chat + + + + + Enter new title for group {}: + + + + + Set title + + + + + Create group chat + + + + + Invite to group chat + + + + + Leave chat + + MenuWindow - + Send screenshot Send screenshot - + Send file Send file - + Add smiley - + Send sticker @@ -542,42 +582,42 @@ Version: PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + No description available - + Disable plugin - + Enable plugin - + No plugins found - + Error @@ -718,67 +758,67 @@ Version: WelcomeScreen - + Don't show again - + Tip of the day - + Press Esc if you want hide app to tray. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. - + Right click on screenshot button hides app to tray during screenshot. - + Use Settings -> Interface to customize interface. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu - + Use right click on inline image to save it - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 @@ -786,17 +826,17 @@ Version: audioSettingsForm - + Audio settings Audio settings - + Input device: Input device: - + Output device: Output device: @@ -804,32 +844,32 @@ Version: incoming_call - + Incoming video call Incoming video call - + Incoming audio call Incoming audio call - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -837,82 +877,82 @@ Version: interfaceForm - + Interface settings - + Theme: - + Language: - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Restart app to apply settings - + Restart required - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Show avatars in chat - + Close to tray - + Select font @@ -993,25 +1033,30 @@ Version: notificationsForm - + Notification settings - + Enable notifications - + Enable call's sound - + Enable sound notifications + + + Notify about all messages in groups + + pass @@ -1155,57 +1200,57 @@ Version: updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date @@ -1213,22 +1258,22 @@ Version: videoSettingsForm - + Video settings - + Device: - + Desktop - + Select region diff --git a/toxygen/translations/fr_FR.ts b/toxygen/translations/fr_FR.ts index b389de0..2beba5b 100644 --- a/toxygen/translations/fr_FR.ts +++ b/toxygen/translations/fr_FR.ts @@ -86,58 +86,58 @@ peut entrainer une fuite d'IP MainWindow - + Profile Profil - + Settings Paramètres - + About À Propos - + Add contact Ajouter un contact - + Privacy Confidentialité - + Interface Interface - + Notifications Notifications - + Network Réseau - + About program À propos de toxygen - + User {} wants to add you to contact list. Message: {} L'Utilisateur {} veut vous ajouter à sa liste de contacts. Message : {} - + Friend request Demande de contact @@ -147,27 +147,27 @@ peut entrainer une fuite d'IP Toxygen est un client Tox écris en Python 2.7. Version : - + Choose file Sélectionner un fichier - + Disallow auto accept Désactiver l'auto-réception - + Allow auto accept Activer l'auto-réception - + Set alias Définir un alias - + Clear history Vider l'historique @@ -177,17 +177,17 @@ peut entrainer une fuite d'IP Copier la clé publique - + Remove friend Retirer ce contact - + Enter new alias for friend {} or leave empty to use friend's name: Entrez un nouvel alias pour le contact {} ou laissez vide pour garder son nom de base : - + Audio Audio @@ -197,24 +197,24 @@ peut entrainer une fuite d'IP Trouver le contact - + Friend added Contact ajouté - + Toxygen is Tox client written on Python. Version: Toxygen est un client Tox écrit en Python. Version : - + Friend added without sending friend request Contact ajouté sans envoi de demande - + Choose folder Sélectionner un dossier @@ -229,47 +229,47 @@ Version : Envoyer le fichier - + Send message Envoyer le message - + Start audio call with friend Démarrer un appel audio avec un ami - + Plugins Plugins - + List of plugins Liste de plugins - + Search Chercher - + All Tous - + Online En ligne - + Notes Notes - + Notes about user Notes sur l'utilisateur @@ -319,7 +319,7 @@ Version : Sauvegarder - + User {} is now known as {} L'utilisateur {} s'appelle désormais {} @@ -329,32 +329,32 @@ Version : Supprimer ce message - + Lock Verrouiller - + Cannot lock app Impossible de verrouiller l'application - + Error. Profile password is not set. Erreur. Le profil n'a pas de mot de passe. - + Name Nom - + Status message Status - + Public key Clé publique @@ -369,32 +369,32 @@ Version : Un profil ayant ce nom existe déjà - + Choose folder with sticker pack Sélectionner le dossier contenant le pack de stickers - + Choose folder with smiley pack Sélectionner le dossier contenant le pack de smileys - + Import plugin Importer un plugin - + Choose folder with plugin Sélectionner un dossier avec des plugins - + Restart Toxygen Redémarrer Toxyger - + Plugin will be loaded after restart Le plugin sera chargé après le redémarrage @@ -404,85 +404,125 @@ Version : Citer le texte sélectionné - + Chat history Historique de la conversation - + Export as text Exporter comme texte - + Export as HTML Exporter comme HTML - + Updates Mises à jour - + Online first En ligne d'abord - + Online and by name En ligne et par nom - + Online first and by name En ligne d'abord puis par nom - + Block friend Bloquer le contact - + Not found Non trouvé - + Text "{}" was not found Le texte "{}" n'a pas été trouvé - + Reload plugins Recharger les plugins - + Video Vidéo + + + User {} invites you to group chat. Accept? + + + + + Group chat invite + + + + + {} users in chat + + + + + Enter new title for group {}: + + + + + Set title + + + + + Create group chat + + + + + Invite to group chat + + + + + Leave chat + + MenuWindow - + Send screenshot Envoyer une capture d'écran - + Send file Envoyer un fichier - + Add smiley Ajouter un smiley - + Send sticker Ajouter un sticker @@ -554,42 +594,42 @@ Version : PluginsForm - + Plugins Plugins - + Open selected plugin Ouvrir le plugin sélectionné - + No GUI found for this plugin Pas d'interface pour ce plugin - + No description available Pas de description - + Disable plugin Désactiver le plugin - + Enable plugin Activer le plugin - + No plugins found Pas de plugin trouvé - + Error Erreur @@ -730,67 +770,67 @@ Version : WelcomeScreen - + Don't show again Ne plus montrer - + Tip of the day Astuce du jou - + Press Esc if you want hide app to tray. Appuyez sur échap pour réduire l'application. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> Vous pouvez utiliser Tox avec Tor. Pour plus d'informations, voir <a href="https://wiki.tox.chat/users/tox_over_tor_tot">cet article</a> - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. Vous pouvez mettre un mot de passe dans Profil -> Paramètres -> Mot de passe pour que toxygen encrypte votre historique et vos paramètres. - + Right click on screenshot button hides app to tray during screenshot. Faire un clic droit sur le bouton de capture d'écran réduit l'application avant de capturer l'écran. - + Use Settings -> Interface to customize interface. Vous pouvez customizer votre interface dans Paramètres -> Interface. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. Toxygen permet d'envoyer des messages et fichiers en différé. Envoyez des messages ou fichiers à un contact hors ligne et il le recevra plus tard. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. Vous pouvez empecher le spam dans les demandes de contact avec Profil -> Paramètres -> Nouveau NoSpam. - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu Pour supprimer un seul message dans une conversation, faites un clic droit sur l'heure du message et sélectionnez "Supprimer ce message" dans le menu - + Use right click on inline image to save it Pour sauvegarder une image intégrée, faites un clic droit dessus - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> Depuis la version 0.1.3 Toxygen supporte les plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">En savoir plus</a> - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 Nouveau dans Toxygen 0.3.0 : <br>Appels vidéo<br>Support de Python3.6<br>Migration vers PyQt5 @@ -798,17 +838,17 @@ Version : audioSettingsForm - + Audio settings Paramètres audio - + Input device: Péripherique d'entrée : - + Output device: Péripherique de sortie : @@ -816,32 +856,32 @@ Version : incoming_call - + Incoming video call Appel vidéo entrant - + Incoming audio call Appel audio entrant - + Outgoing video call Appel vidéo sortant - + Outgoing audio call Appel audio sortant - + Call declined Appel refusé - + Call finished Appel terminé @@ -849,82 +889,82 @@ Version : interfaceForm - + Interface settings Paramètres de l'interface - + Theme: Thème : - + Language: Langue : - + Smileys Smileys - + Smiley pack: Pack de smileys : - + Mirror mode Mode miroir - + Messages font size: Taille des messages : - + Restart app to apply settings Redémarrer toxygen pour appliquer les paramètres - + Restart required Redémarrage nécessaire - + Select unread messages notification color Sélectionner la couleur des messages non-lus - + Compact contact list Liste de contacts compacte - + Import smiley pack Importer un pack de smileys - + Import sticker pack Importer un pack de stickers - + Show avatars in chat Montrer les avatars dans la conversation - + Close to tray Réduire - + Select font Sélectionner la police @@ -1010,25 +1050,30 @@ Version : notificationsForm - + Notification settings Paramètres de notification - + Enable notifications Activer les notifications - + Enable call's sound Activer les sons d'appel - + Enable sound notifications Activer les sons de notifications + + + Notify about all messages in groups + + pass @@ -1172,57 +1217,57 @@ Version : updateSettingsForm - + Update settings Paramètres de mise à jour - + Select update mode: Sélectionner le mode de mise à jour : - + Update Toxygen Mettre à jour toxygen - + Disabled Désactivé - + Manual Manuel - + Auto Automatique - + Error Erreur - + Problems with internet connection Il y à des problèmes avec votre connexion internet - + Updater not found Updater non trouvé - + No updates found Pas de mises à jour trouvés - + Toxygen is up to date Toxygen est à jour @@ -1230,22 +1275,22 @@ Version : videoSettingsForm - + Video settings Paramètres vidéo - + Device: Périphérique : - + Desktop - + Select region diff --git a/toxygen/translations/ru_RU.qm b/toxygen/translations/ru_RU.qm index fddbe6bc379ad9dd4a54d73e1eed9650710413d7..123188400ea7c40d15b33ae8c435dfd989377d23 100644 GIT binary patch delta 2727 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 delta 1885 zcmeA;#d!80;{=g<1qKEW9R>y#GX@5)BMb~24;dKztQZ&=r!X)?PGDeQDq>*iSS4;jz%RkT+P$8E!Ac~Mfpx}F1_se;2G%3%85kt)Gq7K- zXJBxPVc@jA$G{LG&!DX5%fP_AioxU=69WVPT?UhXEDQ|1vnHMuuUBR8nVH1Epy&vp zLp2#fn!6Yn)ORz4W*uf=;9t!U*0q#@L2w;IcxDv?1LJLm@M%987?_wDVt(ysV31nN zFnt;e14GCQhNE7^3=Dz345w{)7#M6y7*0nVU|?X4XSjatHUk5DGb7{XUknWHM;R6M zcQP>8_A^HIC^0b9J1%03WmjfkaPVO)y0L|U!DlIB*{pL64BVlN`?!=D7__D_9yiuw zU=USi;<9|gz##CCNkJ)ufx)_-$$Rk>1_o9>rr@V33=B$sOl8wz85kI?nf8Z$VPN2y z$#h^>CZ{^hZz`T_?bRvMKUl5FJt;#>&(F5&B4qd_K2aLLF)sv*tg9L43YZG znpt%W3`&cbeYPhtFgTxLj;L#8U{L(PoYXXrfx&PUbL(eM28JLp=2JXV7#KXSF@Lyn zlz~B4k@**s9RmYL6AQDuB?E)36ol5;#}edM!oVQ-h$U_MQw9cU6_(8XUl|yjOIXVG z&ShYbyvWin{=c4q!Iht7^0iqE4E~ZV^IEPlF!;B!tk|)Xfx+30WsS~K1_n-HmebE} zGcfQwvb@&kVqmaOWff5`VPNplVO2ER$-uz=pVf5QKL!Rj6IP4p8U}{YL{_H+O9lp& z-K>GH-ZC(#e_%~DaAjZ+sDjWMEf6~R3+wET`WXxiq8nJ3O<%&mAhRDrtFW=IeX)gs zL9QP{YdNv5JKV*<;4H#=%>FY2gFPqfy~8OC3@T}?AJeBYFev!3aXg;Fz~IQp#((25 z1B0*_o3M{51B3m4HiP%V3=HAZ*upL@Wnd6fV2h4SW?+z=0HHN4*)rO&)!90a2{ABeSh4Myc$9%5>@M3$H&F%#bsM%TJKixcg#Kl_;roSw zK~0aH<=;F82ECc=T;D%4FjzIP3rL$XFt~TJE82xHF!0#0D|zo>VBmhpZgS-u0|Or; zyXDWP3=Dc7*{vTiGcX7hvDbqc%53aD7R(F`K}*>y%U>}t_}Q~Jic7tS_?BU zcrD|Y|LFk(g9IBVbKF!02Gv`fEE~TtFoe$GG?Fo4U=UxxX>aAqz`!QP=@`z+z#yK^ z=`rIJ0|S>7ru87KdOfk9_KXN89p14CFLXU%VI1_pL6&f{rY7#N&X zInVCtWMBxaU(IW z=l?y&z~CjuT{6Xwfx+S<_Y{Ml3=FK7A+-N~?!|w27#KwFbFccgn}NY@75CbJJcfD( z^DORdMxqQ10;{<%*1cn3P%Pr{G(FA05Ujv>HX?lCYJEaWvi_@thJLGB)i!N8#H#2fPK3IhX^E^o|LWd;Vm ze%`rhw-^|Fukap^*JEG^cjUckxs`!I(3SVKj41fg%1AQR80FKNfXV8FuXnU=@E5OIgk>les@7x^YH*~h?O^oZ|LwH5<|Q!l^q^Q27- z4AMvVU7SBLFtA1ON2akbFtEh&S3Tauz@WU9zcXnI0|Vz&{*^bTFfe$z@}JbzW?-=M z MainWindow - + Profile Профиль - + Settings Настройки - + About О программе - + Add contact Добавить контакт - + Privacy Приватность - + Interface Интерфейс - + Notifications Уведомления - + Network Сеть - + About program О программе - + User {} wants to add you to contact list. Message: {} Пользователь {} хочет добавить Вас в список контактов. Сообщение: {} - + Friend request Запрос на добавление в друзья - + Choose file Выберите файл - + Disallow auto accept Запретить автоматическое получение файлов - + Allow auto accept Разрешить автоматическое получение файлов - + Set alias Изменить псевдоним - + Clear history Очистить историю @@ -174,17 +174,17 @@ can produce IP leak Копировать публичный ключ - + Remove friend Удалить друга - + Enter new alias for friend {} or leave empty to use friend's name: Введите новый псевдоним для друга {} или оставьте пустым для использования его имени: - + Audio Аудио @@ -194,23 +194,23 @@ can produce IP leak Найти контакт - + Friend added Друг добавлен - + Toxygen is Tox client written on Python. Version: Toxygen - клиент для мессенджера Tox, написанный на Python. Версия: - + Friend added without sending friend request Друг добавлен без отправки запроса на добавление в друзья - + Choose folder Выбрать папку @@ -225,47 +225,47 @@ Version: Отправить файл - + Send message Отправить сообщение - + Start audio call with friend Начать аудиозвонок с другом - + Plugins Плагины - + List of plugins Список плагинов - + Search Поиск - + All Все - + Online Онлайн - + Notes Заметки - + Notes about user Заметки о пользователе @@ -315,7 +315,7 @@ Version: Сохранить - + User {} is now known as {} Пользователь {} сейчас известен как {} @@ -325,32 +325,32 @@ Version: Удалить сообщение - + Lock Заблокировать - + Cannot lock app Невозможно заблокировать приложение - + Error. Profile password is not set. Ошибка. Пароль профиля не установлен. - + Name Имя - + Status message Статус - + Public key Публичный ключ @@ -365,32 +365,32 @@ Version: Профиль с данным именем уже существует - + Choose folder with sticker pack Выберите папку в паком стикеров - + Choose folder with smiley pack Выберите папку с паком смайлов - + Import plugin Импортировать плагин - + Choose folder with plugin Выберите папку с плагином - + Restart Toxygen Перезапустите Toxygen - + Plugin will be loaded after restart Плагин будет загружен после перезапуска @@ -400,65 +400,105 @@ Version: Цитировать выбранный текст - + Chat history История чата - + Export as text Экспортировать как текст - + Export as HTML Экспортировать как HTML - + Updates Обновления - + Online first Сначала онлайн - + Online and by name Онлайн и по имени - + Online first and by name Сначала онлайн и по имени - + Block friend Заблокировать друга - + Not found Не найдено - + Text "{}" was not found Текст "{}" не был найден - + Reload plugins Перезагрузить плагины - + Video Видео + + + User {} invites you to group chat. Accept? + Пользователь {} приглашает Вас в групповой чат. Принять приглашение? + + + + Group chat invite + Приглашение в групповой чат + + + + {} users in chat + {} пользователей в чате + + + + Enter new title for group {}: + Введите название для группы {}: + + + + Set title + Изменить название + + + + Create group chat + Создать групповой чат + + + + Invite to group chat + Пригласить в групповой чат + + + + Leave chat + Покинуть чат + MenuWindow @@ -478,12 +518,12 @@ Version: Остановить запись - + Send screenshot Отправить снимок экрана - + Send file Отправить файл @@ -498,12 +538,12 @@ Version: Отправить видеосообщение - + Add smiley Добавить смайлик - + Send sticker Отправить стикер @@ -575,42 +615,42 @@ Version: PluginsForm - + Plugins Плагины - + Open selected plugin Открыть выбранный плагин - + No GUI found for this plugin GUI для данного плагина не найден - + No description available Описание недоступно - + Disable plugin Отключить плагин - + Enable plugin Включить плагин - + No plugins found Плагины не найдены - + Error Ошибка @@ -756,17 +796,17 @@ Version: WelcomeScreen - + Don't show again Не показывать снова - + Tip of the day Подсказка дня - + Press Esc if you want hide app to tray. Нажатие Esc сворачивает приложение в трей. @@ -776,7 +816,7 @@ Version: Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> Вы можете использовать Tox через Tor. Дополнительная информация <a href="https://wiki.tox.chat/users/tox_over_tor_tot">тут</a> @@ -786,7 +826,7 @@ Version: Используйте Настройки -> Интерфейс для настройки интерфейса - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. Установите пароль профиля: Профиль -> Настройки. Пароль позволяет шифровать историю переписки и настройки. @@ -811,22 +851,22 @@ Version: Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam - + Right click on screenshot button hides app to tray during screenshot. Правый клик на кнопке скриншота сворачивает приложение в трей на время скриншота. - + Use Settings -> Interface to customize interface. Используйте Настройки -> Интерфейс для настройки интерфейса. - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. Toxygen поддерживает псевдооффлайн сообщения и файл трансферы. - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. Установите новый NoSpam, чтобы избежать спам запросов в друзья: Профиль->Настройки->Новый NoSpam. @@ -836,12 +876,12 @@ Version: Новое в Toxygen 0.2.3:<br>Соответствие TCS<br>Импорт плагинов, смайлов и стикеров<br>Исправления ошибок - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu Чтобы удалить отдельное сообщение в чате сделайте правый клик на спиннер или время сообщения и выберите "Удалить" в меню - + Use right click on inline image to save it Правый клик на инлайн изображении позволит сохранить его @@ -856,12 +896,12 @@ Version: Новое в Toxygen v0.2.6:<br>Поддержка обновлений<br>Улучшенная сортировка контактов<br>Улучшения в работе плагинов - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> С версии 0.1.3 Toxygen поддерживает плагины. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Узнать больше.</a> - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 Новое в Toxygen 0.3.0:<br>Видеозвонки<br>Поддержка Python3.6<br>Миграция на PyQt5 @@ -869,17 +909,17 @@ Version: audioSettingsForm - + Audio settings Настройки аудио - + Input device: Устройство ввода: - + Output device: Устройство вывода: @@ -887,32 +927,32 @@ Version: incoming_call - + Incoming video call Входящий видеозвонок - + Incoming audio call Входящий аудиозвонок - + Outgoing video call Исходящий видеозвонок - + Outgoing audio call Исходящий аудиозвонок - + Call declined Звонок отменен - + Call finished Звонок завершен @@ -920,82 +960,82 @@ Version: interfaceForm - + Interface settings Настройки интерфейса - + Theme: Тема: - + Language: Язык: - + Smileys Смайлики - + Smiley pack: Набор смайликов: - + Mirror mode Зеркальный режим - + Messages font size: Размер шрифта сообщений: - + Restart app to apply settings Для применения настроек необходимо перезапустить приложение - + Restart required Требуется перезапуск - + Select unread messages notification color Цвет уведомления о сообщении - + Compact contact list Компактный список контактов - + Import smiley pack Импортировать смайлы - + Import sticker pack Импортировать стикеры - + Show avatars in chat Показывать аватары в чате - + Close to tray Сворачивать в трей - + Select font Выбрать шрифт @@ -1081,26 +1121,31 @@ Version: notificationsForm - + Notification settings Настройки уведомлений - + Enable notifications Включить уведомления - + Enable call's sound Включить звук звонка - + Enable sound notifications Включить звуковые уведомления + + + Notify about all messages in groups + Уведомлять обо всех сообщениях в группах + pass @@ -1244,57 +1289,57 @@ Version: updateSettingsForm - + Update settings Обновить настройки - + Select update mode: Выбрать режим обновлений: - + Update Toxygen Обновить Toxygen - + Disabled Отключены - + Manual Вручную - + Auto Автоматически - + Error Ошибка - + Problems with internet connection Проблемы с соединением - + Updater not found Апдейтер не был найден - + No updates found Обновления не найдены - + Toxygen is up to date Toxygen уже обновлен @@ -1302,22 +1347,22 @@ Version: videoSettingsForm - + Video settings Настройки видео - + Device: Устройство: - + Desktop Рабочий стол - + Select region Выберите область diff --git a/toxygen/translations/uk_UA.ts b/toxygen/translations/uk_UA.ts index 5a7760a..fb21766 100644 --- a/toxygen/translations/uk_UA.ts +++ b/toxygen/translations/uk_UA.ts @@ -84,27 +84,27 @@ can produce IP leak MainWindow - + About program Про проґраму - + Friend request Запит дружби - + About Про - + Audio Звук - + Friend added Друга додано @@ -114,19 +114,19 @@ can produce IP leak Надіслати файл - + User {} wants to add you to contact list. Message: {} Користувач {} хоче додати вас до списку контактів. Повідомлення {} - + Network Мережа - + Clear history Очистити журнал @@ -136,69 +136,69 @@ can produce IP leak Копіювати публічний ключ - + Send message Надіслати повідомлення - + Set alias Встановити скорочення - + Privacy Приватність - + Profile Профіль - + Toxygen is Tox client written on Python. Version: Toxygen — це клієнт Tox написаний на Python. Версія: - + Choose file Обрати файл - + Enter new alias for friend {} or leave empty to use friend's name: Введіть нове скорочення для друга {} або залишіть порожнім, щоб використовувати його псевдо: - + Add contact Додати контакт - + Friend added without sending friend request Друга додано без надсилання запиту дружби - + Interface Зовнішній вигляд - + Settings Налаштування - + Notifications Сповіщення - + Remove friend Вилучити друга @@ -208,22 +208,22 @@ Version: Знайти контакт - + Choose folder Обрати теку - + Allow auto accept Дозволити автоприймання - + Disallow auto accept Заборонити автоприймання - + Start audio call with friend Почати звуковий дзвінок @@ -243,17 +243,17 @@ Version: - + User {} is now known as {} - + Choose folder with sticker pack - + Choose folder with smiley pack @@ -263,7 +263,7 @@ Version: - + Plugins @@ -273,107 +273,107 @@ Version: - + Lock - + List of plugins - + Video - + Updates - + Search - + All - + Online - + Online first - + Name - + Online and by name - + Online first and by name - + Import plugin - + Reload plugins - + Choose folder with plugin - + Restart Toxygen - + Plugin will be loaded after restart - + Cannot lock app - + Error. Profile password is not set. - + Chat history Журнал бесіди - + Export as text - + Export as HTML @@ -383,27 +383,27 @@ Version: - + Status message - + Public key - + Block friend - + Notes - + Notes about user @@ -448,35 +448,75 @@ Version: - + Text "{}" was not found - + Not found + + + User {} invites you to group chat. Accept? + + + + + Group chat invite + + + + + {} users in chat + + + + + Enter new title for group {}: + + + + + Set title + + + + + Create group chat + + + + + Invite to group chat + + + + + Leave chat + + MenuWindow - + Send screenshot Надіслати знімок екрану - + Send file Надіслати файл - + Add smiley - + Send sticker @@ -548,42 +588,42 @@ Version: PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + Error - + No description available - + Disable plugin - + Enable plugin - + No plugins found @@ -724,67 +764,67 @@ Version: WelcomeScreen - + Don't show again - + Tip of the day - + Press Esc if you want hide app to tray. - + Right click on screenshot button hides app to tray during screenshot. - + You can use Tox over Tor. For more info read <a href="https://wiki.tox.chat/users/tox_over_tor_tot">this post</a> - + Use Settings -> Interface to customize interface. - + Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings. - + Since v0.1.3 Toxygen supports plugins. <a href="https://github.com/toxygen-project/toxygen/blob/master/docs/plugins.md">Read more</a> - + Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - + New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 - + Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu - + Use right click on inline image to save it - + Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. @@ -792,17 +832,17 @@ Version: audioSettingsForm - + Output device: Пристрій виводу: - + Audio settings Налаштування звуку - + Input device: Пристрій вводу: @@ -810,32 +850,32 @@ Version: incoming_call - + Incoming video call Вхідний відеодзвінок - + Incoming audio call Вхідний аудіодзвінок - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -843,82 +883,82 @@ Version: interfaceForm - + Language: Мова: - + Theme: Тема: - + Interface settings Налаштування зовнішнього вигляду - + Show avatars in chat - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Close to tray - + Select font - + Restart app to apply settings - + Restart required @@ -1004,25 +1044,30 @@ Version: notificationsForm - + Enable sound notifications Увімкнути звукові сповіщення - + Enable notifications Увімкнути сповіщення - + Notification settings Налаштування сповіщень - + Enable call's sound Увімкнути звук дзвінка + + + Notify about all messages in groups + + pass @@ -1166,57 +1211,57 @@ Version: updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date @@ -1224,22 +1269,22 @@ Version: videoSettingsForm - + Video settings - + Device: - + Desktop - + Select region From 6efb1790bba9d9e502ed687e2ffc43b23b8d4146 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 19 Jul 2017 19:39:56 +0300 Subject: [PATCH 067/163] notifications fix --- toxygen/callbacks.py | 9 +++++---- toxygen/group_chat.py | 3 +++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 94e954e..fd25d97 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -384,13 +384,14 @@ def group_invite(tox, friend_number, gc_type, data, length, user_data): bytes(data[:length])) -def show_gc_notification(window, tray, message, group_number): +def show_gc_notification(window, tray, message, group_number, peer_number): profile = Profile.get_instance() settings = Settings.get_instance() chat = profile.get_group_by_number(group_number) + peer_name = chat.get_peer_name(peer_number) if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']): if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: - invoke_in_main_thread(tray_notification, chat.name, message, tray, window) + invoke_in_main_thread(tray_notification, chat.name + ' ' + peer_name, message, tray, window) if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: sound_notification(SOUND_NOTIFICATION['MESSAGE']) invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) @@ -401,7 +402,7 @@ def group_message(window, tray): message = str(message[:length], 'utf-8') invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, peer_number, TOX_MESSAGE_TYPE['NORMAL'], message) - show_gc_notification(window, tray, message, group_number) + show_gc_notification(window, tray, message, group_number, peer_number) return wrapped @@ -410,7 +411,7 @@ def group_action(window, tray): message = str(message[:length], 'utf-8') invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, peer_number, TOX_MESSAGE_TYPE['ACTION'], message) - show_gc_notification(window, tray, message, group_number) + show_gc_notification(window, tray, message, group_number, peer_number) return wrapped diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py index 0ee4b35..f7921a1 100644 --- a/toxygen/group_chat.py +++ b/toxygen/group_chat.py @@ -44,3 +44,6 @@ class GroupChat(contact.Contact): def get_full_status(self): names = self.get_names() return '\n'.join(names) + + def get_peer_name(self, peer_number): + return self._tox.group_peername(self._number, peer_number) From 80b0ea4f0e810ededb8e0d9714363e9ad6f1e01b Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 20 Jul 2017 23:51:40 +0300 Subject: [PATCH 068/163] history for gc fixes --- toxygen/contact.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/toxygen/contact.py b/toxygen/contact.py index 4475e53..9f27a1d 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -61,6 +61,8 @@ class Contact(basecontact.BaseContact): """ Get all chat history from db for current friend """ + if self._message_getter is None: + return data = list(self._message_getter.get_all()) if data is not None and len(data): data.reverse() @@ -124,7 +126,7 @@ class Contact(basecontact.BaseContact): # ----------------------------------------------------------------------------------------------------------------- def delete_message(self, time): - elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0] + elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0] tmp = list(filter(lambda x: x.get_type() <= 1, self._corr)) if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: self._unsaved_messages -= 1 From 3ddb7470fc00f6e853baf9f70b74aae874d0b31b Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 21 Jul 2017 18:39:10 +0300 Subject: [PATCH 069/163] v0.4.0 --- toxygen/main.py | 2 +- toxygen/util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/toxygen/main.py b/toxygen/main.py index 04bad7f..24ac0b6 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -106,7 +106,7 @@ class Toxygen: return self.tox = profile.tox_factory() self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') - self.tox.self_set_status_message(b'Toxing on T03') + self.tox.self_set_status_message(b'Toxing on Toxygen') reply = QtWidgets.QMessageBox.question(None, 'Profile {}'.format(name), QtWidgets.QApplication.translate("login", diff --git a/toxygen/util.py b/toxygen/util.py index 8881380..c237501 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.3.2' +program_version = '0.4.0' def cached(func): From 24c8b18f7ed2636b2c479329dfac31fb833032a4 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 30 Aug 2017 22:20:31 +0300 Subject: [PATCH 070/163] minor fixes --- toxygen/main.py | 2 +- toxygen/menu.py | 2 +- toxygen/plugin_support.py | 11 +++++++---- toxygen/util.py | 2 +- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/toxygen/main.py b/toxygen/main.py index 24ac0b6..c3a2e40 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -6,7 +6,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets from bootstrap import node_generator from mainscreen import MainWindow from callbacks import init_callbacks, stop, start -from util import curr_directory, program_version, remove, is_64_bit +from util import curr_directory, program_version, remove import styles.style import platform import toxes diff --git a/toxygen/menu.py b/toxygen/menu.py index 37f7bb6..0d640f4 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -611,7 +611,7 @@ class InterfaceSettings(CenteredWidget): self.messages_font_size_label.setFont(font) self.messages_font_size = QtWidgets.QComboBox(self) self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30)) - self.messages_font_size.addItems([str(x) for x in range(10, 19)]) + self.messages_font_size.addItems([str(x) for x in range(10, 25)]) self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10) self.unread = QtWidgets.QPushButton(self) diff --git a/toxygen/plugin_support.py b/toxygen/plugin_support.py index a7949f0..0ff7421 100644 --- a/toxygen/plugin_support.py +++ b/toxygen/plugin_support.py @@ -97,10 +97,13 @@ class PluginLoader(util.Singleton): """ result = [] for data in self._plugins.values(): - result.append([data[0].get_name(), # plugin full name - data[1], # is enabled - data[0].get_description(), # plugin description - data[0].get_short_name()]) # key - short unique name + try: + result.append([data[0].get_name(), # plugin full name + data[1], # is enabled + data[0].get_description(), # plugin description + data[0].get_short_name()]) # key - short unique name + except: + continue return result def plugin_window(self, key): diff --git a/toxygen/util.py b/toxygen/util.py index c237501..e8d702a 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.4.0' +program_version = '0.4.1' def cached(func): From 5a5b0e90693b76684f24fe94e9563f23c7770967 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 8 Oct 2017 00:39:08 +0300 Subject: [PATCH 071/163] desktop sharing bug fix --- toxygen/calls.py | 3 +++ toxygen/menu.py | 2 +- toxygen/profile.py | 8 +++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index a16a79d..7b16e84 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -153,6 +153,9 @@ class AV: if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video: self.start_video_thread() + def is_video_call(self, number): + return self._calls[number].in_video + # ----------------------------------------------------------------------------------------------------------------- # Threads # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/menu.py b/toxygen/menu.py index 0d640f4..5b3f6f1 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -816,7 +816,7 @@ class DesktopAreaSelectionWindow(RubberBandWindow): rect = self.rubberband.geometry() width, height = rect.width(), rect.height() if width >= 8 and height >= 8: - self.parent.save(rect.x(), rect.y(), width, height) + self.parent.save(rect.x(), rect.y(), width - (width % 4), height - (height % 4)) self.close() diff --git a/toxygen/profile.py b/toxygen/profile.py index d8bf085..59c0e69 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1281,11 +1281,17 @@ class Profile(basecontact.BaseContact, Singleton): else: text = QtWidgets.QApplication.translate("incoming_call", "Call finished") self._screen.call_finished() + is_video = self._call.is_video_call(friend_number) self._call.finish_call(friend_number, by_friend) # finish or decline call if hasattr(self, '_call_widget'): self._call_widget[friend_number].close() del self._call_widget[friend_number] - threading.Timer(2.0, lambda: cv2.destroyWindow(str(friend_number))).start() + + def destroy_window(): + if is_video: + cv2.destroyWindow(str(friend_number)) + + threading.Timer(2.0, destroy_window).start() friend = self.get_friend_by_number(friend_number) friend.append_message(InfoMessage(text, time.time())) if friend_number == self.get_active_number(): From 23b55522ba2d230f0f8f624117ed86f599c14620 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 22 Oct 2017 16:36:46 +0300 Subject: [PATCH 072/163] file transfer cancelling fix --- toxygen/profile.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index 59c0e69..e453059 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -995,11 +995,12 @@ class Profile(basecontact.BaseContact, Singleton): if friend_number == self.get_active_number() and self.is_active_a_friend(): tmp = self._messages.count() + i if tmp >= 0: - self._messages.itemWidget(self._messages.item(tmp)).update(TOX_FILE_TRANSFER_STATE['CANCELLED'], - 0, -1) + self._messages.itemWidget( + self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'], + 0, -1) - def cancel_not_started_transfer(self, time): - self.get_curr_friend().delete_one_unsent_file(time) + def cancel_not_started_transfer(self, cancel_time): + self.get_curr_friend().delete_one_unsent_file(cancel_time) self.update() def pause_transfer(self, friend_number, file_number, by_friend=False): From a6633f1e7792b05edf53dad23255e3ef6154f656 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 24 Oct 2017 21:43:12 +0300 Subject: [PATCH 073/163] minor video changes --- toxygen/calls.py | 10 +++++----- toxygen/profile.py | 4 +++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index 7b16e84..6477c03 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -284,12 +284,12 @@ class AV: try: y, u, v = self.convert_bgr_to_yuv(frame) self._toxav.video_send_frame(friend_num, width, height, y, u, v) - except Exception as e: - print(e) - except Exception as e: - print(e) + except: + pass + except: + pass - time.sleep(0.01) + time.sleep(0.01) def convert_bgr_to_yuv(self, frame): """ diff --git a/toxygen/profile.py b/toxygen/profile.py index e453059..204419a 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -72,6 +72,8 @@ class Profile(basecontact.BaseContact, Singleton): friend = Friend(message_getter, i, name, status_message, item, tox_id) friend.set_alias(alias) self._contacts.append(friend) + if len(self._contacts): + self.set_active(0) self.filtration_and_sorting(self._sorting) # ----------------------------------------------------------------------------------------------------------------- @@ -1241,7 +1243,7 @@ class Profile(basecontact.BaseContact, Singleton): def incoming_call(self, audio, video, friend_number): """ - Incoming call from friend. Only audio is supported now + Incoming call from friend. """ if not Settings.get_instance().audio['enabled']: return From 32055050eec5d69b331a10c0be3c3b4f8c2de1e1 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 5 Nov 2017 12:13:28 +0300 Subject: [PATCH 074/163] hide tray icon on exit --- setup.py | 1 + toxygen/bootstrap.py | 1 - toxygen/list_items.py | 4 ---- toxygen/main.py | 3 ++- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index adcd58c..bc06944 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ class InstallScript(install): except: pass + setup(name='Toxygen', version=version, description='Toxygen - Tox client', diff --git a/toxygen/bootstrap.py b/toxygen/bootstrap.py index d6473ee..b80fdb8 100644 --- a/toxygen/bootstrap.py +++ b/toxygen/bootstrap.py @@ -81,4 +81,3 @@ def node_generator(): arr = sorted(nodes, key=lambda x: x.rand)[:4] for elem in arr: yield elem.get_data() - diff --git a/toxygen/list_items.py b/toxygen/list_items.py index 8e58c59..9b92f2a 100644 --- a/toxygen/list_items.py +++ b/toxygen/list_items.py @@ -543,7 +543,3 @@ class InlineImageItem(QtWidgets.QScrollArea): def mark_as_sent(self): return False - - - - diff --git a/toxygen/main.py b/toxygen/main.py index c3a2e40..cfc137d 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -7,7 +7,7 @@ from bootstrap import node_generator from mainscreen import MainWindow from callbacks import init_callbacks, stop, start from util import curr_directory, program_version, remove -import styles.style +import styles.style # reqired for styles loading import platform import toxes from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen @@ -327,6 +327,7 @@ class Toxygen: self.mainloop.wait() self.init.wait() self.avloop.wait() + self.tray.hide() data = self.tox.get_savedata() ProfileHelper.get_instance().save_profile(data) settings.close() From 55a127a82049e8cf0bcefcb70382a7a86d37831f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 24 Jan 2018 22:45:58 +0300 Subject: [PATCH 075/163] ability to use nodes from tox.chat added --- toxygen/bootstrap.py | 138 ++++++++++++++++++++----------------------- toxygen/main.py | 8 ++- toxygen/menu.py | 13 ++-- toxygen/nodes.json | 1 + toxygen/settings.py | 3 +- 5 files changed, 82 insertions(+), 81 deletions(-) create mode 100644 toxygen/nodes.json diff --git a/toxygen/bootstrap.py b/toxygen/bootstrap.py index b80fdb8..0589940 100644 --- a/toxygen/bootstrap.py +++ b/toxygen/bootstrap.py @@ -1,83 +1,75 @@ import random +import urllib.request +from util import log, curr_directory +import settings +from PyQt5 import QtNetwork, QtCore +import json class Node: - def __init__(self, ip, port, tox_key, rand): - self._ip, self._port, self._tox_key, self.rand = ip, port, tox_key, rand + def __init__(self, node): + self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key'] + self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0 + + def get_priority(self): + return self._priority + + priority = property(get_priority) def get_data(self): return bytes(self._ip, 'utf-8'), 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 range(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() +def generate_nodes(): + with open(curr_directory() + '/nodes.json', 'rt') as fl: + json_nodes = json.loads(fl.read())['nodes'] + nodes = map(lambda json_node: Node(json_node), json_nodes) + sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:] + for node in sorted_nodes: + yield node.get_data() + + +def save_nodes(nodes): + if not nodes: + return + print('Saving nodes...') + with open(curr_directory() + '/nodes.json', 'wb') as fl: + fl.write(nodes) + + +def download_nodes_list(): + url = 'https://nodes.tox.chat/json' + s = settings.Settings.get_instance() + if not s['download_nodes_list']: + return + + if not s['proxy_type']: # no proxy + try: + req = urllib.request.Request(url) + req.add_header('Content-Type', 'application/json') + response = urllib.request.urlopen(req) + result = response.read() + save_nodes(result) + except Exception as ex: + log('TOX nodes loading error: ' + str(ex)) + else: # proxy + netman = QtNetwork.QNetworkAccessManager() + proxy = QtNetwork.QNetworkProxy() + proxy.setType( + QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) + proxy.setHostName(s['proxy_host']) + proxy.setPort(s['proxy_port']) + netman.setProxy(proxy) + try: + request = QtNetwork.QNetworkRequest() + request.setUrl(QtCore.QUrl(url)) + reply = netman.get(request) + + while not reply.isFinished(): + QtCore.QThread.msleep(1) + QtCore.QCoreApplication.processEvents() + data = bytes(reply.readAll().data()) + save_nodes(data) + except Exception as ex: + log('TOX nodes loading error: ' + str(ex)) diff --git a/toxygen/main.py b/toxygen/main.py index cfc137d..d630bb6 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -3,7 +3,7 @@ from loginscreen import LoginScreen import profile from settings import * from PyQt5 import QtCore, QtGui, QtWidgets -from bootstrap import node_generator +from bootstrap import generate_nodes, download_nodes_list from mainscreen import MainWindow from callbacks import init_callbacks, stop, start from util import curr_directory, program_version, remove @@ -379,9 +379,11 @@ class Toxygen: def run(self): # initializing callbacks init_callbacks(self.tox, self.ms, self.tray) + # download list of nodes if needed + download_nodes_list() # bootstrap try: - for data in node_generator(): + for data in generate_nodes(): if self.stop: return self.tox.bootstrap(*data) @@ -394,7 +396,7 @@ class Toxygen: self.msleep(1000) while not self.tox.self_get_connection_status(): try: - for data in node_generator(): + for data in generate_nodes(): if self.stop: return self.tox.bootstrap(*data) diff --git a/toxygen/menu.py b/toxygen/menu.py index 5b3f6f1..17f4e17 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -294,10 +294,10 @@ class NetworkSettings(CenteredWidget): def initUI(self): self.setObjectName("NetworkSettings") - self.resize(300, 330) - self.setMinimumSize(QtCore.QSize(300, 330)) - self.setMaximumSize(QtCore.QSize(300, 330)) - self.setBaseSize(QtCore.QSize(300, 330)) + self.resize(300, 400) + self.setMinimumSize(QtCore.QSize(300, 400)) + self.setMaximumSize(QtCore.QSize(300, 400)) + self.setBaseSize(QtCore.QSize(300, 400)) self.ipv = QtWidgets.QCheckBox(self) self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22)) self.ipv.setObjectName("ipv") @@ -332,6 +332,9 @@ class NetworkSettings(CenteredWidget): self.warning = QtWidgets.QLabel(self) self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60)) self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') + self.nodes = QtWidgets.QCheckBox(self) + self.nodes.setGeometry(QtCore.QRect(20, 350, 270, 22)) + self.nodes.setChecked(settings['download_nodes_list']) self.retranslateUi() self.proxy.stateChanged.connect(lambda x: self.activate()) self.activate() @@ -346,6 +349,7 @@ class NetworkSettings(CenteredWidget): self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:")) self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core")) self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP")) + self.nodes.setText(QtWidgets.QApplication.translate("Form", "Download nodes list from tox.chat")) self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) def activate(self): @@ -362,6 +366,7 @@ class NetworkSettings(CenteredWidget): settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 settings['proxy_host'] = str(self.proxyip.text()) settings['proxy_port'] = int(self.proxyport.text()) + settings['download_nodes_list'] = self.nodes.isChecked() settings.save() # recreate tox instance Profile.get_instance().reset(self.reset) diff --git a/toxygen/nodes.json b/toxygen/nodes.json new file mode 100644 index 0000000..003bbc0 --- /dev/null +++ b/toxygen/nodes.json @@ -0,0 +1 @@ +{"last_scan":1516822981,"last_refresh":1516822982,"nodes":[{"ipv4":"node.tox.biribiri.org","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67","maintainer":"nurupo","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Welcome, stranger #7985. I'm up for 5d 14h 34m 34s, running since Jan 19 05:08:27 UTC. If I get outdated, please ping my maintainer at nurupo.contributions@gmail.com","last_ping":1516822981},{"ipv4":"nodes.tox.chat","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"6FC41E2BD381D37E9748FC0E0328CE086AF9598BECC8FEB7DDF2E440475F300E","maintainer":"Impyy","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Straps boots like no other","last_ping":1516822981},{"ipv4":"130.133.110.14","ipv6":"2001:6f8:1c3c:babe::14:1","port":33445,"tcp_ports":[33445],"public_key":"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F","maintainer":"Manolis","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Spline tox bootstrap node","last_ping":1516822981},{"ipv4":"205.185.116.116","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"198.98.51.198","ipv6":"2605:6400:1:fed5:22:45af:ec10:f329","port":33445,"tcp_ports":[33445,3389],"public_key":"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"85.172.30.117","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832","maintainer":"ray65536","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Ray's Tox Node","last_ping":1516822981},{"ipv4":"194.249.212.109","ipv6":"2001:1470:fbfe::109","port":33445,"tcp_ports":[33445,3389],"public_key":"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B","maintainer":"fluke571","location":"SI","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"185.25.116.107","ipv6":"2a00:7a60:0:746b::3","port":33445,"tcp_ports":[33445,3389],"public_key":"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43","maintainer":"MAH69K","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Saluton! Mia Tox ID: B229B7BD68FC66C2716EAB8671A461906321C764782D7B3EDBB650A315F6C458EF744CE89F07. Scribu! ;)","last_ping":1516822981},{"ipv4":"5.189.176.217","ipv6":"2a02:c200:1:10:3:1:605:1337","port":5190,"tcp_ports":[3389,33445,5190],"public_key":"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F","maintainer":"tastytea","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"217.182.143.254","ipv6":"2001:41d0:302:1000::e111","port":2306,"tcp_ports":[33445,2306,443],"public_key":"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147","maintainer":"pucetox","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"by pucetox,\nipv4/ipv6 UDP:2306 TCP:21/80/443/2306/33445\nsync your nodes here tox.0x10k.com/bootstrapd-conf , \n for communication: 1D1C0B992DEB6D7F18561176F7F5E572BCC7F2BA5CFA7E9E437B9134122CE96D906A6119F9D2","last_ping":1516822981},{"ipv4":"104.223.122.15","ipv6":"2607:ff48:aa81:800::35eb:1","port":33445,"tcp_ports":[3389,33445],"public_key":"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"built on: Tue Feb 21st 2017, 10:52:30 UTC+3\nplease note: running on TokTox Toxcore!\nmore info on the matter: goo.gl/Gz5KhK \u0026 goo.gl/i2TZJr\n\ntox id for queries and general info: EBD2A7B649ABB10ED9F47E5113F04000F39D46F087CEB62FCCE1069471FD6915256D197F2A97","last_ping":1516822981},{"ipv4":"tox.verdict.gg","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976","maintainer":"Deliran","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Praise The Sun!","last_ping":1516822981},{"ipv4":"d4rk4.ru","ipv6":"-","port":1813,"tcp_ports":[1813],"public_key":"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039","maintainer":"D4rk4","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"TOX ID: 35EDC07AEB18B163E07EE33F6CDDA63969F394FF6A617CEAB22A7EBBEAAAF854C0EDFBD46898","last_ping":1516822981},{"ipv4":"51.254.84.212","ipv6":"2001:41d0:a:1a3b::18","port":33445,"tcp_ports":[3389,33445],"public_key":"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D","maintainer":"a68366","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Since 26.12.2015","last_ping":1516822981},{"ipv4":"88.99.133.52","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211","maintainer":"Skey","location":"FR","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"92.54.84.70","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802","maintainer":"t3mp","location":"RU","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tox.uplinklabs.net","ipv6":"tox.uplinklabs.net","port":33445,"tcp_ports":[3389,33445],"public_key":"1A56EA3EDF5DF4C0AEABBF3C2E4E603890F87E983CAC8A0D532A335F2C6E3E1F","maintainer":"AbacusAvenger","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"i don't know what this is for","last_ping":1516822981},{"ipv4":"toxnode.nek0.net","ipv6":"toxnode.nek0.net","port":33445,"tcp_ports":[3389,33445],"public_key":"20965721D32CE50C3E837DD75B33908B33037E6225110BFF209277AEAF3F9639","maintainer":"Phsm","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"95.215.44.78","ipv6":"2a02:7aa0:1619::c6fe:d0cb","port":33445,"tcp_ports":[33445,3389],"public_key":"672DBE27B4ADB9D5FB105A6BB648B2F8FDB89B3323486A7A21968316E012023C","maintainer":"HooinKyoma","location":"SE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Thanx to Hooin Kyoma","last_ping":1516822981},{"ipv4":"163.172.136.118","ipv6":"2001:bc8:4400:2100::1c:50f","port":33445,"tcp_ports":[33445,3389],"public_key":"2C289F9F37C20D09DA83565588BF496FAB3764853FA38141817A72E3F18ACA0B","maintainer":"LittleVulpix","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"LittleTox - your friendly neighbourhood tox node!","last_ping":1516822981},{"ipv4":"sorunome.de","ipv6":"sorunome.de","port":33445,"tcp_ports":[3389,33445],"public_key":"02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46","maintainer":"Sorunome","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Keep calm and pony on","last_ping":1516822981},{"ipv4":"37.97.185.116","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E59A0E71ADA20D35BD1B0957059D7EF7E7792B3D680AE25C6F4DBBA09114D165","maintainer":"Yani","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Yani's node of pleasure and leisure","last_ping":1516822981},{"ipv4":"80.87.193.193","ipv6":"2a01:230:2:6::46a8","port":33445,"tcp_ports":[3389,33445],"public_key":"B38255EE4B054924F6D79A5E6E5889EC94B6ADF6FE9906F97A3D01E3D083223A","maintainer":"linxon","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Tox DHT node by Linxon. Author ToxID: EC774ED05A7E71EEE2EBA939A27CD4FF403D7D79E1E685CFD0394B1770498217C6107E4D3C26","last_ping":1516822981},{"ipv4":"initramfs.io","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25","maintainer":"initramfs","location":"TW","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"initramfs' Tox DHT Node","last_ping":1516822981},{"ipv4":"hibiki.eve.moe","ipv6":"hibiki.eve.moe","port":33445,"tcp_ports":[33445],"public_key":"D3EB45181B343C2C222A5BCF72B760638E15ED87904625AAD351C594EEFAE03E","maintainer":"EveNeko","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd@hibiki.eve.moe","last_ping":1516822981},{"ipv4":"tox.deadteam.org","ipv6":"tox.deadteam.org","port":33445,"tcp_ports":[33445],"public_key":"C7D284129E83877D63591F14B3F658D77FF9BA9BA7293AEB2BDFBFE1A803AF47","maintainer":"DeadTeam","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Vive le TOX","last_ping":1516822981},{"ipv4":"46.229.52.198","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307","maintainer":"Stranger","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Freedom to parrots!","last_ping":1516822981},{"ipv4":"node.tox.ngc.network","ipv6":"node.tox.ngc.network","port":33445,"tcp_ports":[3389,33445],"public_key":"A856243058D1DE633379508ADCAFCF944E40E1672FF402750EF712E30C42012A","maintainer":"Nolz","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Unlike Others","last_ping":1516822981},{"ipv4":"149.56.140.5","ipv6":"2607:5300:0201:3100:0000:0000:0000:3ec2","port":33445,"tcp_ports":[3389,33445],"public_key":"7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C","maintainer":"velusip","location":"CA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Jera","last_ping":1516822981},{"ipv4":"185.14.30.213","ipv6":"2a00:1ca8:a7::e8b","port":443,"tcp_ports":[33445,3389,443],"public_key":"2555763C8C460495B14157D234DD56B86300A2395554BCAE4621AC345B8C1B1B","maintainer":"dvor","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Just another tox node.","last_ping":1516822981},{"ipv4":"tox.natalenko.name","ipv6":"tox.natalenko.name","port":33445,"tcp_ports":[33445],"public_key":"1CB6EBFD9D85448FA70D3CAE1220B76BF6FCE911B46ACDCF88054C190589650B","maintainer":"post-factum","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"136.243.141.187","ipv6":"2a01:4f8:212:2459::a:1337","port":443,"tcp_ports":[33445,3389,443],"public_key":"6EE1FADE9F55CC7938234CC07C864081FC606D8FE7B751EDA217F268F1078A39","maintainer":"CeBe","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"uTox is the future! - maintained by CeBe - contact: tox@cebe.cc - tox: 7F50119368DC8FD3B1ECAF5D18E3F8854F0484CEC5BBF625D420B8E38638733C02486E387AF8","last_ping":1516822981},{"ipv4":"tox.abilinski.com","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"0E9D7FEE2AA4B42A4C18FE81C038E32FFD8D907AAA7896F05AA76C8D31A20065","maintainer":"flobe","location":"CA","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"m.loskiq.it","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"88124F3C18C6CFA8778B7679B7329A333616BD27A4DFB562D476681315CF143D","maintainer":"loskiq","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/loskiq","last_ping":1516822981},{"ipv4":"192.99.232.158","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"7B6CB208C811DEA8782711CE0CAD456AAC0C7B165A0498A1AA7010D2F2EC996C","maintainer":"basiljose","location":"CA","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tmux.ru","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"7467AFA626D3246343170B309BA5BDC975DF3924FC9D7A5917FBFA9F5CD5CD38","maintainer":"nrn","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/nyoroon","last_ping":1516822981},{"ipv4":"37.48.122.22","ipv6":"2001:1af8:4700:a115:6::b","port":33445,"tcp_ports":[33445,3389],"public_key":"1B5A8AB25FFFB66620A531C4646B47F0F32B74C547B30AF8BD8266CA50A3AB59","maintainer":"Pokemon","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Those who would give up essential Liberty, to purchase a little temporary Safety, deserve neither Liberty nor Safety","last_ping":1516822981},{"ipv4":"tox.novg.net","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"D527E5847F8330D628DAB1814F0A422F6DC9D0A300E6C357634EE2DA88C35463","maintainer":"blind_oracle","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"t0x-node1.weba.ru","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"5A59705F86B9FC0671FDF72ED9BB5E55015FF20B349985543DDD4B0656CA1C63","maintainer":"Amin","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"T0X-Node #1","last_ping":1516822981},{"ipv4":"109.195.99.39","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"EF937F61B4979B60BBF306752D8F32029A2A05CD2615B2E9FBFFEADD8E7D5032","maintainer":"NaCl","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"NaCl node respond","last_ping":1516822981},{"ipv4":"79.140.30.52","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"FFAC871E85B1E1487F87AE7C76726AE0E60318A85F6A1669E04C47EB8DC7C72D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"94.41.167.70","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E519B2C1098999B60190012C7B53E8C43A73C535721036CD9DEC7CCA06741A7D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"104.223.122.204","ipv6":"-","port":33445,"tcp_ports":[3389],"public_key":"3925752E43BF2F8EB4E12B0E9414311064FF2D76707DC7D5D2CCB43F75081F6B","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"rmnc_third_node","last_ping":1516822981},{"ipv4":"77.55.211.53","ipv6":"-","port":53,"tcp_ports":[443,33445,3389],"public_key":"B9D109CC820C69A5D97A4A1A15708107C6BA85C13BC6188CC809D374AFF18E63","maintainer":"GDR!","location":"PL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"GDR!'s tox-bootstrapd https://gdr.name/","last_ping":1516822922},{"ipv4":"boseburo.ddns.net","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"AF3FC9FC3D121E82E362B4FA84A53E63F58C11C2BA61D988855289B8CABC9B18","maintainer":"LowEel","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"This is the Bose Buro bootstrap daemon","last_ping":1516822981},{"ipv4":"46.101.197.175","ipv6":"2a03:b0c0:3:d0::ac:5001","port":443,"tcp_ports":[443,33445,3389],"public_key":"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707","maintainer":"clearmartin","location":"DE","status_udp":false,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"104.233.104.126","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414","maintainer":"wildermesser","location":"CA","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"195.93.190.6","ipv6":"2a01:d0:ffff:a8a::2","port":33445,"tcp_ports":[],"public_key":"FB4CE0DDEFEED45F26917053E5D24BDDA0FA0A3D83A672A9DA2375928B37023D","maintainer":"strngr","location":"UA","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"tox node at strngr.name","last_ping":1516816803},{"ipv4":"193.124.186.205","ipv6":"2a02:f680:1:1100::542a","port":5228,"tcp_ports":[],"public_key":"9906D65F2A4751068A59D30505C5FC8AE1A95E0843AE9372EAFA3BAB6AC16C2C","maintainer":"Cactus","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"85.21.144.224","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"8F738BBC8FA9394670BCAB146C67A507B9907C8E564E28C2B59BEBB2FF68711B","maintainer":"himura","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"37.187.122.30","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"BEB71F97ED9C99C04B8489BB75579EB4DC6AB6F441B603D63533122F1858B51D","maintainer":"dolohow","location":"FR","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"#stay frosty 8218DB335926393789859EDF2D79AC4CC805ADF73472D08165FEA51555502A58AE84FCE7C3D4","last_ping":1515853621},{"ipv4":"95.215.46.114","ipv6":"2a02:7aa0:1619::bdbd:17b8","port":33445,"tcp_ports":[],"public_key":"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23","maintainer":"isotoxin","location":"SE","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"tox.dumalogiya.ru","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"2DAE6EB8C16131761A675D7C723F618FBA9D29DD8B4E0A39E7E3E8D7055EF113","maintainer":"mikhailnov","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0}]} \ No newline at end of file diff --git a/toxygen/settings.py b/toxygen/settings.py index 21c8bec..431688a 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -145,7 +145,8 @@ class Settings(dict, Singleton): 'close_to_tray': False, 'font': 'Times New Roman', 'update': 1, - 'group_notifications': True + 'group_notifications': True, + 'download_nodes_list': False } @staticmethod From 62c5df751dd10e0d6d8298c5c29cbbaf641c2a19 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 24 Jan 2018 23:42:03 +0300 Subject: [PATCH 076/163] fix and translations update --- README.md | 1 + toxygen/callbacks.py | 2 + toxygen/mainscreen_widgets.py | 2 +- toxygen/translations/en_GB.ts | 189 ++++++++++++++++---------------- toxygen/translations/fr_FR.ts | 194 +++++++++++++++++---------------- toxygen/translations/ru_RU.ts | 197 ++++++++++++++++++---------------- toxygen/translations/uk_UA.ts | 197 +++++++++++++++++----------------- 7 files changed, 407 insertions(+), 375 deletions(-) diff --git a/README.md b/README.md index 6ded3c9..914fdfe 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu - File transfers - Audio calls - Video calls +- Group chats - Plugins support - Desktop sharing - Chat history diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index fd25d97..b59d17c 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -33,6 +33,7 @@ class Invoker(QtCore.QObject): event.fn(*event.args, **event.kwargs) return True + _invoker = Invoker() @@ -66,6 +67,7 @@ class FileTransfersThread(threading.Thread): except Exception as ex: util.log('Exception in _thread: ' + str(ex)) + _thread = FileTransfersThread() diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index 700f16b..9955771 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -318,7 +318,7 @@ class WelcomeScreen(CenteredWidget): 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') elif num == 7: text = QtWidgets.QApplication.translate('WelcomeScreen', - 'New in Toxygen 0.3.0:
Video calls
Python3.6 support
Migration to PyQt5') + 'New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes') elif num == 8: text = QtWidgets.QApplication.translate('WelcomeScreen', 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') diff --git a/toxygen/translations/en_GB.ts b/toxygen/translations/en_GB.ts index fa975db..7186005 100644 --- a/toxygen/translations/en_GB.ts +++ b/toxygen/translations/en_GB.ts @@ -26,7 +26,7 @@ Callback - + File from @@ -39,27 +39,27 @@ Send request - + IPv6 IPv6 - + UDP UDP - + Proxy Proxy - + IP: IP: - + Port: Port: @@ -69,17 +69,22 @@ Online contacts - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak + + + Download nodes list from tox.chat + + MainWindow @@ -129,13 +134,13 @@ can produce IP leak - + User {} wants to add you to contact list. Message: {} - + Friend request @@ -170,7 +175,7 @@ can produce IP leak - + Enter new alias for friend {} or leave empty to use friend's name: Enter new alias for friend {} or leave empty to use friend's name: @@ -185,7 +190,7 @@ can produce IP leak Find contact - + Friend added Friend added @@ -197,7 +202,7 @@ Version: Version: - + Friend added without sending friend request Friend added without sending friend request @@ -307,7 +312,7 @@ Version: - + User {} is now known as {} @@ -357,12 +362,12 @@ Version: - + Choose folder with sticker pack - + Choose folder with smiley pack @@ -452,22 +457,22 @@ Version: - + User {} invites you to group chat. Accept? - + Group chat invite - + {} users in chat - + Enter new title for group {}: @@ -518,12 +523,12 @@ Version: NetworkSettings - + Network settings Network settings - + Restart TOX core Restart Tox core @@ -582,42 +587,42 @@ Version: PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + No description available - + Disable plugin - + Enable plugin - + No plugins found - + Error @@ -819,24 +824,24 @@ Version: - New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 + New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes audioSettingsForm - + Audio settings Audio settings - + Input device: Input device: - + Output device: Output device: @@ -844,32 +849,32 @@ Version: incoming_call - + Incoming video call Incoming video call - + Incoming audio call Incoming audio call - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -877,82 +882,82 @@ Version: interfaceForm - + Interface settings - + Theme: - + Language: - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Restart app to apply settings - + Restart required - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Show avatars in chat - + Close to tray - + Select font @@ -1033,27 +1038,27 @@ Version: notificationsForm - + Notification settings - + Enable notifications - + Enable call's sound - + Enable sound notifications - + Notify about all messages in groups @@ -1079,72 +1084,72 @@ Version: privacySettings - + Privacy settings - + Save chat history - + Allow file auto accept - + Send typing notifications - + Auto accept default path: - + Change - + Allow inlines - + Chat history - + History will be cleaned! Continue? - + Blocked users: Blocked users: - + Unblock Unblock - + Block user Block user - + Add to friend list Add to friend list - + Do you want to add this user to friend list? Do you want to add this user to friend list? @@ -1154,12 +1159,12 @@ Version: Block by TOX ID: - + Block by public key: - + Save unsent messages only @@ -1200,57 +1205,57 @@ Version: updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date @@ -1258,22 +1263,22 @@ Version: videoSettingsForm - + Video settings - + Device: - + Desktop - + Select region diff --git a/toxygen/translations/fr_FR.ts b/toxygen/translations/fr_FR.ts index 2beba5b..1931a26 100644 --- a/toxygen/translations/fr_FR.ts +++ b/toxygen/translations/fr_FR.ts @@ -26,7 +26,7 @@ Callback - + File from Fichier de @@ -39,27 +39,27 @@ Envoyer une demande - + IPv6 IPv6 - + UDP UDP - + Proxy Proxy - + IP: IP : - + Port: Port : @@ -69,12 +69,12 @@ Contacts connectés - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -82,6 +82,11 @@ can produce IP leak Utiliser un proxy avec UDP peut entrainer une fuite d'IP + + + Download nodes list from tox.chat + + MainWindow @@ -131,13 +136,13 @@ peut entrainer une fuite d'IP À propos de toxygen - + User {} wants to add you to contact list. Message: {} L'Utilisateur {} veut vous ajouter à sa liste de contacts. Message : {} - + Friend request Demande de contact @@ -182,7 +187,7 @@ peut entrainer une fuite d'IP Retirer ce contact - + Enter new alias for friend {} or leave empty to use friend's name: Entrez un nouvel alias pour le contact {} ou laissez vide pour garder son nom de base : @@ -197,7 +202,7 @@ peut entrainer une fuite d'IP Trouver le contact - + Friend added Contact ajouté @@ -209,7 +214,7 @@ Version: Version : - + Friend added without sending friend request Contact ajouté sans envoi de demande @@ -319,7 +324,7 @@ Version : Sauvegarder - + User {} is now known as {} L'utilisateur {} s'appelle désormais {} @@ -369,12 +374,12 @@ Version : Un profil ayant ce nom existe déjà - + Choose folder with sticker pack Sélectionner le dossier contenant le pack de stickers - + Choose folder with smiley pack Sélectionner le dossier contenant le pack de smileys @@ -464,22 +469,22 @@ Version : Vidéo - + User {} invites you to group chat. Accept? - + Group chat invite - + {} users in chat - + Enter new title for group {}: @@ -530,12 +535,12 @@ Version : NetworkSettings - + Network settings Paramètres réseaux - + Restart TOX core Relancer le noyau Tox @@ -594,42 +599,42 @@ Version : PluginsForm - + Plugins Plugins - + Open selected plugin Ouvrir le plugin sélectionné - + No GUI found for this plugin Pas d'interface pour ce plugin - + No description available Pas de description - + Disable plugin Désactiver le plugin - + Enable plugin Activer le plugin - + No plugins found Pas de plugin trouvé - + Error Erreur @@ -832,23 +837,28 @@ Version : New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 - Nouveau dans Toxygen 0.3.0 : <br>Appels vidéo<br>Support de Python3.6<br>Migration vers PyQt5 + Nouveau dans Toxygen 0.3.0 : <br>Appels vidéo<br>Support de Python3.6<br>Migration vers PyQt5 + + + + New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes + audioSettingsForm - + Audio settings Paramètres audio - + Input device: Péripherique d'entrée : - + Output device: Péripherique de sortie : @@ -856,32 +866,32 @@ Version : incoming_call - + Incoming video call Appel vidéo entrant - + Incoming audio call Appel audio entrant - + Outgoing video call Appel vidéo sortant - + Outgoing audio call Appel audio sortant - + Call declined Appel refusé - + Call finished Appel terminé @@ -889,82 +899,82 @@ Version : interfaceForm - + Interface settings Paramètres de l'interface - + Theme: Thème : - + Language: Langue : - + Smileys Smileys - + Smiley pack: Pack de smileys : - + Mirror mode Mode miroir - + Messages font size: Taille des messages : - + Restart app to apply settings Redémarrer toxygen pour appliquer les paramètres - + Restart required Redémarrage nécessaire - + Select unread messages notification color Sélectionner la couleur des messages non-lus - + Compact contact list Liste de contacts compacte - + Import smiley pack Importer un pack de smileys - + Import sticker pack Importer un pack de stickers - + Show avatars in chat Montrer les avatars dans la conversation - + Close to tray Réduire - + Select font Sélectionner la police @@ -1050,27 +1060,27 @@ Version : notificationsForm - + Notification settings Paramètres de notification - + Enable notifications Activer les notifications - + Enable call's sound Activer les sons d'appel - + Enable sound notifications Activer les sons de notifications - + Notify about all messages in groups @@ -1096,72 +1106,72 @@ Version : privacySettings - + Privacy settings Paramètres de confidentialité - + Save chat history Sauvegarder l'historique de conversation - + Allow file auto accept Autoriser les fichier automatiquement - + Send typing notifications Informer de la frappe - + Auto accept default path: Chemin par défaut des fichiers acceptés automatiquement : - + Change Modifier - + Allow inlines Activer l'affichage integré - + Chat history Historique de conversation - + History will be cleaned! Continue? L'Historique va être vidé ! Confirmer ? - + Blocked users: Utilisateurs bloqués : - + Unblock Débloquer - + Block user Bloquer l'utilisateur - + Add to friend list Ajouter à la liste de contacts - + Do you want to add this user to friend list? Voulez vous aajouter cet utilisateur à votre liste de contacts ? @@ -1171,12 +1181,12 @@ Version : Bloquer l'ID TOX : - + Block by public key: Bloquer par clé publique : - + Save unsent messages only Sauvegarder les messages non envoyés uniquement @@ -1217,57 +1227,57 @@ Version : updateSettingsForm - + Update settings Paramètres de mise à jour - + Select update mode: Sélectionner le mode de mise à jour : - + Update Toxygen Mettre à jour toxygen - + Disabled Désactivé - + Manual Manuel - + Auto Automatique - + Error Erreur - + Problems with internet connection Il y à des problèmes avec votre connexion internet - + Updater not found Updater non trouvé - + No updates found Pas de mises à jour trouvés - + Toxygen is up to date Toxygen est à jour @@ -1275,22 +1285,22 @@ Version : videoSettingsForm - + Video settings Paramètres vidéo - + Device: Périphérique : - + Desktop - + Select region diff --git a/toxygen/translations/ru_RU.ts b/toxygen/translations/ru_RU.ts index f8220fe..8d6c63c 100644 --- a/toxygen/translations/ru_RU.ts +++ b/toxygen/translations/ru_RU.ts @@ -1,6 +1,5 @@ - - + AddContact @@ -27,7 +26,7 @@ Callback - + File from Файл от @@ -40,27 +39,27 @@ Отправить запрос - + IPv6 IPv6 - + UDP UDP - + Proxy Прокси - + IP: IP: - + Port: Порт: @@ -70,12 +69,12 @@ Контакты в сети - + HTTP HTTP - + WARNING: using proxy with enabled UDP can produce IP leak @@ -83,6 +82,11 @@ can produce IP leak использование прокси с UDP может привести к утечке IP + + + Download nodes list from tox.chat + + MainWindow @@ -132,14 +136,14 @@ can produce IP leak О программе - + User {} wants to add you to contact list. Message: {} Пользователь {} хочет добавить Вас в список контактов. Сообщение: {} - + Friend request Запрос на добавление в друзья @@ -179,7 +183,7 @@ can produce IP leak Удалить друга - + Enter new alias for friend {} or leave empty to use friend's name: Введите новый псевдоним для друга {} или оставьте пустым для использования его имени: @@ -194,7 +198,7 @@ can produce IP leak Найти контакт - + Friend added Друг добавлен @@ -205,7 +209,7 @@ Version: Toxygen - клиент для мессенджера Tox, написанный на Python. Версия: - + Friend added without sending friend request Друг добавлен без отправки запроса на добавление в друзья @@ -315,7 +319,7 @@ Version: Сохранить - + User {} is now known as {} Пользователь {} сейчас известен как {} @@ -365,12 +369,12 @@ Version: Профиль с данным именем уже существует - + Choose folder with sticker pack Выберите папку в паком стикеров - + Choose folder with smiley pack Выберите папку с паком смайлов @@ -460,22 +464,22 @@ Version: Видео - + User {} invites you to group chat. Accept? Пользователь {} приглашает Вас в групповой чат. Принять приглашение? - + Group chat invite Приглашение в групповой чат - + {} users in chat {} пользователей в чате - + Enter new title for group {}: Введите название для группы {}: @@ -551,12 +555,12 @@ Version: NetworkSettings - + Network settings Настройки сети - + Restart TOX core Перезапустить ядро TOX @@ -615,42 +619,42 @@ Version: PluginsForm - + Plugins Плагины - + Open selected plugin Открыть выбранный плагин - + No GUI found for this plugin GUI для данного плагина не найден - + No description available Описание недоступно - + Disable plugin Отключить плагин - + Enable plugin Включить плагин - + No plugins found Плагины не найдены - + Error Ошибка @@ -903,23 +907,28 @@ Version: New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 - Новое в Toxygen 0.3.0:<br>Видеозвонки<br>Поддержка Python3.6<br>Миграция на PyQt5 + Новое в Toxygen 0.3.0:<br>Видеозвонки<br>Поддержка Python3.6<br>Миграция на PyQt5 + + + + New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes + audioSettingsForm - + Audio settings Настройки аудио - + Input device: Устройство ввода: - + Output device: Устройство вывода: @@ -927,32 +936,32 @@ Version: incoming_call - + Incoming video call Входящий видеозвонок - + Incoming audio call Входящий аудиозвонок - + Outgoing video call Исходящий видеозвонок - + Outgoing audio call Исходящий аудиозвонок - + Call declined Звонок отменен - + Call finished Звонок завершен @@ -960,82 +969,82 @@ Version: interfaceForm - + Interface settings Настройки интерфейса - + Theme: Тема: - + Language: Язык: - + Smileys Смайлики - + Smiley pack: Набор смайликов: - + Mirror mode Зеркальный режим - + Messages font size: Размер шрифта сообщений: - + Restart app to apply settings Для применения настроек необходимо перезапустить приложение - + Restart required Требуется перезапуск - + Select unread messages notification color Цвет уведомления о сообщении - + Compact contact list Компактный список контактов - + Import smiley pack Импортировать смайлы - + Import sticker pack Импортировать стикеры - + Show avatars in chat Показывать аватары в чате - + Close to tray Сворачивать в трей - + Select font Выбрать шрифт @@ -1121,28 +1130,28 @@ Version: notificationsForm - + Notification settings Настройки уведомлений - + Enable notifications Включить уведомления - + Enable call's sound Включить звук звонка - + Enable sound notifications Включить звуковые уведомления - + Notify about all messages in groups Уведомлять обо всех сообщениях в группах @@ -1168,72 +1177,72 @@ Version: privacySettings - + Privacy settings Настройки приватности - + Save chat history Сохранять историю переписки - + Allow file auto accept Разрешить автополучение файлов - + Send typing notifications Посылать уведомления о наборе текста - + Auto accept default path: Путь автоприема файлов: - + Change Изменить - + Allow inlines Разрешать инлайны - + Chat history История чата - + History will be cleaned! Continue? История переписки будет очищена! Продолжить? - + Blocked users: Заблокированные пользователи: - + Unblock Разблокировать - + Block user Заблокировать пользователя - + Add to friend list Добавить в список друзей - + Do you want to add this user to friend list? Добавить этого пользователя в список друзей? @@ -1243,12 +1252,12 @@ Version: Блокировать по TOX ID: - + Block by public key: Блокировать по публичному ключу: - + Save unsent messages only Сохранять только неотправленные сообщения @@ -1289,57 +1298,57 @@ Version: updateSettingsForm - + Update settings Обновить настройки - + Select update mode: Выбрать режим обновлений: - + Update Toxygen Обновить Toxygen - + Disabled Отключены - + Manual Вручную - + Auto Автоматически - + Error Ошибка - + Problems with internet connection Проблемы с соединением - + Updater not found Апдейтер не был найден - + No updates found Обновления не найдены - + Toxygen is up to date Toxygen уже обновлен @@ -1347,22 +1356,22 @@ Version: videoSettingsForm - + Video settings Настройки видео - + Device: Устройство: - + Desktop Рабочий стол - + Select region Выберите область diff --git a/toxygen/translations/uk_UA.ts b/toxygen/translations/uk_UA.ts index fb21766..c36ecf0 100644 --- a/toxygen/translations/uk_UA.ts +++ b/toxygen/translations/uk_UA.ts @@ -26,7 +26,7 @@ Callback - + File from @@ -34,32 +34,32 @@ Form - + IP: IP: - + UDP UDP - + HTTP HTTP - + IPv6 IPv6 - + Port: Порт: - + Proxy Проксі @@ -74,12 +74,17 @@ Відправити запит - + WARNING: using proxy with enabled UDP can produce IP leak + + + Download nodes list from tox.chat + + MainWindow @@ -89,7 +94,7 @@ can produce IP leak Про проґраму - + Friend request Запит дружби @@ -104,7 +109,7 @@ can produce IP leak Звук - + Friend added Друга додано @@ -114,7 +119,7 @@ can produce IP leak Надіслати файл - + User {} wants to add you to contact list. Message: {} Користувач {} хоче додати вас до списку контактів. Повідомлення @@ -168,7 +173,7 @@ Version: Обрати файл - + Enter new alias for friend {} or leave empty to use friend's name: Введіть нове скорочення для друга {} або залишіть порожнім, щоб використовувати його псевдо: @@ -178,7 +183,7 @@ Version: Додати контакт - + Friend added without sending friend request Друга додано без надсилання запиту дружби @@ -243,17 +248,17 @@ Version: - + User {} is now known as {} - + Choose folder with sticker pack - + Choose folder with smiley pack @@ -458,22 +463,22 @@ Version: - + User {} invites you to group chat. Accept? - + Group chat invite - + {} users in chat - + Enter new title for group {}: @@ -524,12 +529,12 @@ Version: NetworkSettings - + Network settings Налаштування мережі - + Restart TOX core Перезапустити ядро Tox @@ -588,42 +593,42 @@ Version: PluginsForm - + Plugins - + Open selected plugin - + No GUI found for this plugin - + Error - + No description available - + Disable plugin - + Enable plugin - + No plugins found @@ -808,11 +813,6 @@ Version: Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later. - - - New in Toxygen 0.3.0:<br>Video calls<br>Python3.6 support<br>Migration to PyQt5 - - Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu @@ -828,21 +828,26 @@ Version: Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam. + + + New in Toxygen 0.4.1:<br>Downloading nodes from tox.chat<br>Bug fixes + + audioSettingsForm - + Output device: Пристрій виводу: - + Audio settings Налаштування звуку - + Input device: Пристрій вводу: @@ -850,32 +855,32 @@ Version: incoming_call - + Incoming video call Вхідний відеодзвінок - + Incoming audio call Вхідний аудіодзвінок - + Outgoing video call - + Outgoing audio call - + Call declined - + Call finished @@ -883,82 +888,82 @@ Version: interfaceForm - + Language: Мова: - + Theme: Тема: - + Interface settings Налаштування зовнішнього вигляду - + Show avatars in chat - + Smileys - + Smiley pack: - + Mirror mode - + Messages font size: - + Select unread messages notification color - + Compact contact list - + Import smiley pack - + Import sticker pack - + Close to tray - + Select font - + Restart app to apply settings - + Restart required @@ -1044,27 +1049,27 @@ Version: notificationsForm - + Enable sound notifications Увімкнути звукові сповіщення - + Enable notifications Увімкнути сповіщення - + Notification settings Налаштування сповіщень - + Enable call's sound Увімкнути звук дзвінка - + Notify about all messages in groups @@ -1090,12 +1095,12 @@ Version: privacySettings - + Privacy settings Налаштування приватності - + Add to friend list Додати до списку друзів @@ -1105,72 +1110,72 @@ Version: Блокувати по TOX ID: - + Blocked users: Блоковані користувачі: - + Change Змінити - + Send typing notifications Надсилати сповіщення про те, що я друкую - + Allow file auto accept Дозволити автоприймання файлів - + Allow inlines Дозволити інлайни - + Save chat history Зберігати журнал бесіди - + Block user Блокувати користувача - + Chat history Журнал бесіди - + Unblock Розблокувати - + History will be cleaned! Continue? Журнал буде очищено! Продовжити? - + Auto accept default path: Шлях за замовчуванням для автоприймання: - + Do you want to add this user to friend list? Ви хочете додати цього користувача у список друзів? - + Block by public key: - + Save unsent messages only @@ -1211,57 +1216,57 @@ Version: updateSettingsForm - + Update settings - + Select update mode: - + Update Toxygen - + Disabled - + Manual - + Auto - + Error - + Problems with internet connection - + Updater not found - + No updates found - + Toxygen is up to date @@ -1269,22 +1274,22 @@ Version: videoSettingsForm - + Video settings - + Device: - + Desktop - + Select region From bb2a857ecf19e7178cc2281c17f46df627b2d0cb Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 26 Jan 2018 18:43:19 +0300 Subject: [PATCH 077/163] use opencv-python module on linux --- docs/install.md | 4 ++-- docs/plugins.md | 2 +- setup.py | 4 ++++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/install.md b/docs/install.md index 3f29b79..00f8188 100644 --- a/docs/install.md +++ b/docs/install.md @@ -17,7 +17,7 @@ Run app using ``toxygen`` command. 2. Install PortAudio: ``sudo apt-get install portaudio19-dev`` 3. For 32-bit Linux install PyQt5: ``sudo apt-get install python3-pyqt5`` -4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo apt-get install python3-opencv`` +4. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python`` 5. Install toxygen: ``sudo pip3 install toxygen`` 6. Run toxygen using ``toxygen`` command. @@ -63,7 +63,7 @@ Optional: install toxygen using setup.py: ``python setup.py install`` 4. Install PyAudio: ``sudo apt-get install portaudio19-dev`` and ``sudo apt-get install python3-pyaudio`` (or ``sudo pip3 install pyaudio``) 5. Install NumPy: ``sudo pip3 install numpy`` -6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo apt-get install python3-opencv`` +6. Install [OpenCV](http://docs.opencv.org/trunk/d7/d9f/tutorial_linux_install.html) or via ``sudo pip3 install opencv-python`` 7. [Download toxygen](https://github.com/toxygen-project/toxygen/archive/master.zip) 8. Unpack archive 9. Run app: diff --git a/docs/plugins.md b/docs/plugins.md index dc8c38d..98fbac8 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -1,6 +1,6 @@ # Plugins -Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.4 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality. +Toxygen is the first [Tox](https://tox.chat/) client with plugins support. Plugin is Python 3.5 - 3.6 module (.py file) and directory with plugin's data which provide some additional functionality. # How to write plugin diff --git a/setup.py b/setup.py index bc06944..746163e 100644 --- a/setup.py +++ b/setup.py @@ -25,6 +25,10 @@ else: import numpy except ImportError: MODULES.append('numpy') + try: + import cv2 + except ImportError: + MODULES.append('opencv-python') class InstallScript(install): From f76a1c0fbe57770ea109b45999311117873238f2 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 27 Jan 2018 19:53:07 +0300 Subject: [PATCH 078/163] manifest.in updated --- MANIFEST.in | 1 + docs/smileys_and_stickers.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index dba3ada..6629fb6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,3 +16,4 @@ include toxygen/styles/*.qss include toxygen/translations/*.qm include toxygen/libs/libtox.dll include toxygen/libs/libsodium.a +include toxygen/nodes.json diff --git a/docs/smileys_and_stickers.md b/docs/smileys_and_stickers.md index 91ade33..8705ba8 100644 --- a/docs/smileys_and_stickers.md +++ b/docs/smileys_and_stickers.md @@ -8,6 +8,6 @@ Animated smileys (.gif) are supported too. # Stickers -Sticker is inline image. If you want to create your own smiley pack, create directory in src/stickers/ and place your stickers there. +Sticker is inline image. If you want to create your own sticker pack, create directory in src/stickers/ and place your stickers there. Users can import smileys and stickers using menu: Settings -> Interface From b428bd54c4199843b2111164b5f40adfc5ec9bcb Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 30 Jan 2018 18:45:55 +0300 Subject: [PATCH 079/163] export history fixed --- toxygen/mainscreen.py | 19 +++++++++++-------- toxygen/util.py | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index c76f19b..e0616b8 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -660,15 +660,18 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): def export_history(self, num, as_text=True): s = self.profile.export_history(num, as_text) - directory = QtWidgets.QFileDialog.getExistingDirectory(None, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + extension = 'txt' if as_text else 'html' + file_name, _ = QtWidgets.QFileDialog.getSaveFileName(None, + QtWidgets.QApplication.translate("MainWindow", + 'Choose file name'), + curr_directory(), + filter=extension, + options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - if directory: - name = 'exported_history_{}.{}'.format(convert_time(time.time()), 'txt' if as_text else 'html') - with open(directory + '/' + name, 'wt') as fl: + if file_name: + if not file_name.endswith('.' + extension): + file_name += '.' + extension + with open(file_name, 'wt') as fl: fl.write(s) def set_alias(self, num): diff --git a/toxygen/util.py b/toxygen/util.py index e8d702a..d862d56 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.4.1' +program_version = '0.4.2' def cached(func): From 762eb89a467c88846b6fbff0144d797880362453 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 30 Jan 2018 20:24:36 +0300 Subject: [PATCH 080/163] clickable links in about dialog --- toxygen/mainscreen.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index e0616b8..7d7b9e7 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -418,8 +418,10 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): import util msgBox = QtWidgets.QMessageBox() msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About")) - text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.\nVersion: ')) - msgBox.setText(text + util.program_version + '\nGitHub: https://github.com/toxygen-project/toxygen/') + text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.
Version: ')) + github = '
Github' + submit_a_bug = '
Submit a bug' + msgBox.setText(text + util.program_version + github + submit_a_bug) msgBox.exec_() def network_settings(self): From 9b5d768819b8eebdb6d5884427866d75365054ff Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 30 Jan 2018 20:36:59 +0300 Subject: [PATCH 081/163] reconnect bug fixed --- toxygen/profile.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index 204419a..2107878 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -870,8 +870,11 @@ class Profile(basecontact.BaseContact, Singleton): Recreate tox instance :param restart: method which calls restart and returns new tox instance """ - for friend in self._contacts: - self.friend_exit(friend.number) + for contact in self._contacts: + if type(contact) is Friend: + self.friend_exit(contact.number) + else: + self.leave_gc(contact.number) self._call.stop() del self._call del self._tox From 98cc288bcdd38a8bdd00d877dbe0319d641dc09d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 5 Feb 2018 23:32:33 +0300 Subject: [PATCH 082/163] fix for ipv6 setting (#59) --- toxygen/profile.py | 2 ++ toxygen/settings.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index 2107878..16d117d 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1438,6 +1438,8 @@ def tox_factory(data=None, settings=None): if settings is None: settings = Settings.get_default_settings() tox_options = Tox.options_new() + # see lines 393-401 + tox_options.contents.ipv6_enabled = settings['ipv6_enabled'] tox_options.contents.udp_enabled = settings['udp_enabled'] tox_options.contents.proxy_type = settings['proxy_type'] tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8') diff --git a/toxygen/settings.py b/toxygen/settings.py index 431688a..101f372 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -104,7 +104,7 @@ class Settings(dict, Singleton): """ return { 'theme': 'dark', - 'ipv6_enabled': True, + 'ipv6_enabled': False, 'udp_enabled': True, 'proxy_type': 0, 'proxy_host': '127.0.0.1', From ce84cc526b372b2580e35d11ef1fcc6f865b6b95 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 8 Apr 2018 11:48:40 +0300 Subject: [PATCH 083/163] drag n drop fixes --- toxygen/mainscreen_widgets.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index 9955771..0d1c26b 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -3,6 +3,7 @@ from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWi from profile import Profile import smileys import util +import platform class MessageArea(QtWidgets.QPlainTextEdit): @@ -70,10 +71,18 @@ class MessageArea(QtWidgets.QPlainTextEdit): def pasteEvent(self, text=None): text = text or QtWidgets.QApplication.clipboard().text() if text.startswith('file://'): - self.parent.profile.send_file(text[7:]) + file_name = self.parse_file_name(text) + self.parent.profile.send_file(file_name) else: self.insertPlainText(text) + def parse_file_name(self, file_name): + import urllib + if file_name.endswith('\r\n'): + file_name = file_name[:-2] + file_name = urllib.parse.unquote(file_name) + return file_name[8 if platform.system() == 'Windows' else 7:] + class ScreenShotWindow(RubberBandWindow): From 74396834cf51cd9582edc3f461bd4ea3e8833047 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 13 Apr 2018 20:12:27 +0300 Subject: [PATCH 084/163] Calls bug fixes --- toxygen/calls.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/toxygen/calls.py b/toxygen/calls.py index 6477c03..3d02110 100644 --- a/toxygen/calls.py +++ b/toxygen/calls.py @@ -63,7 +63,7 @@ class Call: return self._out_video def set_out_video(self, value): - self._in_video = value + self._out_video = value out_video = property(get_out_video, set_out_video) @@ -144,8 +144,8 @@ class AV: call = self._calls[friend_number] call.is_active = True - call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] - call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] + call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] > 0 + call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0 if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio: self.start_audio_thread() @@ -154,7 +154,7 @@ class AV: self.start_video_thread() def is_video_call(self, number): - return self._calls[number].in_video + return number in self and self._calls[number].in_video # ----------------------------------------------------------------------------------------------------------------- # Threads From 3582722faa9623061da29331a8f35fc9d3cd16ee Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 13 Sep 2018 23:23:25 +0300 Subject: [PATCH 085/163] fixed 2 bugs with gc --- toxygen/mainscreen.py | 2 +- toxygen/profile.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 7d7b9e7..93ec72d 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -618,7 +618,7 @@ class MainWindow(QtWidgets.QMainWindow, Singleton): for i in range(len(chats)): name, number = chats[i] item = invite_menu.addAction(name) - item.triggered.connect(lambda: self.invite_friend_to_gc(num, number)) + item.triggered.connect(lambda number=number: self.invite_friend_to_gc(num, number)) plugins_loader = plugin_support.PluginLoader.get_instance() if plugins_loader is not None: diff --git a/toxygen/profile.py b/toxygen/profile.py index 16d117d..ec5b545 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1316,6 +1316,8 @@ class Profile(basecontact.BaseContact, Singleton): return list(groups)[0] def add_gc(self, number): + if number == -1: + return widget = self.create_friend_item() gc = GroupChat('Group chat #' + str(number), '', widget, self._tox, number) self._contacts.append(gc) From 1fa13db4e4343c60d66d78642c82b60eace067e6 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 15 Sep 2018 22:29:30 +0300 Subject: [PATCH 086/163] fixed bug with history loading and qtox screenshots autoaccept --- toxygen/file_transfers.py | 2 +- toxygen/profile.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/toxygen/file_transfers.py b/toxygen/file_transfers.py index 7e0b193..1bbabe5 100644 --- a/toxygen/file_transfers.py +++ b/toxygen/file_transfers.py @@ -29,7 +29,7 @@ ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png') def is_inline(file_name): - return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') + return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') or file_name.startswith('qTox_Image_') class StateSignal(QtCore.QObject): diff --git a/toxygen/profile.py b/toxygen/profile.py index ec5b545..ab73b69 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -567,7 +567,7 @@ class Profile(basecontact.BaseContact, Singleton): data.reverse() data = data[self._messages.count():self._messages.count() + PAGE_SIZE] for message in data: - if message.get_type() <= 1: # text message + if message.get_type() <= 1 or message.get_type() >= 5: # text message data = message.get_data() self.create_message_item(data[0], data[2], From 2aea5df33c4dd5d3155916c8e7ef25efdcf7cfc1 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 15 Sep 2018 22:50:25 +0300 Subject: [PATCH 087/163] proper fix for gc history --- toxygen/profile.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index ab73b69..47f15f2 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -567,7 +567,7 @@ class Profile(basecontact.BaseContact, Singleton): data.reverse() data = data[self._messages.count():self._messages.count() + PAGE_SIZE] for message in data: - if message.get_type() <= 1 or message.get_type() >= 5: # text message + if message.get_type() <= 1: # text message data = message.get_data() self.create_message_item(data[0], data[2], @@ -588,13 +588,16 @@ class Profile(basecontact.BaseContact, Singleton): print('Incoming not started transfer - no info found') elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image self.create_inline_item(message.get_data(), False) - else: # info message + elif message.get_type() < 5: # info message data = message.get_data() self.create_message_item(data[0], data[2], '', data[3], False) + else: + data = message.get_data() + self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) self._load_history = True def export_db(self, directory): From a984b624b5b8894135856c576cc9152cb9c1913d Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 4 Mar 2020 00:34:10 +0300 Subject: [PATCH 088/163] Added ability to paste image --- toxygen/mainscreen_widgets.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py index 0d1c26b..dcbc075 100644 --- a/toxygen/mainscreen_widgets.py +++ b/toxygen/mainscreen_widgets.py @@ -73,8 +73,16 @@ class MessageArea(QtWidgets.QPlainTextEdit): if text.startswith('file://'): file_name = self.parse_file_name(text) self.parent.profile.send_file(file_name) - else: + elif text: self.insertPlainText(text) + else: + image = QtWidgets.QApplication.clipboard().image() + if image is not None: + byte_array = QtCore.QByteArray() + buffer = QtCore.QBuffer(byte_array) + buffer.open(QtCore.QIODevice.WriteOnly) + image.save(buffer, 'PNG') + self.parent.profile.send_screenshot(bytes(byte_array.data())) def parse_file_name(self, file_name): import urllib From 1554d9e53a76490534797e1df53b991953e85249 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 14 Mar 2020 15:33:57 +0300 Subject: [PATCH 089/163] Fixed bug with sending faux offline inlines --- toxygen/profile.py | 7 ++++--- toxygen/util.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index 47f15f2..8cc6c6e 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1099,10 +1099,11 @@ class Profile(basecontact.BaseContact, Singleton): file_name, friend.number, st.get_file_number()) - item = self.create_file_transfer_item(tm) friend.append_message(tm) - st.set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() + if friend_number == self.get_active_number(): + item = self.create_file_transfer_item(tm) + st.set_state_changed_handler(item.update_transfer_state) + self._messages.scrollToBottom() def send_file(self, path, number=None, is_resend=False, file_id=None): """ diff --git a/toxygen/util.py b/toxygen/util.py index d862d56..73959c6 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.4.2' +program_version = '0.4.3' def cached(func): From 5019535c0d4dedf7e5a42f98f9f695d1346bfb3f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 21 Mar 2020 22:05:17 +0300 Subject: [PATCH 090/163] Fixed bug with loading old messages for groups --- toxygen/profile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index 8cc6c6e..cf15e02 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -597,7 +597,7 @@ class Profile(basecontact.BaseContact, Singleton): False) else: data = message.get_data() - self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) + self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3], False) self._load_history = True def export_db(self, directory): From 021ec52e3d9ef3fc812e6567a84f95c249b448fe Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 23 May 2020 18:43:52 +0300 Subject: [PATCH 091/163] Fixed travis build --- .travis.yml | 9 +++++---- tests/tests.py | 15 --------------- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index cfabadd..7fc55ca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,12 +12,13 @@ before_install: - sudo apt-get install -y checkinstall build-essential - sudo apt-get install portaudio19-dev - sudo apt-get install libsecret-1-dev + - sudo apt-get install libconfig-dev libvpx-dev check -qq install: - - pip install sip - - pip install pyqt5 - - pip install pyaudio - - pip install opencv-python + - pip3 install sip + - pip3 install pyaudio + - pip3 install pyqt5==5.14 + - pip3 install opencv-python before_script: # Opus - wget http://downloads.xiph.org/releases/opus/opus-1.0.3.tar.gz diff --git a/tests/tests.py b/tests/tests.py index 27618af..bbb877c 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -36,21 +36,6 @@ class TestProfileHelper: assert os.path.exists(path + 'avatars/') -class TestDNS: - - def test_dns(self): - Settings._instance = Settings.get_default_settings() - bot_id = '56A1ADE4B65B86BCD51CC73E2CD4E542179F47959FE3E0E21B4B0ACDADE51855D34D34D37CB5' - tox_id = tox_dns('groupbot@toxme.io') - assert tox_id == bot_id - - def test_dns2(self): - Settings._instance = Settings.get_default_settings() - bot_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - tox_id = tox_dns('echobot@toxme.io') - assert tox_id == bot_id - - class TestEncryption: def test_encr_decr(self): From 0a54012cf5ee72434b923bcde7d8f1a4e575ce2f Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 24 May 2020 22:01:09 +0300 Subject: [PATCH 092/163] Fixed bug with auto accept if dir doesn't exist --- toxygen/profile.py | 4 +++- toxygen/util.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index cf15e02..a0d8cd4 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -951,7 +951,9 @@ class Profile(basecontact.BaseContact, Singleton): file_number) elif auto: - path = settings['auto_accept_path'] or curr_directory() + path = settings['auto_accept_path'] + if not path or not os.path.exists(path): + path = curr_directory() self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) tm = TransferMessage(MESSAGE_OWNER['FRIEND'], time.time(), diff --git a/toxygen/util.py b/toxygen/util.py index 73959c6..6157775 100644 --- a/toxygen/util.py +++ b/toxygen/util.py @@ -5,7 +5,7 @@ import sys import re -program_version = '0.4.3' +program_version = '0.4.4' def cached(func): From fda07698db7635e08da9259ce9d4b3d5ac3c1ffe Mon Sep 17 00:00:00 2001 From: emdee Date: Tue, 27 Sep 2022 12:36:20 +0000 Subject: [PATCH 093/163] merge in next_gen branch --- toxygen/app.py | 424 +++++++++++++++++++++++++++++++++++ toxygen/smileys/__init__.py | 0 toxygen/smileys/smileys.py | 74 ++++++ toxygen/stickers/__init__.py | 0 toxygen/stickers/stickers.py | 18 ++ 5 files changed, 516 insertions(+) create mode 100644 toxygen/app.py create mode 100644 toxygen/smileys/__init__.py create mode 100644 toxygen/smileys/smileys.py create mode 100644 toxygen/stickers/__init__.py create mode 100644 toxygen/stickers/stickers.py diff --git a/toxygen/app.py b/toxygen/app.py new file mode 100644 index 0000000..a23816d --- /dev/null +++ b/toxygen/app.py @@ -0,0 +1,424 @@ +from middleware import threads +import middleware.callbacks as callbacks +from PyQt5 import QtWidgets, QtGui, QtCore +import ui.password_screen as password_screen +import updater.updater as updater +import os +from middleware.tox_factory import tox_factory +import wrapper.toxencryptsave as tox_encrypt_save +import user_data.toxes +from user_data.settings import Settings +from ui.login_screen import LoginScreen +from user_data.profile_manager import ProfileManager +from plugin_support.plugin_support import PluginLoader +from ui.main_screen import MainWindow +from ui import tray +import utils.ui as util_ui +import utils.util as util +from contacts.profile import Profile +from file_transfers.file_transfers_handler import FileTransfersHandler +from contacts.contact_provider import ContactProvider +from contacts.friend_factory import FriendFactory +from contacts.group_factory import GroupFactory +from contacts.contacts_manager import ContactsManager +from av.calls_manager import CallsManager +from history.database import Database +from ui.widgets_factory import WidgetsFactory +from smileys.smileys import SmileyLoader +from ui.items_factories import MessagesItemsFactory, ContactItemsFactory +from messenger.messenger import Messenger +from network.tox_dns import ToxDns +from history.history import History +from file_transfers.file_transfers_messages_service import FileTransfersMessagesService +from groups.groups_service import GroupsService +from ui.create_profile_screen import CreateProfileScreen +from common.provider import Provider +from contacts.group_peer_factory import GroupPeerFactory +from user_data.backup_service import BackupService +import styles.style # TODO: dynamic loading + + +class App: + + def __init__(self, version, path_to_profile=None, uri=None): + self._version = version + self._app = self._settings = self._profile_manager = self._plugin_loader = self._messenger = None + self._tox = self._ms = self._init = self._main_loop = self._av_loop = None + self._uri = self._toxes = self._tray = self._file_transfer_handler = self._contacts_provider = None + self._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = None + self._group_peer_factory = self._tox_dns = self._backup_service = None + self._group_factory = self._groups_service = self._profile = None + if uri is not None and uri.startswith('tox:'): + self._uri = uri[4:] + self._path = path_to_profile + + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + + def main(self): + """ + Main function of app. loads login screen if needed and starts main screen + """ + self._app = QtWidgets.QApplication([]) + self._load_icon() + + if util.get_platform() == 'Linux': + QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) + + self._load_base_style() + + if not self._select_and_load_profile(): + return + + if self._try_to_update(): + return + + self._load_app_styles() + self._load_app_translations() + + self._create_dependencies() + self._start_threads() + + if self._uri is not None: + self._ms.add_contact(self._uri) + + self._app.lastWindowClosed.connect(self._app.quit) + + self._execute_app() + + self._stop_app() + + # ----------------------------------------------------------------------------------------------------------------- + # App executing + # ----------------------------------------------------------------------------------------------------------------- + + def _execute_app(self): + while True: + try: + self._app.exec_() + except Exception as ex: + util.log('Unhandled exception: ' + str(ex)) + else: + break + + def _stop_app(self): + self._plugin_loader.stop() + self._stop_threads() + self._file_transfer_handler.stop() + self._tray.hide() + self._save_profile() + self._settings.close() + self._kill_toxav() + self._kill_tox() + + # ----------------------------------------------------------------------------------------------------------------- + # App loading + # ----------------------------------------------------------------------------------------------------------------- + + def _load_base_style(self): + with open(util.join_path(util.get_styles_directory(), 'dark_style.qss')) as fl: + style = fl.read() + self._app.setStyleSheet(style) + + def _load_app_styles(self): + # application color scheme + if self._settings['theme'] == 'dark': + return + for theme in self._settings.built_in_themes().keys(): + if self._settings['theme'] != theme: + continue + theme_path = self._settings.built_in_themes()[theme] + file_path = util.join_path(util.get_styles_directory(), theme_path) + with open(file_path) as fl: + style = fl.read() + self._app.setStyleSheet(style) + break + + def _load_login_screen_translations(self): + current_language, supported_languages = self._get_languages() + if current_language not in supported_languages: + return + lang_path = supported_languages[current_language] + translator = QtCore.QTranslator() + translator.load(util.get_translations_directory() + lang_path) + self._app.installTranslator(translator) + self._app.translator = translator + + def _load_icon(self): + icon_file = os.path.join(util.get_images_directory(), 'icon.png') + self._app.setWindowIcon(QtGui.QIcon(icon_file)) + + @staticmethod + def _get_languages(): + current_locale = QtCore.QLocale() + curr_language = current_locale.languageToString(current_locale.language()) + supported_languages = Settings.supported_languages() + + return curr_language, supported_languages + + def _load_app_translations(self): + lang = Settings.supported_languages()[self._settings['language']] + translator = QtCore.QTranslator() + translator.load(os.path.join(util.get_translations_directory(), lang)) + self._app.installTranslator(translator) + self._app.translator = translator + + def _select_and_load_profile(self): + encrypt_save = tox_encrypt_save.ToxEncryptSave() + self._toxes = user_data.toxes.ToxES(encrypt_save) + + if self._path is not None: # toxygen was started with path to profile + self._load_existing_profile(self._path) + else: + auto_profile = Settings.get_auto_profile() + if auto_profile is None: # no default profile + result = self._select_profile() + if result is None: + return False + if result.is_new_profile(): # create new profile + if not self._create_new_profile(result.profile_path): + return False + else: # load existing profile + self._load_existing_profile(result.profile_path) + self._path = result.profile_path + else: # default profile + self._path = auto_profile + self._load_existing_profile(auto_profile) + + if Settings.is_active_profile(self._path): # profile is in use + profile_name = util.get_profile_name_from_path(self._path) + title = util_ui.tr('Profile {}').format(profile_name) + text = util_ui.tr( + 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?') + reply = util_ui.question(text, title) + if not reply: + return False + + self._settings.set_active_profile() + + return True + + # ----------------------------------------------------------------------------------------------------------------- + # Threads + # ----------------------------------------------------------------------------------------------------------------- + + def _start_threads(self, initial_start=True): + # init thread + self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings, initial_start) + self._init.start() + + # starting threads for tox iterate and toxav iterate + self._main_loop = threads.ToxIterateThread(self._tox) + self._main_loop.start() + self._av_loop = threads.ToxAVIterateThread(self._tox.AV) + self._av_loop.start() + + if initial_start: + threads.start_file_transfer_thread() + + def _stop_threads(self, is_app_closing=True): + self._init.stop_thread() + + self._av_loop.stop_thread() + self._main_loop.stop_thread() + + if is_app_closing: + threads.stop_file_transfer_thread() + + # ----------------------------------------------------------------------------------------------------------------- + # Profiles + # ----------------------------------------------------------------------------------------------------------------- + + def _select_profile(self): + self._load_login_screen_translations() + ls = LoginScreen() + profiles = ProfileManager.find_profiles() + ls.update_select(profiles) + ls.show() + self._app.exec_() + + return ls.result + + def _load_existing_profile(self, profile_path): + self._profile_manager = ProfileManager(self._toxes, profile_path) + data = self._profile_manager.open_profile() + if self._toxes.is_data_encrypted(data): + data = self._enter_password(data) + self._settings = Settings(self._toxes, profile_path.replace('.tox', '.json')) + self._tox = self._create_tox(data) + + def _create_new_profile(self, profile_name): + result = self._get_create_profile_screen_result() + if result is None: + return False + if result.save_into_default_folder: + profile_path = util.join_path(Settings.get_default_path(), profile_name + '.tox') + else: + profile_path = util.join_path(util.curr_directory(__file__), profile_name + '.tox') + if os.path.isfile(profile_path): + util_ui.message_box(util_ui.tr('Profile with this name already exists'), + util_ui.tr('Error')) + return False + name = profile_name or 'toxygen_user' + self._tox = tox_factory() + self._tox.self_set_name(name if name else 'Toxygen User') + self._tox.self_set_status_message('Toxing on Toxygen') + self._path = profile_path + if result.password: + self._toxes.set_password(result.password) + self._settings = Settings(self._toxes, self._path.replace('.tox', '.json')) + self._profile_manager = ProfileManager(self._toxes, profile_path) + try: + self._save_profile() + except Exception as ex: + print(ex) + util.log('Profile creation exception: ' + str(ex)) + text = util_ui.tr('Profile saving error! Does Toxygen have permission to write to this directory?') + util_ui.message_box(text, util_ui.tr('Error')) + + return False + current_language, supported_languages = self._get_languages() + if current_language in supported_languages: + self._settings['language'] = current_language + self._settings.save() + + return True + + def _get_create_profile_screen_result(self): + cps = CreateProfileScreen() + cps.show() + self._app.exec_() + + return cps.result + + def _save_profile(self, data=None): + data = data or self._tox.get_savedata() + self._profile_manager.save_profile(data) + + # ----------------------------------------------------------------------------------------------------------------- + # Other private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _enter_password(self, data): + """ + Show password screen + """ + p = password_screen.PasswordScreen(self._toxes, data) + p.show() + self._app.lastWindowClosed.connect(self._app.quit) + self._app.exec_() + if p.result is not None: + return p.result + self._force_exit() + + def _reset(self): + """ + Create new tox instance (new network settings) + :return: tox instance + """ + self._contacts_manager.reset_contacts_statuses() + self._stop_threads(False) + data = self._tox.get_savedata() + self._save_profile(data) + self._kill_toxav() + self._kill_tox() + # create new tox instance + self._tox = self._create_tox(data) + self._start_threads(False) + + tox_savers = [self._friend_factory, self._group_factory, self._plugin_loader, self._contacts_manager, + self._contacts_provider, self._messenger, self._file_transfer_handler, self._groups_service, + self._profile] + for tox_saver in tox_savers: + tox_saver.set_tox(self._tox) + + self._calls_manager.set_toxav(self._tox.AV) + self._contacts_manager.update_friends_numbers() + self._contacts_manager.update_groups_lists() + self._contacts_manager.update_groups_numbers() + + self._init_callbacks() + + def _create_dependencies(self): + self._backup_service = BackupService(self._settings, self._profile_manager) + self._smiley_loader = SmileyLoader(self._settings) + self._tox_dns = ToxDns(self._settings) + self._ms = MainWindow(self._settings, self._tray) + db = Database(self._path.replace('.tox', '.db'), self._toxes) + + contact_items_factory = ContactItemsFactory(self._settings, self._ms) + self._friend_factory = FriendFactory(self._profile_manager, self._settings, + self._tox, db, contact_items_factory) + self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory) + self._group_peer_factory = GroupPeerFactory(self._tox, self._profile_manager, db, contact_items_factory) + self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory, + self._group_peer_factory) + self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset) + self._init_profile() + self._plugin_loader = PluginLoader(self._settings, self) + history = None + messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader, + self._ms, lambda m: history.delete_message(m)) + history = History(self._contacts_provider, db, self._settings, self._ms, messages_items_factory) + self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager, + self._contacts_provider, history, self._tox_dns, + messages_items_factory) + history.set_contacts_manager(self._contacts_manager) + self._calls_manager = CallsManager(self._tox.AV, self._settings, self._ms, self._contacts_manager) + self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager, + self._contacts_provider, messages_items_factory, self._profile, + self._calls_manager) + file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory, + self._profile, self._ms) + self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider, + file_transfers_message_service, self._profile) + messages_items_factory.set_file_transfers_handler(self._file_transfer_handler) + widgets_factory = None + widgets_factory_provider = Provider(lambda: widgets_factory) + self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms, + widgets_factory_provider) + widgets_factory = WidgetsFactory(self._settings, self._profile, self._profile_manager, self._contacts_manager, + self._file_transfer_handler, self._smiley_loader, self._plugin_loader, + self._toxes, self._version, self._groups_service, history, + self._contacts_provider) + self._tray = tray.init_tray(self._profile, self._settings, self._ms, self._toxes) + self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, self._profile, + self._plugin_loader, self._file_transfer_handler, history, self._calls_manager, + self._groups_service, self._toxes) + + self._tray.show() + self._ms.show() + + self._init_callbacks() + + def _try_to_update(self): + updating = updater.start_update_if_needed(self._version, self._settings) + if updating: + self._save_profile() + self._settings.close() + self._kill_toxav() + self._kill_tox() + return updating + + def _create_tox(self, data): + return tox_factory(data, self._settings) + + def _force_exit(self): + raise SystemExit() + + def _init_callbacks(self): + callbacks.init_callbacks(self._tox, self._profile, self._settings, self._plugin_loader, self._contacts_manager, + self._calls_manager, self._file_transfer_handler, self._ms, self._tray, + self._messenger, self._groups_service, self._contacts_provider) + + def _init_profile(self): + if not self._profile.has_avatar(): + self._profile.reset_avatar(self._settings['identicons']) + + def _kill_toxav(self): + self._calls_manager.set_toxav(None) + self._tox.AV.kill() + + def _kill_tox(self): + self._tox.kill() diff --git a/toxygen/smileys/__init__.py b/toxygen/smileys/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/smileys/smileys.py b/toxygen/smileys/smileys.py new file mode 100644 index 0000000..0391856 --- /dev/null +++ b/toxygen/smileys/smileys.py @@ -0,0 +1,74 @@ +from utils import util +import json +import os +from collections import OrderedDict +from PyQt5 import QtCore + + +class SmileyLoader: + """ + Class which loads smileys packs and insert smileys into messages + """ + + def __init__(self, settings): + super().__init__() + self._settings = settings + self._curr_pack = None # current pack name + self._smileys = {} # smileys dict. key - smiley (str), value - path to image (str) + self._list = [] # smileys list without duplicates + self.load_pack() + + def load_pack(self): + """ + Loads smiley pack + """ + pack_name = self._settings['smiley_pack'] + if self._settings['smileys'] and self._curr_pack != pack_name: + self._curr_pack = pack_name + path = util.join_path(self.get_smileys_path(), 'config.json') + try: + with open(path, encoding='utf8') as fl: + self._smileys = json.loads(fl.read()) + fl.seek(0) + tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict) + print('Smiley pack {} loaded'.format(pack_name)) + keys, values, self._list = [], [], [] + for key, value in tmp.items(): + value = util.join_path(self.get_smileys_path(), value) + if value not in values: + keys.append(key) + values.append(value) + self._list = list(zip(keys, values)) + except Exception as ex: + self._smileys = {} + self._list = [] + print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex)) + + def get_smileys_path(self): + return util.join_path(util.get_smileys_directory(), self._curr_pack) if self._curr_pack is not None else None + + @staticmethod + def get_packs_list(): + d = util.get_smileys_directory() + return [x[1] for x in os.walk(d)][0] + + def get_smileys(self): + return list(self._list) + + def add_smileys_to_text(self, text, edit): + """ + Adds smileys to text + :param text: message + :param edit: MessageEdit instance + :return text with smileys + """ + if not self._settings['smileys'] or not len(self._smileys): + return text + arr = text.split(' ') + for i in range(len(arr)): + if arr[i] in self._smileys: + file_name = self._smileys[arr[i]] # image name + arr[i] = ''.format(arr[i], file_name) + if file_name.endswith('.gif'): # animated smiley + edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name) + return ' '.join(arr) diff --git a/toxygen/stickers/__init__.py b/toxygen/stickers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/stickers/stickers.py b/toxygen/stickers/stickers.py new file mode 100644 index 0000000..14142c7 --- /dev/null +++ b/toxygen/stickers/stickers.py @@ -0,0 +1,18 @@ +import os +import utils.util as util + + +def load_stickers(): + """ + :return list of stickers + """ + result = [] + d = util.get_stickers_directory() + keys = [x[1] for x in os.walk(d)][0] + for key in keys: + path = util.join_path(d, key) + files = filter(lambda f: f.endswith('.png'), os.listdir(path)) + files = map(lambda f: util.join_path(path, f), files) + result.extend(files) + + return result From b51ec9bd71eee8b46c5a4d2e17824a870e6374cd Mon Sep 17 00:00:00 2001 From: emdee Date: Tue, 27 Sep 2022 12:38:39 +0000 Subject: [PATCH 094/163] merge in next_gen branch --- build/Dockerfile | 13 + build/build.sh | 33 + toxygen/av/__init__.py | 0 toxygen/av/call.py | 58 + toxygen/av/calls.py | 281 ++ toxygen/av/calls_manager.py | 116 + toxygen/av/screen_sharing.py | 22 + toxygen/bootstrap/__init__.py | 0 toxygen/bootstrap/bootstrap.py | 83 + toxygen/bootstrap/nodes.json | 1 + toxygen/common/__init__.py | 0 toxygen/common/event.py | 26 + toxygen/common/provider.py | 13 + toxygen/common/tox_save.py | 18 + toxygen/contacts/__init__.py | 0 toxygen/contacts/basecontact.py | 180 ++ toxygen/contacts/common.py | 50 + toxygen/contacts/contact.py | 333 +++ toxygen/contacts/contact_menu.py | 229 ++ toxygen/contacts/contact_provider.py | 107 + toxygen/contacts/contacts_manager.py | 575 ++++ toxygen/contacts/friend.py | 74 + toxygen/contacts/friend_factory.py | 44 + toxygen/contacts/group_chat.py | 137 + toxygen/contacts/group_factory.py | 53 + toxygen/contacts/group_peer_contact.py | 20 + toxygen/contacts/group_peer_factory.py | 23 + toxygen/contacts/profile.py | 87 + toxygen/file_transfers/__init__.py | 0 toxygen/file_transfers/file_transfers.py | 351 +++ .../file_transfers/file_transfers_handler.py | 304 ++ .../file_transfers_messages_service.py | 78 + toxygen/groups/__init__.py | 0 toxygen/groups/group_ban.py | 23 + toxygen/groups/group_invite.py | 23 + toxygen/groups/group_peer.py | 70 + toxygen/groups/groups_service.py | 242 ++ toxygen/groups/peers_list.py | 104 + toxygen/history/__init__.py | 0 toxygen/history/database.py | 201 ++ toxygen/history/history.py | 138 + toxygen/history/history_logs_generators.py | 48 + toxygen/messenger/__init__.py | 0 toxygen/messenger/messages.py | 239 ++ toxygen/messenger/messenger.py | 310 ++ toxygen/middleware/__init__.py | 0 toxygen/middleware/callbacks.py | 605 ++++ toxygen/middleware/threads.py | 172 ++ toxygen/middleware/tox_factory.py | 34 + toxygen/network/__init__.py | 0 toxygen/network/tox_dns.py | 65 + toxygen/notifications/__init__.py | 0 toxygen/notifications/sound.py | 54 + toxygen/notifications/tray.py | 22 + toxygen/plugin_support/__init__.py | 0 toxygen/plugin_support/plugin_support.py | 194 ++ toxygen/plugins/plugin_super_class.py | 39 +- toxygen/styles/dark_style.qss | 23 +- toxygen/styles/style.qss | 13 +- toxygen/ui/__init__.py | 0 toxygen/ui/av_widgets.py | 130 + toxygen/ui/contact_items.py | 97 + toxygen/ui/create_profile_screen.py | 52 + toxygen/ui/group_bans_widgets.py | 68 + toxygen/ui/group_invites_widgets.py | 127 + toxygen/ui/group_peers_list.py | 33 + toxygen/ui/group_settings_widgets.py | 77 + toxygen/ui/groups_widgets.py | 123 + toxygen/ui/items_factories.py | 90 + toxygen/ui/login_screen.py | 77 + toxygen/ui/main_screen.py | 718 +++++ toxygen/ui/main_screen_widgets.py | 496 ++++ toxygen/ui/menu.py | 680 +++++ toxygen/ui/messages_widgets.py | 449 +++ toxygen/ui/password_screen.py | 153 + toxygen/ui/peer_screen.py | 111 + toxygen/ui/profile_settings_screen.py | 157 + toxygen/ui/self_peer_screen.py | 66 + toxygen/ui/tray.py | 111 + toxygen/ui/widgets.py | 197 ++ toxygen/ui/widgets_factory.py | 97 + toxygen/updater/__init__.py | 0 toxygen/updater/updater.py | 124 + toxygen/user_data/__init__.py | 0 toxygen/user_data/backup_service.py | 40 + toxygen/user_data/profile_manager.py | 90 + toxygen/user_data/settings.py | 244 ++ toxygen/user_data/toxes.py | 24 + toxygen/utils/__init__.py | 0 toxygen/utils/ui.py | 54 + toxygen/utils/util.py | 170 ++ toxygen/wrapper/__init__.py | 0 toxygen/wrapper/libtox.py | 61 + toxygen/wrapper/tox.py | 2532 +++++++++++++++++ toxygen/wrapper/toxav.py | 363 +++ toxygen/wrapper/toxav_enums.py | 131 + toxygen/wrapper/toxcore_enums_and_consts.py | 954 +++++++ toxygen/wrapper/toxencryptsave.py | 74 + .../toxencryptsave_enums_and_consts.py | 29 + 99 files changed, 14895 insertions(+), 32 deletions(-) create mode 100644 build/Dockerfile create mode 100644 build/build.sh create mode 100644 toxygen/av/__init__.py create mode 100644 toxygen/av/call.py create mode 100644 toxygen/av/calls.py create mode 100644 toxygen/av/calls_manager.py create mode 100644 toxygen/av/screen_sharing.py create mode 100644 toxygen/bootstrap/__init__.py create mode 100644 toxygen/bootstrap/bootstrap.py create mode 100644 toxygen/bootstrap/nodes.json create mode 100644 toxygen/common/__init__.py create mode 100644 toxygen/common/event.py create mode 100644 toxygen/common/provider.py create mode 100644 toxygen/common/tox_save.py create mode 100644 toxygen/contacts/__init__.py create mode 100644 toxygen/contacts/basecontact.py create mode 100644 toxygen/contacts/common.py create mode 100644 toxygen/contacts/contact.py create mode 100644 toxygen/contacts/contact_menu.py create mode 100644 toxygen/contacts/contact_provider.py create mode 100644 toxygen/contacts/contacts_manager.py create mode 100644 toxygen/contacts/friend.py create mode 100644 toxygen/contacts/friend_factory.py create mode 100644 toxygen/contacts/group_chat.py create mode 100644 toxygen/contacts/group_factory.py create mode 100644 toxygen/contacts/group_peer_contact.py create mode 100644 toxygen/contacts/group_peer_factory.py create mode 100644 toxygen/contacts/profile.py create mode 100644 toxygen/file_transfers/__init__.py create mode 100644 toxygen/file_transfers/file_transfers.py create mode 100644 toxygen/file_transfers/file_transfers_handler.py create mode 100644 toxygen/file_transfers/file_transfers_messages_service.py create mode 100644 toxygen/groups/__init__.py create mode 100644 toxygen/groups/group_ban.py create mode 100644 toxygen/groups/group_invite.py create mode 100644 toxygen/groups/group_peer.py create mode 100644 toxygen/groups/groups_service.py create mode 100644 toxygen/groups/peers_list.py create mode 100644 toxygen/history/__init__.py create mode 100644 toxygen/history/database.py create mode 100644 toxygen/history/history.py create mode 100644 toxygen/history/history_logs_generators.py create mode 100644 toxygen/messenger/__init__.py create mode 100644 toxygen/messenger/messages.py create mode 100644 toxygen/messenger/messenger.py create mode 100644 toxygen/middleware/__init__.py create mode 100644 toxygen/middleware/callbacks.py create mode 100644 toxygen/middleware/threads.py create mode 100644 toxygen/middleware/tox_factory.py create mode 100644 toxygen/network/__init__.py create mode 100644 toxygen/network/tox_dns.py create mode 100644 toxygen/notifications/__init__.py create mode 100644 toxygen/notifications/sound.py create mode 100644 toxygen/notifications/tray.py create mode 100644 toxygen/plugin_support/__init__.py create mode 100644 toxygen/plugin_support/plugin_support.py create mode 100644 toxygen/ui/__init__.py create mode 100644 toxygen/ui/av_widgets.py create mode 100644 toxygen/ui/contact_items.py create mode 100644 toxygen/ui/create_profile_screen.py create mode 100644 toxygen/ui/group_bans_widgets.py create mode 100644 toxygen/ui/group_invites_widgets.py create mode 100644 toxygen/ui/group_peers_list.py create mode 100644 toxygen/ui/group_settings_widgets.py create mode 100644 toxygen/ui/groups_widgets.py create mode 100644 toxygen/ui/items_factories.py create mode 100644 toxygen/ui/login_screen.py create mode 100644 toxygen/ui/main_screen.py create mode 100644 toxygen/ui/main_screen_widgets.py create mode 100644 toxygen/ui/menu.py create mode 100644 toxygen/ui/messages_widgets.py create mode 100644 toxygen/ui/password_screen.py create mode 100644 toxygen/ui/peer_screen.py create mode 100644 toxygen/ui/profile_settings_screen.py create mode 100644 toxygen/ui/self_peer_screen.py create mode 100644 toxygen/ui/tray.py create mode 100644 toxygen/ui/widgets.py create mode 100644 toxygen/ui/widgets_factory.py create mode 100644 toxygen/updater/__init__.py create mode 100644 toxygen/updater/updater.py create mode 100644 toxygen/user_data/__init__.py create mode 100644 toxygen/user_data/backup_service.py create mode 100644 toxygen/user_data/profile_manager.py create mode 100644 toxygen/user_data/settings.py create mode 100644 toxygen/user_data/toxes.py create mode 100644 toxygen/utils/__init__.py create mode 100644 toxygen/utils/ui.py create mode 100644 toxygen/utils/util.py create mode 100644 toxygen/wrapper/__init__.py create mode 100644 toxygen/wrapper/libtox.py create mode 100644 toxygen/wrapper/tox.py create mode 100644 toxygen/wrapper/toxav.py create mode 100644 toxygen/wrapper/toxav_enums.py create mode 100644 toxygen/wrapper/toxcore_enums_and_consts.py create mode 100644 toxygen/wrapper/toxencryptsave.py create mode 100644 toxygen/wrapper/toxencryptsave_enums_and_consts.py diff --git a/build/Dockerfile b/build/Dockerfile new file mode 100644 index 0000000..0b45358 --- /dev/null +++ b/build/Dockerfile @@ -0,0 +1,13 @@ +FROM ubuntu:16.04 + +RUN apt-get update && \ +apt-get install build-essential libtool autotools-dev automake checkinstall cmake check git yasm libsodium-dev libopus-dev libvpx-dev pkg-config -y && \ +git clone https://github.com/ingvar1995/toxcore.git --branch=ngc_rebase && \ +cd toxcore && mkdir _build && cd _build && \ +cmake .. && make && make install + +RUN apt-get install portaudio19-dev python3-pyqt5 python3-pyaudio python3-pip -y && \ +pip3 install numpy pydenticon opencv-python pyinstaller + +RUN useradd -ms /bin/bash toxygen +USER toxygen diff --git a/build/build.sh b/build/build.sh new file mode 100644 index 0000000..fb6c4b2 --- /dev/null +++ b/build/build.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +cd ~ +git clone https://github.com/toxygen-project/toxygen.git --branch=next_gen +cd toxygen/toxygen + +pyinstaller --windowed --icon=images/icon.ico main.py + +cp -r styles dist/main/ +find . -type f ! -name '*.qss' -delete +cp -r plugins dist/main/ +mkdir -p dist/main/ui/views +cp -r ui/views dist/main/ui/ +cp -r sounds dist/main/ +cp -r smileys dist/main/ +cp -r stickers dist/main/ +cp -r bootstrap dist/main/ +find . -type f ! -name '*.json' -delete +cp -r images dist/main/ +cp -r translations dist/main/ +find . -name "*.ts" -type f -delete + +cd dist +mv main toxygen +cd toxygen +mv main toxygen +wget -O updater https://github.com/toxygen-project/toxygen_updater/releases/download/v0.1/toxygen_updater_linux_64 +echo "[Paths]" >> qt.conf +echo "Prefix = PyQt5/Qt" >> qt.conf +cd .. + +tar -zcvf toxygen_linux_64.tar.gz toxygen > /dev/null +rm -rf toxygen diff --git a/toxygen/av/__init__.py b/toxygen/av/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/av/call.py b/toxygen/av/call.py new file mode 100644 index 0000000..d3e023b --- /dev/null +++ b/toxygen/av/call.py @@ -0,0 +1,58 @@ + + +class Call: + + def __init__(self, out_audio, out_video, in_audio=False, in_video=False): + self._in_audio = in_audio + self._in_video = in_video + self._out_audio = out_audio + self._out_video = out_video + self._is_active = False + + def get_is_active(self): + return self._is_active + + def set_is_active(self, value): + self._is_active = value + + is_active = property(get_is_active, set_is_active) + + # ----------------------------------------------------------------------------------------------------------------- + # Audio + # ----------------------------------------------------------------------------------------------------------------- + + def get_in_audio(self): + return self._in_audio + + def set_in_audio(self, value): + self._in_audio = value + + in_audio = property(get_in_audio, set_in_audio) + + def get_out_audio(self): + return self._out_audio + + def set_out_audio(self, value): + self._out_audio = value + + out_audio = property(get_out_audio, set_out_audio) + + # ----------------------------------------------------------------------------------------------------------------- + # Video + # ----------------------------------------------------------------------------------------------------------------- + + def get_in_video(self): + return self._in_video + + def set_in_video(self, value): + self._in_video = value + + in_video = property(get_in_video, set_in_video) + + def get_out_video(self): + return self._out_video + + def set_out_video(self, value): + self._out_video = value + + out_video = property(get_out_video, set_out_video) diff --git a/toxygen/av/calls.py b/toxygen/av/calls.py new file mode 100644 index 0000000..d5f2fe7 --- /dev/null +++ b/toxygen/av/calls.py @@ -0,0 +1,281 @@ +import pyaudio +import time +import threading +from wrapper.toxav_enums import * +import cv2 +import itertools +import numpy as np +from av import screen_sharing +from av.call import Call +import common.tox_save + + +class AV(common.tox_save.ToxAvSave): + + def __init__(self, toxav, settings): + super().__init__(toxav) + self._settings = settings + self._running = True + + self._calls = {} # dict: key - friend number, value - Call instance + + self._audio = None + self._audio_stream = None + self._audio_thread = None + self._audio_running = False + self._out_stream = None + + self._audio_rate = 8000 + self._audio_channels = 1 + self._audio_duration = 60 + self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000 + + self._video = None + self._video_thread = None + self._video_running = False + + self._video_width = 640 + self._video_height = 480 + + def stop(self): + self._running = False + self.stop_audio_thread() + self.stop_video_thread() + + def __contains__(self, friend_number): + return friend_number in self._calls + + # ----------------------------------------------------------------------------------------------------------------- + # Calls + # ----------------------------------------------------------------------------------------------------------------- + + def __call__(self, friend_number, audio, video): + """Call friend with specified number""" + self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0) + self._calls[friend_number] = Call(audio, video) + threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start() + + def accept_call(self, friend_number, audio_enabled, video_enabled): + if self._running: + self._calls[friend_number] = Call(audio_enabled, video_enabled) + self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0) + if audio_enabled: + self.start_audio_thread() + if video_enabled: + self.start_video_thread() + + def finish_call(self, friend_number, by_friend=False): + if not by_friend: + self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL']) + if friend_number in self._calls: + del self._calls[friend_number] + if not len(list(filter(lambda c: c.out_audio, self._calls))): + self.stop_audio_thread() + if not len(list(filter(lambda c: c.out_video, self._calls))): + self.stop_video_thread() + + def finish_not_started_call(self, friend_number): + if friend_number in self: + call = self._calls[friend_number] + if not call.is_active: + self.finish_call(friend_number) + + def toxav_call_state_cb(self, friend_number, state): + """ + New call state + """ + call = self._calls[friend_number] + call.is_active = True + + call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] > 0 + call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0 + + if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio: + self.start_audio_thread() + + if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video: + self.start_video_thread() + + def is_video_call(self, number): + return number in self and self._calls[number].in_video + + # ----------------------------------------------------------------------------------------------------------------- + # Threads + # ----------------------------------------------------------------------------------------------------------------- + + def start_audio_thread(self): + """ + Start audio sending + """ + if self._audio_thread is not None: + return + + self._audio_running = True + + self._audio = pyaudio.PyAudio() + self._audio_stream = self._audio.open(format=pyaudio.paInt16, + rate=self._audio_rate, + channels=self._audio_channels, + input=True, + input_device_index=self._settings.audio['input'], + frames_per_buffer=self._audio_sample_count * 10) + + self._audio_thread = threading.Thread(target=self.send_audio) + self._audio_thread.start() + + def stop_audio_thread(self): + + if self._audio_thread is None: + return + + self._audio_running = False + + self._audio_thread.join() + + self._audio_thread = None + self._audio_stream = None + self._audio = None + + if self._out_stream is not None: + self._out_stream.stop_stream() + self._out_stream.close() + self._out_stream = None + + def start_video_thread(self): + if self._video_thread is not None: + return + + self._video_running = True + self._video_width = s.video['width'] + self._video_height = s.video['height'] + + if s.video['device'] == -1: + self._video = screen_sharing.DesktopGrabber(self._settings.video['x'], self._settings.video['y'], + self._settings.video['width'], self._settings.video['height']) + else: + self._video = cv2.VideoCapture(self._settings.video['device']) + self._video.set(cv2.CAP_PROP_FPS, 25) + self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width) + self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) + + self._video_thread = threading.Thread(target=self.send_video) + self._video_thread.start() + + def stop_video_thread(self): + if self._video_thread is None: + return + + self._video_running = False + self._video_thread.join() + self._video_thread = None + self._video = None + + # ----------------------------------------------------------------------------------------------------------------- + # Incoming chunks + # ----------------------------------------------------------------------------------------------------------------- + + def audio_chunk(self, samples, channels_count, rate): + """ + Incoming chunk + """ + + if self._out_stream is None: + self._out_stream = self._audio.open(format=pyaudio.paInt16, + channels=channels_count, + rate=rate, + output_device_index=self._settings.audio['output'], + output=True) + self._out_stream.write(samples) + + # ----------------------------------------------------------------------------------------------------------------- + # AV sending + # ----------------------------------------------------------------------------------------------------------------- + + def send_audio(self): + """ + This method sends audio to friends + """ + + while self._audio_running: + try: + pcm = self._audio_stream.read(self._audio_sample_count) + if pcm: + for friend_num in self._calls: + if self._calls[friend_num].out_audio: + try: + self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count, + self._audio_channels, self._audio_rate) + except: + pass + except: + pass + + time.sleep(0.01) + + def send_video(self): + """ + This method sends video to friends + """ + while self._video_running: + try: + result, frame = self._video.read() + if result: + height, width, channels = frame.shape + for friend_num in self._calls: + if self._calls[friend_num].out_video: + try: + y, u, v = self.convert_bgr_to_yuv(frame) + self._toxav.video_send_frame(friend_num, width, height, y, u, v) + except: + pass + except: + pass + + time.sleep(0.01) + + def convert_bgr_to_yuv(self, frame): + """ + :param frame: input bgr frame + :return y, u, v: y, u, v values of frame + + How this function works: + OpenCV creates YUV420 frame from BGR + This frame has following structure and size: + width, height - dim of input frame + width, height * 1.5 - dim of output frame + + width + ------------------------- + | | + | Y | height + | | + ------------------------- + | | | + | U even | U odd | height // 4 + | | | + ------------------------- + | | | + | V even | V odd | height // 4 + | | | + ------------------------- + + width // 2 width // 2 + + Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable() + Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes + """ + frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) + + y = frame[:self._video_height, :] + y = list(itertools.chain.from_iterable(y)) + + u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int) + u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2] + u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:] + u = list(itertools.chain.from_iterable(u)) + v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int) + v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2] + v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:] + v = list(itertools.chain.from_iterable(v)) + + return bytes(y), bytes(u), bytes(v) diff --git a/toxygen/av/calls_manager.py b/toxygen/av/calls_manager.py new file mode 100644 index 0000000..5a48672 --- /dev/null +++ b/toxygen/av/calls_manager.py @@ -0,0 +1,116 @@ +import threading +import cv2 +import av.calls +from messenger.messages import * +from ui import av_widgets +import common.event as event + + +class CallsManager: + + def __init__(self, toxav, settings, screen, contacts_manager): + self._call = av.calls.AV(toxav, settings) # object with data about calls + self._call_widgets = {} # dict of incoming call widgets + self._incoming_calls = set() + self._settings = settings + self._screen = screen + self._contacts_manager = contacts_manager + self._call_started_event = event.Event() # friend_number, audio, video, is_outgoing + self._call_finished_event = event.Event() # friend_number, is_declined + + def set_toxav(self, toxav): + self._call.set_toxav(toxav) + + # ----------------------------------------------------------------------------------------------------------------- + # Events + # ----------------------------------------------------------------------------------------------------------------- + + def get_call_started_event(self): + return self._call_started_event + + call_started_event = property(get_call_started_event) + + def get_call_finished_event(self): + return self._call_finished_event + + call_finished_event = property(get_call_finished_event) + + # ----------------------------------------------------------------------------------------------------------------- + # AV support + # ----------------------------------------------------------------------------------------------------------------- + + def call_click(self, audio=True, video=False): + """User clicked audio button in main window""" + num = self._contacts_manager.get_active_number() + if not self._contacts_manager.is_active_a_friend(): + return + if num not in self._call and self._contacts_manager.is_active_online(): # start call + if not self._settings.audio['enabled']: + return + self._call(num, audio, video) + self._screen.active_call() + self._call_started_event(num, audio, video, True) + elif num in self._call: # finish or cancel call if you call with active friend + self.stop_call(num, False) + + def incoming_call(self, audio, video, friend_number): + """ + Incoming call from friend. + """ + if not self._settings.audio['enabled']: + return + friend = self._contacts_manager.get_friend_by_number(friend_number) + self._call_started_event(friend_number, audio, video, False) + self._incoming_calls.add(friend_number) + if friend_number == self._contacts_manager.get_active_number(): + self._screen.incoming_call() + else: + friend.actions = True + text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call") + self._call_widgets[friend_number] = self._get_incoming_call_widget(friend_number, text, friend.name) + self._call_widgets[friend_number].set_pixmap(friend.get_pixmap()) + self._call_widgets[friend_number].show() + + def accept_call(self, friend_number, audio, video): + """ + Accept incoming call with audio or video + """ + self._call.accept_call(friend_number, audio, video) + self._screen.active_call() + if friend_number in self._incoming_calls: + self._incoming_calls.remove(friend_number) + del self._call_widgets[friend_number] + + def stop_call(self, friend_number, by_friend): + """ + Stop call with friend + """ + if friend_number in self._incoming_calls: + self._incoming_calls.remove(friend_number) + is_declined = True + else: + is_declined = False + self._screen.call_finished() + is_video = self._call.is_video_call(friend_number) + self._call.finish_call(friend_number, by_friend) # finish or decline call + if friend_number in self._call_widgets: + self._call_widgets[friend_number].close() + del self._call_widgets[friend_number] + + def destroy_window(): + if is_video: + cv2.destroyWindow(str(friend_number)) + + threading.Timer(2.0, destroy_window).start() + self._call_finished_event(friend_number, is_declined) + + def friend_exit(self, friend_number): + if friend_number in self._call: + self._call.finish_call(friend_number, True) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _get_incoming_call_widget(self, friend_number, text, friend_name): + return av_widgets.IncomingCallWidget(self._settings, self, friend_number, text, friend_name) diff --git a/toxygen/av/screen_sharing.py b/toxygen/av/screen_sharing.py new file mode 100644 index 0000000..265658c --- /dev/null +++ b/toxygen/av/screen_sharing.py @@ -0,0 +1,22 @@ +import numpy as np +from PyQt5 import QtWidgets + + +class DesktopGrabber: + + def __init__(self, x, y, width, height): + self._x = x + self._y = y + self._width = width + self._height = height + self._width -= width % 4 + self._height -= height % 4 + self._screen = QtWidgets.QApplication.primaryScreen() + + def read(self): + pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height) + image = pixmap.toImage() + s = image.bits().asstring(self._width * self._height * 4) + arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4)) + + return True, arr diff --git a/toxygen/bootstrap/__init__.py b/toxygen/bootstrap/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/bootstrap/bootstrap.py b/toxygen/bootstrap/bootstrap.py new file mode 100644 index 0000000..fad68c4 --- /dev/null +++ b/toxygen/bootstrap/bootstrap.py @@ -0,0 +1,83 @@ +import random +import urllib.request +from utils.util import * +from PyQt5 import QtNetwork, QtCore +import json + + +DEFAULT_NODES_COUNT = 4 + + +class Node: + + def __init__(self, node): + self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key'] + self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0 + + def get_priority(self): + return self._priority + + priority = property(get_priority) + + def get_data(self): + return self._ip, self._port, self._tox_key + + +def generate_nodes(nodes_count=DEFAULT_NODES_COUNT): + with open(_get_nodes_path(), 'rt') as fl: + json_nodes = json.loads(fl.read())['nodes'] + nodes = map(lambda json_node: Node(json_node), json_nodes) + nodes = filter(lambda n: n.priority > 0, nodes) + sorted_nodes = sorted(nodes, key=lambda x: x.priority) + if nodes_count is not None: + sorted_nodes = sorted_nodes[-DEFAULT_NODES_COUNT:] + for node in sorted_nodes: + yield node.get_data() + + +def download_nodes_list(settings): + url = 'https://nodes.tox.chat/json' + if not settings['download_nodes_list']: + return + + if not settings['proxy_type']: # no proxy + try: + req = urllib.request.Request(url) + req.add_header('Content-Type', 'application/json') + response = urllib.request.urlopen(req) + result = response.read() + _save_nodes(result) + except Exception as ex: + log('TOX nodes loading error: ' + str(ex)) + else: # proxy + netman = QtNetwork.QNetworkAccessManager() + proxy = QtNetwork.QNetworkProxy() + proxy.setType( + QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) + proxy.setHostName(settings['proxy_host']) + proxy.setPort(settings['proxy_port']) + netman.setProxy(proxy) + try: + request = QtNetwork.QNetworkRequest() + request.setUrl(QtCore.QUrl(url)) + reply = netman.get(request) + + while not reply.isFinished(): + QtCore.QThread.msleep(1) + QtCore.QCoreApplication.processEvents() + data = bytes(reply.readAll().data()) + _save_nodes(data) + except Exception as ex: + log('TOX nodes loading error: ' + str(ex)) + + +def _get_nodes_path(): + return join_path(curr_directory(__file__), 'nodes.json') + + +def _save_nodes(nodes): + if not nodes: + return + print('Saving nodes...') + with open(_get_nodes_path(), 'wb') as fl: + fl.write(nodes) diff --git a/toxygen/bootstrap/nodes.json b/toxygen/bootstrap/nodes.json new file mode 100644 index 0000000..5314998 --- /dev/null +++ b/toxygen/bootstrap/nodes.json @@ -0,0 +1 @@ +{"nodes":[{"ipv4":"80.211.19.83","ipv6":"-","port":33445,"public_key":"A2D7BF17C10A12C339B9F4E8DD77DEEE8457D580535A6F0D0F9AF04B8B4C4420","status_udp":true,"status_tcp":true}]} \ No newline at end of file diff --git a/toxygen/common/__init__.py b/toxygen/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/common/event.py b/toxygen/common/event.py new file mode 100644 index 0000000..687a34d --- /dev/null +++ b/toxygen/common/event.py @@ -0,0 +1,26 @@ + + +class Event: + + def __init__(self): + self._callbacks = set() + + def __iadd__(self, callback): + self.add_callback(callback) + + return self + + def __isub__(self, callback): + self.remove_callback(callback) + + return self + + def __call__(self, *args, **kwargs): + for callback in self._callbacks: + callback(*args, **kwargs) + + def add_callback(self, callback): + self._callbacks.add(callback) + + def remove_callback(self, callback): + self._callbacks.discard(callback) diff --git a/toxygen/common/provider.py b/toxygen/common/provider.py new file mode 100644 index 0000000..d16edb4 --- /dev/null +++ b/toxygen/common/provider.py @@ -0,0 +1,13 @@ + + +class Provider: + + def __init__(self, get_item_action): + self._get_item_action = get_item_action + self._item = None + + def get_item(self): + if self._item is None: + self._item = self._get_item_action() + + return self._item diff --git a/toxygen/common/tox_save.py b/toxygen/common/tox_save.py new file mode 100644 index 0000000..09c159b --- /dev/null +++ b/toxygen/common/tox_save.py @@ -0,0 +1,18 @@ + + +class ToxSave: + + def __init__(self, tox): + self._tox = tox + + def set_tox(self, tox): + self._tox = tox + + +class ToxAvSave: + + def __init__(self, toxav): + self._toxav = toxav + + def set_toxav(self, toxav): + self._toxav = toxav diff --git a/toxygen/contacts/__init__.py b/toxygen/contacts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/contacts/basecontact.py b/toxygen/contacts/basecontact.py new file mode 100644 index 0000000..2058890 --- /dev/null +++ b/toxygen/contacts/basecontact.py @@ -0,0 +1,180 @@ +from user_data.settings import * +from PyQt5 import QtCore, QtGui +from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE +import utils.util as util +import common.event as event +import contacts.common as common + + +class BaseContact: + """ + Class encapsulating TOX contact + Properties: name (alias of contact or name), status_message, status (connection status) + widget - widget for update, tox id (or public key) + Base class for all contacts. + """ + + def __init__(self, profile_manager, name, status_message, widget, tox_id): + """ + :param name: name, example: 'Toxygen user' + :param status_message: status message, example: 'Toxing on Toxygen' + :param widget: ContactItem instance + :param tox_id: tox id of contact + """ + self._profile_manager = profile_manager + self._name, self._status_message = name, status_message + self._status, self._widget = None, widget + self._tox_id = tox_id + self._name_changed_event = event.Event() + self._status_message_changed_event = event.Event() + self._status_changed_event = event.Event() + self._avatar_changed_event = event.Event() + self.init_widget() + + # ----------------------------------------------------------------------------------------------------------------- + # Name - current name or alias of user + # ----------------------------------------------------------------------------------------------------------------- + + def get_name(self): + return self._name + + def set_name(self, value): + if self._name == value: + return + self._name = value + self._widget.name.setText(self._name) + self._widget.name.repaint() + self._name_changed_event(self._name) + + name = property(get_name, set_name) + + def get_name_changed_event(self): + return self._name_changed_event + + name_changed_event = property(get_name_changed_event) + + # ----------------------------------------------------------------------------------------------------------------- + # Status message + # ----------------------------------------------------------------------------------------------------------------- + + def get_status_message(self): + return self._status_message + + def set_status_message(self, value): + if self._status_message == value: + return + self._status_message = value + self._widget.status_message.setText(self._status_message) + self._widget.status_message.repaint() + self._status_message_changed_event(self._status_message) + + status_message = property(get_status_message, set_status_message) + + def get_status_message_changed_event(self): + return self._status_message_changed_event + + status_message_changed_event = property(get_status_message_changed_event) + + # ----------------------------------------------------------------------------------------------------------------- + # Status + # ----------------------------------------------------------------------------------------------------------------- + + def get_status(self): + return self._status + + def set_status(self, value): + if self._status == value: + return + self._status = value + self._widget.connection_status.update(value) + self._status_changed_event(self._status) + + status = property(get_status, set_status) + + def get_status_changed_event(self): + return self._status_changed_event + + status_changed_event = property(get_status_changed_event) + + # ----------------------------------------------------------------------------------------------------------------- + # TOX ID. WARNING: for friend it will return public key, for profile - full address + # ----------------------------------------------------------------------------------------------------------------- + + def get_tox_id(self): + return self._tox_id + + tox_id = property(get_tox_id) + + # ----------------------------------------------------------------------------------------------------------------- + # Avatars + # ----------------------------------------------------------------------------------------------------------------- + + def load_avatar(self): + """ + Tries to load avatar of contact or uses default avatar + """ + avatar_path = self.get_avatar_path() + width = self._widget.avatar_label.width() + pixmap = QtGui.QPixmap(avatar_path) + self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, + QtCore.Qt.SmoothTransformation)) + self._widget.avatar_label.repaint() + self._avatar_changed_event(avatar_path) + + def reset_avatar(self, generate_new): + avatar_path = self.get_avatar_path() + if os.path.isfile(avatar_path) and not avatar_path == self._get_default_avatar_path(): + os.remove(avatar_path) + if generate_new: + self.set_avatar(common.generate_avatar(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])) + else: + self.load_avatar() + + def set_avatar(self, avatar): + avatar_path = self.get_contact_avatar_path() + with open(avatar_path, 'wb') as f: + f.write(avatar) + self.load_avatar() + + def get_pixmap(self): + return self._widget.avatar_label.pixmap() + + def get_avatar_path(self): + avatar_path = self.get_contact_avatar_path() + if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image + avatar_path = self._get_default_avatar_path() + + return avatar_path + + def get_contact_avatar_path(self): + directory = util.join_path(self._profile_manager.get_dir(), 'avatars') + + return util.join_path(directory, '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])) + + def has_avatar(self): + path = self.get_contact_avatar_path() + + return util.file_exists(path) + + def get_avatar_changed_event(self): + return self._avatar_changed_event + + avatar_changed_event = property(get_avatar_changed_event) + + # ----------------------------------------------------------------------------------------------------------------- + # Widgets + # ----------------------------------------------------------------------------------------------------------------- + + def init_widget(self): + self._widget.name.setText(self._name) + self._widget.status_message.setText(self._status_message) + self._widget.connection_status.update(self._status) + self.load_avatar() + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def _get_default_avatar_path(): + return util.join_path(util.get_images_directory(), 'avatar.png') diff --git a/toxygen/contacts/common.py b/toxygen/contacts/common.py new file mode 100644 index 0000000..27750a2 --- /dev/null +++ b/toxygen/contacts/common.py @@ -0,0 +1,50 @@ +from pydenticon import Generator +import hashlib + + +# ----------------------------------------------------------------------------------------------------------------- +# Typing notifications +# ----------------------------------------------------------------------------------------------------------------- + +class BaseTypingNotificationHandler: + + DEFAULT_HANDLER = None + + def __init__(self): + pass + + def send(self, tox, is_typing): + pass + + +class FriendTypingNotificationHandler(BaseTypingNotificationHandler): + + def __init__(self, friend_number): + super().__init__() + self._friend_number = friend_number + + def send(self, tox, is_typing): + tox.self_set_typing(self._friend_number, is_typing) + + +BaseTypingNotificationHandler.DEFAULT_HANDLER = BaseTypingNotificationHandler() + + +# ----------------------------------------------------------------------------------------------------------------- +# Identicons support +# ----------------------------------------------------------------------------------------------------------------- + + +def generate_avatar(public_key): + foreground = ['rgb(45,79,255)', 'rgb(185, 66, 244)', 'rgb(185, 66, 244)', + 'rgb(254,180,44)', 'rgb(252, 2, 2)', 'rgb(109, 198, 0)', + 'rgb(226,121,234)', 'rgb(130, 135, 124)', + 'rgb(30,179,253)', 'rgb(160, 157, 0)', + 'rgb(232,77,65)', 'rgb(102, 4, 4)', + 'rgb(49,203,115)', + 'rgb(141,69,170)'] + generator = Generator(5, 5, foreground=foreground, background='rgba(42,42,42,0)') + digest = hashlib.sha256(public_key.encode('utf-8')).hexdigest() + identicon = generator.generate(digest, 220, 220, padding=(10, 10, 10, 10)) + + return identicon diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py new file mode 100644 index 0000000..e88acf2 --- /dev/null +++ b/toxygen/contacts/contact.py @@ -0,0 +1,333 @@ +from history.database import * +from contacts import basecontact, common +from messenger.messages import * +from contacts.contact_menu import * +from file_transfers import file_transfers as ft +import re + + +class Contact(basecontact.BaseContact): + """ + Class encapsulating TOX contact + Properties: number, message getter, history etc. Base class for friend and gc classes + """ + + def __init__(self, profile_manager, message_getter, number, name, status_message, widget, tox_id): + """ + :param message_getter: gets messages from db + :param number: number of friend. + """ + super().__init__(profile_manager, name, status_message, widget, tox_id) + self._number = number + self._new_messages = False + self._visible = True + self._alias = False + self._message_getter = message_getter + self._corr = [] + self._unsaved_messages = 0 + self._history_loaded = self._new_actions = False + self._curr_text = self._search_string = '' + self._search_index = 0 + + def __del__(self): + self.set_visibility(False) + del self._widget + if hasattr(self, '_message_getter'): + del self._message_getter + + # ----------------------------------------------------------------------------------------------------------------- + # History support + # ----------------------------------------------------------------------------------------------------------------- + + def load_corr(self, first_time=True): + """ + :param first_time: friend became active, load first part of messages + """ + try: + if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')): + return + if self._message_getter is None: + return + data = list(self._message_getter.get(PAGE_SIZE)) + if data is not None and len(data): + data.reverse() + else: + return + data = list(map(lambda p: self._get_text_message(p), data)) + self._corr = data + self._corr + except: + pass + finally: + self._history_loaded = True + + def load_all_corr(self): + """ + Get all chat history from db for current friend + """ + if self._message_getter is None: + return + data = list(self._message_getter.get_all()) + if data is not None and len(data): + data.reverse() + data = list(map(lambda p: self._get_text_message(p), data)) + self._corr = data + self._corr + self._history_loaded = True + + def get_corr_for_saving(self): + """ + Get data to save in db + :return: list of unsaved messages or [] + """ + messages = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) + return messages[-self._unsaved_messages:] if self._unsaved_messages else [] + + def get_corr(self): + return self._corr[:] + + def append_message(self, message): + """ + :param message: text or file transfer message + """ + self._corr.append(message) + if message.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): + self._unsaved_messages += 1 + + def get_last_message_text(self): + messages = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) + and m.author.type != MESSAGE_AUTHOR['FRIEND'], self._corr)) + if messages: + return messages[-1].text + else: + return '' + + def remove_messages_widgets(self): + for message in self._corr: + message.remove_widget() + + def get_message(self, _filter): + return list(filter(lambda m: _filter(m), self._corr))[0] + + @staticmethod + def _get_text_message(params): + (message, author_type, author_name, unix_time, message_type, unique_id) = params + author = MessageAuthor(author_name, author_type) + + return TextMessage(message, author, unix_time, message_type, unique_id) + + # ----------------------------------------------------------------------------------------------------------------- + # Unsent messages + # ----------------------------------------------------------------------------------------------------------------- + + def get_unsent_messages(self): + """ + :return list of unsent messages + """ + messages = filter(lambda m: m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) + return list(messages) + + def get_unsent_messages_for_saving(self): + """ + :return list of unsent messages for saving + """ + messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) + and m.author.type == MESSAGE_AUTHOR['NOT_SENT'], self._corr) + return list(messages) + + def mark_as_sent(self, tox_message_id): + try: + message = list(filter(lambda m: m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT'] + and m.tox_message_id == tox_message_id, self._corr))[0] + message.mark_as_sent() + except Exception as ex: + util.log('Mark as sent ex: ' + str(ex)) + + # ----------------------------------------------------------------------------------------------------------------- + # Message deletion + # ----------------------------------------------------------------------------------------------------------------- + + def delete_message(self, message_id): + elem = list(filter(lambda m: m.message_id == message_id, self._corr))[0] + tmp = list(filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr)) + if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: + self._unsaved_messages -= 1 + self._corr.remove(elem) + self._message_getter.delete_one() + self._search_index = 0 + + def delete_old_messages(self): + """ + Delete old messages (reduces RAM usage if messages saving is not enabled) + """ + def save_message(m): + if m.type == MESSAGE_TYPE['FILE_TRANSFER'] and (m.state not in ACTIVE_FILE_TRANSFERS): + return True + return m.author is not None and m.author.type == MESSAGE_AUTHOR['NOT_SENT'] + + old = filter(save_message, self._corr[:-SAVE_MESSAGES]) + self._corr = list(old) + self._corr[-SAVE_MESSAGES:] + text_messages = filter(lambda m: m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']), self._corr) + self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages))) + self._search_index = 0 + + def clear_corr(self, save_unsent=False): + """ + Clear messages list + """ + if hasattr(self, '_message_getter'): + del self._message_getter + self._search_index = 0 + # don't delete data about active file transfer + if not save_unsent: + self._corr = list(filter(lambda m: m.type == MESSAGE_TYPE['FILE_TRANSFER'] and + m.state in ft.ACTIVE_FILE_TRANSFERS, self._corr)) + self._unsaved_messages = 0 + else: + self._corr = list(filter(lambda m: (m.type == MESSAGE_TYPE['FILE_TRANSFER'] + and m.state in ft.ACTIVE_FILE_TRANSFERS) + or (m.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']) + and m.author.type == MESSAGE_AUTHOR['NOT_SENT']), + self._corr)) + self._unsaved_messages = len(self.get_unsent_messages()) + + # ----------------------------------------------------------------------------------------------------------------- + # Chat history search + # ----------------------------------------------------------------------------------------------------------------- + + def search_string(self, search_string): + self._search_string, self._search_index = search_string, 0 + return self.search_prev() + + def search_prev(self): + while True: + l = len(self._corr) + for i in range(self._search_index - 1, -l - 1, -1): + if self._corr[i].type not in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): + continue + message = self._corr[i].text + if re.search(self._search_string, message, re.IGNORECASE) is not None: + self._search_index = i + return i + self._search_index = -l + self.load_corr(False) + if len(self._corr) == l: + return None # not found + + def search_next(self): + if not self._search_index: + return None + for i in range(self._search_index + 1, 0): + if self._corr[i].type not in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): + continue + message = self._corr[i].text + if re.search(self._search_string, message, re.IGNORECASE) is not None: + self._search_index = i + return i + return None # not found + + # ----------------------------------------------------------------------------------------------------------------- + # Current text - text from message area + # ----------------------------------------------------------------------------------------------------------------- + + def get_curr_text(self): + return self._curr_text + + def set_curr_text(self, value): + self._curr_text = value + + curr_text = property(get_curr_text, set_curr_text) + + # ----------------------------------------------------------------------------------------------------------------- + # Alias support + # ----------------------------------------------------------------------------------------------------------------- + + def set_name(self, value): + """ + Set new name or ignore if alias exists + :param value: new name + """ + if not self._alias: + super().set_name(value) + + def set_alias(self, alias): + self._alias = bool(alias) + + def has_alias(self): + return self._alias + + # ----------------------------------------------------------------------------------------------------------------- + # Visibility in friends' list + # ----------------------------------------------------------------------------------------------------------------- + + def get_visibility(self): + return self._visible + + def set_visibility(self, value): + self._visible = value + + visibility = property(get_visibility, set_visibility) + + # ----------------------------------------------------------------------------------------------------------------- + # Unread messages and other actions from friend + # ----------------------------------------------------------------------------------------------------------------- + + def get_actions(self): + return self._new_actions + + def set_actions(self, value): + self._new_actions = value + self._widget.connection_status.update(self.status, value) + + actions = property(get_actions, set_actions) # unread messages, incoming files, av calls + + def get_messages(self): + return self._new_messages + + def inc_messages(self): + self._new_messages += 1 + self._new_actions = True + self._widget.connection_status.update(self.status, True) + self._widget.messages.update(self._new_messages) + + def reset_messages(self): + self._new_actions = False + self._new_messages = 0 + self._widget.messages.update(self._new_messages) + self._widget.connection_status.update(self.status, False) + + messages = property(get_messages) + + # ----------------------------------------------------------------------------------------------------------------- + # Friend's or group's number (can be used in toxcore) + # ----------------------------------------------------------------------------------------------------------------- + + def get_number(self): + return self._number + + def set_number(self, value): + self._number = value + + number = property(get_number, set_number) + + # ----------------------------------------------------------------------------------------------------------------- + # Typing notifications + # ----------------------------------------------------------------------------------------------------------------- + + def get_typing_notification_handler(self): + return common.BaseTypingNotificationHandler.DEFAULT_HANDLER + + typing_notification_handler = property(get_typing_notification_handler) + + # ----------------------------------------------------------------------------------------------------------------- + # Context menu support + # ----------------------------------------------------------------------------------------------------------------- + + def get_context_menu_generator(self): + return BaseContactMenuGenerator(self) + + # ----------------------------------------------------------------------------------------------------------------- + # Filtration support + # ----------------------------------------------------------------------------------------------------------------- + + def set_widget(self, widget): + self._widget = widget + self.init_widget() diff --git a/toxygen/contacts/contact_menu.py b/toxygen/contacts/contact_menu.py new file mode 100644 index 0000000..8178d31 --- /dev/null +++ b/toxygen/contacts/contact_menu.py @@ -0,0 +1,229 @@ +from PyQt5 import QtWidgets +import utils.ui as util_ui + + +# ----------------------------------------------------------------------------------------------------------------- +# Builder +# ----------------------------------------------------------------------------------------------------------------- + +def _create_menu(menu_name, parent): + menu_name = menu_name or '' + + return QtWidgets.QMenu(menu_name) if parent is None else parent.addMenu(menu_name) + + +class ContactMenuBuilder: + + def __init__(self): + self._actions = {} + self._submenus = {} + self._name = None + self._index = 0 + + def with_name(self, name): + self._name = name + + return self + + def with_action(self, text, handler): + self._add_action(text, handler) + + return self + + def with_optional_action(self, text, handler, show_action): + if show_action: + self._add_action(text, handler) + + return self + + def with_actions(self, actions): + for action in actions: + (text, handler) = action + self._add_action(text, handler) + + return self + + def with_submenu(self, submenu_builder): + self._add_submenu(submenu_builder) + + return self + + def with_optional_submenu(self, submenu_builder): + if submenu_builder is not None: + self._add_submenu(submenu_builder) + + return self + + def build(self, parent=None): + menu = _create_menu(self._name, parent) + + for i in range(self._index): + if i in self._actions: + text, handler = self._actions[i] + action = menu.addAction(text) + action.triggered.connect(handler) + else: + submenu_builder = self._submenus[i] + submenu = submenu_builder.build(menu) + menu.addMenu(submenu) + + return menu + + def _add_submenu(self, submenu): + self._submenus[self._index] = submenu + self._index += 1 + + def _add_action(self, text, handler): + self._actions[self._index] = (text, handler) + self._index += 1 + +# ----------------------------------------------------------------------------------------------------------------- +# Generators +# ----------------------------------------------------------------------------------------------------------------- + + +class BaseContactMenuGenerator: + + def __init__(self, contact): + self._contact = contact + + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader): + return ContactMenuBuilder().build() + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _generate_copy_menu_builder(self, main_screen): + copy_menu_builder = ContactMenuBuilder() + (copy_menu_builder + .with_name(util_ui.tr('Copy')) + .with_action(util_ui.tr('Name'), lambda: main_screen.copy_text(self._contact.name)) + .with_action(util_ui.tr('Status message'), lambda: main_screen.copy_text(self._contact.status_message)) + .with_action(util_ui.tr('Public key'), lambda: main_screen.copy_text(self._contact.tox_id)) + ) + + return copy_menu_builder + + def _generate_history_menu_builder(self, history_loader, main_screen): + history_menu_builder = ContactMenuBuilder() + (history_menu_builder + .with_name(util_ui.tr('Chat history')) + .with_action(util_ui.tr('Clear history'), lambda: history_loader.clear_history(self._contact) + or main_screen.messages.clear()) + .with_action(util_ui.tr('Export as text'), lambda: history_loader.export_history(self._contact)) + .with_action(util_ui.tr('Export as HTML'), lambda: history_loader.export_history(self._contact, False)) + ) + + return history_menu_builder + + +class FriendMenuGenerator(BaseContactMenuGenerator): + + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader): + history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen) + copy_menu_builder = self._generate_copy_menu_builder(main_screen) + plugins_menu_builder = self._generate_plugins_menu_builder(plugin_loader, number) + groups_menu_builder = self._generate_groups_menu(contacts_manager, groups_service) + + allowed = self._contact.tox_id in settings['auto_accept_from_friends'] + auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept') + + builder = ContactMenuBuilder() + menu = (builder + .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number)) + .with_submenu(history_menu_builder) + .with_submenu(copy_menu_builder) + .with_action(auto, lambda: main_screen.auto_accept(number, not allowed)) + .with_action(util_ui.tr('Remove friend'), lambda: main_screen.remove_friend(number)) + .with_action(util_ui.tr('Block friend'), lambda: main_screen.block_friend(number)) + .with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact)) + .with_optional_submenu(plugins_menu_builder) + .with_optional_submenu(groups_menu_builder) + ).build() + + return menu + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def _generate_plugins_menu_builder(plugin_loader, number): + if plugin_loader is None: + return None + plugins_actions = plugin_loader.get_menu(number) + if not len(plugins_actions): + return None + plugins_menu_builder = ContactMenuBuilder() + (plugins_menu_builder + .with_name(util_ui.tr('Plugins')) + .with_actions(plugins_actions) + ) + + return plugins_menu_builder + + def _generate_groups_menu(self, contacts_manager, groups_service): + chats = contacts_manager.get_group_chats() + if not len(chats) or self._contact.status is None: + return None + groups_menu_builder = ContactMenuBuilder() + (groups_menu_builder + .with_name(util_ui.tr('Invite to group')) + .with_actions([(g.name, lambda: groups_service.invite_friend(self._contact.number, g.number)) for g in chats]) + ) + + return groups_menu_builder + + +class GroupMenuGenerator(BaseContactMenuGenerator): + + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader): + copy_menu_builder = self._generate_copy_menu_builder(main_screen) + history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen) + + builder = ContactMenuBuilder() + menu = (builder + .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number)) + .with_submenu(copy_menu_builder) + .with_submenu(history_menu_builder) + .with_optional_action(util_ui.tr('Manage group'), + lambda: groups_service.show_group_management_screen(self._contact), + self._contact.is_self_founder()) + .with_optional_action(util_ui.tr('Group settings'), + lambda: groups_service.show_group_settings_screen(self._contact), + not self._contact.is_self_founder()) + .with_optional_action(util_ui.tr('Set topic'), + lambda: groups_service.set_group_topic(self._contact), + self._contact.is_self_moderator_or_founder()) + .with_action(util_ui.tr('Bans list'), + lambda: groups_service.show_bans_list(self._contact)) + .with_action(util_ui.tr('Reconnect to group'), + lambda: groups_service.reconnect_to_group(self._contact.number)) + .with_optional_action(util_ui.tr('Disconnect from group'), + lambda: groups_service.disconnect_from_group(self._contact.number), + self._contact.status is not None) + .with_action(util_ui.tr('Leave group'), lambda: groups_service.leave_group(self._contact.number)) + .with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact)) + ).build() + + return menu + + +class GroupPeerMenuGenerator(BaseContactMenuGenerator): + + def generate(self, plugin_loader, contacts_manager, main_screen, settings, number, groups_service, history_loader): + copy_menu_builder = self._generate_copy_menu_builder(main_screen) + history_menu_builder = self._generate_history_menu_builder(history_loader, main_screen) + + builder = ContactMenuBuilder() + menu = (builder + .with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number)) + .with_submenu(copy_menu_builder) + .with_submenu(history_menu_builder) + .with_action(util_ui.tr('Quit chat'), + lambda: contacts_manager.remove_group_peer(self._contact)) + .with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact)) + ).build() + + return menu diff --git a/toxygen/contacts/contact_provider.py b/toxygen/contacts/contact_provider.py new file mode 100644 index 0000000..76e8e79 --- /dev/null +++ b/toxygen/contacts/contact_provider.py @@ -0,0 +1,107 @@ +import common.tox_save as tox_save + + +class ContactProvider(tox_save.ToxSave): + + def __init__(self, tox, friend_factory, group_factory, group_peer_factory): + super().__init__(tox) + self._friend_factory = friend_factory + self._group_factory = group_factory + self._group_peer_factory = group_peer_factory + self._cache = {} # key - contact's public key, value - contact instance + + # ----------------------------------------------------------------------------------------------------------------- + # Friends + # ----------------------------------------------------------------------------------------------------------------- + + def get_friend_by_number(self, friend_number): + public_key = self._tox.friend_get_public_key(friend_number) + + return self.get_friend_by_public_key(public_key) + + def get_friend_by_public_key(self, public_key): + friend = self._get_contact_from_cache(public_key) + if friend is not None: + return friend + friend = self._friend_factory.create_friend_by_public_key(public_key) + self._add_to_cache(public_key, friend) + + return friend + + def get_all_friends(self): + friend_numbers = self._tox.self_get_friend_list() + friends = map(lambda n: self.get_friend_by_number(n), friend_numbers) + + return list(friends) + + # ----------------------------------------------------------------------------------------------------------------- + # Groups + # ----------------------------------------------------------------------------------------------------------------- + + def get_all_groups(self): + group_numbers = range(self._tox.group_get_number_groups()) + groups = map(lambda n: self.get_group_by_number(n), group_numbers) + + return list(groups) + + def get_group_by_number(self, group_number): + public_key = self._tox.group_get_chat_id(group_number) + + return self.get_group_by_public_key(public_key) + + def get_group_by_public_key(self, public_key): + group = self._get_contact_from_cache(public_key) + if group is not None: + return group + group = self._group_factory.create_group_by_public_key(public_key) + self._add_to_cache(public_key, group) + + return group + + # ----------------------------------------------------------------------------------------------------------------- + # Group peers + # ----------------------------------------------------------------------------------------------------------------- + + def get_all_group_peers(self): + return list() + + def get_group_peer_by_id(self, group, peer_id): + peer = group.get_peer_by_id(peer_id) + + return self._get_group_peer(group, peer) + + def get_group_peer_by_public_key(self, group, public_key): + peer = group.get_peer_by_public_key(public_key) + + return self._get_group_peer(group, peer) + + # ----------------------------------------------------------------------------------------------------------------- + # All contacts + # ----------------------------------------------------------------------------------------------------------------- + + def get_all(self): + return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers() + + # ----------------------------------------------------------------------------------------------------------------- + # Caching + # ----------------------------------------------------------------------------------------------------------------- + + def clear_cache(self): + self._cache.clear() + + def remove_contact_from_cache(self, contact_public_key): + if contact_public_key in self._cache: + del self._cache[contact_public_key] + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _get_contact_from_cache(self, public_key): + return self._cache[public_key] if public_key in self._cache else None + + def _add_to_cache(self, public_key, contact): + self._cache[public_key] = contact + + def _get_group_peer(self, group, peer): + return self._group_peer_factory.create_group_peer(group, peer) diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py new file mode 100644 index 0000000..87a61ff --- /dev/null +++ b/toxygen/contacts/contacts_manager.py @@ -0,0 +1,575 @@ +from contacts.friend import Friend +from contacts.group_chat import GroupChat +from messenger.messages import * +from common.tox_save import ToxSave +from contacts.group_peer_contact import GroupPeerContact + + +class ContactsManager(ToxSave): + """ + Represents contacts list. + """ + + def __init__(self, tox, settings, screen, profile_manager, contact_provider, history, tox_dns, + messages_items_factory): + super().__init__(tox) + self._settings = settings + self._screen = screen + self._profile_manager = profile_manager + self._contact_provider = contact_provider + self._tox_dns = tox_dns + self._messages_items_factory = messages_items_factory + self._messages = screen.messages + self._contacts, self._active_contact = [], -1 + self._active_contact_changed = Event() + self._sorting = settings['sorting'] + self._filter_string = '' + screen.contacts_filter.setCurrentIndex(int(self._sorting)) + self._history = history + self._load_contacts() + + def get_contact(self, num): + if num < 0 or num >= len(self._contacts): + return None + return self._contacts[num] + + def get_curr_contact(self): + return self._contacts[self._active_contact] if self._active_contact + 1 else None + + def save_profile(self): + data = self._tox.get_savedata() + self._profile_manager.save_profile(data) + + def is_friend_active(self, friend_number): + if not self.is_active_a_friend(): + return False + + return self.get_curr_contact().number == friend_number + + def is_group_active(self, group_number): + if self.is_active_a_friend(): + return False + + return self.get_curr_contact().number == group_number + + def is_contact_active(self, contact): + return self._contacts[self._active_contact].tox_id == contact.tox_id + + # ----------------------------------------------------------------------------------------------------------------- + # Reconnection support + # ----------------------------------------------------------------------------------------------------------------- + + def reset_contacts_statuses(self): + for contact in self._contacts: + contact.status = None + + # ----------------------------------------------------------------------------------------------------------------- + # Work with active friend + # ----------------------------------------------------------------------------------------------------------------- + + def get_active(self): + return self._active_contact + + def set_active(self, value): + """ + Change current active friend or update info + :param value: number of new active friend in friend's list + """ + if value is None and self._active_contact == -1: # nothing to update + return + if value == -1: # all friends were deleted + self._screen.account_name.setText('') + self._screen.account_status.setText('') + self._screen.account_status.setToolTip('') + self._active_contact = -1 + self._screen.account_avatar.setHidden(True) + self._messages.clear() + self._screen.messageEdit.clear() + return + try: + self._screen.typing.setVisible(False) + current_contact = self.get_curr_contact() + if current_contact is not None: + # TODO: send when needed + current_contact.typing_notification_handler.send(self._tox, False) + current_contact.remove_messages_widgets() # TODO: if required + self._unsubscribe_from_events(current_contact) + + if self._active_contact + 1 and self._active_contact != value: + try: + current_contact.curr_text = self._screen.messageEdit.toPlainText() + except: + pass + contact = self._contacts[value] + self._subscribe_to_events(contact) + contact.remove_invalid_unsent_files() + if self._active_contact != value: + self._screen.messageEdit.setPlainText(contact.curr_text) + self._active_contact = value + contact.reset_messages() + if not self._settings['save_history']: + contact.delete_old_messages() + self._messages.clear() + contact.load_corr() + corr = contact.get_corr()[-PAGE_SIZE:] + for message in corr: + if message.type == MESSAGE_TYPE['FILE_TRANSFER']: + self._messages_items_factory.create_file_transfer_item(message) + elif message.type == MESSAGE_TYPE['INLINE']: + self._messages_items_factory.create_inline_item(message) + else: + self._messages_items_factory.create_message_item(message) + self._messages.scrollToBottom() + # if value in self._call: + # self._screen.active_call() + # elif value in self._incoming_calls: + # self._screen.incoming_call() + # else: + # self._screen.call_finished() + self._set_current_contact_data(contact) + self._active_contact_changed(contact) + except Exception as ex: # no friend found. ignore + util.log('Friend value: ' + str(value)) + util.log('Error in set active: ' + str(ex)) + raise + + active_contact = property(get_active, set_active) + + def get_active_contact_changed(self): + return self._active_contact_changed + + active_contact_changed = property(get_active_contact_changed) + + def update(self): + if self._active_contact + 1: + self.set_active(self._active_contact) + + def is_active_a_friend(self): + return type(self.get_curr_contact()) is Friend + + def is_active_a_group(self): + return type(self.get_curr_contact()) is GroupChat + + def is_active_a_group_chat_peer(self): + return type(self.get_curr_contact()) is GroupPeerContact + + # ----------------------------------------------------------------------------------------------------------------- + # Filtration + # ----------------------------------------------------------------------------------------------------------------- + + def filtration_and_sorting(self, sorting=0, filter_str=''): + """ + Filtration of friends list + :param sorting: 0 - no sorting, 1 - online only, 2 - online first, 3 - by name, + 4 - online and by name, 5 - online first and by name + :param filter_str: show contacts which name contains this substring + """ + filter_str = filter_str.lower() + current_contact = self.get_curr_contact() + + if sorting > 5 or sorting < 0: + sorting = 0 + + if sorting in (1, 2, 4, 5): # online first + self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) + sort_by_name = sorting in (4, 5) + # save results of previous sorting + online_friends = filter(lambda x: x.status is not None, self._contacts) + online_friends_count = len(list(online_friends)) + part1 = self._contacts[:online_friends_count] + part2 = self._contacts[online_friends_count:] + key_lambda = lambda x: x.name.lower() if sort_by_name else x.number + part1 = sorted(part1, key=key_lambda) + part2 = sorted(part2, key=key_lambda) + self._contacts = part1 + part2 + elif sorting == 0: + contacts = sorted(self._contacts, key=lambda c: c.number) + friends = filter(lambda c: type(c) is Friend, contacts) + groups = filter(lambda c: type(c) is GroupChat, contacts) + group_peers = filter(lambda c: type(c) is GroupPeerContact, contacts) + self._contacts = list(friends) + list(groups) + list(group_peers) + else: + self._contacts = sorted(self._contacts, key=lambda x: x.name.lower()) + + # change item widgets + for index, contact in enumerate(self._contacts): + list_item = self._screen.friends_list.item(index) + item_widget = self._screen.friends_list.itemWidget(list_item) + contact.set_widget(item_widget) + + for index, friend in enumerate(self._contacts): + filtered_by_name = filter_str in friend.name.lower() + friend.visibility = (friend.status is not None or sorting not in (1, 4)) and filtered_by_name + # show friend even if it's hidden when there any unread messages/actions + friend.visibility = friend.visibility or friend.messages or friend.actions + item = self._screen.friends_list.item(index) + item_widget = self._screen.friends_list.itemWidget(item) + item.setSizeHint(QtCore.QSize(250, item_widget.height() if friend.visibility else 0)) + + # save soring results + self._sorting, self._filter_string = sorting, filter_str + self._settings['sorting'] = self._sorting + self._settings.save() + + # update active contact + if current_contact is not None: + index = self._contacts.index(current_contact) + self.set_active(index) + + def update_filtration(self): + """ + Update list of contacts when 1 of friends change connection status + """ + self.filtration_and_sorting(self._sorting, self._filter_string) + + # ----------------------------------------------------------------------------------------------------------------- + # Contact getters + # ----------------------------------------------------------------------------------------------------------------- + + def get_friend_by_number(self, number): + return list(filter(lambda c: c.number == number and type(c) is Friend, self._contacts))[0] + + def get_group_by_number(self, number): + return list(filter(lambda c: c.number == number and type(c) is GroupChat, self._contacts))[0] + + def get_or_create_group_peer_contact(self, group_number, peer_id): + group = self.get_group_by_number(group_number) + peer = group.get_peer_by_id(peer_id) + if not self.check_if_contact_exists(peer.public_key): + self.add_group_peer(group, peer) + + return self.get_contact_by_tox_id(peer.public_key) + + def check_if_contact_exists(self, tox_id): + return any(filter(lambda c: c.tox_id == tox_id, self._contacts)) + + def get_contact_by_tox_id(self, tox_id): + return list(filter(lambda c: c.tox_id == tox_id, self._contacts))[0] + + def get_active_number(self): + return self.get_curr_contact().number if self._active_contact + 1 else -1 + + def get_active_name(self): + return self.get_curr_contact().name if self._active_contact + 1 else '' + + def is_active_online(self): + return self._active_contact + 1 and self.get_curr_contact().status is not None + + # ----------------------------------------------------------------------------------------------------------------- + # Work with friends (remove, block, set alias, get public key) + # ----------------------------------------------------------------------------------------------------------------- + + def set_alias(self, num): + """ + Set new alias for friend + """ + friend = self._contacts[num] + name = friend.name + text = util_ui.tr("Enter new alias for friend {} or leave empty to use friend's name:").format(name) + title = util_ui.tr('Set alias') + text, ok = util_ui.text_dialog(text, title, name) + if not ok: + return + aliases = self._settings['friends_aliases'] + if text: + friend.name = text + try: + index = list(map(lambda x: x[0], aliases)).index(friend.tox_id) + aliases[index] = (friend.tox_id, text) + except: + aliases.append((friend.tox_id, text)) + friend.set_alias(text) + else: # use default name + friend.name = self._tox.friend_get_name(friend.number) + friend.set_alias('') + try: + index = list(map(lambda x: x[0], aliases)).index(friend.tox_id) + del aliases[index] + except: + pass + self._settings.save() + + def friend_public_key(self, num): + return self._contacts[num].tox_id + + def delete_friend(self, num): + """ + Removes friend from contact list + :param num: number of friend in list + """ + friend = self._contacts[num] + self._cleanup_contact_data(friend) + self._tox.friend_delete(friend.number) + self._delete_contact(num) + + def add_friend(self, tox_id): + """ + Adds friend to list + """ + self._tox.friend_add_norequest(tox_id) + self._add_friend(tox_id) + self.update_filtration() + + def block_user(self, tox_id): + """ + Block user with specified tox id (or public key) - delete from friends list and ignore friend requests + """ + tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] + if tox_id == self._tox.self_get_address[:TOX_PUBLIC_KEY_SIZE * 2]: + return + if tox_id not in self._settings['blocked']: + self._settings['blocked'].append(tox_id) + self._settings.save() + try: + num = self._tox.friend_by_public_key(tox_id) + self.delete_friend(num) + self.save_profile() + except: # not in friend list + pass + + def unblock_user(self, tox_id, add_to_friend_list): + """ + Unblock user + :param tox_id: tox id of contact + :param add_to_friend_list: add this contact to friend list or not + """ + self._settings['blocked'].remove(tox_id) + self._settings.save() + if add_to_friend_list: + self.add_friend(tox_id) + self.save_profile() + + # ----------------------------------------------------------------------------------------------------------------- + # Groups support + # ----------------------------------------------------------------------------------------------------------------- + + def get_group_chats(self): + return list(filter(lambda c: type(c) is GroupChat, self._contacts)) + + def add_group(self, group_number): + group = self._contact_provider.get_group_by_number(group_number) + index = len(self._contacts) + self._contacts.append(group) + group.reset_avatar(self._settings['identicons']) + self._save_profile() + self.set_active(index) + self.update_filtration() + + def delete_group(self, group_number): + group = self.get_group_by_number(group_number) + self._cleanup_contact_data(group) + num = self._contacts.index(group) + self._delete_contact(num) + + # ----------------------------------------------------------------------------------------------------------------- + # Groups private messaging + # ----------------------------------------------------------------------------------------------------------------- + + def add_group_peer(self, group, peer): + contact = self._contact_provider.get_group_peer_by_id(group, peer.id) + if self.check_if_contact_exists(contact.tox_id): + return + self._contacts.append(contact) + contact.reset_avatar(self._settings['identicons']) + self._save_profile() + + def remove_group_peer_by_id(self, group, peer_id): + peer = group.get_peer_by_id(peer_id) + if not self.check_if_contact_exists(peer.public_key): + return + contact = self.get_contact_by_tox_id(peer.public_key) + self.remove_group_peer(contact) + + def remove_group_peer(self, group_peer_contact): + contact = self.get_contact_by_tox_id(group_peer_contact.tox_id) + self._cleanup_contact_data(contact) + num = self._contacts.index(contact) + self._delete_contact(num) + + def get_gc_peer_name(self, name): + group = self.get_curr_contact() + + names = sorted(group.get_peers_names()) + if name in names: # return next nick + index = names.index(name) + index = (index + 1) % len(names) + + return names[index] + + suggested_names = list(filter(lambda x: x.startswith(name), names)) + if not len(suggested_names): + return '\t' + + return suggested_names[0] + + # ----------------------------------------------------------------------------------------------------------------- + # Friend requests + # ----------------------------------------------------------------------------------------------------------------- + + def send_friend_request(self, tox_id, message): + """ + Function tries to send request to contact with specified id + :param tox_id: id of new contact or tox dns 4 value + :param message: additional message + :return: True on success else error string + """ + try: + message = message or 'Hello! Add me to your contact list please' + if '@' in tox_id: # value like groupbot@toxme.io + tox_id = self._tox_dns.lookup(tox_id) + if tox_id is None: + raise Exception('TOX DNS lookup failed') + if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key + self.add_friend(tox_id) + title = util_ui.tr('Friend added') + text = util_ui.tr('Friend added without sending friend request') + util_ui.message_box(text, title) + else: + self._tox.friend_add(tox_id, message.encode('utf-8')) + tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] + self._add_friend(tox_id) + self.update_filtration() + self.save_profile() + return True + except Exception as ex: # wrong data + util.log('Friend request failed with ' + str(ex)) + return str(ex) + + def process_friend_request(self, tox_id, message): + """ + Accept or ignore friend request + :param tox_id: tox id of contact + :param message: message + """ + if tox_id in self._settings['blocked']: + return + try: + text = util_ui.tr('User {} wants to add you to contact list. Message:\n{}') + reply = util_ui.question(text.format(tox_id, message), util_ui.tr('Friend request')) + if reply: # accepted + self.add_friend(tox_id) + data = self._tox.get_savedata() + self._profile_manager.save_profile(data) + except Exception as ex: # something is wrong + util.log('Accept friend request failed! ' + str(ex)) + + def can_send_typing_notification(self): + return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer() + + # ----------------------------------------------------------------------------------------------------------------- + # Contacts numbers update + # ----------------------------------------------------------------------------------------------------------------- + + def update_friends_numbers(self): + for friend in self._contact_provider.get_all_friends(): + friend.number = self._tox.friend_by_public_key(friend.tox_id) + self.update_filtration() + + def update_groups_numbers(self): + groups = self._contact_provider.get_all_groups() + for i in range(len(groups)): + chat_id = self._tox.group_get_chat_id(i) + group = self.get_contact_by_tox_id(chat_id) + group.number = i + self.update_filtration() + + def update_groups_lists(self): + groups = self._contact_provider.get_all_groups() + for group in groups: + group.remove_all_peers_except_self() + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _load_contacts(self): + self._load_friends() + self._load_groups() + if len(self._contacts): + self.set_active(0) + for contact in filter(lambda c: not c.has_avatar(), self._contacts): + contact.reset_avatar(self._settings['identicons']) + self.update_filtration() + + def _load_friends(self): + self._contacts.extend(self._contact_provider.get_all_friends()) + + def _load_groups(self): + self._contacts.extend(self._contact_provider.get_all_groups()) + + # ----------------------------------------------------------------------------------------------------------------- + # Current contact subscriptions + # ----------------------------------------------------------------------------------------------------------------- + + def _subscribe_to_events(self, contact): + contact.name_changed_event.add_callback(self._current_contact_name_changed) + contact.status_changed_event.add_callback(self._current_contact_status_changed) + contact.status_message_changed_event.add_callback(self._current_contact_status_message_changed) + contact.avatar_changed_event.add_callback(self._current_contact_avatar_changed) + + def _unsubscribe_from_events(self, contact): + contact.name_changed_event.remove_callback(self._current_contact_name_changed) + contact.status_changed_event.remove_callback(self._current_contact_status_changed) + contact.status_message_changed_event.remove_callback(self._current_contact_status_message_changed) + contact.avatar_changed_event.remove_callback(self._current_contact_avatar_changed) + + def _current_contact_name_changed(self, name): + self._screen.account_name.setText(name) + + def _current_contact_status_changed(self, status): + pass + + def _current_contact_status_message_changed(self, status_message): + self._screen.account_status.setText(status_message) + + def _current_contact_avatar_changed(self, avatar_path): + self._set_current_contact_avatar(avatar_path) + + def _set_current_contact_data(self, contact): + self._screen.account_name.setText(contact.name) + self._screen.account_status.setText(contact.status_message) + self._set_current_contact_avatar(contact.get_avatar_path()) + + def _set_current_contact_avatar(self, avatar_path): + width = self._screen.account_avatar.width() + pixmap = QtGui.QPixmap(avatar_path) + self._screen.account_avatar.setPixmap(pixmap.scaled(width, width, + QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) + + def _add_friend(self, tox_id): + self._history.add_friend_to_db(tox_id) + friend = self._contact_provider.get_friend_by_public_key(tox_id) + index = len(self._contacts) + self._contacts.append(friend) + if not friend.has_avatar(): + friend.reset_avatar(self._settings['identicons']) + self._save_profile() + self.set_active(index) + + def _save_profile(self): + data = self._tox.get_savedata() + self._profile_manager.save_profile(data) + + def _cleanup_contact_data(self, contact): + try: + index = list(map(lambda x: x[0], self._settings['friends_aliases'])).index(contact.tox_id) + del self._settings['friends_aliases'][index] + except: + pass + if contact.tox_id in self._settings['notes']: + del self._settings['notes'][contact.tox_id] + self._settings.save() + self._history.delete_history(contact) + if contact.has_avatar(): + avatar_path = contact.get_contact_avatar_path() + remove(avatar_path) + + def _delete_contact(self, num): + self.set_active(-1 if len(self._contacts) == 1 else 0) + + self._contact_provider.remove_contact_from_cache(self._contacts[num].tox_id) + del self._contacts[num] + self._screen.friends_list.takeItem(num) + self._save_profile() + + self.update_filtration() diff --git a/toxygen/contacts/friend.py b/toxygen/contacts/friend.py new file mode 100644 index 0000000..5c8eabb --- /dev/null +++ b/toxygen/contacts/friend.py @@ -0,0 +1,74 @@ +from contacts import contact, common +from messenger.messages import * +import os +from contacts.contact_menu import * + + +class Friend(contact.Contact): + """ + Friend in list of friends. + """ + + def __init__(self, profile_manager, message_getter, number, name, status_message, widget, tox_id): + super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id) + self._receipts = 0 + self._typing_notification_handler = common.FriendTypingNotificationHandler(number) + + # ----------------------------------------------------------------------------------------------------------------- + # File transfers support + # ----------------------------------------------------------------------------------------------------------------- + + def insert_inline(self, before_message_id, inline): + """ + Update status of active transfer and load inline if needed + """ + try: + tr = list(filter(lambda m: m.message_id == before_message_id, self._corr))[0] + i = self._corr.index(tr) + if inline: # inline was loaded + self._corr.insert(i, inline) + return i - len(self._corr) + except: + pass + + def get_unsent_files(self): + messages = filter(lambda m: type(m) is UnsentFileMessage, self._corr) + return list(messages) + + def clear_unsent_files(self): + self._corr = list(filter(lambda m: type(m) is not UnsentFileMessage, self._corr)) + + def remove_invalid_unsent_files(self): + def is_valid(message): + if type(message) is not UnsentFileMessage: + return True + if message.data is not None: + return True + return os.path.exists(message.path) + + self._corr = list(filter(is_valid, self._corr)) + + def delete_one_unsent_file(self, message_id): + self._corr = list(filter(lambda m: not (type(m) is UnsentFileMessage and m.message_id == message_id), + self._corr)) + + # ----------------------------------------------------------------------------------------------------------------- + # Full status + # ----------------------------------------------------------------------------------------------------------------- + + def get_full_status(self): + return self._status_message + + # ----------------------------------------------------------------------------------------------------------------- + # Typing notifications + # ----------------------------------------------------------------------------------------------------------------- + + def get_typing_notification_handler(self): + return self._typing_notification_handler + + # ----------------------------------------------------------------------------------------------------------------- + # Context menu support + # ----------------------------------------------------------------------------------------------------------------- + + def get_context_menu_generator(self): + return FriendMenuGenerator(self) diff --git a/toxygen/contacts/friend_factory.py b/toxygen/contacts/friend_factory.py new file mode 100644 index 0000000..8ebafd6 --- /dev/null +++ b/toxygen/contacts/friend_factory.py @@ -0,0 +1,44 @@ +from contacts.friend import Friend +from common.tox_save import ToxSave + + +class FriendFactory(ToxSave): + + def __init__(self, profile_manager, settings, tox, db, items_factory): + super().__init__(tox) + self._profile_manager = profile_manager + self._settings = settings + self._db = db + self._items_factory = items_factory + + def create_friend_by_public_key(self, public_key): + friend_number = self._tox.friend_by_public_key(public_key) + + return self.create_friend_by_number(friend_number) + + def create_friend_by_number(self, friend_number): + aliases = self._settings['friends_aliases'] + tox_id = self._tox.friend_get_public_key(friend_number) + try: + alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1] + except: + alias = '' + item = self._create_friend_item() + name = alias or self._tox.friend_get_name(friend_number) or tox_id + status_message = self._tox.friend_get_status_message(friend_number) + message_getter = self._db.messages_getter(tox_id) + friend = Friend(self._profile_manager, message_getter, friend_number, name, status_message, item, tox_id) + friend.set_alias(alias) + + return friend + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _create_friend_item(self): + """ + Method-factory + :return: new widget for friend instance + """ + return self._items_factory.create_contact_item() diff --git a/toxygen/contacts/group_chat.py b/toxygen/contacts/group_chat.py new file mode 100644 index 0000000..19ebc8e --- /dev/null +++ b/toxygen/contacts/group_chat.py @@ -0,0 +1,137 @@ +from contacts import contact +from contacts.contact_menu import GroupMenuGenerator +import utils.util as util +from groups.group_peer import GroupChatPeer +from wrapper import toxcore_enums_and_consts as constants +from common.tox_save import ToxSave +from groups.group_ban import GroupBan + + +class GroupChat(contact.Contact, ToxSave): + + def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id, is_private): + super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id) + ToxSave.__init__(self, tox) + + self._is_private = is_private + self._password = str() + self._peers_limit = 512 + self._peers = [] + self._add_self_to_gc() + + def remove_invalid_unsent_files(self): + pass + + def get_context_menu_generator(self): + return GroupMenuGenerator(self) + + # ----------------------------------------------------------------------------------------------------------------- + # Properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_is_private(self): + return self._is_private + + def set_is_private(self, is_private): + self._is_private = is_private + + is_private = property(get_is_private, set_is_private) + + def get_password(self): + return self._password + + def set_password(self, password): + self._password = password + + password = property(get_password, set_password) + + def get_peers_limit(self): + return self._peers_limit + + def set_peers_limit(self, peers_limit): + self._peers_limit = peers_limit + + peers_limit = property(get_peers_limit, set_peers_limit) + + # ----------------------------------------------------------------------------------------------------------------- + # Peers methods + # ----------------------------------------------------------------------------------------------------------------- + + def get_self_peer(self): + return self._peers[0] + + def get_self_name(self): + return self._peers[0].name + + def get_self_role(self): + return self._peers[0].role + + def is_self_moderator_or_founder(self): + return self.get_self_role() <= constants.TOX_GROUP_ROLE['MODERATOR'] + + def is_self_founder(self): + return self.get_self_role() == constants.TOX_GROUP_ROLE['FOUNDER'] + + def add_peer(self, peer_id, is_current_user=False): + peer = GroupChatPeer(peer_id, + self._tox.group_peer_get_name(self._number, peer_id), + self._tox.group_peer_get_status(self._number, peer_id), + self._tox.group_peer_get_role(self._number, peer_id), + self._tox.group_peer_get_public_key(self._number, peer_id), + is_current_user) + self._peers.append(peer) + + def remove_peer(self, peer_id): + if peer_id == self.get_self_peer().id: # we were kicked or banned + self.remove_all_peers_except_self() + else: + peer = self.get_peer_by_id(peer_id) + self._peers.remove(peer) + + def get_peer_by_id(self, peer_id): + peers = list(filter(lambda p: p.id == peer_id, self._peers)) + + return peers[0] + + def get_peer_by_public_key(self, public_key): + peers = list(filter(lambda p: p.public_key == public_key, self._peers)) + + return peers[0] + + def remove_all_peers_except_self(self): + self._peers = self._peers[:1] + + def get_peers_names(self): + peers_names = map(lambda p: p.name, self._peers) + + return list(peers_names) + + def get_peers(self): + return self._peers[:] + + peers = property(get_peers) + + def get_bans(self): + ban_ids = self._tox.group_ban_get_list(self._number) + bans = [] + for ban_id in ban_ids: + ban = GroupBan(ban_id, + self._tox.group_ban_get_target(self._number, ban_id), + self._tox.group_ban_get_time_set(self._number, ban_id)) + bans.append(ban) + + return bans + + bans = property(get_bans) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def _get_default_avatar_path(): + return util.join_path(util.get_images_directory(), 'group.png') + + def _add_self_to_gc(self): + peer_id = self._tox.group_self_get_peer_id(self._number) + self.add_peer(peer_id, True) diff --git a/toxygen/contacts/group_factory.py b/toxygen/contacts/group_factory.py new file mode 100644 index 0000000..4083438 --- /dev/null +++ b/toxygen/contacts/group_factory.py @@ -0,0 +1,53 @@ +from contacts.group_chat import GroupChat +from common.tox_save import ToxSave +import wrapper.toxcore_enums_and_consts as constants + + +class GroupFactory(ToxSave): + + def __init__(self, profile_manager, settings, tox, db, items_factory): + super().__init__(tox) + self._profile_manager = profile_manager + self._settings = settings + self._db = db + self._items_factory = items_factory + + def create_group_by_public_key(self, public_key): + group_number = self._get_group_number_by_chat_id(public_key) + + return self.create_group_by_number(group_number) + + def create_group_by_number(self, group_number): + aliases = self._settings['friends_aliases'] + tox_id = self._tox.group_get_chat_id(group_number) + try: + alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1] + except: + alias = '' + item = self._create_group_item() + name = alias or self._tox.group_get_name(group_number) or tox_id + status_message = self._tox.group_get_topic(group_number) + message_getter = self._db.messages_getter(tox_id) + is_private = self._tox.group_get_privacy_state(group_number) == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE'] + group = GroupChat(self._tox, self._profile_manager, message_getter, group_number, name, status_message, + item, tox_id, is_private) + group.set_alias(alias) + + return group + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _create_group_item(self): + """ + Method-factory + :return: new widget for group instance + """ + return self._items_factory.create_contact_item() + + def _get_group_number_by_chat_id(self, chat_id): + for i in range(self._tox.group_get_number_groups()): + if self._tox.group_get_chat_id(i) == chat_id: + return i + return -1 diff --git a/toxygen/contacts/group_peer_contact.py b/toxygen/contacts/group_peer_contact.py new file mode 100644 index 0000000..8854198 --- /dev/null +++ b/toxygen/contacts/group_peer_contact.py @@ -0,0 +1,20 @@ +import contacts.contact +from contacts.contact_menu import GroupPeerMenuGenerator + + +class GroupPeerContact(contacts.contact.Contact): + + def __init__(self, profile_manager, message_getter, peer_number, name, widget, tox_id, group_pk): + super().__init__(profile_manager, message_getter, peer_number, name, str(), widget, tox_id) + self._group_pk = group_pk + + def get_group_pk(self): + return self._group_pk + + group_pk = property(get_group_pk) + + def remove_invalid_unsent_files(self): + pass + + def get_context_menu_generator(self): + return GroupPeerMenuGenerator(self) diff --git a/toxygen/contacts/group_peer_factory.py b/toxygen/contacts/group_peer_factory.py new file mode 100644 index 0000000..38b3a20 --- /dev/null +++ b/toxygen/contacts/group_peer_factory.py @@ -0,0 +1,23 @@ +from common.tox_save import ToxSave +from contacts.group_peer_contact import GroupPeerContact + + +class GroupPeerFactory(ToxSave): + + def __init__(self, tox, profile_manager, db, items_factory): + super().__init__(tox) + self._profile_manager = profile_manager + self._db = db + self._items_factory = items_factory + + def create_group_peer(self, group, peer): + item = self._create_group_peer_item() + message_getter = self._db.messages_getter(peer.public_key) + group_peer_contact = GroupPeerContact(self._profile_manager, message_getter, peer.id, peer.name, + item, peer.public_key, group.tox_id) + group_peer_contact.status = peer.status + + return group_peer_contact + + def _create_group_peer_item(self): + return self._items_factory.create_contact_item() diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py new file mode 100644 index 0000000..81220af --- /dev/null +++ b/toxygen/contacts/profile.py @@ -0,0 +1,87 @@ +from contacts import basecontact +import random +import threading +import common.tox_save as tox_save +from middleware.threads import invoke_in_main_thread + + +class Profile(basecontact.BaseContact, tox_save.ToxSave): + """ + Profile of current toxygen user. + """ + def __init__(self, profile_manager, tox, screen, contacts_provider, reset_action): + """ + :param tox: tox instance + :param screen: ref to main screen + """ + basecontact.BaseContact.__init__(self, + profile_manager, + tox.self_get_name(), + tox.self_get_status_message(), + screen, + tox.self_get_address()) + tox_save.ToxSave.__init__(self, tox) + self._screen = screen + self._messages = screen.messages + self._contacts_provider = contacts_provider + self._reset_action = reset_action + self._waiting_for_reconnection = False + self._timer = None + + # ----------------------------------------------------------------------------------------------------------------- + # Edit current user's data + # ----------------------------------------------------------------------------------------------------------------- + + def change_status(self): + """ + Changes status of user (online, away, busy) + """ + if self._status is not None: + self.set_status((self._status + 1) % 3) + + def set_status(self, status): + super().set_status(status) + if status is not None: + self._tox.self_set_status(status) + elif not self._waiting_for_reconnection: + self._waiting_for_reconnection = True + self._timer = threading.Timer(50, self._reconnect) + self._timer.start() + + def set_name(self, value): + if self.name == value: + return + super().set_name(value) + self._tox.self_set_name(self._name) + + def set_status_message(self, value): + super().set_status_message(value) + self._tox.self_set_status_message(self._status_message) + + def set_new_nospam(self): + """Sets new nospam part of tox id""" + self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32 + self._tox_id = self._tox.self_get_address() + + return self._tox_id + + # ----------------------------------------------------------------------------------------------------------------- + # Reset + # ----------------------------------------------------------------------------------------------------------------- + + def restart(self): + """ + Recreate tox instance + """ + self.status = None + invoke_in_main_thread(self._reset_action) + + def _reconnect(self): + self._waiting_for_reconnection = False + contacts = self._contacts_provider.get_all_friends() + all_friends_offline = all(list(map(lambda x: x.status is None, contacts))) + if self.status is None or (all_friends_offline and len(contacts)): + self._waiting_for_reconnection = True + self.restart() + self._timer = threading.Timer(50, self._reconnect) + self._timer.start() diff --git a/toxygen/file_transfers/__init__.py b/toxygen/file_transfers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/file_transfers/file_transfers.py b/toxygen/file_transfers/file_transfers.py new file mode 100644 index 0000000..0f04e5b --- /dev/null +++ b/toxygen/file_transfers/file_transfers.py @@ -0,0 +1,351 @@ +from wrapper.toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL +from os.path import basename, getsize, exists, dirname +from os import remove, rename, chdir +from time import time +from wrapper.tox import Tox +from common.event import Event +from middleware.threads import invoke_in_main_thread + + +FILE_TRANSFER_STATE = { + 'RUNNING': 0, + 'PAUSED_BY_USER': 1, + 'CANCELLED': 2, + 'FINISHED': 3, + 'PAUSED_BY_FRIEND': 4, + 'INCOMING_NOT_STARTED': 5, + 'OUTGOING_NOT_STARTED': 6, + 'UNSENT': 7 +} + +ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6) + +PAUSED_FILE_TRANSFERS = (1, 4, 5, 6) + +DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6) + +SHOW_PROGRESS_BAR = (0, 1, 4) + + +def is_inline(file_name): + allowed_inlines = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png') + + return file_name in allowed_inlines or file_name.startswith('qTox_Image_') + + +class FileTransfer: + """ + 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 = FILE_TRANSFER_STATE['RUNNING'] + self._file_number = file_number + self._creation_time = None + self._size = float(size) + self._done = 0 + self._state_changed_event = Event() + self._finished_event = Event() + self._file_id = self._file = None + + def set_state_changed_handler(self, handler): + self._state_changed_event += lambda *args: invoke_in_main_thread(handler, *args) + + def set_transfer_finished_handler(self, handler): + self._finished_event += lambda *args: invoke_in_main_thread(handler, *args) + + def get_file_number(self): + return self._file_number + + file_number = property(get_file_number) + + def get_state(self): + return self._state + + def set_state(self, value): + self._state = value + self._signal() + + state = property(get_state, set_state) + + def get_friend_number(self): + return self._friend_number + + friend_number = property(get_friend_number) + + def get_file_id(self): + return self._file_id + + file_id = property(get_file_id) + + def get_path(self): + return self._path + + path = property(get_path) + + def get_size(self): + return self._size + + size = property(get_size) + + def cancel(self): + self.send_control(TOX_FILE_CONTROL['CANCEL']) + if self._file is not None: + self._file.close() + self._signal() + + def cancelled(self): + if self._file is not None: + self._file.close() + self.set_state(FILE_TRANSFER_STATE['CANCELLED']) + + def pause(self, by_friend): + if not by_friend: + self.send_control(TOX_FILE_CONTROL['PAUSE']) + else: + self.set_state(FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']) + + def send_control(self, control): + if self._tox.file_control(self._friend_number, self._file_number, control): + self.set_state(control) + + def get_file_id(self): + return self._tox.file_get_file_id(self._friend_number, self._file_number) + + def _signal(self): + percentage = self._done / self._size if self._size else 0 + if self._creation_time is None or not percentage: + t = -1 + else: + t = ((time() - self._creation_time) / percentage) * (1 - percentage) + self._state_changed_event(self.state, percentage, int(t)) + + def _finished(self): + self._finished_event(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: + fl = open(path, 'rb') + size = getsize(path) + else: + fl = None + size = 0 + super().__init__(path, tox, friend_number, size) + self._file = fl + self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] + self._file_number = tox.file_send(friend_number, kind, size, file_id, + bytes(basename(path), 'utf-8') if path else b'') + self._file_id = self.get_file_id() + + def send_chunk(self, position, size): + """ + Send chunk + :param position: start position in file + :param size: chunk max size + """ + if self._creation_time is None: + self._creation_time = time() + 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 + self._signal() + else: + if self._file is not None: + self._file.close() + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() + + +class SendAvatar(SendTransfer): + """ + Send avatar to friend. Doesn't need file transfer item + """ + + def __init__(self, path, tox, friend_number): + if path is None: + avatar_hash = None + else: + with open(path, 'rb') as fl: + avatar_hash = Tox.hash(fl.read()) + super().__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], avatar_hash) + + +class SendFromBuffer(FileTransfer): + """ + Send inline image + """ + + def __init__(self, tox, friend_number, data, file_name): + super().__init__(None, tox, friend_number, len(data)) + self.state = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] + self._data = data + self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], + len(data), None, bytes(file_name, 'utf-8')) + + def get_data(self): + return self._data + + data = property(get_data) + + def send_chunk(self, position, size): + if self._creation_time is None: + self._creation_time = time() + if size: + data = self._data[position:position + size] + self._tox.file_send_chunk(self._friend_number, self._file_number, position, data) + self._done += size + else: + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() + self._signal() + + +class SendFromFileBuffer(SendTransfer): + + def __init__(self, *args): + super().__init__(*args) + + def send_chunk(self, position, size): + super().send_chunk(position, size) + if not size: + chdir(dirname(self._path)) + remove(self._path) + +# ----------------------------------------------------------------------------------------------------------------- +# Receive file +# ----------------------------------------------------------------------------------------------------------------- + + +class ReceiveTransfer(FileTransfer): + + def __init__(self, path, tox, friend_number, size, file_number, position=0): + super().__init__(path, tox, friend_number, size, file_number) + self._file = open(self._path, 'wb') + self._file_size = position + self._file.truncate(position) + self._missed = set() + self._file_id = self.get_file_id() + self._done = position + + def cancel(self): + super().cancel() + remove(self._path) + + def total_size(self): + self._missed.add(self._file_size) + + return min(self._missed) + + def write_chunk(self, position, data): + """ + Incoming chunk + :param position: position in file to save data + :param data: raw data (string) + """ + if self._creation_time is None: + self._creation_time = time() + if data is None: + self._file.close() + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() + else: + data = bytearray(data) + if self._file_size < position: + self._file.seek(0, 2) + self._file.write(b'\0' * (position - self._file_size)) + self._missed.add(self._file_size) + else: + self._missed.discard(position) + self._file.seek(position) + self._file.write(data) + l = len(data) + if position + l > self._file_size: + self._file_size = position + l + self._done += l + self._signal() + + +class ReceiveToBuffer(FileTransfer): + """ + Inline image - save in buffer not in file system + """ + + def __init__(self, tox, friend_number, size, file_number): + super().__init__(None, tox, friend_number, size, file_number) + self._data = bytes() + self._data_size = 0 + + def get_data(self): + return self._data + + data = property(get_data) + + def write_chunk(self, position, data): + if self._creation_time is None: + self._creation_time = time() + if data is None: + self.state = FILE_TRANSFER_STATE['FINISHED'] + self._finished() + else: + data = bytes(data) + l = len(data) + if self._data_size < position: + self._data += (b'\0' * (position - self._data_size)) + self._data = self._data[:position] + data + self._data[position + l:] + if position + l > self._data_size: + self._data_size = position + l + self._done += l + self._signal() + + +class ReceiveAvatar(ReceiveTransfer): + """ + Get friend's avatar. Doesn't need file transfer item + """ + MAX_AVATAR_SIZE = 512 * 1024 + + def __init__(self, path, tox, friend_number, size, file_number): + full_path = path + '.tmp' + super().__init__(full_path, tox, friend_number, size, file_number) + if size > self.MAX_AVATAR_SIZE: + self.send_control(TOX_FILE_CONTROL['CANCEL']) + self._file.close() + remove(full_path) + elif not size: + self.send_control(TOX_FILE_CONTROL['CANCEL']) + self._file.close() + remove(full_path) + elif exists(path): + hash = self.get_file_id() + with open(path, 'rb') as fl: + data = fl.read() + existing_hash = Tox.hash(data) + if hash == existing_hash: + self.send_control(TOX_FILE_CONTROL['CANCEL']) + self._file.close() + remove(full_path) + else: + self.send_control(TOX_FILE_CONTROL['RESUME']) + else: + self.send_control(TOX_FILE_CONTROL['RESUME']) + + def write_chunk(self, position, data): + if data is None: + avatar_path = self._path[:-4] + if exists(avatar_path): + chdir(dirname(avatar_path)) + remove(avatar_path) + rename(self._path, avatar_path) + super().write_chunk(position, data) diff --git a/toxygen/file_transfers/file_transfers_handler.py b/toxygen/file_transfers/file_transfers_handler.py new file mode 100644 index 0000000..114383b --- /dev/null +++ b/toxygen/file_transfers/file_transfers_handler.py @@ -0,0 +1,304 @@ +from messenger.messages import * +from ui.contact_items import * +import utils.util as util +from common.tox_save import ToxSave + + +class FileTransfersHandler(ToxSave): + + def __init__(self, tox, settings, contact_provider, file_transfers_message_service, profile): + super().__init__(tox) + self._settings = settings + self._contact_provider = contact_provider + self._file_transfers_message_service = file_transfers_message_service + self._file_transfers = {} + # key = (friend number, file number), value - transfer instance + self._paused_file_transfers = dict(settings['paused_file_transfers']) + # key - file id, value: [path, friend number, is incoming, start position] + self._insert_inline_before = {} + # key = (friend number, file number), value - message id + + profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts) + + def stop(self): + self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {} + self._settings.save() + + # ----------------------------------------------------------------------------------------------------------------- + # File transfers support + # ----------------------------------------------------------------------------------------------------------------- + + def incoming_file_transfer(self, friend_number, file_number, size, file_name): + """ + 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 + """ + friend = self._get_friend_by_number(friend_number) + auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends'] + inline = is_inline(file_name) and self._settings['allow_inline'] + file_id = self._tox.file_get_file_id(friend_number, file_number) + accepted = True + if file_id in self._paused_file_transfers: + (path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[file_id] + pos = start_position if os.path.exists(path) else 0 + if pos >= size: + self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) + return + self._tox.file_seek(friend_number, file_number, pos) + self._file_transfers_message_service.add_incoming_transfer_message( + friend, accepted, size, file_name, file_number) + self.accept_transfer(path, friend_number, file_number, size, False, pos) + elif inline and size < 1024 * 1024: + self._file_transfers_message_service.add_incoming_transfer_message( + friend, accepted, size, file_name, file_number) + self.accept_transfer('', friend_number, file_number, size, True) + elif auto: + path = self._settings['auto_accept_path'] or util.curr_directory() + self._file_transfers_message_service.add_incoming_transfer_message( + friend, accepted, size, file_name, file_number) + self.accept_transfer(path + '/' + file_name, friend_number, file_number, size) + else: + accepted = False + self._file_transfers_message_service.add_incoming_transfer_message( + friend, accepted, size, file_name, file_number) + + 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() + if (friend_number, file_number) in self._file_transfers: + del tr + del self._file_transfers[(friend_number, file_number)] + elif not already_cancelled: + self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) + + def cancel_not_started_transfer(self, friend_number, message_id): + self._get_friend_by_number(friend_number).delete_one_unsent_file(message_id) + + def pause_transfer(self, friend_number, file_number, by_friend=False): + """ + Pause transfer with specified data + """ + tr = self._file_transfers[(friend_number, file_number)] + tr.pause(by_friend) + + def resume_transfer(self, friend_number, file_number, by_friend=False): + """ + Resume transfer with specified data + """ + tr = self._file_transfers[(friend_number, file_number)] + if by_friend: + tr.state = FILE_TRANSFER_STATE['RUNNING'] + else: + tr.send_control(TOX_FILE_CONTROL['RESUME']) + + def accept_transfer(self, path, friend_number, file_number, size, inline=False, from_position=0): + """ + :param path: path for saving + :param friend_number: friend number + :param file_number: file number + :param size: file size + :param inline: is inline image + :param from_position: position for start + """ + path = self._generate_valid_path(path, from_position) + friend = self._get_friend_by_number(friend_number) + if not inline: + rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position) + else: + rt = ReceiveToBuffer(self._tox, friend_number, size, file_number) + rt.set_transfer_finished_handler(self.transfer_finished) + message = friend.get_message(lambda m: m.type == MESSAGE_TYPE['FILE_TRANSFER'] + and m.state in (FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'], + FILE_TRANSFER_STATE['RUNNING']) + and m.file_number == file_number) + rt.set_state_changed_handler(message.transfer_updated) + self._file_transfers[(friend_number, file_number)] = rt + rt.send_control(TOX_FILE_CONTROL['RESUME']) + if inline: + self._insert_inline_before[(friend_number, file_number)] = message.message_id + + def send_screenshot(self, data, friend_number): + """ + Send screenshot + :param data: raw data - png format + :param friend_number: friend number + """ + self.send_inline(data, 'toxygen_inline.png', friend_number) + + def send_sticker(self, path, friend_number): + with open(path, 'rb') as fl: + data = fl.read() + self.send_inline(data, 'sticker.png', friend_number) + + def send_inline(self, data, file_name, friend_number, is_resend=False): + friend = self._get_friend_by_number(friend_number) + if friend.status is None and not is_resend: + self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data) + return + elif friend.status is None and is_resend: + raise RuntimeError() + st = SendFromBuffer(self._tox, friend.number, data, file_name) + self._send_file_add_set_handlers(st, friend, file_name, True) + + def send_file(self, path, friend_number, is_resend=False, file_id=None): + """ + Send file to current active friend + :param path: file path + :param friend_number: friend_number + :param is_resend: is 'offline' message + :param file_id: file id of transfer + """ + friend = self._get_friend_by_number(friend_number) + if friend.status is None and not is_resend: + self._file_transfers_message_service.add_unsent_file_message(friend, path, None) + return + elif friend.status is None and is_resend: + print('Error in sending') + return + st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) + file_name = os.path.basename(path) + self._send_file_add_set_handlers(st, friend, file_name) + + def incoming_chunk(self, friend_number, file_number, position, data): + """ + Incoming chunk + """ + self._file_transfers[(friend_number, file_number)].write_chunk(position, data) + + def outgoing_chunk(self, friend_number, file_number, position, size): + """ + Outgoing chunk + """ + self._file_transfers[(friend_number, file_number)].send_chunk(position, size) + + def transfer_finished(self, friend_number, file_number): + transfer = self._file_transfers[(friend_number, file_number)] + t = type(transfer) + if t is ReceiveAvatar: + self._get_friend_by_number(friend_number).load_avatar() + elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image + print('inline') + inline = InlineImageMessage(transfer.data) + message_id = self._insert_inline_before[(friend_number, file_number)] + del self._insert_inline_before[(friend_number, file_number)] + index = self._get_friend_by_number(friend_number).insert_inline(message_id, inline) + self._file_transfers_message_service.add_inline_message(transfer, index) + del self._file_transfers[(friend_number, file_number)] + + def send_files(self, friend_number): + friend = self._get_friend_by_number(friend_number) + friend.remove_invalid_unsent_files() + files = friend.get_unsent_files() + try: + for fl in files: + data, path = fl.data, fl.path + if data is not None: + self.send_inline(data, path, friend_number, True) + else: + self.send_file(path, friend_number, True) + friend.clear_unsent_files() + for key in self._paused_file_transfers.keys(): + (path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[key] + if not os.path.exists(path): + del self._paused_file_transfers[key] + elif ft_friend_number == friend_number and not is_incoming: + self.send_file(path, friend_number, True, key) + del self._paused_file_transfers[key] + except Exception as ex: + print('Exception in file sending: ' + str(ex)) + + def friend_exit(self, friend_number): + for friend_num, file_num in self._file_transfers.keys(): + if friend_num != friend_number: + continue + ft = self._file_transfers[(friend_num, file_num)] + if type(ft) is SendTransfer: + self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, False, -1] + elif type(ft) is ReceiveTransfer and ft.state != FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, True, ft.total_size()] + self.cancel_transfer(friend_num, file_num, True) + + # ----------------------------------------------------------------------------------------------------------------- + # Avatars support + # ----------------------------------------------------------------------------------------------------------------- + + def send_avatar(self, friend_number, avatar_path=None): + """ + :param friend_number: number of friend who should get new avatar + :param avatar_path: path to avatar or None if reset + """ + sa = SendAvatar(avatar_path, self._tox, friend_number) + self._file_transfers[(friend_number, sa.file_number)] = sa + + def incoming_avatar(self, friend_number, file_number, size): + """ + Friend changed avatar + :param friend_number: friend number + :param file_number: file number + :param size: size of avatar or 0 (default avatar) + """ + friend = self._get_friend_by_number(friend_number) + ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number) + if ra.state != FILE_TRANSFER_STATE['CANCELLED']: + self._file_transfers[(friend_number, file_number)] = ra + ra.set_transfer_finished_handler(self.transfer_finished) + elif not size: + friend.reset_avatar(self._settings['identicons']) + + def _send_avatar_to_contacts(self, _): + friends = self._get_all_friends() + for friend in filter(self._is_friend_online, friends): + self.send_avatar(friend.number) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _is_friend_online(self, friend_number): + friend = self._get_friend_by_number(friend_number) + + return friend.status is not None + + def _get_friend_by_number(self, friend_number): + return self._contact_provider.get_friend_by_number(friend_number) + + def _get_all_friends(self): + return self._contact_provider.get_all_friends() + + def _send_file_add_set_handlers(self, st, friend, file_name, inline=False): + st.set_transfer_finished_handler(self.transfer_finished) + file_number = st.get_file_number() + self._file_transfers[(friend.number, file_number)] = st + tm = self._file_transfers_message_service.add_outgoing_transfer_message(friend, st.size, file_name, file_number) + st.set_state_changed_handler(tm.transfer_updated) + if inline: + self._insert_inline_before[(friend.number, file_number)] = tm.message_id + + @staticmethod + def _generate_valid_path(path, from_position): + path, file_name = os.path.split(path) + new_file_name, i = file_name, 1 + if not from_position: + while os.path.isfile(join_path(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 + path = join_path(path, new_file_name) + + return path diff --git a/toxygen/file_transfers/file_transfers_messages_service.py b/toxygen/file_transfers/file_transfers_messages_service.py new file mode 100644 index 0000000..4509183 --- /dev/null +++ b/toxygen/file_transfers/file_transfers_messages_service.py @@ -0,0 +1,78 @@ +from messenger.messenger import * +import utils.util as util +from file_transfers.file_transfers import * + + +class FileTransfersMessagesService: + + def __init__(self, contacts_manager, messages_items_factory, profile, main_screen): + self._contacts_manager = contacts_manager + self._messages_items_factory = messages_items_factory + self._profile = profile + self._messages = main_screen.messages + + def add_incoming_transfer_message(self, friend, accepted, size, file_name, file_number): + author = MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']) + status = FILE_TRANSFER_STATE['RUNNING'] if accepted else FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'] + tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number) + + if self._is_friend_active(friend.number): + self._create_file_transfer_item(tm) + self._messages.scrollToBottom() + else: + friend.actions = True + + friend.append_message(tm) + + return tm + + def add_outgoing_transfer_message(self, friend, size, file_name, file_number): + author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME']) + status = FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] + tm = TransferMessage(author, util.get_unix_time(), status, size, file_name, friend.number, file_number) + + if self._is_friend_active(friend.number): + self._create_file_transfer_item(tm) + self._messages.scrollToBottom() + + friend.append_message(tm) + + return tm + + def add_inline_message(self, transfer, index): + if not self._is_friend_active(transfer.friend_number): + return + count = self._messages.count() + if count + index + 1 >= 0: + self._create_inline_item(transfer.data, count + index + 1) + + def add_unsent_file_message(self, friend, file_path, data): + author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['ME']) + size = os.path.getsize(file_path) if data is None else len(data) + tm = UnsentFileMessage(file_path, data, util.get_unix_time(), author, size, friend.number) + friend.append_message(tm) + + if self._is_friend_active(friend.number): + self._create_unsent_file_item(tm) + self._messages.scrollToBottom() + + return tm + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _is_friend_active(self, friend_number): + if not self._contacts_manager.is_active_a_friend(): + return False + + return friend_number == self._contacts_manager.get_active_number() + + def _create_file_transfer_item(self, tm): + return self._messages_items_factory.create_file_transfer_item(tm) + + def _create_inline_item(self, data, position): + return self._messages_items_factory.create_inline_item(data, False, position) + + def _create_unsent_file_item(self, tm): + return self._messages_items_factory.create_unsent_file_item(tm) diff --git a/toxygen/groups/__init__.py b/toxygen/groups/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/groups/group_ban.py b/toxygen/groups/group_ban.py new file mode 100644 index 0000000..89ecc7e --- /dev/null +++ b/toxygen/groups/group_ban.py @@ -0,0 +1,23 @@ + + +class GroupBan: + + def __init__(self, ban_id, ban_target, ban_time): + self._ban_id = ban_id + self._ban_target = ban_target + self._ban_time = ban_time + + def get_ban_id(self): + return self._ban_id + + ban_id = property(get_ban_id) + + def get_ban_target(self): + return self._ban_target + + ban_target = property(get_ban_target) + + def get_ban_time(self): + return self._ban_time + + ban_time = property(get_ban_time) diff --git a/toxygen/groups/group_invite.py b/toxygen/groups/group_invite.py new file mode 100644 index 0000000..a2eed47 --- /dev/null +++ b/toxygen/groups/group_invite.py @@ -0,0 +1,23 @@ + + +class GroupInvite: + + def __init__(self, friend_public_key, chat_name, invite_data): + self._friend_public_key = friend_public_key + self._chat_name = chat_name + self._invite_data = invite_data[:] + + def get_friend_public_key(self): + return self._friend_public_key + + friend_public_key = property(get_friend_public_key) + + def get_chat_name(self): + return self._chat_name + + chat_name = property(get_chat_name) + + def get_invite_data(self): + return self._invite_data[:] + + invite_data = property(get_invite_data) diff --git a/toxygen/groups/group_peer.py b/toxygen/groups/group_peer.py new file mode 100644 index 0000000..4eaf255 --- /dev/null +++ b/toxygen/groups/group_peer.py @@ -0,0 +1,70 @@ + + +class GroupChatPeer: + """ + Represents peer in group chat. + """ + + def __init__(self, peer_id, name, status, role, public_key, is_current_user=False, is_muted=False): + self._peer_id = peer_id + self._name = name + self._status = status + self._role = role + self._public_key = public_key + self._is_current_user = is_current_user + self._is_muted = is_muted + + # ----------------------------------------------------------------------------------------------------------------- + # Readonly properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_id(self): + return self._peer_id + + id = property(get_id) + + def get_public_key(self): + return self._public_key + + public_key = property(get_public_key) + + def get_is_current_user(self): + return self._is_current_user + + is_current_user = property(get_is_current_user) + + # ----------------------------------------------------------------------------------------------------------------- + # Read-write properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_name(self): + return self._name + + def set_name(self, name): + self._name = name + + name = property(get_name, set_name) + + def get_status(self): + return self._status + + def set_status(self, status): + self._status = status + + status = property(get_status, set_status) + + def get_role(self): + return self._role + + def set_role(self, role): + self._role = role + + role = property(get_role, set_role) + + def get_is_muted(self): + return self._is_muted + + def set_is_muted(self, is_muted): + self._is_muted = is_muted + + is_muted = property(get_is_muted, set_is_muted) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py new file mode 100644 index 0000000..b8fc7cc --- /dev/null +++ b/toxygen/groups/groups_service.py @@ -0,0 +1,242 @@ +import common.tox_save as tox_save +import utils.ui as util_ui +from groups.peers_list import PeersListGenerator +from groups.group_invite import GroupInvite +import wrapper.toxcore_enums_and_consts as constants + + +class GroupsService(tox_save.ToxSave): + + def __init__(self, tox, contacts_manager, contacts_provider, main_screen, widgets_factory_provider): + super().__init__(tox) + self._contacts_manager = contacts_manager + self._contacts_provider = contacts_provider + self._main_screen = main_screen + self._peers_list_widget = main_screen.peers_list + self._widgets_factory_provider = widgets_factory_provider + self._group_invites = [] + self._screen = None + + def set_tox(self, tox): + super().set_tox(tox) + for group in self._get_all_groups(): + group.set_tox(tox) + + # ----------------------------------------------------------------------------------------------------------------- + # Groups creation + # ----------------------------------------------------------------------------------------------------------------- + + def create_new_gc(self, name, privacy_state, nick, status): + group_number = self._tox.group_new(privacy_state, name, nick, status) + if group_number == -1: + return + + self._add_new_group_by_number(group_number) + group = self._get_group_by_number(group_number) + group.status = constants.TOX_USER_STATUS['NONE'] + self._contacts_manager.update_filtration() + + def join_gc_by_id(self, chat_id, password, nick, status): + group_number = self._tox.group_join(chat_id, password, nick, status) + self._add_new_group_by_number(group_number) + + # ----------------------------------------------------------------------------------------------------------------- + # Groups reconnect and leaving + # ----------------------------------------------------------------------------------------------------------------- + + def leave_group(self, group_number): + self._tox.group_leave(group_number) + self._contacts_manager.delete_group(group_number) + + def disconnect_from_group(self, group_number): + self._tox.group_disconnect(group_number) + group = self._get_group_by_number(group_number) + group.status = None + self._clear_peers_list(group) + + def reconnect_to_group(self, group_number): + self._tox.group_reconnect(group_number) + group = self._get_group_by_number(group_number) + group.status = constants.TOX_USER_STATUS['NONE'] + self._clear_peers_list(group) + + # ----------------------------------------------------------------------------------------------------------------- + # Group invites + # ----------------------------------------------------------------------------------------------------------------- + + def invite_friend(self, friend_number, group_number): + self._tox.group_invite_friend(group_number, friend_number) + + def process_group_invite(self, friend_number, group_name, invite_data): + friend = self._get_friend_by_number(friend_number) + invite = GroupInvite(friend.tox_id, group_name, invite_data) + self._group_invites.append(invite) + self._update_invites_button_state() + + def accept_group_invite(self, invite, name, status, password): + pk = invite.friend_public_key + friend = self._get_friend_by_public_key(pk) + self._join_gc_via_invite(invite.invite_data, friend.number, name, status, password) + self._delete_group_invite(invite) + self._update_invites_button_state() + + def decline_group_invite(self, invite): + self._delete_group_invite(invite) + self._main_screen.update_gc_invites_button_state() + + def get_group_invites(self): + return self._group_invites[:] + + group_invites = property(get_group_invites) + + def get_group_invites_count(self): + return len(self._group_invites) + + group_invites_count = property(get_group_invites_count) + + # ----------------------------------------------------------------------------------------------------------------- + # Group info methods + # ----------------------------------------------------------------------------------------------------------------- + + def update_group_info(self, group): + group.name = self._tox.group_get_name(group.number) + group.status_message = self._tox.group_get_topic(group.number) + + def set_group_topic(self, group): + if not group.is_self_moderator_or_founder(): + return + text = util_ui.tr('New topic for group "{}":'.format(group.name)) + title = util_ui.tr('Set group topic') + topic, ok = util_ui.text_dialog(text, title, group.status_message) + if not ok or not topic: + return + self._tox.group_set_topic(group.number, topic) + group.status_message = topic + + def show_group_management_screen(self, group): + widgets_factory = self._get_widgets_factory() + self._screen = widgets_factory.create_group_management_screen(group) + self._screen.show() + + def show_group_settings_screen(self, group): + widgets_factory = self._get_widgets_factory() + self._screen = widgets_factory.create_group_settings_screen(group) + self._screen.show() + + def set_group_password(self, group, password): + if group.password == password: + return + self._tox.group_founder_set_password(group.number, password) + group.password = password + + def set_group_peers_limit(self, group, peers_limit): + if group.peers_limit == peers_limit: + return + self._tox.group_founder_set_peer_limit(group.number, peers_limit) + group.peers_limit = peers_limit + + def set_group_privacy_state(self, group, privacy_state): + is_private = privacy_state == constants.TOX_GROUP_PRIVACY_STATE['PRIVATE'] + if group.is_private == is_private: + return + self._tox.group_founder_set_privacy_state(group.number, privacy_state) + group.is_private = is_private + + # ----------------------------------------------------------------------------------------------------------------- + # Peers list + # ----------------------------------------------------------------------------------------------------------------- + + def generate_peers_list(self): + if not self._contacts_manager.is_active_a_group(): + return + group = self._contacts_manager.get_curr_contact() + PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id) + + def peer_selected(self, chat_id, peer_id): + widgets_factory = self._get_widgets_factory() + group = self._get_group_by_public_key(chat_id) + self_peer = group.get_self_peer() + if self_peer.id != peer_id: + self._screen = widgets_factory.create_peer_screen_window(group, peer_id) + else: + self._screen = widgets_factory.create_self_peer_screen_window(group) + self._screen.show() + + # ----------------------------------------------------------------------------------------------------------------- + # Peers actions + # ----------------------------------------------------------------------------------------------------------------- + + def set_new_peer_role(self, group, peer, role): + self._tox.group_mod_set_role(group.number, peer.id, role) + peer.role = role + self.generate_peers_list() + + def toggle_ignore_peer(self, group, peer, ignore): + self._tox.group_toggle_ignore(group.number, peer.id, ignore) + peer.is_muted = ignore + + def set_self_info(self, group, name, status): + self._tox.group_self_set_name(group.number, name) + self._tox.group_self_set_status(group.number, status) + self_peer = group.get_self_peer() + self_peer.name = name + self_peer.status = status + self.generate_peers_list() + + # ----------------------------------------------------------------------------------------------------------------- + # Bans support + # ----------------------------------------------------------------------------------------------------------------- + + def show_bans_list(self, group): + widgets_factory = self._get_widgets_factory() + self._screen = widgets_factory.create_groups_bans_screen(group) + self._screen.show() + + def ban_peer(self, group, peer_id, ban_type): + self._tox.group_mod_ban_peer(group.number, peer_id, ban_type) + + def kick_peer(self, group, peer_id): + self._tox.group_mod_remove_peer(group.number, peer_id) + + def cancel_ban(self, group_number, ban_id): + self._tox.group_mod_remove_ban(group_number, ban_id) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _add_new_group_by_number(self, group_number): + self._contacts_manager.add_group(group_number) + + def _get_group_by_number(self, group_number): + return self._contacts_provider.get_group_by_number(group_number) + + def _get_group_by_public_key(self, public_key): + return self._contacts_provider.get_group_by_public_key(public_key) + + def _get_all_groups(self): + return self._contacts_provider.get_all_groups() + + def _get_friend_by_number(self, friend_number): + return self._contacts_provider.get_friend_by_number(friend_number) + + def _get_friend_by_public_key(self, public_key): + return self._contacts_provider.get_friend_by_public_key(public_key) + + def _clear_peers_list(self, group): + group.remove_all_peers_except_self() + self.generate_peers_list() + + def _delete_group_invite(self, invite): + if invite in self._group_invites: + self._group_invites.remove(invite) + + def _join_gc_via_invite(self, invite_data, friend_number, nick, status, password): + group_number = self._tox.group_invite_accept(invite_data, friend_number, nick, status, password) + self._add_new_group_by_number(group_number) + + def _update_invites_button_state(self): + self._main_screen.update_gc_invites_button_state() + + def _get_widgets_factory(self): + return self._widgets_factory_provider.get_item() diff --git a/toxygen/groups/peers_list.py b/toxygen/groups/peers_list.py new file mode 100644 index 0000000..17495f5 --- /dev/null +++ b/toxygen/groups/peers_list.py @@ -0,0 +1,104 @@ +from ui.group_peers_list import PeerItem, PeerTypeItem +from wrapper.toxcore_enums_and_consts import * +from ui.widgets import * + + +# ----------------------------------------------------------------------------------------------------------------- +# Builder +# ----------------------------------------------------------------------------------------------------------------- + + +class PeerListBuilder: + + def __init__(self): + self._peers = {} + self._titles = {} + self._index = 0 + self._handler = None + + def with_click_handler(self, handler): + self._handler = handler + + return self + + def with_title(self, title): + self._titles[self._index] = title + self._index += 1 + + return self + + def with_peers(self, peers): + for peer in peers: + self._add_peer(peer) + + return self + + def build(self, list_widget): + list_widget.clear() + + for i in range(self._index): + if i in self._peers: + peer = self._peers[i] + self._add_peer_item(peer, list_widget) + else: + title = self._titles[i] + self._add_peer_type_item(title, list_widget) + + def _add_peer_item(self, peer, parent): + item = PeerItem(peer, self._handler, parent.width(), parent) + self._add_item(parent, item) + + def _add_peer_type_item(self, text, parent): + item = PeerTypeItem(text, parent.width(), parent) + self._add_item(parent, item) + + @staticmethod + def _add_item(parent, item): + elem = QtWidgets.QListWidgetItem(parent) + elem.setSizeHint(QtCore.QSize(parent.width(), item.height())) + parent.addItem(elem) + parent.setItemWidget(elem, item) + + def _add_peer(self, peer): + self._peers[self._index] = peer + self._index += 1 + +# ----------------------------------------------------------------------------------------------------------------- +# Generators +# ----------------------------------------------------------------------------------------------------------------- + + +class PeersListGenerator: + + @staticmethod + def generate(peers_list, groups_service, list_widget, chat_id): + admin_title = util_ui.tr('Administrator') + moderators_title = util_ui.tr('Moderators') + users_title = util_ui.tr('Users') + observers_title = util_ui.tr('Observers') + + admins = list(filter(lambda p: p.role == TOX_GROUP_ROLE['FOUNDER'], peers_list)) + moderators = list(filter(lambda p: p.role == TOX_GROUP_ROLE['MODERATOR'], peers_list)) + users = list(filter(lambda p: p.role == TOX_GROUP_ROLE['USER'], peers_list)) + observers = list(filter(lambda p: p.role == TOX_GROUP_ROLE['OBSERVER'], peers_list)) + + builder = (PeerListBuilder() + .with_click_handler(lambda peer_id: groups_service.peer_selected(chat_id, peer_id))) + if len(admins): + (builder + .with_title(admin_title) + .with_peers(admins)) + if len(moderators): + (builder + .with_title(moderators_title) + .with_peers(moderators)) + if len(users): + (builder + .with_title(users_title) + .with_peers(users)) + if len(observers): + (builder + .with_title(observers_title) + .with_peers(observers)) + + builder.build(list_widget) diff --git a/toxygen/history/__init__.py b/toxygen/history/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/history/database.py b/toxygen/history/database.py new file mode 100644 index 0000000..751c74b --- /dev/null +++ b/toxygen/history/database.py @@ -0,0 +1,201 @@ +from sqlite3 import connect +import os.path +import utils.util as util + + +TIMEOUT = 11 + +SAVE_MESSAGES = 500 + +MESSAGE_AUTHOR = { + 'ME': 0, + 'FRIEND': 1, + 'NOT_SENT': 2, + 'GC_PEER': 3 +} + +CONTACT_TYPE = { + 'FRIEND': 0, + 'GC_PEER': 1, + 'GC_PEER_PRIVATE': 2 +} + + +class Database: + + def __init__(self, path, toxes): + self._path, self._toxes = path, toxes + self._name = os.path.basename(path) + if os.path.exists(path): + try: + with open(path, 'rb') as fin: + data = fin.read() + if toxes.is_data_encrypted(data): + data = toxes.pass_decrypt(data) + with open(path, 'wb') as fout: + fout.write(data) + except Exception as ex: + util.log('Db reading error: ' + str(ex)) + os.remove(path) + + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + + def save(self): + if self._toxes.has_password(): + with open(self._path, 'rb') as fin: + data = fin.read() + data = self._toxes.pass_encrypt(bytes(data)) + with open(self._path, 'wb') as fout: + fout.write(data) + + def export(self, directory): + new_path = util.join_path(directory, self._name) + with open(self._path, 'rb') as fin: + data = fin.read() + if self._toxes.has_password(): + data = self._toxes.pass_encrypt(data) + with open(new_path, 'wb') as fout: + fout.write(data) + + def add_friend_to_db(self, tox_id): + db = self._connect() + try: + cursor = db.cursor() + cursor.execute('CREATE TABLE IF NOT EXISTS id' + tox_id + '(' + ' id INTEGER PRIMARY KEY,' + ' author_name TEXT,' + ' message TEXT,' + ' author_type INTEGER,' + ' unix_time REAL,' + ' message_type INTEGER' + ')') + db.commit() + except: + print('Database is locked!') + db.rollback() + finally: + db.close() + + def delete_friend_from_db(self, tox_id): + db = self._connect() + try: + cursor = db.cursor() + cursor.execute('DROP TABLE id' + tox_id + ';') + db.commit() + except: + print('Database is locked!') + db.rollback() + finally: + db.close() + + def save_messages_to_db(self, tox_id, messages_iter): + db = self._connect() + try: + cursor = db.cursor() + cursor.executemany('INSERT INTO id' + tox_id + + '(message, author_name, author_type, unix_time, message_type) ' + + 'VALUES (?, ?, ?, ?, ?, ?);', messages_iter) + db.commit() + except: + print('Database is locked!') + db.rollback() + finally: + db.close() + + def update_messages(self, tox_id, message_id): + db = self._connect() + try: + cursor = db.cursor() + cursor.execute('UPDATE id' + tox_id + ' SET author = 0 ' + 'WHERE id = ' + str(message_id) + ' AND author = 2;') + db.commit() + except: + print('Database is locked!') + db.rollback() + finally: + db.close() + + def delete_message(self, tox_id, unique_id): + db = self._connect() + try: + cursor = db.cursor() + cursor.execute('DELETE FROM id' + tox_id + ' WHERE id = ' + str(unique_id) + ';') + db.commit() + except: + print('Database is locked!') + db.rollback() + finally: + db.close() + + def delete_messages(self, tox_id): + db = self._connect() + try: + cursor = db.cursor() + cursor.execute('DELETE FROM id' + tox_id + ';') + db.commit() + except: + print('Database is locked!') + db.rollback() + finally: + db.close() + + def messages_getter(self, tox_id): + self.add_friend_to_db(tox_id) + + return Database.MessageGetter(self._path, tox_id) + + # ----------------------------------------------------------------------------------------------------------------- + # Messages loading + # ----------------------------------------------------------------------------------------------------------------- + + class MessageGetter: + + def __init__(self, path, tox_id): + self._count = 0 + self._path = path + self._tox_id = tox_id + self._db = self._cursor = None + + def get_one(self): + return self.get(1) + + def get_all(self): + self._connect() + data = self._cursor.fetchall() + self._disconnect() + self._count = len(data) + return data + + def get(self, count): + self._connect() + self.skip() + data = self._cursor.fetchmany(count) + self._disconnect() + self._count += len(data) + return data + + def skip(self): + if self._count: + self._cursor.fetchmany(self._count) + + def delete_one(self): + if self._count: + self._count -= 1 + + def _connect(self): + self._db = connect(self._path, timeout=TIMEOUT) + self._cursor = self._db.cursor() + self._cursor.execute('SELECT message, author_type, author_name, unix_time, message_type, id FROM id' + + self._tox_id + ' ORDER BY unix_time DESC;') + + def _disconnect(self): + self._db.close() + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _connect(self): + return connect(self._path, timeout=TIMEOUT) diff --git a/toxygen/history/history.py b/toxygen/history/history.py new file mode 100644 index 0000000..bd7e353 --- /dev/null +++ b/toxygen/history/history.py @@ -0,0 +1,138 @@ +from history.history_logs_generators import * + + +class History: + + def __init__(self, contact_provider, db, settings, main_screen, messages_items_factory): + self._contact_provider = contact_provider + self._db = db + self._settings = settings + self._messages = main_screen.messages + self._messages_items_factory = messages_items_factory + self._is_loading = False + self._contacts_manager = None + + def __del__(self): + del self._db + + def set_contacts_manager(self, contacts_manager): + self._contacts_manager = contacts_manager + + # ----------------------------------------------------------------------------------------------------------------- + # History support + # ----------------------------------------------------------------------------------------------------------------- + + def save_history(self): + """ + Save history to db + """ + if self._settings['save_db']: + for friend in self._contact_provider.get_all_friends(): + self._db.add_friend_to_db(friend.tox_id) + if not self._settings['save_unsent_only']: + messages = friend.get_corr_for_saving() + else: + messages = friend.get_unsent_messages_for_saving() + self._db.delete_messages(friend.tox_id) + messages = map(lambda m: (m.text, m.author.name, m.author.type, m.time, m.type), messages) + self._db.save_messages_to_db(friend.tox_id, messages) + + self._db.save() + + def clear_history(self, friend, save_unsent=False): + """ + Clear chat history + """ + friend.clear_corr(save_unsent) + self._db.delete_friend_from_db(friend.tox_id) + + def export_history(self, contact, as_text=True): + extension = 'txt' if as_text else 'html' + file_name, _ = util_ui.save_file_dialog(util_ui.tr('Choose file name'), extension) + + if not file_name: + return + + if not file_name.endswith('.' + extension): + file_name += '.' + extension + + history = self.generate_history(contact, as_text) + with open(file_name, 'wt') as fl: + fl.write(history) + + def delete_message(self, message): + contact = self._contacts_manager.get_curr_contact() + if message.type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): + if message.is_saved(): + self._db.delete_message(contact.tox_id, message.id) + contact.delete_message(message.message_id) + + def load_history(self, friend): + """ + Tries to load next part of messages + """ + if self._is_loading: + return + self._is_loading = True + friend.load_corr(False) + messages = friend.get_corr() + if not messages: + self._is_loading = False + return + messages.reverse() + messages = messages[self._messages.count():self._messages.count() + PAGE_SIZE] + for message in messages: + message_type = message.get_type() + if message_type in (MESSAGE_TYPE['TEXT'], MESSAGE_TYPE['ACTION']): # text message + self._create_message_item(message) + elif message_type == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer + if message.state == FILE_TRANSFER_STATE['UNSENT']: + self._create_unsent_file_item(message) + else: + self._create_file_transfer_item(message) + elif message_type == MESSAGE_TYPE['INLINE']: # inline image + self._create_inline_item(message) + else: # info message + self._create_message_item(message) + self._is_loading = False + + def get_message_getter(self, friend_public_key): + self._db.add_friend_to_db(friend_public_key) + + return self._db.messages_getter(friend_public_key) + + def delete_history(self, friend): + self._db.delete_friend_from_db(friend.tox_id) + + def add_friend_to_db(self, tox_id): + self._db.add_friend_to_db(tox_id) + + @staticmethod + def generate_history(contact, as_text=True, _range=None): + if _range is None: + contact.load_all_corr() + corr = contact.get_corr() + elif _range[1] + 1: + corr = contact.get_corr()[_range[0]:_range[1] + 1] + else: + corr = contact.get_corr()[_range[0]:] + + generator = TextHistoryGenerator(corr, contact.name) if as_text else HtmlHistoryGenerator(corr, contact.name) + + return generator.generate() + + # ----------------------------------------------------------------------------------------------------------------- + # Items creation + # ----------------------------------------------------------------------------------------------------------------- + + def _create_message_item(self, message): + return self._messages_items_factory.create_message_item(message, False) + + def _create_unsent_file_item(self, message): + return self._messages_items_factory.create_unsent_file_item(message, False) + + def _create_file_transfer_item(self, message): + return self._messages_items_factory.create_file_transfer_item(message, False) + + def _create_inline_item(self, message): + return self._messages_items_factory.create_inline_item(message, False) diff --git a/toxygen/history/history_logs_generators.py b/toxygen/history/history_logs_generators.py new file mode 100644 index 0000000..b8d0a56 --- /dev/null +++ b/toxygen/history/history_logs_generators.py @@ -0,0 +1,48 @@ +from messenger.messages import * +import utils.util as util + + +class HistoryLogsGenerator: + + def __init__(self, history, contact_name): + self._history = history + self._contact_name = contact_name + + def generate(self): + return str() + + @staticmethod + def _get_message_time(message): + return util.convert_time(message.time) if message.author.type != MESSAGE_AUTHOR['NOT_SENT'] else 'Unsent' + + +class HtmlHistoryGenerator(HistoryLogsGenerator): + + def __init__(self, history, contact_name): + super().__init__(history, contact_name) + + def generate(self): + arr = [] + for message in self._history: + if type(message) is TextMessage: + x = '[{}] {}: {}
' + arr.append(x.format(self._get_message_time(message), message.author.name, message.text)) + s = '
'.join(arr) + html = '{}{}' + + return html.format(self._contact_name, s) + + +class TextHistoryGenerator(HistoryLogsGenerator): + + def __init__(self, history, contact_name): + super().__init__(history, contact_name) + + def generate(self): + arr = [self._contact_name] + for message in self._history: + if type(message) is TextMessage: + x = '[{}] {}: {}\n' + arr.append(x.format(self._get_message_time(message), message.author.name, message.text)) + + return '\n'.join(arr) diff --git a/toxygen/messenger/__init__.py b/toxygen/messenger/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py new file mode 100644 index 0000000..e777c4b --- /dev/null +++ b/toxygen/messenger/messages.py @@ -0,0 +1,239 @@ +from history.database import MESSAGE_AUTHOR +import os.path +from ui.messages_widgets import * + + +MESSAGE_TYPE = { + 'TEXT': 0, + 'ACTION': 1, + 'FILE_TRANSFER': 2, + 'INLINE': 3, + 'INFO_MESSAGE': 4 +} + +PAGE_SIZE = 42 + + +class MessageAuthor: + + def __init__(self, author_name, author_type): + self._name = author_name + self._type = author_type + + def get_name(self): + return self._name + + name = property(get_name) + + def get_type(self): + return self._type + + def set_type(self, value): + self._type = value + + type = property(get_type, set_type) + + +class Message: + + MESSAGE_ID = 0 + + def __init__(self, message_type, author, time): + self._time = time + self._type = message_type + self._author = author + self._widget = None + self._message_id = self._get_id() + + def get_type(self): + return self._type + + type = property(get_type) + + def get_author(self): + return self._author + + author = property(get_author) + + def get_time(self): + return self._time + + time = property(get_time) + + def get_message_id(self): + return self._message_id + + message_id = property(get_message_id) + + def get_widget(self, *args): + self._widget = self._create_widget(*args) + + return self._widget + + widget = property(get_widget) + + def remove_widget(self): + self._widget = None + + def mark_as_sent(self): + self._author.type = MESSAGE_AUTHOR['ME'] + if self._widget is not None: + self._widget.mark_as_sent() + + def _create_widget(self, *args): + pass + + @staticmethod + def _get_id(): + Message.MESSAGE_ID += 1 + + return int(Message.MESSAGE_ID) + + +class TextMessage(Message): + """ + Plain text or action message + """ + + def __init__(self, message, owner, time, message_type, message_id=0): + super().__init__(message_type, owner, time) + self._message = message + self._id = message_id + + def get_text(self): + return self._message + + text = property(get_text) + + def get_id(self): + return self._id + + id = property(get_id) + + def is_saved(self): + return self._id > 0 + + def _create_widget(self, *args): + return MessageItem(self, *args) + + +class OutgoingTextMessage(TextMessage): + + def __init__(self, message, owner, time, message_type, tox_message_id=0): + super().__init__(message, owner, time, message_type) + self._tox_message_id = tox_message_id + + def get_tox_message_id(self): + return self._tox_message_id + + def set_tox_message_id(self, tox_message_id): + self._tox_message_id = tox_message_id + + tox_message_id = property(get_tox_message_id, set_tox_message_id) + + +class GroupChatMessage(TextMessage): + + def __init__(self, id, message, owner, time, message_type, name): + super().__init__(id, message, owner, time, message_type) + self._user_name = name + + +class TransferMessage(Message): + """ + Message with info about file transfer + """ + + def __init__(self, author, time, state, size, file_name, friend_number, file_number): + super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], author, time) + self._state = state + self._size = size + self._file_name = file_name + self._friend_number, self._file_number = friend_number, file_number + + def is_active(self, file_number): + if self._file_number != file_number: + return False + + return self._state not in (FILE_TRANSFER_STATE['FINISHED'], FILE_TRANSFER_STATE['CANCELLED']) + + def get_friend_number(self): + return self._friend_number + + friend_number = property(get_friend_number) + + def get_file_number(self): + return self._file_number + + file_number = property(get_file_number) + + def get_state(self): + return self._state + + def set_state(self, value): + self._state = value + + state = property(get_state, set_state) + + def get_size(self): + return self._size + + size = property(get_size) + + def get_file_name(self): + return self._file_name + + file_name = property(get_file_name) + + def transfer_updated(self, state, percentage, time): + self._state = state + if self._widget is not None: + self._widget.update_transfer_state(state, percentage, time) + + def _create_widget(self, *args): + return FileTransferItem(self, *args) + + +class UnsentFileMessage(TransferMessage): + + def __init__(self, path, data, time, author, size, friend_number): + file_name = os.path.basename(path) + super().__init__(author, time, FILE_TRANSFER_STATE['UNSENT'], size, file_name, friend_number, -1) + self._data, self._path = data, path + + def get_data(self): + return self._data + + data = property(get_data) + + def get_path(self): + return self._path + + path = property(get_path) + + def _create_widget(self, *args): + return UnsentFileItem(self, *args) + + +class InlineImageMessage(Message): + """ + Inline image + """ + + def __init__(self, data): + super().__init__(MESSAGE_TYPE['INLINE'], None, None) + self._data = data + + def get_data(self): + return self._data + + data = property(get_data) + + def _create_widget(self, *args): + return InlineImageItem(self, *args) + + +class InfoMessage(TextMessage): + + def __init__(self, message, time): + super().__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) diff --git a/toxygen/messenger/messenger.py b/toxygen/messenger/messenger.py new file mode 100644 index 0000000..e859135 --- /dev/null +++ b/toxygen/messenger/messenger.py @@ -0,0 +1,310 @@ +import common.tox_save as tox_save +from messenger.messages import * + + +class Messenger(tox_save.ToxSave): + + def __init__(self, tox, plugin_loader, screen, contacts_manager, contacts_provider, items_factory, profile, + calls_manager): + super().__init__(tox) + self._plugin_loader = plugin_loader + self._screen = screen + self._contacts_manager = contacts_manager + self._contacts_provider = contacts_provider + self._items_factory = items_factory + self._profile = profile + self._profile_name = profile.name + + profile.name_changed_event.add_callback(self._on_profile_name_changed) + calls_manager.call_started_event.add_callback(self._on_call_started) + calls_manager.call_finished_event.add_callback(self._on_call_finished) + + def get_last_message(self): + contact = self._contacts_manager.get_curr_contact() + if contact is None: + return str() + + return contact.get_last_message_text() + + # ----------------------------------------------------------------------------------------------------------------- + # Messaging - friends + # ----------------------------------------------------------------------------------------------------------------- + + def new_message(self, friend_number, message_type, message): + """ + Current user gets new message + :param friend_number: friend_num of friend who sent message + :param message_type: message type - plain text or action message (/me) + :param message: text of message + """ + t = util.get_unix_time() + friend = self._get_friend_by_number(friend_number) + text_message = TextMessage(message, MessageAuthor(friend.name, MESSAGE_AUTHOR['FRIEND']), t, message_type) + self._add_message(text_message, friend) + + def send_message(self): + text = self._screen.messageEdit.toPlainText() + + plugin_command_prefix = '/plugin ' + if text.startswith(plugin_command_prefix): + self._plugin_loader.command(text[len(plugin_command_prefix):]) + self._screen.messageEdit.clear() + return + + action_message_prefix = '/me ' + if text.startswith(action_message_prefix): + message_type = TOX_MESSAGE_TYPE['ACTION'] + text = text[len(action_message_prefix):] + else: + message_type = TOX_MESSAGE_TYPE['NORMAL'] + + if self._contacts_manager.is_active_a_friend(): + self.send_message_to_friend(text, message_type) + elif self._contacts_manager.is_active_a_group(): + self.send_message_to_group(text, message_type) + elif self._contacts_manager.is_active_a_group_chat_peer(): + self.send_message_to_group_peer(text, message_type) + + def send_message_to_friend(self, text, message_type, friend_number=None): + """ + Send message + :param text: message text + :param friend_number: number of friend + """ + if friend_number is None: + friend_number = self._contacts_manager.get_active_number() + + if not text or friend_number < 0: + return + + friend = self._get_friend_by_number(friend_number) + messages = self._split_message(text.encode('utf-8')) + t = util.get_unix_time() + for message in messages: + if friend.status is not None: + message_id = self._tox.friend_send_message(friend_number, message_type, message) + else: + message_id = 0 + message_author = MessageAuthor(self._profile.name, MESSAGE_AUTHOR['NOT_SENT']) + message = OutgoingTextMessage(text, message_author, t, message_type, message_id) + friend.append_message(message) + if not self._contacts_manager.is_friend_active(friend_number): + return + self._create_message_item(message) + self._screen.messageEdit.clear() + self._screen.messages.scrollToBottom() + + def send_messages(self, friend_number): + """ + Send 'offline' messages to friend + """ + friend = self._get_friend_by_number(friend_number) + friend.load_corr() + messages = friend.get_unsent_messages() + try: + for message in messages: + message_id = self._tox.friend_send_message(friend_number, message.type, message.text.encode('utf-8')) + message.tox_message_id = message_id + except Exception as ex: + util.log('Sending pending messages failed with ' + str(ex)) + + # ----------------------------------------------------------------------------------------------------------------- + # Messaging - groups + # ----------------------------------------------------------------------------------------------------------------- + + def send_message_to_group(self, text, message_type, group_number=None): + if group_number is None: + group_number = self._contacts_manager.get_active_number() + + if not text or group_number < 0: + return + + group = self._get_group_by_number(group_number) + messages = self._split_message(text.encode('utf-8')) + t = util.get_unix_time() + for message in messages: + self._tox.group_send_message(group_number, message_type, message) + message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER']) + message = OutgoingTextMessage(text, message_author, t, message_type) + group.append_message(message) + if not self._contacts_manager.is_group_active(group_number): + return + self._create_message_item(message) + self._screen.messageEdit.clear() + self._screen.messages.scrollToBottom() + + def new_group_message(self, group_number, message_type, message, peer_id): + """ + Current user gets new message + :param message_type: message type - plain text or action message (/me) + :param message: text of message + """ + t = util.get_unix_time() + group = self._get_group_by_number(group_number) + peer = group.get_peer_by_id(peer_id) + text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type) + self._add_message(text_message, group) + + # ----------------------------------------------------------------------------------------------------------------- + # Messaging - group peers + # ----------------------------------------------------------------------------------------------------------------- + + def send_message_to_group_peer(self, text, message_type, group_number=None, peer_id=None): + if group_number is None or peer_id is None: + group_peer_contact = self._contacts_manager.get_curr_contact() + peer_id = group_peer_contact.number + group = self._get_group_by_public_key(group_peer_contact.group_pk) + group_number = group.number + + if not text or group_number < 0 or peer_id < 0: + return + + group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id) + group = self._get_group_by_number(group_number) + messages = self._split_message(text.encode('utf-8')) + t = util.get_unix_time() + for message in messages: + self._tox.group_send_private_message(group_number, peer_id, message_type, message) + message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER']) + message = OutgoingTextMessage(text, message_author, t, message_type) + group_peer_contact.append_message(message) + if not self._contacts_manager.is_contact_active(group_peer_contact): + return + self._create_message_item(message) + self._screen.messageEdit.clear() + self._screen.messages.scrollToBottom() + + def new_group_private_message(self, group_number, message_type, message, peer_id): + """ + Current user gets new message + :param message: text of message + """ + t = util.get_unix_time() + group = self._get_group_by_number(group_number) + peer = group.get_peer_by_id(peer_id) + text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), + t, message_type) + group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id) + self._add_message(text_message, group_peer_contact) + + # ----------------------------------------------------------------------------------------------------------------- + # Message receipts + # ----------------------------------------------------------------------------------------------------------------- + + def receipt(self, friend_number, message_id): + friend = self._get_friend_by_number(friend_number) + friend.mark_as_sent(message_id) + + # ----------------------------------------------------------------------------------------------------------------- + # Typing notifications + # ----------------------------------------------------------------------------------------------------------------- + + def send_typing(self, typing): + """ + Send typing notification to a friend + """ + if not self._contacts_manager.can_send_typing_notification(): + return + contact = self._contacts_manager.get_curr_contact() + contact.typing_notification_handler.send(self._tox, typing) + + def friend_typing(self, friend_number, typing): + """ + Display incoming typing notification + """ + if self._contacts_manager.is_friend_active(friend_number): + self._screen.typing.setVisible(typing) + + # ----------------------------------------------------------------------------------------------------------------- + # Contact info updated + # ----------------------------------------------------------------------------------------------------------------- + + def new_friend_name(self, friend, old_name, new_name): + if old_name == new_name or friend.has_alias(): + return + message = util_ui.tr('User {} is now known as {}') + message = message.format(old_name, new_name) + if not self._contacts_manager.is_friend_active(friend.number): + friend.actions = True + self._add_info_message(friend.number, message) + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def _split_message(message): + messages = [] + while len(message) > TOX_MAX_MESSAGE_LENGTH: + size = TOX_MAX_MESSAGE_LENGTH * 4 // 5 + last_part = message[size:TOX_MAX_MESSAGE_LENGTH] + if b' ' in last_part: + index = last_part.index(b' ') + elif b',' in last_part: + index = last_part.index(b',') + elif b'.' in last_part: + index = last_part.index(b'.') + else: + index = TOX_MAX_MESSAGE_LENGTH - size - 1 + index += size + 1 + messages.append(message[:index]) + message = message[index:] + if message: + messages.append(message) + + return messages + + def _get_friend_by_number(self, friend_number): + return self._contacts_provider.get_friend_by_number(friend_number) + + def _get_group_by_number(self, group_number): + return self._contacts_provider.get_group_by_number(group_number) + + def _get_group_by_public_key(self, public_key): + return self._contacts_provider.get_group_by_public_key( public_key) + + def _on_profile_name_changed(self, new_name): + if self._profile_name == new_name: + return + message = util_ui.tr('User {} is now known as {}') + message = message.format(self._profile_name, new_name) + for friend in self._contacts_provider.get_all_friends(): + self._add_info_message(friend.number, message) + self._profile_name = new_name + + def _on_call_started(self, friend_number, audio, video, is_outgoing): + if is_outgoing: + text = util_ui.tr("Outgoing video call") if video else util_ui.tr("Outgoing audio call") + else: + text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call") + self._add_info_message(friend_number, text) + + def _on_call_finished(self, friend_number, is_declined): + text = util_ui.tr("Call declined") if is_declined else util_ui.tr("Call finished") + self._add_info_message(friend_number, text) + + def _add_info_message(self, friend_number, text): + friend = self._get_friend_by_number(friend_number) + message = InfoMessage(text, util.get_unix_time()) + friend.append_message(message) + if self._contacts_manager.is_friend_active(friend_number): + self._create_info_message_item(message) + + def _create_info_message_item(self, message): + self._items_factory.create_message_item(message) + self._screen.messages.scrollToBottom() + + def _add_message(self, text_message, contact): + if self._contacts_manager.is_contact_active(contact): # add message to list + self._create_message_item(text_message) + self._screen.messages.scrollToBottom() + self._contacts_manager.get_curr_contact().append_message(text_message) + else: + contact.inc_messages() + contact.append_message(text_message) + if not contact.visibility: + self._contacts_manager.update_filtration() + + def _create_message_item(self, text_message): + # pixmap = self._contacts_manager.get_curr_contact().get_pixmap() + self._items_factory.create_message_item(text_message) diff --git a/toxygen/middleware/__init__.py b/toxygen/middleware/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py new file mode 100644 index 0000000..b9a4099 --- /dev/null +++ b/toxygen/middleware/callbacks.py @@ -0,0 +1,605 @@ +from PyQt5 import QtGui +from wrapper.toxcore_enums_and_consts import * +from wrapper.toxav_enums import * +from wrapper.tox import bin_to_string +import utils.ui as util_ui +import utils.util as util +import cv2 +import numpy as np +from middleware.threads import invoke_in_main_thread, execute +from notifications.tray import tray_notification +from notifications.sound import * +import threading + +# TODO: refactoring. Use contact provider instead of manager + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - current user +# ----------------------------------------------------------------------------------------------------------------- + + +def self_connection_status(tox, profile): + """ + Current user changed connection status (offline, TCP, UDP) + """ + def wrapped(tox_link, connection, user_data): + print('Connection status: ', str(connection)) + status = tox.self_get_status() if connection != TOX_CONNECTION['NONE'] else None + invoke_in_main_thread(profile.set_status, status) + + return wrapped + + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - friends +# ----------------------------------------------------------------------------------------------------------------- + + +def friend_status(contacts_manager, file_transfer_handler, profile, settings): + def wrapped(tox, friend_number, new_status, user_data): + """ + Check friend's status (none, busy, away) + """ + print("Friend's #{} status changed!".format(friend_number)) + friend = contacts_manager.get_friend_by_number(friend_number) + if friend.status is None and settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS']) + invoke_in_main_thread(friend.set_status, new_status) + + def set_timer(): + t = threading.Timer(5, lambda: file_transfer_handler.send_files(friend_number)) + t.start() + invoke_in_main_thread(set_timer) + invoke_in_main_thread(contacts_manager.update_filtration) + + return wrapped + + +def friend_connection_status(contacts_manager, profile, settings, plugin_loader, file_transfer_handler, + messenger, calls_manager): + def wrapped(tox, friend_number, new_status, user_data): + """ + Check friend's connection status (offline, udp, tcp) + """ + print("Friend #{} connection status: {}".format(friend_number, new_status)) + friend = contacts_manager.get_friend_by_number(friend_number) + if new_status == TOX_CONNECTION['NONE']: + invoke_in_main_thread(friend.set_status, None) + invoke_in_main_thread(file_transfer_handler.friend_exit, friend_number) + invoke_in_main_thread(contacts_manager.update_filtration) + invoke_in_main_thread(messenger.friend_typing, friend_number, False) + invoke_in_main_thread(calls_manager.friend_exit, friend_number) + if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS']) + elif friend.status is None: + invoke_in_main_thread(file_transfer_handler.send_avatar, friend_number) + invoke_in_main_thread(plugin_loader.friend_online, friend_number) + + return wrapped + + +def friend_name(contacts_provider, messenger): + def wrapped(tox, friend_number, name, size, user_data): + """ + Friend changed his name + """ + print('New name friend #' + str(friend_number)) + friend = contacts_provider.get_friend_by_number(friend_number) + old_name = friend.name + new_name = str(name, 'utf-8') + invoke_in_main_thread(friend.set_name, new_name) + invoke_in_main_thread(messenger.new_friend_name, friend, old_name, new_name) + + return wrapped + + +def friend_status_message(contacts_manager, messenger): + def wrapped(tox, friend_number, status_message, size, user_data): + """ + :return: function for callback friend_status_message. It updates friend's status message + and calls window repaint + """ + friend = contacts_manager.get_friend_by_number(friend_number) + invoke_in_main_thread(friend.set_status_message, str(status_message, 'utf-8')) + print('User #{} has new status message'.format(friend_number)) + invoke_in_main_thread(messenger.send_messages, friend_number) + + return wrapped + + +def friend_message(messenger, contacts_manager, profile, settings, window, tray): + def wrapped(tox, friend_number, message_type, message, size, user_data): + """ + New message from friend + """ + message = str(message, 'utf-8') + invoke_in_main_thread(messenger.new_message, friend_number, message_type, message) + if not window.isActiveWindow(): + friend = contacts_manager.get_friend_by_number(friend_number) + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: + invoke_in_main_thread(tray_notification, friend.name, message, tray, window) + if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['MESSAGE']) + icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + + return wrapped + + +def friend_request(contacts_manager): + def wrapped(tox, public_key, message, message_size, user_data): + """ + Called when user get new friend request + """ + print('Friend request') + key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE]) + tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE) + invoke_in_main_thread(contacts_manager.process_friend_request, tox_id, str(message, 'utf-8')) + + return wrapped + + +def friend_typing(messenger): + def wrapped(tox, friend_number, typing, user_data): + invoke_in_main_thread(messenger.friend_typing, friend_number, typing) + + return wrapped + + +def friend_read_receipt(messenger): + def wrapped(tox, friend_number, message_id, user_data): + invoke_in_main_thread(messenger.receipt, friend_number, message_id) + + return wrapped + + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - file transfers +# ----------------------------------------------------------------------------------------------------------------- + + +def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager, settings): + """ + New incoming file + """ + def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data): + if file_type == TOX_FILE_KIND['DATA']: + print('File') + try: + file_name = str(file_name[:file_name_size], 'utf-8') + except: + file_name = 'toxygen_file' + invoke_in_main_thread(file_transfer_handler.incoming_file_transfer, + friend_number, + file_number, + size, + file_name) + if not window.isActiveWindow(): + friend = contacts_manager.get_friend_by_number(friend_number) + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: + file_from = util_ui.tr("File from") + invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window) + if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER']) + icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + else: # avatar + print('Avatar') + invoke_in_main_thread(file_transfer_handler.incoming_avatar, + friend_number, + file_number, + size) + return wrapped + + +def file_recv_chunk(file_transfer_handler): + """ + Incoming chunk + """ + def wrapped(tox, friend_number, file_number, position, chunk, length, user_data): + chunk = chunk[:length] if length else None + execute(file_transfer_handler.incoming_chunk, friend_number, file_number, position, chunk) + + return wrapped + + +def file_chunk_request(file_transfer_handler): + """ + Outgoing chunk + """ + def wrapped(tox, friend_number, file_number, position, size, user_data): + execute(file_transfer_handler.outgoing_chunk, friend_number, file_number, position, size) + + return wrapped + + +def file_recv_control(file_transfer_handler): + """ + Friend cancelled, paused or resumed file transfer + """ + def wrapped(tox, friend_number, file_number, file_control, user_data): + if file_control == TOX_FILE_CONTROL['CANCEL']: + file_transfer_handler.cancel_transfer(friend_number, file_number, True) + elif file_control == TOX_FILE_CONTROL['PAUSE']: + file_transfer_handler.pause_transfer(friend_number, file_number, True) + elif file_control == TOX_FILE_CONTROL['RESUME']: + file_transfer_handler.resume_transfer(friend_number, file_number, True) + + return wrapped + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - custom packets +# ----------------------------------------------------------------------------------------------------------------- + + +def lossless_packet(plugin_loader): + def wrapped(tox, friend_number, data, length, user_data): + """ + Incoming lossless packet + """ + data = data[:length] + invoke_in_main_thread(plugin_loader.callback_lossless, friend_number, data) + + return wrapped + + +def lossy_packet(plugin_loader): + def wrapped(tox, friend_number, data, length, user_data): + """ + Incoming lossy packet + """ + data = data[:length] + invoke_in_main_thread(plugin_loader.callback_lossy, friend_number, data) + + return wrapped + + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - audio +# ----------------------------------------------------------------------------------------------------------------- + +def call_state(calls_manager): + def wrapped(toxav, friend_number, mask, user_data): + """ + New call state + """ + print(friend_number, mask) + if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']: + invoke_in_main_thread(calls_manager.stop_call, friend_number, True) + else: + calls_manager.toxav_call_state_cb(friend_number, mask) + + return wrapped + + +def call(calls_manager): + def wrapped(toxav, friend_number, audio, video, user_data): + """ + Incoming call from friend + """ + print(friend_number, audio, video) + invoke_in_main_thread(calls_manager.incoming_call, audio, video, friend_number) + + return wrapped + + +def callback_audio(calls_manager): + def wrapped(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data): + """ + New audio chunk + """ + calls_manager.call.audio_chunk( + bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), + audio_channels_count, + rate) + + return wrapped + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - video +# ----------------------------------------------------------------------------------------------------------------- + + +def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data): + """ + Creates yuv frame from y, u, v and shows it using OpenCV + For yuv => bgr we need this YUV420 frame: + + width + ------------------------- + | | + | Y | height + | | + ------------------------- + | | | + | U even | U odd | height // 4 + | | | + ------------------------- + | | | + | V even | V odd | height // 4 + | | | + ------------------------- + + width // 2 width // 2 + + It can be created from initial y, u, v using slices + """ + try: + y_size = abs(max(width, abs(ystride))) + u_size = abs(max(width // 2, abs(ustride))) + v_size = abs(max(width // 2, abs(vstride))) + + y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size) + u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size) + v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size) + + width -= width % 4 + height -= height % 4 + + frame = np.zeros((int(height * 1.5), width), dtype=np.uint8) + + frame[:height, :] = y[:height, :width] + frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2] + frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2] + + frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2] + frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2] + + frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) + + invoke_in_main_thread(cv2.imshow, str(friend_number), frame) + except Exception as ex: + print(ex) + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - groups +# ----------------------------------------------------------------------------------------------------------------- + + +def group_message(window, tray, tox, messenger, settings, profile): + """ + New message in group chat + """ + def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data): + message = str(message[:length], 'utf-8') + invoke_in_main_thread(messenger.new_group_message, group_number, message_type, message, peer_id) + if window.isActiveWindow(): + return + bl = settings['notify_all_gc'] or profile.name in message + name = tox.group_peer_get_name(group_number, peer_id) + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl: + invoke_in_main_thread(tray_notification, name, message, tray, window) + if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['MESSAGE']) + icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + + return wrapped + + +def group_private_message(window, tray, tox, messenger, settings, profile): + """ + New private message in group chat + """ + def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data): + message = str(message[:length], 'utf-8') + invoke_in_main_thread(messenger.new_group_private_message, group_number, message_type, message, peer_id) + if window.isActiveWindow(): + return + bl = settings['notify_all_gc'] or profile.name in message + name = tox.group_peer_get_name(group_number, peer_id) + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl: + invoke_in_main_thread(tray_notification, name, message, tray, window) + if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']: + sound_notification(SOUND_NOTIFICATION['MESSAGE']) + icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + + return wrapped + + +def group_invite(window, settings, tray, profile, groups_service, contacts_provider): + def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data): + group_name = str(bytes(group_name[:group_name_length]), 'utf-8') + invoke_in_main_thread(groups_service.process_group_invite, + friend_number, group_name, + bytes(invite_data[:length])) + if window.isActiveWindow(): + return + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: + friend = contacts_provider.get_friend_by_number(friend_number) + title = util_ui.tr('New invite to group chat') + text = util_ui.tr('{} invites you to group "{}"').format(friend.name, group_name) + invoke_in_main_thread(tray_notification, title, text, tray, window) + icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png') + invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon)) + + return wrapped + + +def group_self_join(contacts_provider, contacts_manager, groups_service): + def wrapped(tox, group_number, user_data): + group = contacts_provider.get_group_by_number(group_number) + invoke_in_main_thread(group.set_status, TOX_USER_STATUS['NONE']) + invoke_in_main_thread(groups_service.update_group_info, group) + invoke_in_main_thread(contacts_manager.update_filtration) + + return wrapped + + +def group_peer_join(contacts_provider, groups_service): + def wrapped(tox, group_number, peer_id, user_data): + group = contacts_provider.get_group_by_number(group_number) + group.add_peer(peer_id) + invoke_in_main_thread(groups_service.generate_peers_list) + invoke_in_main_thread(groups_service.update_group_info, group) + + return wrapped + + +def group_peer_exit(contacts_provider, groups_service, contacts_manager): + def wrapped(tox, group_number, peer_id, message, length, user_data): + group = contacts_provider.get_group_by_number(group_number) + group.remove_peer(peer_id) + invoke_in_main_thread(groups_service.generate_peers_list) + + return wrapped + + +def group_peer_name(contacts_provider, groups_service): + def wrapped(tox, group_number, peer_id, name, length, user_data): + group = contacts_provider.get_group_by_number(group_number) + peer = group.get_peer_by_id(peer_id) + peer.name = str(name[:length], 'utf-8') + invoke_in_main_thread(groups_service.generate_peers_list) + + return wrapped + + +def group_peer_status(contacts_provider, groups_service): + def wrapped(tox, group_number, peer_id, peer_status, user_data): + group = contacts_provider.get_group_by_number(group_number) + peer = group.get_peer_by_id(peer_id) + peer.status = peer_status + invoke_in_main_thread(groups_service.generate_peers_list) + + return wrapped + + +def group_topic(contacts_provider): + def wrapped(tox, group_number, peer_id, topic, length, user_data): + group = contacts_provider.get_group_by_number(group_number) + topic = str(topic[:length], 'utf-8') + invoke_in_main_thread(group.set_status_message, topic) + + return wrapped + + +def group_moderation(groups_service, contacts_provider, contacts_manager, messenger): + + def update_peer_role(group, mod_peer_id, peer_id, new_role): + peer = group.get_peer_by_id(peer_id) + peer.role = new_role + # TODO: add info message + + def remove_peer(group, mod_peer_id, peer_id, is_ban): + contacts_manager.remove_group_peer_by_id(group, peer_id) + group.remove_peer(peer_id) + # TODO: add info message + + def wrapped(tox, group_number, mod_peer_id, peer_id, event_type, user_data): + group = contacts_provider.get_group_by_number(group_number) + + if event_type == TOX_GROUP_MOD_EVENT['KICK']: + remove_peer(group, mod_peer_id, peer_id, False) + elif event_type == TOX_GROUP_MOD_EVENT['BAN']: + remove_peer(group, mod_peer_id, peer_id, True) + elif event_type == TOX_GROUP_MOD_EVENT['OBSERVER']: + update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['OBSERVER']) + elif event_type == TOX_GROUP_MOD_EVENT['USER']: + update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['USER']) + elif event_type == TOX_GROUP_MOD_EVENT['MODERATOR']: + update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['MODERATOR']) + + invoke_in_main_thread(groups_service.generate_peers_list) + + return wrapped + + +def group_password(contacts_provider): + + def wrapped(tox_link, group_number, password, length, user_data): + password = str(password[:length], 'utf-8') + group = contacts_provider.get_group_by_number(group_number) + group.password = password + + return wrapped + + +def group_peer_limit(contacts_provider): + + def wrapped(tox_link, group_number, peer_limit, user_data): + group = contacts_provider.get_group_by_number(group_number) + group.peer_limit = peer_limit + + return wrapped + + +def group_privacy_state(contacts_provider): + + def wrapped(tox_link, group_number, privacy_state, user_data): + group = contacts_provider.get_group_by_number(group_number) + group.is_private = privacy_state == TOX_GROUP_PRIVACY_STATE['PRIVATE'] + + return wrapped + +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - initialization +# ----------------------------------------------------------------------------------------------------------------- + + +def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager, + calls_manager, file_transfer_handler, main_window, tray, messenger, groups_service, + contacts_provider): + """ + Initialization of all callbacks. + :param tox: Tox instance + :param profile: Profile instance + :param settings: Settings instance + :param contacts_manager: ContactsManager instance + :param contacts_manager: ContactsManager instance + :param calls_manager: CallsManager instance + :param file_transfer_handler: FileTransferHandler instance + :param plugin_loader: PluginLoader instance + :param main_window: MainWindow instance + :param tray: tray (for notifications) + :param messenger: Messenger instance + :param groups_service: GroupsService instance + :param contacts_provider: ContactsProvider instance + """ + # self callbacks + tox.callback_self_connection_status(self_connection_status(tox, profile)) + + # friend callbacks + tox.callback_friend_status(friend_status(contacts_manager, file_transfer_handler, profile, settings)) + tox.callback_friend_message(friend_message(messenger, contacts_manager, profile, settings, main_window, tray)) + tox.callback_friend_connection_status(friend_connection_status(contacts_manager, profile, settings, plugin_loader, + file_transfer_handler, messenger, calls_manager)) + tox.callback_friend_name(friend_name(contacts_provider, messenger)) + tox.callback_friend_status_message(friend_status_message(contacts_manager, messenger)) + tox.callback_friend_request(friend_request(contacts_manager)) + tox.callback_friend_typing(friend_typing(messenger)) + tox.callback_friend_read_receipt(friend_read_receipt(messenger)) + + # file transfer + tox.callback_file_recv(tox_file_recv(main_window, tray, profile, file_transfer_handler, + contacts_manager, settings)) + tox.callback_file_recv_chunk(file_recv_chunk(file_transfer_handler)) + tox.callback_file_chunk_request(file_chunk_request(file_transfer_handler)) + tox.callback_file_recv_control(file_recv_control(file_transfer_handler)) + + # av + toxav = tox.AV + toxav.callback_call_state(call_state(calls_manager), 0) + toxav.callback_call(call(calls_manager), 0) + toxav.callback_audio_receive_frame(callback_audio(calls_manager), 0) + toxav.callback_video_receive_frame(video_receive_frame, 0) + + # custom packets + tox.callback_friend_lossless_packet(lossless_packet(plugin_loader)) + tox.callback_friend_lossy_packet(lossy_packet(plugin_loader)) + + # gc callbacks + tox.callback_group_message(group_message(main_window, tray, tox, messenger, settings, profile), 0) + tox.callback_group_private_message(group_private_message(main_window, tray, tox, messenger, settings, profile), 0) + tox.callback_group_invite(group_invite(main_window, settings, tray, profile, groups_service, contacts_provider), 0) + tox.callback_group_self_join(group_self_join(contacts_provider, contacts_manager, groups_service), 0) + tox.callback_group_peer_join(group_peer_join(contacts_provider, groups_service), 0) + tox.callback_group_peer_exit(group_peer_exit(contacts_provider, groups_service, contacts_manager), 0) + tox.callback_group_peer_name(group_peer_name(contacts_provider, groups_service), 0) + tox.callback_group_peer_status(group_peer_status(contacts_provider, groups_service), 0) + tox.callback_group_topic(group_topic(contacts_provider), 0) + tox.callback_group_moderation(group_moderation(groups_service, contacts_provider, contacts_manager, messenger), 0) + tox.callback_group_password(group_password(contacts_provider), 0) + tox.callback_group_peer_limit(group_peer_limit(contacts_provider), 0) + tox.callback_group_privacy_state(group_privacy_state(contacts_provider), 0) diff --git a/toxygen/middleware/threads.py b/toxygen/middleware/threads.py new file mode 100644 index 0000000..5f9404b --- /dev/null +++ b/toxygen/middleware/threads.py @@ -0,0 +1,172 @@ +from bootstrap.bootstrap import * +import threading +import queue +from utils import util +import time +from PyQt5 import QtCore + + +# ----------------------------------------------------------------------------------------------------------------- +# Base threads +# ----------------------------------------------------------------------------------------------------------------- + +class BaseThread(threading.Thread): + + def __init__(self): + super().__init__() + self._stop_thread = False + + def stop_thread(self): + self._stop_thread = True + self.join() + + +class BaseQThread(QtCore.QThread): + + def __init__(self): + super().__init__() + self._stop_thread = False + + def stop_thread(self): + self._stop_thread = True + self.wait() + + +# ----------------------------------------------------------------------------------------------------------------- +# Toxcore threads +# ----------------------------------------------------------------------------------------------------------------- + +class InitThread(BaseThread): + + def __init__(self, tox, plugin_loader, settings, is_first_start): + super().__init__() + self._tox, self._plugin_loader, self._settings = tox, plugin_loader, settings + self._is_first_start = is_first_start + + def run(self): + if self._is_first_start: + # download list of nodes if needed + download_nodes_list(self._settings) + # start plugins + self._plugin_loader.load() + + # bootstrap + try: + for data in generate_nodes(): + if self._stop_thread: + return + self._tox.bootstrap(*data) + self._tox.add_tcp_relay(*data) + except: + pass + + for _ in range(10): + if self._stop_thread: + return + time.sleep(1) + + while not self._tox.self_get_connection_status(): + try: + for data in generate_nodes(None): + if self._stop_thread: + return + self._tox.bootstrap(*data) + self._tox.add_tcp_relay(*data) + except: + pass + finally: + time.sleep(5) + + +class ToxIterateThread(BaseQThread): + + def __init__(self, tox): + super().__init__() + self._tox = tox + + def run(self): + while not self._stop_thread: + self._tox.iterate() + time.sleep(self._tox.iteration_interval() / 1000) + + +class ToxAVIterateThread(BaseQThread): + + def __init__(self, toxav): + super().__init__() + self._toxav = toxav + + def run(self): + while not self._stop_thread: + self._toxav.iterate() + time.sleep(self._toxav.iteration_interval() / 1000) + + +# ----------------------------------------------------------------------------------------------------------------- +# File transfers thread +# ----------------------------------------------------------------------------------------------------------------- + +class FileTransfersThread(BaseQThread): + + def __init__(self): + super().__init__() + self._queue = queue.Queue() + self._timeout = 0.01 + + def execute(self, func, *args, **kwargs): + self._queue.put((func, args, kwargs)) + + def run(self): + while not self._stop_thread: + try: + func, args, kwargs = self._queue.get(timeout=self._timeout) + func(*args, **kwargs) + except queue.Empty: + pass + except queue.Full: + util.log('Queue is full in _thread') + except Exception as ex: + util.log('Exception in _thread: ' + str(ex)) + + +_thread = FileTransfersThread() + + +def start_file_transfer_thread(): + _thread.start() + + +def stop_file_transfer_thread(): + _thread.stop_thread() + + +def execute(func, *args, **kwargs): + _thread.execute(func, *args, **kwargs) + + +# ----------------------------------------------------------------------------------------------------------------- +# Invoking in main thread +# ----------------------------------------------------------------------------------------------------------------- + +class InvokeEvent(QtCore.QEvent): + EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType()) + + def __init__(self, fn, *args, **kwargs): + QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE) + self.fn = fn + self.args = args + self.kwargs = kwargs + + +class Invoker(QtCore.QObject): + + def event(self, event): + event.fn(*event.args, **event.kwargs) + return True + + +_invoker = Invoker() + + +def invoke_in_main_thread(fn, *args, **kwargs): + QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs)) diff --git a/toxygen/middleware/tox_factory.py b/toxygen/middleware/tox_factory.py new file mode 100644 index 0000000..9ee5c01 --- /dev/null +++ b/toxygen/middleware/tox_factory.py @@ -0,0 +1,34 @@ +import user_data.settings +import wrapper.tox +import wrapper.toxcore_enums_and_consts as enums +import ctypes + + +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 = user_data.settings.Settings.get_default_settings() + + tox_options = wrapper.tox.Tox.options_new() + tox_options.contents.udp_enabled = settings['udp_enabled'] + tox_options.contents.proxy_type = settings['proxy_type'] + tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8') + 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'] + tox_options.contents.local_discovery_enabled = settings['lan_discovery'] + if data: # load existing profile + tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE'] + tox_options.contents.savedata_data = ctypes.c_char_p(data) + tox_options.contents.savedata_length = len(data) + else: # create new profile + tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['NONE'] + tox_options.contents.savedata_data = None + tox_options.contents.savedata_length = 0 + + return wrapper.tox.Tox(tox_options) diff --git a/toxygen/network/__init__.py b/toxygen/network/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/network/tox_dns.py b/toxygen/network/tox_dns.py new file mode 100644 index 0000000..02e97f5 --- /dev/null +++ b/toxygen/network/tox_dns.py @@ -0,0 +1,65 @@ +import json +import urllib.request +import utils.util as util +from PyQt5 import QtNetwork, QtCore + + +class ToxDns: + + def __init__(self, settings): + self._settings = settings + + @staticmethod + def _send_request(url, data): + req = urllib.request.Request(url) + req.add_header('Content-Type', 'application/json') + response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8')) + res = json.loads(str(response.read(), 'utf-8')) + if not res['c']: + return res['tox_id'] + else: + raise LookupError() + + def lookup(self, email): + """ + TOX DNS 4 + :param email: data like 'groupbot@toxme.io' + :return: tox id on success else None + """ + site = email.split('@')[1] + data = {"action": 3, "name": "{}".format(email)} + urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site)) + if not self._settings['proxy_type']: # no proxy + for url in urls: + try: + return self._send_request(url, data) + except Exception as ex: + util.log('TOX DNS ERROR: ' + str(ex)) + else: # proxy + netman = QtNetwork.QNetworkAccessManager() + proxy = QtNetwork.QNetworkProxy() + if self._settings['proxy_type'] == 2: + proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy) + else: + proxy.setType(QtNetwork.QNetworkProxy.HttpProxy) + proxy.setHostName(self._settings['proxy_host']) + proxy.setPort(self._settings['proxy_port']) + netman.setProxy(proxy) + for url in urls: + try: + request = QtNetwork.QNetworkRequest() + request.setUrl(QtCore.QUrl(url)) + request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json") + reply = netman.post(request, bytes(json.dumps(data), 'utf-8')) + + while not reply.isFinished(): + QtCore.QThread.msleep(1) + QtCore.QCoreApplication.processEvents() + data = bytes(reply.readAll().data()) + result = json.loads(str(data, 'utf-8')) + if not result['c']: + return result['tox_id'] + except Exception as ex: + util.log('TOX DNS ERROR: ' + str(ex)) + + return None # error diff --git a/toxygen/notifications/__init__.py b/toxygen/notifications/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/notifications/sound.py b/toxygen/notifications/sound.py new file mode 100644 index 0000000..361cd05 --- /dev/null +++ b/toxygen/notifications/sound.py @@ -0,0 +1,54 @@ +import utils.util +import wave +import pyaudio +import os.path + + +SOUND_NOTIFICATION = { + 'MESSAGE': 0, + 'FRIEND_CONNECTION_STATUS': 1, + 'FILE_TRANSFER': 2 +} + + +class AudioFile: + chunk = 1024 + + def __init__(self, fl): + self.wf = wave.open(fl, 'rb') + self.p = pyaudio.PyAudio() + self.stream = self.p.open( + format=self.p.get_format_from_width(self.wf.getsampwidth()), + channels=self.wf.getnchannels(), + rate=self.wf.getframerate(), + output=True) + + def play(self): + data = self.wf.readframes(self.chunk) + while data: + self.stream.write(data) + data = self.wf.readframes(self.chunk) + + def close(self): + self.stream.close() + self.p.terminate() + + +def sound_notification(t): + """ + Plays sound notification + :param t: type of notification + """ + if t == SOUND_NOTIFICATION['MESSAGE']: + f = get_file_path('message.wav') + elif t == SOUND_NOTIFICATION['FILE_TRANSFER']: + f = get_file_path('file.wav') + else: + f = get_file_path('contact.wav') + a = AudioFile(f) + a.play() + a.close() + + +def get_file_path(file_name): + return os.path.join(utils.util.get_sounds_directory(), file_name) diff --git a/toxygen/notifications/tray.py b/toxygen/notifications/tray.py new file mode 100644 index 0000000..4232253 --- /dev/null +++ b/toxygen/notifications/tray.py @@ -0,0 +1,22 @@ +from PyQt5 import QtCore, QtWidgets + + +def tray_notification(title, text, tray, window): + """ + Show tray notification and activate window icon + NOTE: different behaviour on different OS + :param title: Name of user who sent message or file + :param text: text of message or file info + :param tray: ref to tray icon + :param window: main window + """ + if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): + if len(text) > 30: + text = text[:27] + '...' + tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000) + QtWidgets.QApplication.alert(window, 0) + + def message_clicked(): + window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + window.activateWindow() + tray.messageClicked.connect(message_clicked) diff --git a/toxygen/plugin_support/__init__.py b/toxygen/plugin_support/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/plugin_support/plugin_support.py b/toxygen/plugin_support/plugin_support.py new file mode 100644 index 0000000..ed45910 --- /dev/null +++ b/toxygen/plugin_support/plugin_support.py @@ -0,0 +1,194 @@ +import utils.util as util +import os +import importlib +import inspect +import plugins.plugin_super_class as pl +import sys + + +class Plugin: + + def __init__(self, plugin, is_active): + self._instance = plugin + self._is_active = is_active + + def get_instance(self): + return self._instance + + instance = property(get_instance) + + def get_is_active(self): + return self._is_active + + def set_is_active(self, is_active): + self._is_active = is_active + + is_active = property(get_is_active, set_is_active) + + +class PluginLoader: + + def __init__(self, settings, app): + self._settings = settings + self._app = app + self._plugins = {} # dict. key - plugin unique short name, value - Plugin instance + + def set_tox(self, tox): + """ + New tox instance + """ + for plugin in self._plugins.values(): + plugin.instance.set_tox(tox) + + def load(self): + """ + Load all plugins in plugins folder + """ + path = util.get_plugins_directory() + if not os.path.exists(path): + util.log('Plugin dir not found') + return + else: + sys.path.append(path) + files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] + for fl in files: + if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'): + continue + name = fl[:-3] # module name without .py + try: + module = importlib.import_module(name) # import plugin + except ImportError: + util.log('Import error in module ' + name) + continue + except Exception as ex: + util.log('Exception in module ' + name + ' Exception: ' + str(ex)) + continue + for elem in dir(module): + obj = getattr(module, elem) + # looking for plugin class in module + if not inspect.isclass(obj) or not hasattr(obj, 'is_plugin') or not obj.is_plugin: + continue + print('Plugin', elem) + try: # create instance of plugin class + instance = obj(self._app) + is_active = instance.get_short_name() in self._settings['plugins'] + if is_active: + instance.start() + except Exception as ex: + util.log('Exception in module ' + name + ' Exception: ' + str(ex)) + continue + self._plugins[instance.get_short_name()] = Plugin(instance, is_active) + break + + def callback_lossless(self, friend_number, data): + """ + New incoming custom lossless packet (callback) + """ + l = data[0] - pl.LOSSLESS_FIRST_BYTE + name = ''.join(chr(x) for x in data[1:l + 1]) + if name in self._plugins and self._plugins[name].is_active: + self._plugins[name].instance.lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) + + def callback_lossy(self, friend_number, data): + """ + New incoming custom lossy packet (callback) + """ + l = data[0] - pl.LOSSY_FIRST_BYTE + name = ''.join(chr(x) for x in data[1:l + 1]) + if name in self._plugins and self._plugins[name].is_active: + self._plugins[name].instance.lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) + + def friend_online(self, friend_number): + """ + Friend with specified number is online + """ + for plugin in self._plugins.values(): + if plugin.is_active: + plugin.instance.friend_connected(friend_number) + + def get_plugins_list(self): + """ + Returns list of all plugins + """ + result = [] + for plugin in self._plugins.values(): + try: + result.append([plugin.instance.get_name(), # plugin full name + plugin.is_active, # is enabled + plugin.instance.get_description(), # plugin description + plugin.instance.get_short_name()]) # key - short unique name + except: + continue + + return result + + def plugin_window(self, key): + """ + Return window or None for specified plugin + """ + return self._plugins[key].instance.get_window() + + def toggle_plugin(self, key): + """ + Enable/disable plugin + :param key: plugin short name + """ + plugin = self._plugins[key] + if plugin.is_active: + plugin.instance.stop() + else: + plugin.instance.start() + plugin.is_active = not plugin.is_active + if plugin.is_active: + self._settings['plugins'].append(key) + else: + self._settings['plugins'].remove(key) + self._settings.save() + + def command(self, text): + """ + New command for plugin + """ + text = text.strip() + name = text.split()[0] + if name in self._plugins and self._plugins[name].is_active: + self._plugins[name].instance.command(text[len(name) + 1:]) + + def get_menu(self, num): + """ + Return list of items for menu + """ + result = [] + for plugin in self._plugins.values(): + if not plugin.is_active: + continue + try: + result.extend(plugin.instance.get_menu(num)) + except: + continue + return result + + def get_message_menu(self, menu, selected_text): + result = [] + for plugin in self._plugins.values(): + if not plugin.is_active: + continue + try: + result.extend(plugin.instance.get_message_menu(menu, selected_text)) + except: + pass + return result + + def stop(self): + """ + App is closing, stop all plugins + """ + for key in list(self._plugins.keys()): + if self._plugins[key].is_active: + self._plugins[key].instance.close() + del self._plugins[key] + + def reload(self): + print('Reloading plugins') + self.stop() + self.load() diff --git a/toxygen/plugins/plugin_super_class.py b/toxygen/plugins/plugin_super_class.py index c857c56..0056d36 100644 --- a/toxygen/plugins/plugin_super_class.py +++ b/toxygen/plugins/plugin_super_class.py @@ -1,5 +1,7 @@ import os from PyQt5 import QtCore, QtWidgets +import utils.ui as util_ui +import common.tox_save as tox_save MAX_SHORT_NAME_LENGTH = 5 @@ -26,25 +28,22 @@ def log(name, data): fl.write(str(data) + '\n') -class PluginSuperClass: +class PluginSuperClass(tox_save.ToxSave): """ Superclass for all plugins. Plugin is Python3 module with at least one class derived from PluginSuperClass. """ is_plugin = True - def __init__(self, name, short_name, tox=None, profile=None, settings=None, encrypt_save=None): + def __init__(self, name, short_name, app): """ - Constructor. In plugin __init__ should take only 4 last arguments + Constructor. In plugin __init__ should take only 1 last argument :param name: plugin full name :param short_name: plugin unique short name (length of short name should not exceed MAX_SHORT_NAME_LENGTH) - :param tox: tox instance - :param profile: profile instance - :param settings: profile settings - :param encrypt_save: ToxES instance. + :param app: App instance """ - self._settings = settings - self._profile = profile - self._tox = tox + tox = getattr(app, '_tox') + super().__init__(tox) + self._settings = getattr(app, '_settings') name = name.strip() short_name = short_name.strip() if not name or not short_name: @@ -52,7 +51,6 @@ class PluginSuperClass: self._name = name self._short_name = short_name[:MAX_SHORT_NAME_LENGTH] self._translator = None # translator for plugin's GUI - self._encrypt_save = encrypt_save # ----------------------------------------------------------------------------------------------------------------- # Get methods @@ -76,12 +74,11 @@ class PluginSuperClass: """ return self.__doc__ - def get_menu(self, menu, row_number): + def get_menu(self, row_number): """ This method creates items for menu which called on right click in list of friends - :param menu: menu instance :param row_number: number of selected row in list of contacts - :return list of QAction's + :return list of tuples (text, handler) """ return [] @@ -100,12 +97,6 @@ class PluginSuperClass: """ return None - def set_tox(self, tox): - """ - New tox instance - """ - self._tox = tox - # ----------------------------------------------------------------------------------------------------------------- # Plugin was stopped, started or new command received # ----------------------------------------------------------------------------------------------------------------- @@ -134,11 +125,9 @@ class PluginSuperClass: :param command: string with command """ if command == 'help': - msgbox = QtWidgets.QMessageBox() - title = QtWidgets.QApplication.translate("PluginWindow", "List of commands for plugin {}") - msgbox.setWindowTitle(title.format(self._name)) - msgbox.setText(QtWidgets.QApplication.translate("PluginWindow", "No commands available")) - msgbox.exec_() + text = util_ui.tr('No commands available') + title = util_ui.tr('List of commands for plugin {}').format(self._name) + util_ui.message_box(text, title) # ----------------------------------------------------------------------------------------------------------------- # Translations support diff --git a/toxygen/styles/dark_style.qss b/toxygen/styles/dark_style.qss index 0216f23..ece5ec3 100644 --- a/toxygen/styles/dark_style.qss +++ b/toxygen/styles/dark_style.qss @@ -1207,12 +1207,12 @@ MessageItem border: none; } -MessageEdit +MessageBrowser { border: none; } -MessageEdit::focus +MessageBrowser::focus { border: none; } @@ -1222,7 +1222,7 @@ MessageItem::focus border: none; } -MessageEdit:hover +MessageBrowser:hover { border: none; } @@ -1243,7 +1243,7 @@ QPushButton:hover background-color: #1E90FF; } -MessageEdit +MessageBrowser { background-color: transparent; } @@ -1253,7 +1253,7 @@ MessageEdit background-color: #1E90FF; } -#friends_list:item:selected +#friendsListWidget:item:selected { background-color: #333333; } @@ -1277,7 +1277,7 @@ QListWidget > QLabel color: #A9A9A9; } -#contact_name +#searchLineEdit { padding-left: 22px; } @@ -1322,3 +1322,14 @@ ClickableLabel:hover { background-color: #4A4949; } + +#warningLabel +{ + color: #BC1C1C; +} + +#groupInvitesPushButton +{ + background-color: #009c00; +} + diff --git a/toxygen/styles/style.qss b/toxygen/styles/style.qss index 26fbaf2..ff9f614 100644 --- a/toxygen/styles/style.qss +++ b/toxygen/styles/style.qss @@ -1,4 +1,4 @@ -#contact_name +#searchLineEdit { padding-left: 22px; } @@ -27,3 +27,14 @@ MessageEdit { background-color: transparent; } + +#warningLabel +{ + color: #BC1C1C; +} + +#groupInvitesPushButton +{ + background-color: #009c00; +} + diff --git a/toxygen/ui/__init__.py b/toxygen/ui/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/ui/av_widgets.py b/toxygen/ui/av_widgets.py new file mode 100644 index 0000000..e5773a8 --- /dev/null +++ b/toxygen/ui/av_widgets.py @@ -0,0 +1,130 @@ +from PyQt5 import QtCore, QtGui, QtWidgets +from ui import widgets +import utils.util as util +import pyaudio +import wave + + +class IncomingCallWidget(widgets.CenteredWidget): + + def __init__(self, settings, calls_manager, friend_number, text, name): + super().__init__() + self._settings = settings + self._calls_manager = calls_manager + self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) + self.resize(QtCore.QSize(500, 270)) + self.avatar_label = QtWidgets.QLabel(self) + self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64)) + self.avatar_label.setScaledContents(False) + self.name = widgets.DataLabel(self) + self.name.setGeometry(QtCore.QRect(90, 20, 300, 25)) + self._friend_number = friend_number + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(16) + font.setBold(True) + self.name.setFont(font) + self.call_type = widgets.DataLabel(self) + self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25)) + self.call_type.setFont(font) + self.accept_audio = QtWidgets.QPushButton(self) + self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150)) + self.accept_video = QtWidgets.QPushButton(self) + self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150)) + self.decline = QtWidgets.QPushButton(self) + self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150)) + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_audio.png')) + icon = QtGui.QIcon(pixmap) + self.accept_audio.setIcon(icon) + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'accept_video.png')) + icon = QtGui.QIcon(pixmap) + self.accept_video.setIcon(icon) + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'decline_call.png')) + icon = QtGui.QIcon(pixmap) + self.decline.setIcon(icon) + self.accept_audio.setIconSize(QtCore.QSize(150, 150)) + self.accept_video.setIconSize(QtCore.QSize(140, 140)) + self.decline.setIconSize(QtCore.QSize(140, 140)) + self.accept_audio.setStyleSheet("QPushButton { border: none }") + self.accept_video.setStyleSheet("QPushButton { border: none }") + self.decline.setStyleSheet("QPushButton { border: none }") + self.setWindowTitle(text) + self.name.setText(name) + self.call_type.setText(text) + self._processing = False + self.accept_audio.clicked.connect(self.accept_call_with_audio) + self.accept_video.clicked.connect(self.accept_call_with_video) + self.decline.clicked.connect(self.decline_call) + + class SoundPlay(QtCore.QThread): + + def __init__(self): + QtCore.QThread.__init__(self) + self.a = None + + def run(self): + class AudioFile: + chunk = 1024 + + def __init__(self, fl): + self.stop = False + self.fl = fl + self.wf = wave.open(self.fl, 'rb') + self.p = pyaudio.PyAudio() + self.stream = self.p.open( + format=self.p.get_format_from_width(self.wf.getsampwidth()), + channels=self.wf.getnchannels(), + rate=self.wf.getframerate(), + output=True) + + def play(self): + while not self.stop: + data = self.wf.readframes(self.chunk) + while data and not self.stop: + self.stream.write(data) + data = self.wf.readframes(self.chunk) + self.wf = wave.open(self.fl, 'rb') + + def close(self): + self.stream.close() + self.p.terminate() + + self.a = AudioFile(util.join_path(util.get_sounds_directory(), 'call.wav')) + self.a.play() + self.a.close() + + if self._settings['calls_sound']: + self.thread = SoundPlay() + self.thread.start() + else: + self.thread = None + + def stop(self): + if self.thread is not None: + self.thread.a.stop = True + self.thread.wait() + self.close() + + def accept_call_with_audio(self): + if self._processing: + return + self._processing = True + self._calls_manager.accept_call(self._friend_number, True, False) + self.stop() + + def accept_call_with_video(self): + if self._processing: + return + self._processing = True + self._calls_manager.accept_call(self._friend_number, True, True) + self.stop() + + def decline_call(self): + if self._processing: + return + self._processing = True + self._calls_manager.stop_call(self._friend_number, False) + self.stop() + + def set_pixmap(self, pixmap): + self.avatar_label.setPixmap(pixmap) diff --git a/toxygen/ui/contact_items.py b/toxygen/ui/contact_items.py new file mode 100644 index 0000000..7a32284 --- /dev/null +++ b/toxygen/ui/contact_items.py @@ -0,0 +1,97 @@ +from wrapper.toxcore_enums_and_consts import * +from PyQt5 import QtCore, QtGui, QtWidgets +from utils.util import * +from ui.widgets import DataLabel + + +class ContactItem(QtWidgets.QWidget): + """ + Contact in friends list + """ + + def __init__(self, settings, parent=None): + QtWidgets.QWidget.__init__(self, parent) + mode = settings['compact_mode'] + self.setBaseSize(QtCore.QSize(250, 40 if mode else 70)) + self.avatar_label = QtWidgets.QLabel(self) + size = 32 if mode else 64 + self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size)) + self.avatar_label.setScaledContents(False) + self.avatar_label.setAlignment(QtCore.Qt.AlignCenter) + self.name = DataLabel(self) + self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25)) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(10 if mode else 12) + font.setBold(True) + self.name.setFont(font) + self.status_message = DataLabel(self) + self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20)) + font.setPointSize(10) + font.setBold(False) + self.status_message.setFont(font) + self.connection_status = StatusCircle(self) + self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32)) + self.messages = UnreadMessagesCount(settings, self) + self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20)) + + +class StatusCircle(QtWidgets.QWidget): + """ + Connection status + """ + def __init__(self, parent): + QtWidgets.QWidget.__init__(self, parent) + self.setGeometry(0, 0, 32, 32) + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) + self.unread = False + + def update(self, status, unread_messages=None): + if unread_messages is None: + unread_messages = self.unread + else: + self.unread = unread_messages + if status == TOX_USER_STATUS['NONE']: + name = 'online' + elif status == TOX_USER_STATUS['AWAY']: + name = 'idle' + elif status == TOX_USER_STATUS['BUSY']: + name = 'busy' + else: + name = 'offline' + if unread_messages: + name += '_notification' + self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) + else: + self.label.setGeometry(QtCore.QRect(2, 0, 32, 32)) + pixmap = QtGui.QPixmap(join_path(get_images_directory(), '{}.png'.format(name))) + self.label.setPixmap(pixmap) + + +class UnreadMessagesCount(QtWidgets.QWidget): + + def __init__(self, settings, parent=None): + super().__init__(parent) + self._settings = settings + self.resize(30, 20) + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(0, 0, 30, 20)) + self.label.setVisible(False) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(12) + font.setBold(True) + self.label.setFont(font) + self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter) + color = settings['unread_color'] + self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') + + def update(self, messages_count): + color = self._settings['unread_color'] + self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') + if messages_count: + self.label.setVisible(True) + self.label.setText(str(messages_count)) + else: + self.label.setVisible(False) diff --git a/toxygen/ui/create_profile_screen.py b/toxygen/ui/create_profile_screen.py new file mode 100644 index 0000000..512c141 --- /dev/null +++ b/toxygen/ui/create_profile_screen.py @@ -0,0 +1,52 @@ +from ui.widgets import * +from PyQt5 import uic +import utils.util as util +import utils.ui as util_ui + + +class CreateProfileScreenResult: + + def __init__(self, save_into_default_folder, password): + self._save_into_default_folder = save_into_default_folder + self._password = password + + def get_save_into_default_folder(self): + return self._save_into_default_folder + + save_into_default_folder = property(get_save_into_default_folder) + + def get_password(self): + return self._password + + password = property(get_password) + + +class CreateProfileScreen(CenteredWidget, DialogWithResult): + + def __init__(self): + CenteredWidget.__init__(self) + DialogWithResult.__init__(self) + uic.loadUi(util.get_views_path('create_profile_screen'), self) + self.center() + self.createProfile.clicked.connect(self._create_profile) + self._retranslate_ui() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('New profile settings')) + self.defaultFolder.setText(util_ui.tr('Save in default folder')) + self.programFolder.setText(util_ui.tr('Save in program folder')) + self.password.setPlaceholderText(util_ui.tr('Password')) + self.confirmPassword.setPlaceholderText(util_ui.tr('Confirm password')) + self.createProfile.setText(util_ui.tr('Create profile')) + self.passwordLabel.setText(util_ui.tr('Password (at least 8 symbols):')) + + def _create_profile(self): + password = self.password.text() + if password != self.confirmPassword.text(): + self.errorLabel.setText(util_ui.tr('Passwords do not match')) + return + if 0 < len(password) < 8: + self.errorLabel.setText(util_ui.tr('Password must be at least 8 symbols')) + return + result = CreateProfileScreenResult(self.defaultFolder.isChecked(), password) + self.close_with_result(result) diff --git a/toxygen/ui/group_bans_widgets.py b/toxygen/ui/group_bans_widgets.py new file mode 100644 index 0000000..b2758c7 --- /dev/null +++ b/toxygen/ui/group_bans_widgets.py @@ -0,0 +1,68 @@ +from ui.widgets import CenteredWidget +from PyQt5 import uic, QtWidgets, QtCore +import utils.util as util +import utils.ui as util_ui + + +class GroupBanItem(QtWidgets.QWidget): + + def __init__(self, ban, cancel_ban, can_cancel_ban, parent=None): + super().__init__(parent) + self._ban = ban + self._cancel_ban = cancel_ban + self._can_cancel_ban = can_cancel_ban + + uic.loadUi(util.get_views_path('gc_ban_item'), self) + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self.banTargetLabel.setText(self._ban.ban_target) + ban_time = self._ban.ban_time + self.banTimeLabel.setText(util.unix_time_to_long_str(ban_time)) + + self.cancelPushButton.clicked.connect(self._cancel_ban) + self.cancelPushButton.setEnabled(self._can_cancel_ban) + + def _retranslate_ui(self): + self.cancelPushButton.setText(util_ui.tr('Cancel ban')) + + def _cancel_ban(self): + self._cancel_ban(self._ban.ban_id) + + +class GroupBansScreen(CenteredWidget): + + def __init__(self, groups_service, group): + super().__init__() + self._groups_service = groups_service + self._group = group + + uic.loadUi(util.get_views_path('bans_list_screen'), self) + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self._refresh_bans_list() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Bans list for group "{}"').format(self._group.name)) + + def _refresh_bans_list(self): + self.bansListWidget.clear() + can_cancel_ban = self._group.is_self_moderator_or_founder() + for ban in self._group.bans: + self._create_ban_item(ban, can_cancel_ban) + + def _create_ban_item(self, ban, can_cancel_ban): + item = GroupBanItem(ban, self._on_ban_cancelled, can_cancel_ban, self.bansListWidget) + elem = QtWidgets.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(item.width(), item.height())) + self.bansListWidget.addItem(elem) + self.bansListWidget.setItemWidget(elem, item) + + def _on_ban_cancelled(self, ban_id): + self._groups_service.cancel_ban(self._group.number, ban_id) + self._refresh_bans_list() diff --git a/toxygen/ui/group_invites_widgets.py b/toxygen/ui/group_invites_widgets.py new file mode 100644 index 0000000..d35aca1 --- /dev/null +++ b/toxygen/ui/group_invites_widgets.py @@ -0,0 +1,127 @@ +from PyQt5 import uic, QtWidgets +import utils.util as util +from ui.widgets import * + + +class GroupInviteItem(QtWidgets.QWidget): + + def __init__(self, parent, chat_name, avatar, friend_name): + super().__init__(parent) + uic.loadUi(util.get_views_path('gc_invite_item'), self) + + self.groupNameLabel.setText(chat_name) + self.friendNameLabel.setText(friend_name) + self.friendAvatarLabel.setPixmap(avatar) + + def is_selected(self): + return self.selectCheckBox.isChecked() + + def subscribe_checked_event(self, callback): + self.selectCheckBox.clicked.connect(callback) + + +class GroupInvitesScreen(CenteredWidget): + + def __init__(self, groups_service, profile, contacts_provider): + super().__init__() + self._groups_service = groups_service + self._profile = profile + self._contacts_provider = contacts_provider + + uic.loadUi(util.get_views_path('group_invites_screen'), self) + + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self._refresh_invites_list() + + self.nickLineEdit.setText(self._profile.name) + self.statusComboBox.setCurrentIndex(self._profile.status or 0) + + self.nickLineEdit.textChanged.connect(self._nick_changed) + self.acceptPushButton.clicked.connect(self._accept_invites) + self.declinePushButton.clicked.connect(self._decline_invites) + + self.invitesListWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + self.invitesListWidget.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + + self._update_buttons_state() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Group chat invites')) + self.noInvitesLabel.setText(util_ui.tr('No group invites found')) + self.acceptPushButton.setText(util_ui.tr('Accept')) + self.declinePushButton.setText(util_ui.tr('Decline')) + self.statusComboBox.addItem(util_ui.tr('Online')) + self.statusComboBox.addItem(util_ui.tr('Away')) + self.statusComboBox.addItem(util_ui.tr('Busy')) + self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat')) + self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password')) + + def _get_friend(self, public_key): + return self._contacts_provider.get_friend_by_public_key(public_key) + + def _accept_invites(self): + nick = self.nickLineEdit.text() + password = self.passwordLineEdit.text() + status = self.statusComboBox.currentIndex() + + selected_invites = self._get_selected_invites() + for invite in selected_invites: + self._groups_service.accept_group_invite(invite, nick, status, password) + + self._refresh_invites_list() + self._close_window_if_needed() + + def _decline_invites(self): + selected_invites = self._get_selected_invites() + for invite in selected_invites: + self._groups_service.decline_group_invite(invite) + + self._refresh_invites_list() + self._close_window_if_needed() + + def _get_selected_invites(self): + all_invites = self._groups_service.get_group_invites() + selected = [] + items_count = len(all_invites) + for index in range(items_count): + list_item = self.invitesListWidget.item(index) + item_widget = self.invitesListWidget.itemWidget(list_item) + if item_widget.is_selected(): + selected.append(all_invites[index]) + + return selected + + def _refresh_invites_list(self): + self.invitesListWidget.clear() + invites = self._groups_service.get_group_invites() + for invite in invites: + self._create_invite_item(invite) + + def _create_invite_item(self, invite): + friend = self._get_friend(invite.friend_public_key) + item = GroupInviteItem(self.invitesListWidget, invite.chat_name, friend.get_pixmap(), friend.name) + item.subscribe_checked_event(self._item_selected) + elem = QtWidgets.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(item.width(), item.height())) + self.invitesListWidget.addItem(elem) + self.invitesListWidget.setItemWidget(elem, item) + + def _item_selected(self): + self._update_buttons_state() + + def _nick_changed(self): + self._update_buttons_state() + + def _update_buttons_state(self): + nick = self.nickLineEdit.text() + selected_items = self._get_selected_invites() + self.acceptPushButton.setEnabled(bool(nick) and len(selected_items)) + self.declinePushButton.setEnabled(len(selected_items) > 0) + + def _close_window_if_needed(self): + if self._groups_service.group_invites_count == 0: + self.close() diff --git a/toxygen/ui/group_peers_list.py b/toxygen/ui/group_peers_list.py new file mode 100644 index 0000000..9d2632d --- /dev/null +++ b/toxygen/ui/group_peers_list.py @@ -0,0 +1,33 @@ +from ui.widgets import * +from wrapper.toxcore_enums_and_consts import * + + +class PeerItem(QtWidgets.QWidget): + + def __init__(self, peer, handler, width, parent=None): + super().__init__(parent) + self.resize(QtCore.QSize(width, 34)) + self.nameLabel = DataLabel(self) + self.nameLabel.setGeometry(5, 0, width - 5, 34) + name = peer.name + if peer.is_current_user: + name += util_ui.tr(' (You)') + self.nameLabel.setText(name) + if peer.status == TOX_USER_STATUS['NONE']: + style = 'QLabel {color: green}' + elif peer.status == TOX_USER_STATUS['AWAY']: + style = 'QLabel {color: yellow}' + else: + style = 'QLabel {color: red}' + self.nameLabel.setStyleSheet(style) + self.nameLabel.mousePressEvent = lambda x: handler(peer.id) + + +class PeerTypeItem(QtWidgets.QWidget): + + def __init__(self, text, width, parent=None): + super().__init__(parent) + self.resize(QtCore.QSize(width, 34)) + self.nameLabel = DataLabel(self) + self.nameLabel.setGeometry(5, 0, width - 5, 34) + self.nameLabel.setText(text) diff --git a/toxygen/ui/group_settings_widgets.py b/toxygen/ui/group_settings_widgets.py new file mode 100644 index 0000000..c32168b --- /dev/null +++ b/toxygen/ui/group_settings_widgets.py @@ -0,0 +1,77 @@ +from ui.widgets import CenteredWidget +from PyQt5 import uic +import utils.util as util +import utils.ui as util_ui + + +class GroupManagementScreen(CenteredWidget): + + def __init__(self, groups_service, group): + super().__init__() + self._groups_service = groups_service + self._group = group + + uic.loadUi(util.get_views_path('group_management_screen'), self) + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self.passwordLineEdit.setText(self._group.password) + self.privacyStateComboBox.setCurrentIndex(1 if self._group.is_private else 0) + self.peersLimitSpinBox.setValue(self._group.peers_limit) + + self.savePushButton.clicked.connect(self._save) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Group "{}"').format(self._group.name)) + self.passwordLabel.setText(util_ui.tr('Password:')) + self.peerLimitLabel.setText(util_ui.tr('Peer limit:')) + self.privacyStateLabel.setText(util_ui.tr('Privacy state:')) + self.savePushButton.setText(util_ui.tr('Save')) + + self.privacyStateComboBox.clear() + self.privacyStateComboBox.addItem(util_ui.tr('Public')) + self.privacyStateComboBox.addItem(util_ui.tr('Private')) + + def _save(self): + password = self.passwordLineEdit.text() + privacy_state = self.privacyStateComboBox.currentIndex() + peers_limit = self.peersLimitSpinBox.value() + + self._groups_service.set_group_password(self._group, password) + self._groups_service.set_group_privacy_state(self._group, privacy_state) + self._groups_service.set_group_peers_limit(self._group, peers_limit) + + self.close() + + +class GroupSettingsScreen(CenteredWidget): + + def __init__(self, group): + super().__init__() + self._group = group + + uic.loadUi(util.get_views_path('gc_settings_screen'), self) + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self.copyPasswordPushButton.clicked.connect(self._copy_password) + self.copyPasswordPushButton.setEnabled(bool(self._group.password)) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Group "{}"').format(self._group.name)) + if self._group.password: + password_label_text = '{} {}'.format(util_ui.tr('Password:'), self._group.password) + else: + password_label_text = util_ui.tr('Password is not set') + self.passwordLabel.setText(password_label_text) + self.peerLimitLabel.setText('{} {}'.format(util_ui.tr('Peer limit:'), self._group.peers_limit)) + privacy_state = util_ui.tr('Private') if self._group.is_private else util_ui.tr('Public') + self.privacyStateLabel.setText('{} {}'.format(util_ui.tr('Privacy state:'), privacy_state)) + self.copyPasswordPushButton.setText(util_ui.tr('Copy password')) + + def _copy_password(self): + util_ui.copy_to_clipboard(self._group.password) diff --git a/toxygen/ui/groups_widgets.py b/toxygen/ui/groups_widgets.py new file mode 100644 index 0000000..ad4b703 --- /dev/null +++ b/toxygen/ui/groups_widgets.py @@ -0,0 +1,123 @@ +from PyQt5 import uic +import utils.util as util +from ui.widgets import * +from wrapper.toxcore_enums_and_consts import * + + +class BaseGroupScreen(CenteredWidget): + + def __init__(self, groups_service, profile): + super().__init__() + self._groups_service = groups_service + self._profile = profile + + def _retranslate_ui(self): + self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat')) + self.nickLabel.setText(util_ui.tr('Nickname:')) + self.statusLabel.setText(util_ui.tr('Status:')) + self.statusComboBox.addItem(util_ui.tr('Online')) + self.statusComboBox.addItem(util_ui.tr('Away')) + self.statusComboBox.addItem(util_ui.tr('Busy')) + + +class CreateGroupScreen(BaseGroupScreen): + + def __init__(self, groups_service, profile): + super().__init__(groups_service, profile) + uic.loadUi(util.get_views_path('create_group_screen'), self) + self.center() + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self.statusComboBox.setCurrentIndex(self._profile.status or 0) + self.nickLineEdit.setText(self._profile.name) + + self.addGroupButton.clicked.connect(self._create_group) + self.groupNameLineEdit.textChanged.connect(self._group_name_changed) + self.nickLineEdit.textChanged.connect(self._nick_changed) + + def _retranslate_ui(self): + super()._retranslate_ui() + self.setWindowTitle(util_ui.tr('Create new group chat')) + self.groupNameLabel.setText(util_ui.tr('Group name:')) + self.groupTypeLabel.setText(util_ui.tr('Group type:')) + self.groupNameLineEdit.setPlaceholderText(util_ui.tr('Group\'s persistent name')) + self.addGroupButton.setText(util_ui.tr('Create group')) + self.groupTypeComboBox.addItem(util_ui.tr('Public')) + self.groupTypeComboBox.addItem(util_ui.tr('Private')) + self.groupTypeComboBox.setCurrentIndex(1) + + def _create_group(self): + group_name = self.groupNameLineEdit.text() + privacy_state = self.groupTypeComboBox.currentIndex() + nick = self.nickLineEdit.text() + status = self.statusComboBox.currentIndex() + self._groups_service.create_new_gc(group_name, privacy_state, nick, status) + self.close() + + def _nick_changed(self): + self._update_button_state() + + def _group_name_changed(self): + self._update_button_state() + + def _update_button_state(self): + is_nick_set = bool(self.nickLineEdit.text()) + is_group_name_set = bool(self.groupNameLineEdit.text()) + self.addGroupButton.setEnabled(is_nick_set and is_group_name_set) + + +class JoinGroupScreen(BaseGroupScreen): + + def __init__(self, groups_service, profile): + super().__init__(groups_service, profile) + uic.loadUi(util.get_views_path('join_group_screen'), self) + self.center() + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self.statusComboBox.setCurrentIndex(self._profile.status or 0) + self.nickLineEdit.setText(self._profile.name) + + self.chatIdLineEdit.textChanged.connect(self._chat_id_changed) + self.joinGroupButton.clicked.connect(self._join_group) + self.nickLineEdit.textChanged.connect(self._nick_changed) + + def _retranslate_ui(self): + super()._retranslate_ui() + self.setWindowTitle(util_ui.tr('Join public group chat')) + self.chatIdLabel.setText(util_ui.tr('Group ID:')) + self.passwordLabel.setText(util_ui.tr('Password:')) + self.chatIdLineEdit.setPlaceholderText(util_ui.tr('Group\'s chat ID')) + self.joinGroupButton.setText(util_ui.tr('Join group')) + self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password')) + + def _chat_id_changed(self): + self._update_button_state() + + def _nick_changed(self): + self._update_button_state() + + def _update_button_state(self): + chat_id = self._get_chat_id() + is_nick_set = bool(self.nickLineEdit.text()) + self.joinGroupButton.setEnabled(len(chat_id) == TOX_GROUP_CHAT_ID_SIZE * 2 and is_nick_set) + + def _join_group(self): + chat_id = self._get_chat_id() + password = self.passwordLineEdit.text() + nick = self.nickLineEdit.text() + status = self.statusComboBox.currentIndex() + self._groups_service.join_gc_by_id(chat_id, password, nick, status) + self.close() + + def _get_chat_id(self): + chat_id = self.chatIdLineEdit.text().strip() + if chat_id.startswith('tox:'): + chat_id = chat_id[4:] + + return chat_id diff --git a/toxygen/ui/items_factories.py b/toxygen/ui/items_factories.py new file mode 100644 index 0000000..7346f8f --- /dev/null +++ b/toxygen/ui/items_factories.py @@ -0,0 +1,90 @@ +from ui.contact_items import * +from ui.messages_widgets import * + + +class ContactItemsFactory: + + def __init__(self, settings, main_screen): + self._settings = settings + self._friends_list = main_screen.friends_list + + def create_contact_item(self): + item = ContactItem(self._settings) + elem = QtWidgets.QListWidgetItem(self._friends_list) + elem.setSizeHint(QtCore.QSize(250, 40 if self._settings['compact_mode'] else 70)) + self._friends_list.addItem(elem) + self._friends_list.setItemWidget(elem, item) + + return item + + +class MessagesItemsFactory: + + def __init__(self, settings, plugin_loader, smiley_loader, main_screen, delete_action): + self._file_transfers_handler = None + self._settings, self._plugin_loader = settings, plugin_loader + self._smiley_loader, self._delete_action = smiley_loader, delete_action + self._messages = main_screen.messages + self._message_edit = main_screen.messageEdit + + def set_file_transfers_handler(self, file_transfers_handler): + self._file_transfers_handler = file_transfers_handler + + def create_message_item(self, message, append=True, pixmap=None): + item = message.get_widget(self._settings, self._create_message_browser, + self._delete_action, self._messages) + if pixmap is not None: + item.set_avatar(pixmap) + elem = QtWidgets.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) + if append: + self._messages.addItem(elem) + else: + self._messages.insertItem(0, elem) + self._messages.setItemWidget(elem, item) + + return item + + def create_inline_item(self, message, append=True, position=0): + elem = QtWidgets.QListWidgetItem() + item = InlineImageItem(message.data, self._messages.width(), elem, self._messages) + elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) + if append: + self._messages.addItem(elem) + else: + self._messages.insertItem(position, elem) + self._messages.setItemWidget(elem, item) + + return item + + def create_unsent_file_item(self, message, append=True): + item = message.get_widget(self._file_transfers_handler, self._settings, self._messages.width(), self._messages) + elem = QtWidgets.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) + if append: + self._messages.addItem(elem) + else: + self._messages.insertItem(0, elem) + self._messages.setItemWidget(elem, item) + + return item + + def create_file_transfer_item(self, message, append=True): + item = message.get_widget(self._file_transfers_handler, self._settings, self._messages.width(), self._messages) + elem = QtWidgets.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) + if append: + self._messages.addItem(elem) + else: + self._messages.insertItem(0, elem) + self._messages.setItemWidget(elem, item) + + return item + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _create_message_browser(self, text, width, message_type, parent=None): + return MessageBrowser(self._settings, self._message_edit, self._smiley_loader, self._plugin_loader, + text, width, message_type, parent) diff --git a/toxygen/ui/login_screen.py b/toxygen/ui/login_screen.py new file mode 100644 index 0000000..35e33b5 --- /dev/null +++ b/toxygen/ui/login_screen.py @@ -0,0 +1,77 @@ +from ui.widgets import * +from PyQt5 import uic +import utils.util as util +import utils.ui as util_ui +import os.path + + +class LoginScreenResult: + + def __init__(self, profile_path, load_as_default, password=None): + self._profile_path = profile_path + self._load_as_default = load_as_default + self._password = password + + def get_profile_path(self): + return self._profile_path + + profile_path = property(get_profile_path) + + def get_load_as_default(self): + return self._load_as_default + + load_as_default = property(get_load_as_default) + + def get_password(self): + return self._password + + password = property(get_password) + + def is_new_profile(self): + return not os.path.isfile(self._profile_path) + + +class LoginScreen(CenteredWidget, DialogWithResult): + + def __init__(self): + CenteredWidget.__init__(self) + DialogWithResult.__init__(self) + uic.loadUi(util.get_views_path('login_screen'), self) + self.center() + self._profiles = [] + self._update_ui() + + def update_select(self, profiles): + profiles = sorted(profiles, key=lambda p: p[1]) + self._profiles = list(profiles) + self.profilesComboBox.addItems(list(map(lambda p: p[1], profiles))) + self.loadProfilePushButton.setEnabled(len(profiles) > 0) + + def _update_ui(self): + self.profileNameLineEdit = LineEditWithEnterSupport(self._create_profile, self) + self.profileNameLineEdit.setGeometry(QtCore.QRect(20, 100, 160, 30)) + self._retranslate_ui() + self.createProfilePushButton.clicked.connect(self._create_profile) + self.loadProfilePushButton.clicked.connect(self._load_existing_profile) + + def _create_profile(self): + path = self.profileNameLineEdit.text() + load_as_default = self.defaultProfileCheckBox.isChecked() + result = LoginScreenResult(path, load_as_default) + self.close_with_result(result) + + def _load_existing_profile(self): + index = self.profilesComboBox.currentIndex() + load_as_default = self.defaultProfileCheckBox.isChecked() + path = util.join_path(self._profiles[index][0], self._profiles[index][1] + '.tox') + result = LoginScreenResult(path, load_as_default) + self.close_with_result(result) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Log in')) + self.profileNameLineEdit.setPlaceholderText(util_ui.tr('Profile name')) + self.createProfilePushButton.setText(util_ui.tr('Create')) + self.loadProfilePushButton.setText(util_ui.tr('Load profile')) + self.defaultProfileCheckBox.setText(util_ui.tr('Use as default')) + self.existingProfileGroupBox.setTitle(util_ui.tr('Load existing profile')) + self.newProfileGroupBox.setTitle(util_ui.tr('Create new profile')) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py new file mode 100644 index 0000000..5a510a5 --- /dev/null +++ b/toxygen/ui/main_screen.py @@ -0,0 +1,718 @@ +from ui.contact_items import * +from ui.widgets import MultilineEdit +from ui.main_screen_widgets import * +import utils.util as util +import utils.ui as util_ui +from PyQt5 import uic + + +class MainWindow(QtWidgets.QMainWindow): + + def __init__(self, settings, tray): + super().__init__() + self._settings = settings + self._contacts_manager = None + self._tray = tray + self._widget_factory = None + self._modal_window = None + self._plugins_loader = None + self.setAcceptDrops(True) + self._saved = False + self._smiley_window = None + self._profile = self._toxes = self._messenger = None + self._file_transfer_handler = self._history_loader = self._groups_service = self._calls_manager = None + self._should_show_group_peers_list = False + self.initUI() + + def set_dependencies(self, widget_factory, tray, contacts_manager, messenger, profile, plugins_loader, + file_transfer_handler, history_loader, calls_manager, groups_service, toxes): + self._widget_factory = widget_factory + self._tray = tray + self._contacts_manager = contacts_manager + self._profile = profile + self._plugins_loader = plugins_loader + self._file_transfer_handler = file_transfer_handler + self._history_loader = history_loader + self._calls_manager = calls_manager + self._groups_service = groups_service + self._toxes = toxes + self._messenger = messenger + self._contacts_manager.active_contact_changed.add_callback(self._new_contact_selected) + self.messageEdit.set_dependencies(messenger, contacts_manager, file_transfer_handler) + + self.update_gc_invites_button_state() + + def show(self): + super().show() + self._contacts_manager.update() + if self._settings['show_welcome_screen']: + self._modal_window = self._widget_factory.create_welcome_window() + + def setup_menu(self, window): + self.menubar = QtWidgets.QMenuBar(window) + self.menubar.setObjectName("menubar") + self.menubar.setNativeMenuBar(False) + self.menubar.setMinimumSize(self.width(), 25) + self.menubar.setMaximumSize(self.width(), 25) + self.menubar.setBaseSize(self.width(), 25) + self.menuProfile = QtWidgets.QMenu(self.menubar) + + self.menuProfile = QtWidgets.QMenu(self.menubar) + self.menuProfile.setObjectName("menuProfile") + self.menuGC = QtWidgets.QMenu(self.menubar) + self.menuSettings = QtWidgets.QMenu(self.menubar) + self.menuSettings.setObjectName("menuSettings") + self.menuPlugins = QtWidgets.QMenu(self.menubar) + self.menuPlugins.setObjectName("menuPlugins") + self.menuAbout = QtWidgets.QMenu(self.menubar) + self.menuAbout.setObjectName("menuAbout") + + self.actionAdd_friend = QtWidgets.QAction(window) + self.actionAdd_friend.setObjectName("actionAdd_friend") + self.actionprofilesettings = QtWidgets.QAction(window) + self.actionprofilesettings.setObjectName("actionprofilesettings") + self.actionPrivacy_settings = QtWidgets.QAction(window) + self.actionPrivacy_settings.setObjectName("actionPrivacy_settings") + self.actionInterface_settings = QtWidgets.QAction(window) + self.actionInterface_settings.setObjectName("actionInterface_settings") + self.actionNotifications = QtWidgets.QAction(window) + self.actionNotifications.setObjectName("actionNotifications") + self.actionNetwork = QtWidgets.QAction(window) + self.actionNetwork.setObjectName("actionNetwork") + self.actionAbout_program = QtWidgets.QAction(window) + self.actionAbout_program.setObjectName("actionAbout_program") + self.updateSettings = QtWidgets.QAction(window) + self.actionSettings = QtWidgets.QAction(window) + self.actionSettings.setObjectName("actionSettings") + self.audioSettings = QtWidgets.QAction(window) + self.videoSettings = QtWidgets.QAction(window) + self.pluginData = QtWidgets.QAction(window) + self.importPlugin = QtWidgets.QAction(window) + self.reloadPlugins = QtWidgets.QAction(window) + self.lockApp = QtWidgets.QAction(window) + self.createGC = QtWidgets.QAction(window) + self.joinGC = QtWidgets.QAction(window) + self.gc_invites = QtWidgets.QAction(window) + + self.menuProfile.addAction(self.actionAdd_friend) + self.menuProfile.addAction(self.actionSettings) + self.menuProfile.addAction(self.lockApp) + self.menuGC.addAction(self.createGC) + self.menuGC.addAction(self.joinGC) + self.menuGC.addAction(self.gc_invites) + self.menuSettings.addAction(self.actionPrivacy_settings) + self.menuSettings.addAction(self.actionInterface_settings) + self.menuSettings.addAction(self.actionNotifications) + self.menuSettings.addAction(self.actionNetwork) + self.menuSettings.addAction(self.audioSettings) + self.menuSettings.addAction(self.videoSettings) + self.menuSettings.addAction(self.updateSettings) + self.menuPlugins.addAction(self.pluginData) + self.menuPlugins.addAction(self.importPlugin) + self.menuPlugins.addAction(self.reloadPlugins) + self.menuAbout.addAction(self.actionAbout_program) + + self.menubar.addAction(self.menuProfile.menuAction()) + self.menubar.addAction(self.menuGC.menuAction()) + self.menubar.addAction(self.menuSettings.menuAction()) + self.menubar.addAction(self.menuPlugins.menuAction()) + self.menubar.addAction(self.menuAbout.menuAction()) + + self.actionAbout_program.triggered.connect(self.about_program) + self.actionNetwork.triggered.connect(self.network_settings) + self.actionAdd_friend.triggered.connect(self.add_contact_triggered) + self.createGC.triggered.connect(self.create_gc) + self.joinGC.triggered.connect(self.join_gc) + self.actionSettings.triggered.connect(self.profile_settings) + self.actionPrivacy_settings.triggered.connect(self.privacy_settings) + self.actionInterface_settings.triggered.connect(self.interface_settings) + self.actionNotifications.triggered.connect(self.notification_settings) + self.audioSettings.triggered.connect(self.audio_settings) + self.videoSettings.triggered.connect(self.video_settings) + self.updateSettings.triggered.connect(self.update_settings) + self.pluginData.triggered.connect(self.plugins_menu) + self.lockApp.triggered.connect(self.lock_app) + self.importPlugin.triggered.connect(self.import_plugin) + self.reloadPlugins.triggered.connect(self.reload_plugins) + self.gc_invites.triggered.connect(self._open_gc_invites_list) + + def languageChange(self, *args, **kwargs): + self.retranslateUi() + + def event(self, event): + if event.type() == QtCore.QEvent.WindowActivate: + self._tray.setIcon(QtGui.QIcon(util.join_path(util.get_images_directory(), 'icon.png'))) + self.messages.repaint() + return super().event(event) + + def retranslateUi(self): + self.lockApp.setText(util_ui.tr("Lock")) + self.menuPlugins.setTitle(util_ui.tr("Plugins")) + self.menuGC.setTitle(util_ui.tr("Group chats")) + self.pluginData.setText(util_ui.tr("List of plugins")) + self.menuProfile.setTitle(util_ui.tr("Profile")) + self.menuSettings.setTitle(util_ui.tr("Settings")) + self.menuAbout.setTitle(util_ui.tr("About")) + self.actionAdd_friend.setText(util_ui.tr("Add contact")) + self.createGC.setText(util_ui.tr("Create group chat")) + self.joinGC.setText(util_ui.tr("Join group chat")) + self.gc_invites.setText(util_ui.tr("Group invites")) + self.actionprofilesettings.setText(util_ui.tr("Profile")) + self.actionPrivacy_settings.setText(util_ui.tr("Privacy")) + self.actionInterface_settings.setText(util_ui.tr("Interface")) + self.actionNotifications.setText(util_ui.tr("Notifications")) + self.actionNetwork.setText(util_ui.tr("Network")) + self.actionAbout_program.setText(util_ui.tr("About program")) + self.actionSettings.setText(util_ui.tr("Settings")) + self.audioSettings.setText(util_ui.tr("Audio")) + self.videoSettings.setText(util_ui.tr("Video")) + self.updateSettings.setText(util_ui.tr("Updates")) + self.importPlugin.setText(util_ui.tr("Import plugin")) + self.reloadPlugins.setText(util_ui.tr("Reload plugins")) + + self.searchLineEdit.setPlaceholderText(util_ui.tr("Search")) + self.sendMessageButton.setToolTip(util_ui.tr("Send message")) + self.callButton.setToolTip(util_ui.tr("Start audio call with friend")) + self.contactsFilterComboBox.clear() + self.contactsFilterComboBox.addItem(util_ui.tr("All")) + self.contactsFilterComboBox.addItem(util_ui.tr("Online")) + self.contactsFilterComboBox.addItem(util_ui.tr("Online first")) + self.contactsFilterComboBox.addItem(util_ui.tr("Name")) + self.contactsFilterComboBox.addItem(util_ui.tr("Online and by name")) + self.contactsFilterComboBox.addItem(util_ui.tr("Online first and by name")) + + def setup_right_bottom(self, Form): + Form.resize(650, 60) + self.messageEdit = MessageArea(Form, self) + self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55)) + font = QtGui.QFont() + font.setPointSize(11) + font.setFamily(self._settings['font']) + self.messageEdit.setFont(font) + + self.sendMessageButton = QtWidgets.QPushButton(Form) + self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55)) + + self.menuButton = MenuButton(Form, self.show_menu) + self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55))) + + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'send.png')) + icon = QtGui.QIcon(pixmap) + self.sendMessageButton.setIcon(icon) + self.sendMessageButton.setIconSize(QtCore.QSize(45, 60)) + + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'menu.png')) + icon = QtGui.QIcon(pixmap) + self.menuButton.setIcon(icon) + self.menuButton.setIconSize(QtCore.QSize(40, 40)) + + self.sendMessageButton.clicked.connect(self.send_message) + + QtCore.QMetaObject.connectSlotsByName(Form) + + def setup_left_column(self, left_column): + uic.loadUi(util.get_views_path('ms_left_column'), left_column) + + pixmap = QtGui.QPixmap() + pixmap.load(util.join_path(util.get_images_directory(), 'search.png')) + left_column.searchLabel.setPixmap(pixmap) + + self.name = DataLabel(left_column) + self.name.setGeometry(QtCore.QRect(75, 15, 150, 25)) + font = QtGui.QFont() + font.setFamily(self._settings['font']) + font.setPointSize(14) + font.setBold(True) + self.name.setFont(font) + + self.status_message = DataLabel(left_column) + self.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25)) + + self.connection_status = StatusCircle(left_column) + self.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32)) + + left_column.contactsFilterComboBox.activated[int].connect(lambda x: self._filtering()) + + self.avatar_label = left_column.avatarLabel + self.searchLineEdit = left_column.searchLineEdit + self.contacts_filter = self.contactsFilterComboBox = left_column.contactsFilterComboBox + + self.groupInvitesPushButton = left_column.groupInvitesPushButton + + self.groupInvitesPushButton.clicked.connect(self._open_gc_invites_list) + self.avatar_label.mouseReleaseEvent = self.profile_settings + self.status_message.mouseReleaseEvent = self.profile_settings + self.name.mouseReleaseEvent = self.profile_settings + + self.friends_list = left_column.friendsListWidget + self.friends_list.itemSelectionChanged.connect(self._selected_contact_changed) + self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) + self.friends_list.customContextMenuRequested.connect(self._friend_right_click) + self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu) + + def setup_right_top(self, Form): + Form.resize(650, 75) + self.account_avatar = QtWidgets.QLabel(Form) + self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64)) + self.account_avatar.setScaledContents(False) + self.account_name = DataLabel(Form) + self.account_name.setGeometry(QtCore.QRect(100, 0, 400, 25)) + self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse) + font = QtGui.QFont() + font.setFamily(self._settings['font']) + font.setPointSize(14) + font.setBold(True) + self.account_name.setFont(font) + self.account_status = DataLabel(Form) + self.account_status.setGeometry(QtCore.QRect(100, 20, 400, 25)) + self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse) + font.setPointSize(12) + font.setBold(False) + self.account_status.setFont(font) + self.account_status.setObjectName("account_status") + self.callButton = QtWidgets.QPushButton(Form) + self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) + self.callButton.setObjectName("callButton") + self.callButton.clicked.connect(lambda: self._calls_manager.call_click(True)) + self.videocallButton = QtWidgets.QPushButton(Form) + self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) + self.videocallButton.setObjectName("videocallButton") + self.videocallButton.clicked.connect(lambda: self._calls_manager.call_click(True, True)) + self.groupMenuButton = QtWidgets.QPushButton(Form) + self.groupMenuButton.setGeometry(QtCore.QRect(470, 10, 50, 50)) + self.groupMenuButton.clicked.connect(self._toggle_gc_peers_list) + self.groupMenuButton.setVisible(False) + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'menu.png')) + icon = QtGui.QIcon(pixmap) + self.groupMenuButton.setIcon(icon) + self.groupMenuButton.setIconSize(QtCore.QSize(45, 60)) + self.update_call_state('call') + self.typing = QtWidgets.QLabel(Form) + self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30)) + pixmap = QtGui.QPixmap(QtCore.QSize(50, 30)) + pixmap.load(util.join_path(util.get_images_directory(), 'typing.png')) + self.typing.setScaledContents(False) + self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio)) + self.typing.setVisible(False) + QtCore.QMetaObject.connectSlotsByName(Form) + + def setup_right_center(self, widget): + self.messages = QtWidgets.QListWidget(widget) + self.messages.setGeometry(0, 0, 620, 310) + self.messages.setObjectName("messages") + self.messages.setSpacing(1) + self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.messages.focusOutEvent = lambda event: self.messages.clearSelection() + self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu) + + def load(pos): + if not pos: + contact = self._contacts_manager.get_curr_contact() + self._history_loader.load_history(contact) + self.messages.verticalScrollBar().setValue(1) + self.messages.verticalScrollBar().valueChanged.connect(load) + self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) + + self.peers_list = QtWidgets.QListWidget(widget) + self.peers_list.setGeometry(0, 0, 0, 0) + self.peers_list.setObjectName("peersList") + self.peers_list.setSpacing(1) + self.peers_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) + self.peers_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.peers_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu) + self.peers_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + + def initUI(self): + self.setMinimumSize(920, 500) + s = self._settings + self.setGeometry(s['x'], s['y'], s['width'], s['height']) + self.setWindowTitle('Toxygen') + menu = QtWidgets.QWidget() + main = QtWidgets.QWidget() + grid = QtWidgets.QGridLayout() + info = QtWidgets.QWidget() + left_column = QtWidgets.QWidget() + messages = QtWidgets.QWidget() + message_buttons = QtWidgets.QWidget() + self.setup_right_center(messages) + self.setup_right_top(info) + self.setup_right_bottom(message_buttons) + self.setup_left_column(left_column) + self.setup_menu(menu) + if not s['mirror_mode']: + grid.addWidget(left_column, 1, 0, 4, 1) + grid.addWidget(messages, 2, 1, 2, 1) + grid.addWidget(info, 1, 1) + grid.addWidget(message_buttons, 4, 1) + grid.setColumnMinimumWidth(1, 500) + grid.setColumnMinimumWidth(0, 270) + else: + grid.addWidget(left_column, 1, 1, 4, 1) + grid.addWidget(messages, 2, 0, 2, 1) + grid.addWidget(info, 1, 0) + grid.addWidget(message_buttons, 4, 0) + grid.setColumnMinimumWidth(0, 500) + grid.setColumnMinimumWidth(1, 270) + + grid.addWidget(menu, 0, 0, 1, 2) + grid.setSpacing(0) + grid.setContentsMargins(0, 0, 0, 0) + grid.setRowMinimumHeight(0, 25) + grid.setRowMinimumHeight(1, 75) + grid.setRowMinimumHeight(2, 25) + grid.setRowMinimumHeight(3, 320) + grid.setRowMinimumHeight(4, 55) + grid.setColumnStretch(1, 1) + grid.setRowStretch(3, 1) + main.setLayout(grid) + self.setCentralWidget(main) + self.messageEdit.setFocus() + self.friend_info = info + self.retranslateUi() + + def closeEvent(self, event): + close_setting = self._settings['close_app'] + if close_setting == 0 or self._settings.closing: + if self._saved: + return + self._saved = True + self._settings['x'] = self.geometry().x() + self._settings['y'] = self.geometry().y() + self._settings['width'] = self.width() + self._settings['height'] = self.height() + self._settings.save() + util_ui.close_all_windows() + event.accept() + elif close_setting == 2 and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): + event.ignore() + self.hide() + else: + event.ignore() + self.showMinimized() + + def close_window(self): + self._settings.closing = True + self.close() + + def resizeEvent(self, *args, **kwargs): + width = self.width() - 270 + if not self._should_show_group_peers_list: + self.messages.setGeometry(0, 0, width, self.height() - 155) + self.peers_list.setGeometry(0, 0, 0, 0) + else: + self.messages.setGeometry(0, 0, width * 3 // 4, self.height() - 155) + self.peers_list.setGeometry(width * 3 // 4, 0, width - width * 3 // 4, self.height() - 155) + + invites_button_visible = self.groupInvitesPushButton.isVisible() + self.friends_list.setGeometry(0, 125 if invites_button_visible else 100, + 270, self.height() - 150 if invites_button_visible else self.height() - 125) + + self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50)) + self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50)) + self.groupMenuButton.setGeometry(QtCore.QRect(self.width() - 450, 10, 50, 50)) + self.typing.setGeometry(QtCore.QRect(self.width() - 450, 20, 50, 30)) + + self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55)) + self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55)) + self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55)) + + self.account_name.setGeometry(QtCore.QRect(100, 15, self.width() - 560, 25)) + self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25)) + self.messageEdit.setFocus() + + def keyPressEvent(self, event): + key, modifiers = event.key(), event.modifiers() + if key == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): + self.hide() + elif key == QtCore.Qt.Key_C and modifiers & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): + rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems())) + indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count()) + s = self._history_loader.export_history(self._contacts_manager.get_curr_friend(), True, indexes) + self.copy_text(s) + elif key == QtCore.Qt.Key_Z and modifiers & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): + self.messages.clearSelection() + elif key == QtCore.Qt.Key_F and modifiers & QtCore.Qt.ControlModifier: + self.show_search_field() + else: + super().keyPressEvent(event) + + # ----------------------------------------------------------------------------------------------------------------- + # Functions which called when user click in menu + # ----------------------------------------------------------------------------------------------------------------- + + def about_program(self): + # TODO: replace with window + text = util_ui.tr('Toxygen is Tox client written on Python.\nVersion: ') + text += '' + '\nGitHub: https://github.com/toxygen-project/toxygen/' + title = util_ui.tr('About') + util_ui.message_box(text, title) + + def network_settings(self): + self._modal_window = self._widget_factory.create_network_settings_window() + self._modal_window.show() + + def plugins_menu(self): + self._modal_window = self._widget_factory.create_plugins_settings_window() + self._modal_window.show() + + def add_contact_triggered(self, _): + self.add_contact() + + def add_contact(self, link=''): + self._modal_window = self._widget_factory.create_add_contact_window(link) + self._modal_window.show() + + def create_gc(self): + self._modal_window = self._widget_factory.create_group_screen_window() + self._modal_window.show() + + def join_gc(self): + self._modal_window = self._widget_factory.create_join_group_screen_window() + self._modal_window.show() + + def profile_settings(self, _): + self._modal_window = self._widget_factory.create_profile_settings_window() + self._modal_window.show() + + def privacy_settings(self): + self._modal_window = self._widget_factory.create_privacy_settings_window() + self._modal_window.show() + + def notification_settings(self): + self._modal_window = self._widget_factory.create_notification_settings_window() + self._modal_window.show() + + def interface_settings(self): + self._modal_window = self._widget_factory.create_interface_settings_window() + self._modal_window.show() + + def audio_settings(self): + self._modal_window = self._widget_factory.create_audio_settings_window() + self._modal_window.show() + + def video_settings(self): + self._modal_window = self._widget_factory.create_video_settings_window() + self._modal_window.show() + + def update_settings(self): + self._modal_window = self._widget_factory.create_update_settings_window() + self._modal_window.show() + + def reload_plugins(self): + if self._plugin_loader is not None: + self._plugin_loader.reload() + + @staticmethod + def import_plugin(): + directory = util_ui.directory_dialog(util_ui.tr('Choose folder with plugin')) + if directory: + src = directory + '/' + dest = util.get_plugins_directory() + util.copy(src, dest) + util_ui.message_box(util_ui.tr('Plugin will be loaded after restart'), util_ui.tr("Restart Toxygen")) + + def lock_app(self): + if self._toxes.has_password(): + self._settings.locked = True + self.hide() + else: + util_ui.message_box(util_ui.tr('Error. Profile password is not set.'), util_ui.tr("Cannot lock app")) + + def show_menu(self): + if not hasattr(self, 'menu'): + self.menu = DropdownMenu(self) + self.menu.setGeometry(QtCore.QRect(0 if self._settings['mirror_mode'] else 270, + self.height() - 120, + 180, + 120)) + self.menu.show() + + # ----------------------------------------------------------------------------------------------------------------- + # Messages, calls and file transfers + # ----------------------------------------------------------------------------------------------------------------- + + def send_message(self): + self._messenger.send_message() + + def send_file(self): + self.menu.hide() + if self._contacts_manager.is_active_a_friend(): + caption = util_ui.tr('Choose file') + name = util_ui.file_dialog(caption) + if name[0]: + self._file_transfer_handler.send_file(name[0], self._contacts_manager.get_active_number()) + + def send_screenshot(self, hide=False): + self.menu.hide() + if self._contacts_manager.is_active_a_friend(): + self.sw = self._widget_factory.create_screenshot_window(self) + self.sw.show() + if hide: + self.hide() + + def send_smiley(self): + self.menu.hide() + if self._contacts_manager.get_curr_contact() is None: + return + self._smiley_window = self._widget_factory.create_smiley_window(self) + rect = QtCore.QRect(self.menu.x(), + self.menu.y() - self.menu.height(), + self._smiley_window.width(), + self._smiley_window.height()) + self._smiley_window.setGeometry(rect) + self._smiley_window.show() + + def send_sticker(self): + self.menu.hide() + if self._contacts_manager.is_active_a_friend(): + self.sticker = self._widget_factory.create_sticker_window() + self.sticker.setGeometry(QtCore.QRect(self.x() if self._settings['mirror_mode'] else 270 + self.x(), + self.y() + self.height() - 200, + self.sticker.width(), + self.sticker.height())) + self.sticker.show() + + def active_call(self): + self.update_call_state('finish_call') + + def incoming_call(self): + self.update_call_state('incoming_call') + + def call_finished(self): + self.update_call_state('call') + + def update_call_state(self, state): + pixmap = QtGui.QPixmap(os.path.join(util.get_images_directory(), '{}.png'.format(state))) + icon = QtGui.QIcon(pixmap) + self.callButton.setIcon(icon) + self.callButton.setIconSize(QtCore.QSize(50, 50)) + + pixmap = QtGui.QPixmap(os.path.join(util.get_images_directory(), '{}_video.png'.format(state))) + icon = QtGui.QIcon(pixmap) + self.videocallButton.setIcon(icon) + self.videocallButton.setIconSize(QtCore.QSize(35, 35)) + + # ----------------------------------------------------------------------------------------------------------------- + # Functions which called when user open context menu in friends list + # ----------------------------------------------------------------------------------------------------------------- + + def _friend_right_click(self, pos): + item = self.friends_list.itemAt(pos) + number = self.friends_list.indexFromItem(item).row() + contact = self._contacts_manager.get_contact(number) + if contact is None or item is None: + return + generator = contact.get_context_menu_generator() + self.listMenu = generator.generate(self._plugins_loader, self._contacts_manager, self, self._settings, number, + self._groups_service, self._history_loader) + parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) + self.listMenu.move(parent_position + pos) + self.listMenu.show() + + def show_note(self, friend): + note = self._settings['notes'][friend.tox_id] if friend.tox_id in self._settings['notes'] else '' + user = util_ui.tr('Notes about user') + user = '{} {}'.format(user, friend.name) + + def save_note(text): + if friend.tox_id in self._settings['notes']: + del self._settings['notes'][friend.tox_id] + if text: + self._settings['notes'][friend.tox_id] = text + self._settings.save() + self.note = MultilineEdit(user, note, save_note) + self.note.show() + + def set_alias(self, num): + self._contacts_manager.set_alias(num) + + def remove_friend(self, num): + self._contacts_manager.delete_friend(num) + + def block_friend(self, num): + friend = self._contacts_manager.get_contact(num) + self._contacts_manager.block_user(friend.tox_id) + + @staticmethod + def copy_text(text): + util_ui.copy_to_clipboard(text) + + def auto_accept(self, num, value): + tox_id = self._contacts_manager.friend_public_key(num) + if value: + self._settings['auto_accept_from_friends'].append(tox_id) + else: + self._settings['auto_accept_from_friends'].remove(tox_id) + self._settings.save() + + def invite_friend_to_gc(self, friend_number, group_number): + self._contacts_manager.invite_friend(friend_number, group_number) + + def select_contact_row(self, row_index): + self.friends_list.setCurrentRow(row_index) + + # ----------------------------------------------------------------------------------------------------------------- + # Functions which called when user click somewhere else + # ----------------------------------------------------------------------------------------------------------------- + + def _selected_contact_changed(self): + num = self.friends_list.currentRow() + if self._contacts_manager.active_contact != num: + self._contacts_manager.active_contact = num + self.groupMenuButton.setVisible(self._contacts_manager.is_active_a_group()) + + def mouseReleaseEvent(self, event): + pos = self.connection_status.pos() + x, y = pos.x(), pos.y() + 25 + if (x < event.x() < x + 32) and (y < event.y() < y + 32): + self._profile.change_status() + else: + super().mouseReleaseEvent(event) + + def _filtering(self): + index = self.contactsFilterComboBox.currentIndex() + search_text = self.searchLineEdit.text() + self._contacts_manager.filtration_and_sorting(index, search_text) + + def show_search_field(self): + if hasattr(self, 'search_field') and self.search_field.isVisible(): + return + if self._contacts_manager.get_curr_friend() is None: + return + self.search_field = self._widget_factory.create_search_screen(self.messages) + x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40 + self.search_field.setGeometry(x, y, self.messages.width(), 40) + self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40) + if self._should_show_group_peers_list: + self.peers_list.setFixedHeight(self.peers_list.height() - 40) + self.search_field.show() + + def _toggle_gc_peers_list(self): + self._should_show_group_peers_list = not self._should_show_group_peers_list + self.resizeEvent() + if self._should_show_group_peers_list: + self._groups_service.generate_peers_list() + + def _new_contact_selected(self, _): + if self._should_show_group_peers_list: + self._toggle_gc_peers_list() + index = self.friends_list.currentRow() + if self._contacts_manager.active_contact != index: + self.friends_list.setCurrentRow(self._contacts_manager.active_contact) + self.resizeEvent() + + def _open_gc_invites_list(self): + self._modal_window = self._widget_factory.create_group_invites_window() + self._modal_window.show() + + def update_gc_invites_button_state(self): + invites_count = self._groups_service.group_invites_count + self.groupInvitesPushButton.setVisible(invites_count > 0) + text = util_ui.tr('{} new invites to group chats').format(invites_count) + self.groupInvitesPushButton.setText(text) + self.resizeEvent() diff --git a/toxygen/ui/main_screen_widgets.py b/toxygen/ui/main_screen_widgets.py new file mode 100644 index 0000000..122561b --- /dev/null +++ b/toxygen/ui/main_screen_widgets.py @@ -0,0 +1,496 @@ +from PyQt5 import QtCore, QtGui, QtWidgets +from ui.widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit +import urllib +import re +import utils.util as util +import utils.ui as util_ui +from stickers.stickers import load_stickers + + +class MessageArea(QtWidgets.QPlainTextEdit): + """User types messages here""" + + def __init__(self, parent, form): + super().__init__(parent) + self._messenger = self._contacts_manager = self._file_transfer_handler = None + self.parent = form + self.setAcceptDrops(True) + self._timer = QtCore.QTimer(self) + self._timer.timeout.connect(lambda: self._messenger.send_typing(False)) + + def set_dependencies(self, messenger, contacts_manager, file_transfer_handler): + self._messenger = messenger + self._contacts_manager = contacts_manager + self._file_transfer_handler = file_transfer_handler + + def keyPressEvent(self, event): + if event.matches(QtGui.QKeySequence.Paste): + mimeData = QtWidgets.QApplication.clipboard().mimeData() + if mimeData.hasUrls(): + for url in mimeData.urls(): + self.pasteEvent(url.toString()) + else: + self.pasteEvent() + elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): + modifiers = event.modifiers() + if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier: + self.insertPlainText('\n') + else: + if self._timer.isActive(): + self._timer.stop() + self._messenger.send_typing(False) + self._messenger.send_message() + elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText(): + self.appendPlainText(self._messenger.get_last_message()) + elif event.key() == QtCore.Qt.Key_Tab and self._contacts_manager.is_active_a_group(): + text = self.toPlainText() + text_cursor = self.textCursor() + pos = text_cursor.position() + current_word = re.split("\s+", text[:pos])[-1] + start_index = text.rindex(current_word, 0, pos) + peer_name = self._contacts_manager.get_gc_peer_name(current_word) + self.setPlainText(text[:start_index] + peer_name + text[pos:]) + new_pos = start_index + len(peer_name) + text_cursor.setPosition(new_pos, QtGui.QTextCursor.MoveAnchor) + self.setTextCursor(text_cursor) + else: + self._messenger.send_typing(True) + if self._timer.isActive(): + self._timer.stop() + self._timer.start(5000) + super().keyPressEvent(event) + + def contextMenuEvent(self, event): + menu = create_menu(self.createStandardContextMenu()) + menu.exec_(event.globalPos()) + del menu + + def dragEnterEvent(self, e): + e.accept() + + def dragMoveEvent(self, e): + e.accept() + + def dropEvent(self, e): + if e.mimeData().hasFormat('text/plain') or e.mimeData().hasFormat('text/html'): + e.accept() + self.pasteEvent(e.mimeData().text()) + elif e.mimeData().hasUrls(): + for url in e.mimeData().urls(): + self.pasteEvent(url.toString()) + e.accept() + else: + e.ignore() + + def pasteEvent(self, text=None): + text = text or QtWidgets.QApplication.clipboard().text() + if text.startswith('file://'): + if not self._contacts_manager.is_active_a_friend(): + return + friend_number = self._contacts_manager.get_active_number() + file_path = self._parse_file_path(text) + self._file_transfer_handler.send_file(file_path, friend_number) + else: + self.insertPlainText(text) + + @staticmethod + def _parse_file_path(file_name): + if file_name.endswith('\r\n'): + file_name = file_name[:-2] + file_name = urllib.parse.unquote(file_name) + + return file_name[8 if util.get_platform() == 'Windows' else 7:] + + +class ScreenShotWindow(RubberBandWindow): + + def __init__(self, file_transfer_handler, contacts_manager, *args): + super().__init__(*args) + self._file_transfer_handler = file_transfer_handler + self._contacts_manager = contacts_manager + + def closeEvent(self, *args): + if self.parent.isHidden(): + self.parent.show() + + def mouseReleaseEvent(self, event): + if self.rubberband.isVisible(): + self.rubberband.hide() + rect = self.rubberband.geometry() + if rect.width() and rect.height(): + screen = QtWidgets.QApplication.primaryScreen() + p = screen.grabWindow(0, + rect.x() + 4, + rect.y() + 4, + rect.width() - 8, + rect.height() - 8) + byte_array = QtCore.QByteArray() + buffer = QtCore.QBuffer(byte_array) + buffer.open(QtCore.QIODevice.WriteOnly) + p.save(buffer, 'PNG') + friend = self._contacts_manager.get_curr_contact() + self._file_transfer_handler.send_screenshot(bytes(byte_array.data()), friend.number) + self.close() + + +class SmileyWindow(QtWidgets.QWidget): + """ + Smiley selection window + """ + + def __init__(self, parent, smiley_loader): + super().__init__(parent) + self.setWindowFlags(QtCore.Qt.FramelessWindowHint) + self._parent = parent + self._data = smiley_loader.get_smileys() + + count = len(self._data) + if not count: + self.close() + + self._page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page + if count % self._page_size == 0: + self._page_count = count // self._page_size + else: + self._page_count = round(count / self._page_size + 0.5) + self._page = -1 + self._radio = [] + + for i in range(self._page_count): # pages - radio buttons + elem = QtWidgets.QRadioButton(self) + elem.setGeometry(QtCore.QRect(i * 20 + 5, 160, 20, 20)) + elem.clicked.connect(lambda c, t=i: self._checked(t)) + self._radio.append(elem) + + width = max(self._page_count * 20 + 30, (self._page_size + 5) * 8 // 10) + self.setMaximumSize(width, 200) + self.setMinimumSize(width, 200) + self._buttons = [] + + for i in range(self._page_size): # buttons with smileys + b = QtWidgets.QPushButton(self) + b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20)) + b.clicked.connect(lambda c, t=i: self._clicked(t)) + self._buttons.append(b) + self._checked(0) + + def leaveEvent(self, event): + self.close() + + def _checked(self, pos): # new page opened + self._radio[self._page].setChecked(False) + self._radio[pos].setChecked(True) + self._page = pos + start = self._page * self._page_size + for i in range(self._page_size): + try: + self._buttons[i].setVisible(True) + pixmap = QtGui.QPixmap(self._data[start + i][1]) + icon = QtGui.QIcon(pixmap) + self._buttons[i].setIcon(icon) + except: + self._buttons[i].setVisible(False) + + def _clicked(self, pos): # smiley selected + pos += self._page * self._page_size + smiley = self._data[pos][0] + self._parent.messageEdit.insertPlainText(smiley) + self.close() + + +class MenuButton(QtWidgets.QPushButton): + + def __init__(self, parent, enter): + super().__init__(parent) + self.enter = enter + + def enterEvent(self, event): + self.enter() + super().enterEvent(event) + + +class DropdownMenu(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__(parent) + self.installEventFilter(self) + self.setWindowFlags(QtCore.Qt.FramelessWindowHint) + self.setMaximumSize(120, 120) + self.setMinimumSize(120, 120) + self.screenshotButton = QRightClickButton(self) + self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60)) + self.screenshotButton.setObjectName("screenshotButton") + + self.fileTransferButton = QtWidgets.QPushButton(self) + self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60)) + self.fileTransferButton.setObjectName("fileTransferButton") + + self.smileyButton = QtWidgets.QPushButton(self) + self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60)) + + self.stickerButton = QtWidgets.QPushButton(self) + self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) + + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'file.png')) + icon = QtGui.QIcon(pixmap) + self.fileTransferButton.setIcon(icon) + self.fileTransferButton.setIconSize(QtCore.QSize(50, 50)) + + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'screenshot.png')) + icon = QtGui.QIcon(pixmap) + self.screenshotButton.setIcon(icon) + self.screenshotButton.setIconSize(QtCore.QSize(50, 60)) + + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'smiley.png')) + icon = QtGui.QIcon(pixmap) + self.smileyButton.setIcon(icon) + self.smileyButton.setIconSize(QtCore.QSize(50, 50)) + + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'sticker.png')) + icon = QtGui.QIcon(pixmap) + self.stickerButton.setIcon(icon) + self.stickerButton.setIconSize(QtCore.QSize(55, 55)) + + self.screenshotButton.setToolTip(util_ui.tr("Send screenshot")) + self.fileTransferButton.setToolTip(util_ui.tr("Send file")) + self.smileyButton.setToolTip(util_ui.tr("Add smiley")) + self.stickerButton.setToolTip(util_ui.tr("Send sticker")) + + self.fileTransferButton.clicked.connect(parent.send_file) + self.screenshotButton.clicked.connect(parent.send_screenshot) + self.screenshotButton.rightClicked.connect(lambda: parent.send_screenshot(True)) + self.smileyButton.clicked.connect(parent.send_smiley) + self.stickerButton.clicked.connect(parent.send_sticker) + + def leaveEvent(self, event): + self.close() + + def eventFilter(self, obj, event): + if event.type() == QtCore.QEvent.WindowDeactivate: + self.close() + return False + + +class StickerItem(QtWidgets.QWidget): + + def __init__(self, fl): + super().__init__() + self._image_label = QtWidgets.QLabel(self) + self.path = fl + self.pixmap = QtGui.QPixmap() + self.pixmap.load(fl) + if self.pixmap.width() > 150: + self.pixmap = self.pixmap.scaled(150, 200, QtCore.Qt.KeepAspectRatio) + self.setFixedSize(150, self.pixmap.height()) + self._image_label.setPixmap(self.pixmap) + + +class StickerWindow(QtWidgets.QWidget): + """Sticker selection window""" + + def __init__(self, file_transfer_handler, contacts_manager): + super().__init__() + self._file_transfer_handler = file_transfer_handler + self._contacts_manager = contacts_manager + self.setWindowFlags(QtCore.Qt.FramelessWindowHint) + self.setMaximumSize(250, 200) + self.setMinimumSize(250, 200) + self.list = QtWidgets.QListWidget(self) + self.list.setGeometry(QtCore.QRect(0, 0, 250, 200)) + self._stickers = load_stickers() + for sticker in self._stickers: + item = StickerItem(sticker) + elem = QtWidgets.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(250, item.height())) + self.list.addItem(elem) + self.list.setItemWidget(elem, item) + self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) + self.list.setSpacing(3) + self.list.clicked.connect(self.click) + + def click(self, index): + num = index.row() + friend = self._contacts_manager.get_curr_contact() + self._file_transfer_handler.send_sticker(self._stickers[num], friend.number) + self.close() + + def leaveEvent(self, event): + self.close() + + +class WelcomeScreen(CenteredWidget): + + def __init__(self, settings): + super().__init__() + self._settings = settings + self.setMaximumSize(250, 200) + self.setMinimumSize(250, 200) + self.center() + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.text = QtWidgets.QTextBrowser(self) + self.text.setGeometry(QtCore.QRect(0, 0, 250, 170)) + self.text.setOpenExternalLinks(True) + self.checkbox = QtWidgets.QCheckBox(self) + self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30)) + self.checkbox.setText(util_ui.tr( "Don't show again")) + self.setWindowTitle(util_ui.tr( 'Tip of the day')) + import random + num = random.randint(0, 10) + if num == 0: + text = util_ui.tr('Press Esc if you want hide app to tray.') + elif num == 1: + text = util_ui.tr('Right click on screenshot button hides app to tray during screenshot.') + elif num == 2: + text = util_ui.tr('You can use Tox over Tor. For more info read this post') + elif num == 3: + text = util_ui.tr('Use Settings -> Interface to customize interface.') + elif num == 4: + text = util_ui.tr('Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') + elif num == 5: + text = util_ui.tr('Since v0.1.3 Toxygen supports plugins. Read more') + elif num == 6: + text = util_ui.tr('Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') + elif num == 7: + text = util_ui.tr('New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes') + elif num == 8: + text = util_ui.tr('Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') + elif num == 9: + text = util_ui.tr( 'Use right click on inline image to save it') + else: + text = util_ui.tr('Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') + self.text.setHtml(text) + self.checkbox.stateChanged.connect(self.not_show) + QtCore.QTimer.singleShot(1000, self.show) + + def not_show(self): + self._settings['show_welcome_screen'] = False + self._settings.save() + + +class MainMenuButton(QtWidgets.QPushButton): + + def __init__(self, *args): + super().__init__(*args) + self.setObjectName("mainmenubutton") + + def setText(self, text): + metrics = QtGui.QFontMetrics(self.font()) + self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20) + super().setText(text) + + +class ClickableLabel(QtWidgets.QLabel): + + clicked = QtCore.pyqtSignal() + + def __init__(self, *args): + super().__init__(*args) + + def mouseReleaseEvent(self, ev): + self.clicked.emit() + + +class SearchScreen(QtWidgets.QWidget): + + def __init__(self, contacts_manager, history_loader, messages, width, *args): + super().__init__(*args) + self._contacts_manager = contacts_manager + self._history_loader = history_loader + self.setMaximumSize(width, 40) + self.setMinimumSize(width, 40) + self._messages = messages + + self.search_text = LineEdit(self) + self.search_text.setGeometry(0, 0, width - 160, 40) + + self.search_button = ClickableLabel(self) + self.search_button.setGeometry(width - 160, 0, 40, 40) + pixmap = QtGui.QPixmap() + pixmap.load(util.join_path(util.get_images_directory(), 'search.png')) + self.search_button.setScaledContents(False) + self.search_button.setAlignment(QtCore.Qt.AlignCenter) + self.search_button.setPixmap(pixmap) + self.search_button.clicked.connect(self.search) + + font = QtGui.QFont() + font.setPointSize(32) + font.setBold(True) + + self.prev_button = QtWidgets.QPushButton(self) + self.prev_button.setGeometry(width - 120, 0, 40, 40) + self.prev_button.clicked.connect(self.prev) + self.prev_button.setText('\u25B2') + + self.next_button = QtWidgets.QPushButton(self) + self.next_button.setGeometry(width - 80, 0, 40, 40) + self.next_button.clicked.connect(self.next) + self.next_button.setText('\u25BC') + + self.close_button = QtWidgets.QPushButton(self) + self.close_button.setGeometry(width - 40, 0, 40, 40) + self.close_button.clicked.connect(self.close) + self.close_button.setText('×') + self.close_button.setFont(font) + + font.setPointSize(18) + self.next_button.setFont(font) + self.prev_button.setFont(font) + + self.retranslateUi() + + def retranslateUi(self): + self.search_text.setPlaceholderText(util_ui.tr('Search')) + + def show(self): + super().show() + self.search_text.setFocus() + + def search(self): + self._contacts_manager.update() + text = self.search_text.text() + contact = self._contacts_manager.get_curr_contact() + if text and contact and util.is_re_valid(text): + index = contact.search_string(text) + self.load_messages(index) + + def prev(self): + contact = self._contacts_manager.get_curr_contact() + if contact is not None: + index = contact.search_prev() + self.load_messages(index) + + def next(self): + contact = self._contacts_manager.get_curr_contact() + text = self.search_text.text() + if contact is not None: + index = contact.search_next() + if index is not None: + count = self._messages.count() + index += count + item = self._messages.item(index) + self._messages.scrollToItem(item) + self._messages.itemWidget(item).select_text(text) + else: + self.not_found(text) + + def load_messages(self, index): + text = self.search_text.text() + if index is not None: + count = self._messages.count() + while count + index < 0: + self._history_loader.load_history() + count = self._messages.count() + index += count + item = self._messages.item(index) + self._messages.scrollToItem(item) + self._messages.itemWidget(item).select_text(text) + else: + self.not_found(text) + + def closeEvent(self, *args): + self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40) + super().closeEvent(*args) + + @staticmethod + def not_found(text): + util_ui.message_box(util_ui.tr('Text "{}" was not found').format(text), util_ui.tr('Not found')) diff --git a/toxygen/ui/menu.py b/toxygen/ui/menu.py new file mode 100644 index 0000000..8aec578 --- /dev/null +++ b/toxygen/ui/menu.py @@ -0,0 +1,680 @@ +from PyQt5 import QtCore, QtGui, QtWidgets, uic +from user_data.settings import * +from utils.util import * +from ui.widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow +import pyaudio +import updater.updater as updater +import utils.ui as util_ui +import cv2 + + +class AddContact(CenteredWidget): + """Add contact form""" + + def __init__(self, settings, contacts_manager, tox_id=''): + super().__init__() + self._settings = settings + self._contacts_manager = contacts_manager + uic.loadUi(get_views_path('add_contact_screen'), self) + self._update_ui(tox_id) + self._adding = False + + def _update_ui(self, tox_id): + self.toxIdLineEdit = LineEdit(self) + self.toxIdLineEdit.setGeometry(QtCore.QRect(50, 40, 460, 30)) + self.toxIdLineEdit.setText(tox_id) + + self.messagePlainTextEdit.document().setPlainText(util_ui.tr('Hello! Please add me to your contact list.')) + self.addContactPushButton.clicked.connect(self._add_friend) + self._retranslate_ui() + + def _add_friend(self): + if self._adding: + return + self._adding = True + tox_id = self.toxIdLineEdit.text().strip() + if tox_id.startswith('tox:'): + tox_id = tox_id[4:] + message = self.messagePlainTextEdit.toPlainText() + send = self._contacts_manager.send_friend_request(tox_id, message) + self._adding = False + if send is True: + # request was successful + self.close() + else: # print error data + self.errorLabel.setText(send) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Add contact')) + self.addContactPushButton.setText(util_ui.tr('Send request')) + self.toxIdLabel.setText(util_ui.tr('TOX ID:')) + self.messageLabel.setText(util_ui.tr('Message:')) + self.toxIdLineEdit.setPlaceholderText(util_ui.tr('TOX ID or public key of contact')) + + +class NetworkSettings(CenteredWidget): + """Network settings form: UDP, Ipv6 and proxy""" + def __init__(self, settings, reset): + super().__init__() + self._settings = settings + self._reset = reset + uic.loadUi(get_views_path('network_settings_screen'), self) + self._update_ui() + + def _update_ui(self): + self.ipLineEdit = LineEdit(self) + self.ipLineEdit.setGeometry(100, 280, 270, 30) + + self.portLineEdit = LineEdit(self) + self.portLineEdit.setGeometry(100, 325, 270, 30) + + self.restartCorePushButton.clicked.connect(self._restart_core) + self.ipv6CheckBox.setChecked(self._settings['ipv6_enabled']) + self.udpCheckBox.setChecked(self._settings['udp_enabled']) + self.proxyCheckBox.setChecked(self._settings['proxy_type']) + self.ipLineEdit.setText(self._settings['proxy_host']) + self.portLineEdit.setText(str(self._settings['proxy_port'])) + self.httpProxyRadioButton.setChecked(self._settings['proxy_type'] == 1) + self.socksProxyRadioButton.setChecked(self._settings['proxy_type'] != 1) + self.downloadNodesCheckBox.setChecked(self._settings['download_nodes_list']) + self.lanCheckBox.setChecked(self._settings['lan_discovery']) + self._retranslate_ui() + self.proxyCheckBox.stateChanged.connect(lambda x: self._activate_proxy()) + self._activate_proxy() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr("Network settings")) + self.ipv6CheckBox.setText(util_ui.tr("IPv6")) + self.udpCheckBox.setText(util_ui.tr("UDP")) + self.lanCheckBox.setText(util_ui.tr("LAN")) + self.proxyCheckBox.setText(util_ui.tr("Proxy")) + self.ipLabel.setText(util_ui.tr("IP:")) + self.portLabel.setText(util_ui.tr("Port:")) + self.restartCorePushButton.setText(util_ui.tr("Restart TOX core")) + self.httpProxyRadioButton.setText(util_ui.tr("HTTP")) + self.socksProxyRadioButton.setText(util_ui.tr("Socks 5")) + self.downloadNodesCheckBox.setText(util_ui.tr("Download nodes list from tox.chat")) + self.warningLabel.setText(util_ui.tr("WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) + + def _activate_proxy(self): + bl = self.proxyCheckBox.isChecked() + self.ipLineEdit.setEnabled(bl) + self.portLineEdit.setEnabled(bl) + self.httpProxyRadioButton.setEnabled(bl) + self.socksProxyRadioButton.setEnabled(bl) + self.ipLabel.setEnabled(bl) + self.portLabel.setEnabled(bl) + + def _restart_core(self): + try: + self._settings['ipv6_enabled'] = self.ipv6CheckBox.isChecked() + self._settings['udp_enabled'] = self.udpCheckBox.isChecked() + proxy_enabled = self.proxyCheckBox.isChecked() + self._settings['proxy_type'] = 2 - int(self.httpProxyRadioButton.isChecked()) if proxy_enabled else 0 + self._settings['proxy_host'] = str(self.ipLineEdit.text()) + self._settings['proxy_port'] = int(self.portLineEdit.text()) + self._settings['download_nodes_list'] = self.downloadNodesCheckBox.isChecked() + self._settings['lan_discovery'] = self.lanCheckBox.isChecked() + self._settings.save() + # recreate tox instance + self._reset() + self.close() + except Exception as ex: + log('Exception in restart: ' + str(ex)) + + +class PrivacySettings(CenteredWidget): + """Privacy settings form: history, typing notifications""" + + def __init__(self, contacts_manager, settings): + """ + :type contacts_manager: ContactsManager + """ + super().__init__() + self._contacts_manager = contacts_manager + self._settings = settings + self.initUI() + self.center() + + def initUI(self): + self.setObjectName("privacySettings") + self.resize(370, 600) + self.setMinimumSize(QtCore.QSize(370, 600)) + self.setMaximumSize(QtCore.QSize(370, 600)) + self.saveHistory = QtWidgets.QCheckBox(self) + self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22)) + self.saveUnsentOnly = QtWidgets.QCheckBox(self) + self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22)) + + self.fileautoaccept = QtWidgets.QCheckBox(self) + self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22)) + + self.typingNotifications = QtWidgets.QCheckBox(self) + self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30)) + self.inlines = QtWidgets.QCheckBox(self) + self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30)) + self.auto_path = QtWidgets.QLabel(self) + self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30)) + self.path = QtWidgets.QPlainTextEdit(self) + self.path.setGeometry(QtCore.QRect(10, 265, 350, 45)) + self.change_path = QtWidgets.QPushButton(self) + self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30)) + self.typingNotifications.setChecked(self._settings['typing_notifications']) + self.fileautoaccept.setChecked(self._settings['allow_auto_accept']) + self.saveHistory.setChecked(self._settings['save_history']) + self.inlines.setChecked(self._settings['allow_inline']) + self.saveUnsentOnly.setChecked(self._settings['save_unsent_only']) + self.saveUnsentOnly.setEnabled(self._settings['save_history']) + self.saveHistory.stateChanged.connect(self.update) + self.path.setPlainText(self._settings['auto_accept_path'] or curr_directory()) + self.change_path.clicked.connect(self.new_path) + self.block_user_label = QtWidgets.QLabel(self) + self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30)) + self.block_id = QtWidgets.QPlainTextEdit(self) + self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30)) + self.block = QtWidgets.QPushButton(self) + self.block.setGeometry(QtCore.QRect(10, 430, 350, 30)) + self.block.clicked.connect(lambda: self._contacts_manager.block_user(self.block_id.toPlainText()) or self.close()) + self.blocked_users_label = QtWidgets.QLabel(self) + self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30)) + self.comboBox = QtWidgets.QComboBox(self) + self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30)) + self.comboBox.addItems(self._settings['blocked']) + self.unblock = QtWidgets.QPushButton(self) + self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30)) + self.unblock.clicked.connect(lambda: self.unblock_user()) + self.retranslateUi() + QtCore.QMetaObject.connectSlotsByName(self) + + def retranslateUi(self): + self.setWindowTitle(util_ui.tr("Privacy settings")) + self.saveHistory.setText(util_ui.tr("Save chat history")) + self.fileautoaccept.setText(util_ui.tr("Allow file auto accept")) + self.typingNotifications.setText(util_ui.tr("Send typing notifications")) + self.auto_path.setText(util_ui.tr("Auto accept default path:")) + self.change_path.setText(util_ui.tr("Change")) + self.inlines.setText(util_ui.tr("Allow inlines")) + self.block_user_label.setText(util_ui.tr("Block by public key:")) + self.blocked_users_label.setText(util_ui.tr("Blocked users:")) + self.unblock.setText(util_ui.tr("Unblock")) + self.block.setText(util_ui.tr("Block user")) + self.saveUnsentOnly.setText(util_ui.tr("Save unsent messages only")) + + def update(self, new_state): + self.saveUnsentOnly.setEnabled(new_state) + if not new_state: + self.saveUnsentOnly.setChecked(False) + + def unblock_user(self): + if not self.comboBox.count(): + return + title = util_ui.tr("Add to friend list") + info = util_ui.tr("Do you want to add this user to friend list?") + reply = util_ui.question(info, title) + self._contacts_manager.unblock_user(self.comboBox.currentText(), reply) + self.close() + + def closeEvent(self, event): + self._settings['typing_notifications'] = self.typingNotifications.isChecked() + self._settings['allow_auto_accept'] = self.fileautoaccept.isChecked() + text = util_ui.tr('History will be cleaned! Continue?') + title = util_ui.tr('Chat history') + + if self._settings['save_history'] and not self.saveHistory.isChecked(): # clear history + reply = util_ui.question(text, title) + if reply: + self._history_loader.clear_history() + self._settings['save_history'] = self.saveHistory.isChecked() + else: + self._settings['save_history'] = self.saveHistory.isChecked() + if self.saveUnsentOnly.isChecked() and not self._settings['save_unsent_only']: + reply = util_ui.question(text, title) + if reply: + self._history_loader.clear_history(None, True) + self._settings['save_unsent_only'] = self.saveUnsentOnly.isChecked() + else: + self._settings['save_unsent_only'] = self.saveUnsentOnly.isChecked() + self._settings['auto_accept_path'] = self.path.toPlainText() + self._settings['allow_inline'] = self.inlines.isChecked() + self._settings.save() + + def new_path(self): + directory = util_ui.directory_dialog() + if directory: + self.path.setPlainText(directory) + + +class NotificationsSettings(CenteredWidget): + """Notifications settings form""" + + def __init__(self, setttings): + super().__init__() + self._settings = setttings + uic.loadUi(get_views_path('notifications_settings_screen'), self) + self._update_ui() + self.center() + + def closeEvent(self, *args, **kwargs): + self._settings['notifications'] = self.notificationsCheckBox.isChecked() + self._settings['sound_notifications'] = self.soundNotificationsCheckBox.isChecked() + self._settings['group_notifications'] = self.groupNotificationsCheckBox.isChecked() + self._settings['calls_sound'] = self.callsSoundCheckBox.isChecked() + self._settings.save() + + def _update_ui(self): + self.notificationsCheckBox.setChecked(self._settings['notifications']) + self.soundNotificationsCheckBox.setChecked(self._settings['sound_notifications']) + self.groupNotificationsCheckBox.setChecked(self._settings['group_notifications']) + self.callsSoundCheckBox.setChecked(self._settings['calls_sound']) + self._retranslate_ui() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr("Notifications settings")) + self.notificationsCheckBox.setText(util_ui.tr("Enable notifications")) + self.groupNotificationsCheckBox.setText(util_ui.tr("Notify about all messages in groups")) + self.callsSoundCheckBox.setText(util_ui.tr("Enable call\'s sound")) + self.soundNotificationsCheckBox.setText(util_ui.tr("Enable sound notifications")) + + +class InterfaceSettings(CenteredWidget): + """Interface settings form""" + + def __init__(self, settings, smiley_loader): + super().__init__() + self._settings = settings + self._smiley_loader = smiley_loader + + uic.loadUi(get_views_path('interface_settings_screen'), self) + self._update_ui() + self.center() + + def _update_ui(self): + themes = list(self._settings.built_in_themes().keys()) + self.themeComboBox.addItems(themes) + theme = self._settings['theme'] + if theme in self._settings.built_in_themes().keys(): + index = themes.index(theme) + else: + index = 0 + self.themeComboBox.setCurrentIndex(index) + + supported_languages = sorted(Settings.supported_languages().keys(), reverse=True) + for key in supported_languages: + self.languageComboBox.insertItem(0, key) + if self._settings['language'] == key: + self.languageComboBox.setCurrentIndex(0) + + smiley_packs = self._smiley_loader.get_packs_list() + self.smileysPackComboBox.addItems(smiley_packs) + try: + index = smiley_packs.index(self._settings['smiley_pack']) + except: + index = smiley_packs.index('default') + self.smileysPackComboBox.setCurrentIndex(index) + + app_closing_setting = self._settings['close_app'] + self.closeRadioButton.setChecked(app_closing_setting == 0) + self.hideRadioButton.setChecked(app_closing_setting == 1) + self.closeToTrayRadioButton.setChecked(app_closing_setting == 2) + + self.compactModeCheckBox.setChecked(self._settings['compact_mode']) + self.showAvatarsCheckBox.setChecked(self._settings['show_avatars']) + self.smileysCheckBox.setChecked(self._settings['smileys']) + + self.importSmileysPushButton.clicked.connect(self._import_smileys) + self.importStickersPushButton.clicked.connect(self._import_stickers) + + self._retranslate_ui() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr("Interface settings")) + self.showAvatarsCheckBox.setText(util_ui.tr("Show avatars in chat")) + self.themeLabel.setText(util_ui.tr("Theme:")) + self.languageLabel.setText(util_ui.tr("Language:")) + self.smileysGroupBox.setTitle(util_ui.tr("Smileys settings")) + self.smileysPackLabel.setText(util_ui.tr("Smiley pack:")) + self.smileysCheckBox.setText(util_ui.tr("Smileys")) + self.closeRadioButton.setText(util_ui.tr("Close app")) + self.hideRadioButton.setText(util_ui.tr("Hide app")) + self.closeToTrayRadioButton.setText(util_ui.tr("Close to tray")) + self.mirrorModeCheckBox.setText(util_ui.tr("Mirror mode")) + self.compactModeCheckBox.setText(util_ui.tr("Compact contact list")) + self.importSmileysPushButton.setText(util_ui.tr("Import smiley pack")) + self.importStickersPushButton.setText(util_ui.tr("Import sticker pack")) + self.appClosingGroupBox.setTitle(util_ui.tr("App closing settings")) + + @staticmethod + def _import_stickers(): + directory = util_ui.directory_dialog(util_ui.tr('Choose folder with sticker pack')) + if directory: + dest = join_path(get_stickers_directory(), os.path.basename(directory)) + copy(directory, dest) + + @staticmethod + def _import_smileys(): + directory = util_ui.directory_dialog(util_ui.tr('Choose folder with smiley pack')) + if not directory: + return + src = directory + '/' + dest = join_path(get_smileys_directory(), os.path.basename(directory)) + copy(src, dest) + + def closeEvent(self, event): + app = QtWidgets.QApplication.instance() + + self._settings['theme'] = str(self.themeComboBox.currentText()) + try: + theme = self._settings['theme'] + styles_path = join_path(get_styles_directory(), self._settings.built_in_themes()[theme]) + with open(styles_path) as fl: + style = fl.read() + app.setStyleSheet(style) + except IsADirectoryError: + pass + + self._settings['smileys'] = self.smileysCheckBox.isChecked() + + restart = False + if self._settings['mirror_mode'] != self.mirrorModeCheckBox.isChecked(): + self._settings['mirror_mode'] = self.mirrorModeCheckBox.isChecked() + restart = True + + if self._settings['compact_mode'] != self.compactModeCheckBox.isChecked(): + self._settings['compact_mode'] = self.compactModeCheckBox.isChecked() + restart = True + + if self._settings['show_avatars'] != self.showAvatarsCheckBox.isChecked(): + self._settings['show_avatars'] = self.showAvatarsCheckBox.isChecked() + restart = True + + self._settings['smiley_pack'] = self.smileysPackComboBox.currentText() + self._smiley_loader.load_pack() + + language = self.languageComboBox.currentText() + if self._settings['language'] != language: + self._settings['language'] = language + path = Settings.supported_languages()[language] + app.removeTranslator(app.translator) + app.translator.load(join_path(get_translations_directory(), path)) + app.installTranslator(app.translator) + + app_closing_setting = 0 + if self.hideRadioButton.isChecked(): + app_closing_setting = 1 + elif self.closeToTrayRadioButton.isChecked(): + app_closing_setting = 2 + self._settings['close_app'] = app_closing_setting + self._settings.save() + + if restart: + util_ui.message_box(util_ui.tr('Restart app to apply settings'), util_ui.tr('Restart required')) + + +class AudioSettings(CenteredWidget): + """ + Audio calls settings form + """ + + def __init__(self, settings): + super().__init__() + self._settings = settings + self._in_indexes = self._out_indexes = None + uic.loadUi(get_views_path('audio_settings_screen'), self) + self._update_ui() + self.center() + + def closeEvent(self, event): + self._settings.audio['input'] = self._in_indexes[self.inputDeviceComboBox.currentIndex()] + self._settings.audio['output'] = self._out_indexes[self.outputDeviceComboBox.currentIndex()] + self._settings.save() + + def _update_ui(self): + p = pyaudio.PyAudio() + self._in_indexes, self._out_indexes = [], [] + for i in range(p.get_device_count()): + device = p.get_device_info_by_index(i) + if device["maxInputChannels"]: + self.inputDeviceComboBox.addItem(str(device["name"])) + self._in_indexes.append(i) + if device["maxOutputChannels"]: + self.outputDeviceComboBox.addItem(str(device["name"])) + self._out_indexes.append(i) + self.inputDeviceComboBox.setCurrentIndex(self._in_indexes.index(self._settings.audio['input'])) + self.outputDeviceComboBox.setCurrentIndex(self._out_indexes.index(self._settings.audio['output'])) + self._retranslate_ui() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr("Audio settings")) + self.inputDeviceLabel.setText(util_ui.tr("Input device:")) + self.outputDeviceLabel.setText(util_ui.tr("Output device:")) + + +class DesktopAreaSelectionWindow(RubberBandWindow): + + def mouseReleaseEvent(self, event): + if self.rubberband.isVisible(): + self.rubberband.hide() + rect = self.rubberband.geometry() + width, height = rect.width(), rect.height() + if width >= 8 and height >= 8: + self.parent.save(rect.x(), rect.y(), width - (width % 4), height - (height % 4)) + self.close() + + +class VideoSettings(CenteredWidget): + """ + Audio calls settings form + """ + + def __init__(self, settings): + super().__init__() + self._settings = settings + uic.loadUi(get_views_path('video_settings_screen'), self) + self._devices = self._frame_max_sizes = None + self._update_ui() + self.center() + self.desktopAreaSelection = None + + def closeEvent(self, event): + if self.deviceComboBox.currentIndex() == 0: + return + try: + self._settings.video['device'] = self.devices[self.input.currentIndex()] + text = self.resolutionComboBox.currentText() + self._settings.video['width'] = int(text.split(' ')[0]) + self._settings.video['height'] = int(text.split(' ')[-1]) + self._settings.save() + except Exception as ex: + print('Saving video settings error: ' + str(ex)) + + def save(self, x, y, width, height): + self.desktopAreaSelection = None + self._settings.video['device'] = -1 + self._settings.video['width'] = width + self._settings.video['height'] = height + self._settings.video['x'] = x + self._settings.video['y'] = y + self._settings.save() + + def _update_ui(self): + self.deviceComboBox.currentIndexChanged.connect(self._device_changed) + self.selectRegionPushButton.clicked.connect(self._button_clicked) + self._devices = [-1] + screen = QtWidgets.QApplication.primaryScreen() + size = screen.size() + self._frame_max_sizes = [(size.width(), size.height())] + desktop = util_ui.tr("Desktop") + self.deviceComboBox.addItem(desktop) + for i in range(10): + v = cv2.VideoCapture(i) + if v.isOpened(): + v.set(cv2.CAP_PROP_FRAME_WIDTH, 10000) + v.set(cv2.CAP_PROP_FRAME_HEIGHT, 10000) + + width = int(v.get(cv2.CAP_PROP_FRAME_WIDTH)) + height = int(v.get(cv2.CAP_PROP_FRAME_HEIGHT)) + del v + self._devices.append(i) + self._frame_max_sizes.append((width, height)) + self.deviceComboBox.addItem(util_ui.tr('Device #') + str(i)) + try: + index = self._devices.index(self._settings.video['device']) + self.deviceComboBox.setCurrentIndex(index) + except: + print('Video devices error!') + self._retranslate_ui() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr("Video settings")) + self.deviceLabel.setText(util_ui.tr("Device:")) + self.selectRegionPushButton.setText(util_ui.tr("Select region")) + + def _button_clicked(self): + self.desktopAreaSelection = DesktopAreaSelectionWindow(self) + + def _device_changed(self): + index = self.deviceComboBox.currentIndex() + self.selectRegionPushButton.setVisible(index == 0) + self.resolutionComboBox.setVisible(index != 0) + width, height = self._frame_max_sizes[index] + self.resolutionComboBox.clear() + dims = [ + (320, 240), + (640, 360), + (640, 480), + (720, 480), + (1280, 720), + (1920, 1080), + (2560, 1440) + ] + for w, h in dims: + if w <= width and h <= height: + self.resolutionComboBox.addItem(str(w) + ' * ' + str(h)) + + +class PluginsSettings(CenteredWidget): + """ + Plugins settings form + """ + + def __init__(self, plugin_loader): + super().__init__() + self._plugin_loader = plugin_loader + self._window = None + self.initUI() + self.center() + self.retranslateUi() + + def initUI(self): + self.resize(400, 210) + self.setMinimumSize(QtCore.QSize(400, 210)) + self.setMaximumSize(QtCore.QSize(400, 210)) + self.comboBox = QtWidgets.QComboBox(self) + self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30)) + self.label = QtWidgets.QLabel(self) + self.label.setGeometry(QtCore.QRect(30, 40, 340, 90)) + self.label.setWordWrap(True) + self.button = QtWidgets.QPushButton(self) + self.button.setGeometry(QtCore.QRect(30, 130, 340, 30)) + self.button.clicked.connect(self.button_click) + self.open = QtWidgets.QPushButton(self) + self.open.setGeometry(QtCore.QRect(30, 170, 340, 30)) + self.open.clicked.connect(self.open_plugin) + self.update_list() + self.comboBox.currentIndexChanged.connect(self.show_data) + self.show_data() + + def retranslateUi(self): + self.setWindowTitle(util_ui.tr("Plugins")) + self.open.setText(util_ui.tr("Open selected plugin")) + + def open_plugin(self): + ind = self.comboBox.currentIndex() + plugin = self.data[ind] + window = self.pl_loader.plugin_window(plugin[-1]) + if window is not None: + self._window = window + self._window.show() + else: + util_ui.message_box(util_ui.tr('No GUI found for this plugin'), util_ui.tr('Error')) + + def update_list(self): + self.comboBox.clear() + data = self._plugin_loader.get_plugins_list() + self.comboBox.addItems(list(map(lambda x: x[0], data))) + self.data = data + + def show_data(self): + ind = self.comboBox.currentIndex() + if len(self.data): + plugin = self.data[ind] + descr = plugin[2] or util_ui.tr("No description available") + self.label.setText(descr) + if plugin[1]: + self.button.setText(util_ui.tr("Disable plugin")) + else: + self.button.setText(util_ui.tr("Enable plugin")) + else: + self.open.setVisible(False) + self.button.setVisible(False) + self.label.setText(util_ui.tr("No plugins found")) + + def button_click(self): + ind = self.comboBox.currentIndex() + plugin = self.data[ind] + self._plugin_loader.toggle_plugin(plugin[-1]) + plugin[1] = not plugin[1] + if plugin[1]: + self.button.setText(util_ui.tr("Disable plugin")) + else: + self.button.setText(util_ui.tr("Enable plugin")) + + +class UpdateSettings(CenteredWidget): + """ + Updates settings form + """ + + def __init__(self, settings, version): + super().__init__() + self._settings = settings + self._version = version + uic.loadUi(get_views_path('update_settings_screen'), self) + self._update_ui() + self.center() + + def closeEvent(self, event): + self._settings['update'] = self.updateModeComboBox.currentIndex() + self._settings.save() + + def _update_ui(self): + self.updatePushButton.clicked.connect(self._update_client) + self.updateModeComboBox.currentIndexChanged.connect(self._update_mode_changed) + self._retranslate_ui() + self.updateModeComboBox.setCurrentIndex(self._settings['update']) + + def _update_mode_changed(self): + index = self.updateModeComboBox.currentIndex() + self.updatePushButton.setEnabled(index > 0) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr("Update settings")) + self.updateModeLabel.setText(util_ui.tr("Select update mode:")) + self.updatePushButton.setText(util_ui.tr("Update Toxygen")) + self.updateModeComboBox.addItem(util_ui.tr("Disabled")) + self.updateModeComboBox.addItem(util_ui.tr("Manual")) + self.updateModeComboBox.addItem(util_ui.tr("Auto")) + + def _update_client(self): + if not updater.connection_available(): + util_ui.message_box(util_ui.tr('Problems with internet connection'), util_ui.tr("Error")) + return + if not updater.updater_available(): + util_ui.message_box(util_ui.tr('Updater not found'), util_ui.tr("Error")) + return + version = updater.check_for_updates(self._version, self._settings) + if version is not None: + updater.download(version) + util_ui.close_all_windows() + else: + util_ui.message_box(util_ui.tr('Toxygen is up to date'), util_ui.tr("No updates found")) diff --git a/toxygen/ui/messages_widgets.py b/toxygen/ui/messages_widgets.py new file mode 100644 index 0000000..8a46fd0 --- /dev/null +++ b/toxygen/ui/messages_widgets.py @@ -0,0 +1,449 @@ +from wrapper.toxcore_enums_and_consts import * +import ui.widgets as widgets +import utils.util as util +import ui.menu as menu +import html as h +import re +from ui.widgets import * +from messenger.messages import MESSAGE_AUTHOR +from file_transfers.file_transfers import * + + +class MessageBrowser(QtWidgets.QTextBrowser): + + def __init__(self, settings, message_edit, smileys_loader, plugin_loader, text, width, message_type, parent=None): + super().__init__(parent) + self.urls = {} + self._message_edit = message_edit + self._smileys_loader = smileys_loader + self._plugin_loader = plugin_loader + self._add_contact = None + self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere) + self.document().setTextWidth(width) + self.setOpenExternalLinks(True) + self.setAcceptRichText(True) + self.setOpenLinks(False) + path = smileys_loader.get_smileys_path() + if path is not None: + self.setSearchPaths([path]) + self.document().setDefaultStyleSheet('a { color: #306EFF; }') + text = self.decoratedText(text) + if message_type != TOX_MESSAGE_TYPE['NORMAL']: + self.setHtml('

' + text + '

') + else: + self.setHtml(text) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPixelSize(settings['message_font_size']) + font.setBold(False) + self.setFont(font) + self.resize(width, self.document().size().height()) + self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse) + self.anchorClicked.connect(self.on_anchor_clicked) + + def contextMenuEvent(self, event): + menu = widgets.create_menu(self.createStandardContextMenu(event.pos())) + quote = menu.addAction(util_ui.tr('Quote selected text')) + quote.triggered.connect(self.quote_text) + text = self.textCursor().selection().toPlainText() + if not text: + quote.setEnabled(False) + else: + sub_menu = self._plugin_loader.get_message_menu(menu, text) + if len(sub_menu): + plugins_menu = menu.addMenu(util_ui.tr('Plugins')) + plugins_menu.addActions(sub_menu) + menu.popup(event.globalPos()) + menu.exec_(event.globalPos()) + del menu + + def quote_text(self): + text = self.textCursor().selection().toPlainText() + if not text: + return + text = '>' + '\n>'.join(text.split('\n')) + if self._message_edit.toPlainText(): + text = '\n' + text + self._message_edit.appendPlainText(text) + + def on_anchor_clicked(self, url): + text = str(url.toString()) + if text.startswith('tox:'): + self._add_contact = menu.AddContact(text[4:]) + self._add_contact.show() + else: + QtGui.QDesktopServices.openUrl(url) + self.clearFocus() + + def addAnimation(self, url, file_name): + movie = QtGui.QMovie(self) + movie.setFileName(file_name) + self.urls[movie] = url + movie.frameChanged[int].connect(lambda x: self.animate(movie)) + movie.start() + + def animate(self, movie): + self.document().addResource(QtGui.QTextDocument.ImageResource, + self.urls[movie], + movie.currentPixmap()) + self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth()) + + def decoratedText(self, text): + text = h.escape(text) # replace < and > + exp = QtCore.QRegExp( + '(' + '(?:\\b)((www\\.)|(http[s]?|ftp)://)' + '\\w+\\S+)' + '|(?:\\b)(file:///)([\\S| ]*)' + '|(?:\\b)(tox:[a-zA-Z\\d]{76}$)' + '|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)' + '|(?:\\b)(tox:\\S+@\\S+)') + offset = exp.indexIn(text, 0) + while offset != -1: # add links + url = exp.cap() + if exp.cap(2) == 'www.': + html = '{0}'.format(url) + else: + html = '{0}'.format(url) + text = text[:offset] + html + text[offset + len(exp.cap()):] + offset += len(html) + offset = exp.indexIn(text, offset) + arr = text.split('\n') + for i in range(len(arr)): # quotes + if arr[i].startswith('>'): + arr[i] = '' + arr[i][4:] + '' + text = '
'.join(arr) + text = self._smileys_loader.add_smileys_to_text(text, self) + return text + + +class MessageItem(QtWidgets.QWidget): + """ + Message in messages list + """ + def __init__(self, text_message, settings, message_browser_factory_method, delete_action, parent=None): + QtWidgets.QWidget.__init__(self, parent) + self._message = text_message + self._delete_action = delete_action + self.name = widgets.DataLabel(self) + self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) + self.name.setTextFormat(QtCore.Qt.PlainText) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(11) + font.setBold(True) + if text_message.author is not None: + self.name.setFont(font) + self.name.setText(text_message.author.name) + + self.time = QtWidgets.QLabel(self) + self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) + font.setPointSize(10) + font.setBold(False) + self.time.setFont(font) + self._time = text_message.time + if text_message.author and text_message.author.type == MESSAGE_AUTHOR['NOT_SENT']: + movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) + self.time.setMovie(movie) + movie.start() + self.t = True + else: + self.time.setText(util.convert_time(text_message.time)) + self.t = False + + self.message = message_browser_factory_method(text_message.text, parent.width() - 160, + text_message.type, self) + if text_message.type != TOX_MESSAGE_TYPE['NORMAL']: + self.name.setStyleSheet("QLabel { color: #5CB3FF; }") + self.message.setAlignment(QtCore.Qt.AlignCenter) + self.time.setStyleSheet("QLabel { color: #5CB3FF; }") + self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 160, self.message.height())) + self.setFixedHeight(self.message.height()) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x(): + self.listMenu = QtWidgets.QMenu() + delete_item = self.listMenu.addAction(util_ui.tr('Delete message')) + delete_item.triggered.connect(self.delete) + parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0)) + self.listMenu.move(parent_position) + self.listMenu.show() + + def delete(self): + self._delete_action(self._message) + + def mark_as_sent(self): + if self.t: + self.time.setText(util.convert_time(self._time)) + self.t = False + return True + return False + + def set_avatar(self, pixmap): + self.name.setAlignment(QtCore.Qt.AlignCenter) + self.message.setAlignment(QtCore.Qt.AlignVCenter) + self.setFixedHeight(max(self.height(), 36)) + self.name.setFixedHeight(self.height()) + self.message.setFixedHeight(self.height()) + self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) + + def select_text(self, text): + tmp = self.message.toHtml() + text = h.escape(text) + strings = re.findall(text, tmp, flags=re.IGNORECASE) + for s in strings: + tmp = self.replace_all(tmp, s) + self.message.setHtml(tmp) + + @staticmethod + def replace_all(text, substring): + i, l = 0, len(substring) + while i < len(text) - l + 1: + index = text[i:].find(substring) + if index == -1: + break + i += index + lgt, rgt = text[i:].find('<'), text[i:].find('>') + if rgt < lgt: + i += rgt + 1 + continue + sub = '{}'.format(substring) + text = text[:i] + sub + text[i + l:] + i += len(sub) + return text + + +class FileTransferItem(QtWidgets.QListWidget): + + def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None): + + QtWidgets.QListWidget.__init__(self, parent) + self._file_transfer_handler = file_transfer_handler + self.resize(QtCore.QSize(width, 34)) + if transfer_message.state == FILE_TRANSFER_STATE['CANCELLED']: + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + elif transfer_message.state in PAUSED_FILE_TRANSFERS: + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + else: + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = transfer_message.state + + self.name = DataLabel(self) + self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) + self.name.setTextFormat(QtCore.Qt.PlainText) + font = QtGui.QFont() + font.setFamily(settings['font']) + font.setPointSize(11) + font.setBold(True) + self.name.setFont(font) + self.name.setText(transfer_message.author.name) + + self.time = QtWidgets.QLabel(self) + self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) + font.setPointSize(10) + font.setBold(False) + self.time.setFont(font) + self.time.setText(util.convert_time(transfer_message.time)) + + self.cancel = QtWidgets.QPushButton(self) + self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), 'decline.png')) + icon = QtGui.QIcon(pixmap) + self.cancel.setIcon(icon) + self.cancel.setIconSize(QtCore.QSize(30, 30)) + self.cancel.setVisible(transfer_message.state in ACTIVE_FILE_TRANSFERS or + transfer_message.state == FILE_TRANSFER_STATE['UNSENT']) + self.cancel.clicked.connect( + lambda: self.cancel_transfer(transfer_message.friend_number, transfer_message.file_number)) + self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') + + self.accept_or_pause = QtWidgets.QPushButton(self) + self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) + if transfer_message.state == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + self.accept_or_pause.setVisible(True) + self.button_update('accept') + elif transfer_message.state in DO_NOT_SHOW_ACCEPT_BUTTON: + self.accept_or_pause.setVisible(False) + elif transfer_message.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue + self.accept_or_pause.setVisible(True) + self.button_update('resume') + elif transfer_message.state == FILE_TRANSFER_STATE['UNSENT']: + self.accept_or_pause.setVisible(False) + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + else: # pause + self.accept_or_pause.setVisible(True) + self.button_update('pause') + self.accept_or_pause.clicked.connect( + lambda: self.accept_or_pause_transfer(transfer_message.friend_number, transfer_message.file_number, + transfer_message.size)) + + self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') + + self.pb = QtWidgets.QProgressBar(self) + self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) + self.pb.setValue(0) + self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') + self.pb.setVisible(transfer_message.state in SHOW_PROGRESS_BAR) + + self.file_name = DataLabel(self) + self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20)) + font.setPointSize(12) + self.file_name.setFont(font) + file_size = transfer_message.size // 1024 + if not file_size: + file_size = '{}B'.format(transfer_message.size) + elif file_size >= 1024: + file_size = '{}MB'.format(file_size // 1024) + else: + file_size = '{}KB'.format(file_size) + file_data = '{} {}'.format(file_size, transfer_message.file_name) + self.file_name.setText(file_data) + self.file_name.setToolTip(transfer_message.file_name) + self.saved_name = transfer_message.file_name + self.time_left = QtWidgets.QLabel(self) + self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) + font.setPointSize(10) + self.time_left.setFont(font) + self.time_left.setVisible(transfer_message.state == FILE_TRANSFER_STATE['RUNNING']) + self.setFocusPolicy(QtCore.Qt.NoFocus) + self.paused = False + + def cancel_transfer(self, friend_number, file_number): + self._file_transfer_handler.cancel_transfer(friend_number, file_number) + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + self.cancel.setVisible(False) + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + + def accept_or_pause_transfer(self, friend_number, file_number, size): + if self.state == FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: + directory = util_ui.directory_dialog(util_ui.tr('Choose folder')) + self.pb.setVisible(True) + if directory: + self._file_transfer_handler.accept_transfer(directory + '/' + self.saved_name, + friend_number, file_number, size) + self.button_update('pause') + elif self.state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume + self.paused = False + self._file_transfer_handler.resume_transfer(friend_number, file_number) + self.button_update('pause') + self.state = FILE_TRANSFER_STATE['RUNNING'] + else: # pause + self.paused = True + self.state = FILE_TRANSFER_STATE['PAUSED_BY_USER'] + self._file_transfer_handler.pause_transfer(friend_number, file_number) + self.button_update('resume') + self.accept_or_pause.clearFocus() + + def button_update(self, path): + pixmap = QtGui.QPixmap(util.join_path(util.get_images_directory(), '{}.png'.format(path))) + icon = QtGui.QIcon(pixmap) + self.accept_or_pause.setIcon(icon) + self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) + + def update_transfer_state(self, state, progress, time): + self.pb.setValue(int(progress * 100)) + if time + 1: + m, s = divmod(time, 60) + self.time_left.setText('{0:02d}:{1:02d}'.format(m, s)) + if self.state != state and self.state in ACTIVE_FILE_TRANSFERS: + if state == FILE_TRANSFER_STATE['CANCELLED']: + self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') + self.cancel.setVisible(False) + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + self.state = state + self.time_left.setVisible(False) + elif state == FILE_TRANSFER_STATE['FINISHED']: + self.accept_or_pause.setVisible(False) + self.pb.setVisible(False) + self.cancel.setVisible(False) + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(False) + elif state == FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']: + self.accept_or_pause.setVisible(False) + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + self.state = state + self.time_left.setVisible(False) + elif state == FILE_TRANSFER_STATE['PAUSED_BY_USER']: + self.button_update('resume') # setup button continue + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(False) + elif state == FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']: + self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') + self.accept_or_pause.setVisible(False) + self.time_left.setVisible(False) + self.pb.setVisible(False) + elif not self.paused: # active + self.pb.setVisible(True) + self.accept_or_pause.setVisible(True) # setup to pause + self.button_update('pause') + self.setStyleSheet('QListWidget { border: 1px solid green; }') + self.state = state + self.time_left.setVisible(True) + + +class UnsentFileItem(FileTransferItem): + + def __init__(self, transfer_message, file_transfer_handler, settings, width, parent=None): + super().__init__(transfer_message, file_transfer_handler, settings, width, parent) + self._time = time + movie = QtGui.QMovie(util.join_path(util.get_images_directory(), 'spinner.gif')) + self.time.setMovie(movie) + movie.start() + self._message_id = transfer_message.message_id + self._friend_number = transfer_message.friend_number + + def cancel_transfer(self, *args): + self._file_transfer_handler.cancel_not_started_transfer(self._friend_number, self._message_id) + + +class InlineImageItem(QtWidgets.QScrollArea): + + def __init__(self, data, width, elem, parent=None): + + QtWidgets.QScrollArea.__init__(self, parent) + self.setFocusPolicy(QtCore.Qt.NoFocus) + self._elem = elem + self._image_label = QtWidgets.QLabel(self) + self._image_label.raise_() + self.setWidget(self._image_label) + self._image_label.setScaledContents(False) + self._pixmap = QtGui.QPixmap() + self._pixmap.loadFromData(data, 'PNG') + self._max_size = width - 30 + self._resize_needed = not (self._pixmap.width() <= self._max_size) + self._full_size = not self._resize_needed + if not self._resize_needed: + self._image_label.setPixmap(self._pixmap) + self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5)) + self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) + else: + pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) + self._image_label.setPixmap(pixmap) + self.resize(QtCore.QSize(self._max_size + 5, pixmap.height())) + self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height()) + self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline + if self._full_size: + pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) + self._image_label.setPixmap(pixmap) + self.resize(QtCore.QSize(self._max_size, pixmap.height())) + self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height()) + else: + self._image_label.setPixmap(self._pixmap) + self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17)) + self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) + self._full_size = not self._full_size + self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) + elif event.button() == QtCore.Qt.RightButton: # save inline + directory = util_ui.directory_dialog(util_ui.tr('Choose folder')) + if directory: + fl = QtCore.QFile(directory + '/toxygen_inline_' + util.curr_time().replace(':', '_') + '.png') + self._pixmap.save(fl, 'PNG') diff --git a/toxygen/ui/password_screen.py b/toxygen/ui/password_screen.py new file mode 100644 index 0000000..bbae7ff --- /dev/null +++ b/toxygen/ui/password_screen.py @@ -0,0 +1,153 @@ +from ui.widgets import CenteredWidget, LineEdit, DialogWithResult +from PyQt5 import QtCore, QtWidgets +import utils.ui as util_ui + + +class PasswordArea(LineEdit): + + def __init__(self, parent): + super().__init__(parent) + self._parent = parent + self.setEchoMode(QtWidgets.QLineEdit.Password) + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Return: + self._parent.button_click() + else: + super().keyPressEvent(event) + + +class PasswordScreenBase(CenteredWidget, DialogWithResult): + + def __init__(self, encrypt): + CenteredWidget.__init__(self) + DialogWithResult.__init__(self) + self._encrypt = encrypt + self.initUI() + + def initUI(self): + self.resize(360, 170) + self.setMinimumSize(QtCore.QSize(360, 170)) + self.setMaximumSize(QtCore.QSize(360, 170)) + + self.enter_pass = QtWidgets.QLabel(self) + self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30)) + + self.password = PasswordArea(self) + self.password.setGeometry(QtCore.QRect(30, 50, 300, 30)) + + self.button = QtWidgets.QPushButton(self) + self.button.setGeometry(QtCore.QRect(30, 90, 300, 30)) + self.button.setText(util_ui.tr('OK')) + self.button.clicked.connect(self.button_click) + + self.warning = QtWidgets.QLabel(self) + self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30)) + self.warning.setStyleSheet('QLabel { color: #F70D1A; }') + self.warning.setVisible(False) + + self.retranslateUi() + self.center() + QtCore.QMetaObject.connectSlotsByName(self) + + def button_click(self): + pass + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Enter: + self.button_click() + else: + super(PasswordScreenBase, self).keyPressEvent(event) + + def retranslateUi(self): + self.setWindowTitle(util_ui.tr('Enter password')) + self.enter_pass.setText(util_ui.tr('Password:')) + self.warning.setText(util_ui.tr('Incorrect password')) + + +class PasswordScreen(PasswordScreenBase): + + def __init__(self, encrypt, data): + super().__init__(encrypt) + self._data = data + + def button_click(self): + if self.password.text(): + try: + self._encrypt.set_password(self.password.text()) + new_data = self._encrypt.pass_decrypt(self._data) + except Exception as ex: + self.warning.setVisible(True) + print('Decryption error:', ex) + else: + self.close_with_result(new_data) + + +class UnlockAppScreen(PasswordScreenBase): + + def __init__(self, encrypt, callback): + super(UnlockAppScreen, self).__init__(encrypt) + self._callback = callback + self.setWindowFlags(QtCore.Qt.FramelessWindowHint) + + def button_click(self): + if self.password.text(): + if self._encrypt.is_password(self.password.text()): + self._callback() + self.close() + else: + self.warning.setVisible(True) + print('Wrong password!') + + +class SetProfilePasswordScreen(CenteredWidget): + + def __init__(self, encrypt): + super(SetProfilePasswordScreen, self).__init__() + self._encrypt = encrypt + self.initUI() + self.retranslateUi() + self.center() + + def initUI(self): + self.setMinimumSize(QtCore.QSize(700, 200)) + self.setMaximumSize(QtCore.QSize(700, 200)) + self.password = LineEdit(self) + self.password.setGeometry(QtCore.QRect(40, 10, 300, 30)) + self.password.setEchoMode(QtWidgets.QLineEdit.Password) + self.confirm_password = LineEdit(self) + self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30)) + self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password) + self.set_password = QtWidgets.QPushButton(self) + self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30)) + self.set_password.clicked.connect(self.new_password) + self.not_match = QtWidgets.QLabel(self) + self.not_match.setGeometry(QtCore.QRect(350, 50, 300, 30)) + self.not_match.setVisible(False) + self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }') + self.warning = QtWidgets.QLabel(self) + self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30)) + self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') + + def retranslateUi(self): + self.setWindowTitle(util_ui.tr('Profile password')) + self.password.setPlaceholderText( + util_ui.tr('Password (at least 8 symbols)')) + self.confirm_password.setPlaceholderText( + util_ui.tr('Confirm password')) + self.set_password.setText( + util_ui.tr('Set password')) + self.not_match.setText(util_ui.tr('Passwords do not match')) + self.warning.setText(util_ui.tr('There is no way to recover lost passwords')) + + def new_password(self): + if self.password.text() == self.confirm_password.text(): + if len(self.password.text()) >= 8: + self._encrypt.set_password(self.password.text()) + self.close() + else: + self.not_match.setText(util_ui.tr('Password must be at least 8 symbols')) + self.not_match.setVisible(True) + else: + self.not_match.setText(util_ui.tr('Passwords do not match')) + self.not_match.setVisible(True) diff --git a/toxygen/ui/peer_screen.py b/toxygen/ui/peer_screen.py new file mode 100644 index 0000000..8f2d5ba --- /dev/null +++ b/toxygen/ui/peer_screen.py @@ -0,0 +1,111 @@ +from ui.widgets import CenteredWidget +from PyQt5 import uic +import utils.util as util +import utils.ui as util_ui +from ui.contact_items import * +import wrapper.toxcore_enums_and_consts as consts + + +class PeerScreen(CenteredWidget): + + def __init__(self, contacts_manager, groups_service, group, peer_id): + super().__init__() + self._contacts_manager = contacts_manager + self._groups_service = groups_service + self._group = group + self._peer = group.get_peer_by_id(peer_id) + + self._roles = { + TOX_GROUP_ROLE['FOUNDER']: util_ui.tr('Administrator'), + TOX_GROUP_ROLE['MODERATOR']: util_ui.tr('Moderator'), + TOX_GROUP_ROLE['USER']: util_ui.tr('User'), + TOX_GROUP_ROLE['OBSERVER']: util_ui.tr('Observer') + } + + uic.loadUi(util.get_views_path('peer_screen'), self) + self._update_ui() + + def _update_ui(self): + self.statusCircle = StatusCircle(self) + self.statusCircle.setGeometry(50, 15, 30, 30) + + self.statusCircle.update(self._peer.status) + self.peerNameLabel.setText(self._peer.name) + self.ignorePeerCheckBox.setChecked(self._peer.is_muted) + self.ignorePeerCheckBox.clicked.connect(self._toggle_ignore) + self.sendPrivateMessagePushButton.clicked.connect(self._send_private_message) + self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key) + self.roleNameLabel.setText(self._get_role_name()) + can_change_role_or_ban = self._can_change_role_or_ban() + self.rolesComboBox.setVisible(can_change_role_or_ban) + self.roleNameLabel.setVisible(not can_change_role_or_ban) + self.banGroupBox.setEnabled(can_change_role_or_ban) + self.banPushButton.clicked.connect(self._ban_peer) + self.kickPushButton.clicked.connect(self._kick_peer) + + self._retranslate_ui() + + self.rolesComboBox.currentIndexChanged.connect(self._role_set) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Peer details')) + self.ignorePeerCheckBox.setText(util_ui.tr('Ignore peer')) + self.roleLabel.setText(util_ui.tr('Role:')) + self.copyPublicKeyPushButton.setText(util_ui.tr('Copy public key')) + self.sendPrivateMessagePushButton.setText(util_ui.tr('Send private message')) + self.banPushButton.setText(util_ui.tr('Ban peer')) + self.kickPushButton.setText(util_ui.tr('Kick peer')) + self.banGroupBox.setTitle(util_ui.tr('Ban peer')) + self.ipBanRadioButton.setText(util_ui.tr('IP')) + self.nickBanRadioButton.setText(util_ui.tr('Nickname')) + self.pkBanRadioButton.setText(util_ui.tr('Public key')) + + self.rolesComboBox.clear() + index = self._group.get_self_peer().role + roles = list(self._roles.values()) + for role in roles[index + 1:]: + self.rolesComboBox.addItem(role) + self.rolesComboBox.setCurrentIndex(self._peer.role - index - 1) + + def _can_change_role_or_ban(self): + self_peer = self._group.get_self_peer() + if self_peer.role > TOX_GROUP_ROLE['MODERATOR']: + return False + + return self_peer.role < self._peer.role + + def _role_set(self): + index = self.rolesComboBox.currentIndex() + all_roles_count = len(self._roles) + diff = all_roles_count - self.rolesComboBox.count() + self._groups_service.set_new_peer_role(self._group, self._peer, index + diff) + + def _get_role_name(self): + return self._roles[self._peer.role] + + def _toggle_ignore(self): + ignore = self.ignorePeerCheckBox.isChecked() + self._groups_service.toggle_ignore_peer(self._group, self._peer, ignore) + + def _send_private_message(self): + self._contacts_manager.add_group_peer(self._group, self._peer) + self.close() + + def _copy_public_key(self): + util_ui.copy_to_clipboard(self._peer.public_key) + + def _ban_peer(self): + ban_type = self._get_ban_type() + self._groups_service.ban_peer(self._group, self._peer.id, ban_type) + self.close() + + def _kick_peer(self): + self._groups_service.kick_peer(self._group, self._peer.id) + self.close() + + def _get_ban_type(self): + if self.ipBanRadioButton.isChecked(): + return consts.TOX_GROUP_BAN_TYPE['IP_PORT'] + elif self.nickBanRadioButton.isChecked(): + return consts.TOX_GROUP_BAN_TYPE['NICK'] + return consts.TOX_GROUP_BAN_TYPE['PUBLIC_KEY'] diff --git a/toxygen/ui/profile_settings_screen.py b/toxygen/ui/profile_settings_screen.py new file mode 100644 index 0000000..2e55d3d --- /dev/null +++ b/toxygen/ui/profile_settings_screen.py @@ -0,0 +1,157 @@ +from ui.widgets import CenteredWidget +import utils.ui as util_ui +from utils.util import join_path, get_images_directory, get_views_path +from user_data.settings import Settings +from PyQt5 import QtGui, QtCore, uic + + +class ProfileSettings(CenteredWidget): + """Form with profile settings such as name, status, TOX ID""" + def __init__(self, profile, profile_manager, settings, toxes): + super().__init__() + self._profile = profile + self._profile_manager = profile_manager + self._settings = settings + self._toxes = toxes + self._auto = False + + uic.loadUi(get_views_path('profile_settings_screen'), self) + + self._init_ui() + self.center() + + def closeEvent(self, event): + self._profile.set_name(self.nameLineEdit.text()) + self._profile.set_status_message(self.statusMessageLineEdit.text()) + self._profile.set_status(self.statusComboBox.currentIndex()) + + def _init_ui(self): + self._auto = Settings.get_auto_profile() == self._profile_manager.get_path() + self.toxIdLabel.setText(self._profile.tox_id) + self.nameLineEdit.setText(self._profile.name) + self.statusMessageLineEdit.setText(self._profile.status_message) + self.defaultProfilePushButton.clicked.connect(self._toggle_auto_profile) + self.copyToxIdPushButton.clicked.connect(self._copy_tox_id) + self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key) + self.changePasswordPushButton.clicked.connect(self._save_password) + self.exportProfilePushButton.clicked.connect(self._export_profile) + self.newNoSpamPushButton.clicked.connect(self._set_new_no_spam) + self.newAvatarPushButton.clicked.connect(self._set_avatar) + self.resetAvatarPushButton.clicked.connect(self._reset_avatar) + + self.invalidPasswordsLabel.setVisible(False) + + self._retranslate_ui() + + if self._profile.status is not None: + self.statusComboBox.setCurrentIndex(self._profile.status) + else: + self.statusComboBox.setVisible(False) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr("Profile settings")) + + self.exportProfilePushButton.setText(util_ui.tr("Export profile")) + self.nameLabel.setText(util_ui.tr("Name:")) + self.statusLabel.setText(util_ui.tr("Status:")) + self.toxIdTitleLabel.setText(util_ui.tr("TOX ID:")) + self.copyToxIdPushButton.setText(util_ui.tr("Copy TOX ID")) + self.newAvatarPushButton.setText(util_ui.tr("New avatar")) + self.resetAvatarPushButton.setText(util_ui.tr("Reset avatar")) + self.newNoSpamPushButton.setText(util_ui.tr("New NoSpam")) + self.profilePasswordLabel.setText(util_ui.tr("Profile password")) + self.passwordLineEdit.setPlaceholderText(util_ui.tr("Password (at least 8 symbols)")) + self.confirmPasswordLineEdit.setPlaceholderText(util_ui.tr("Confirm password")) + self.changePasswordPushButton.setText(util_ui.tr("Set password")) + self.invalidPasswordsLabel.setText(util_ui.tr("Passwords do not match")) + self.emptyPasswordLabel.setText(util_ui.tr("Leaving blank will reset current password")) + self.warningLabel.setText(util_ui.tr("There is no way to recover lost passwords")) + self.statusComboBox.addItem(util_ui.tr("Online")) + self.statusComboBox.addItem(util_ui.tr("Away")) + self.statusComboBox.addItem(util_ui.tr("Busy")) + self.copyPublicKeyPushButton.setText(util_ui.tr("Copy public key")) + + self._set_default_profile_button_text() + + def _toggle_auto_profile(self): + if self._auto: + Settings.reset_auto_profile() + else: + Settings.set_auto_profile(self._profile_manager.get_path()) + self._auto = not self._auto + self._set_default_profile_button_text() + + def _set_default_profile_button_text(self): + if self._auto: + self.defaultProfilePushButton.setText(util_ui.tr("Mark as not default profile")) + else: + self.defaultProfilePushButton.setText(util_ui.tr("Mark as default profile")) + + def _save_password(self): + password = self.passwordLineEdit.text() + confirm_password = self.confirmPasswordLineEdit.text() + if password == confirm_password: + if not len(password) or len(password) >= 8: + self._toxes.set_password(password) + self.close() + else: + self.invalidPasswordsLabel.setText( + util_ui.tr("Password must be at least 8 symbols")) + self.invalidPasswordsLabel.setVisible(True) + else: + self.invalidPasswordsLabel.setText(util_ui.tr("Passwords do not match")) + self.invalidPasswordsLabel.setVisible(True) + + def _copy_tox_id(self): + util_ui.copy_to_clipboard(self._profile.tox_id) + + icon = self._get_accept_icon() + self.copyToxIdPushButton.setIcon(icon) + self.copyToxIdPushButton.setIconSize(QtCore.QSize(10, 10)) + + def _copy_public_key(self): + util_ui.copy_to_clipboard(self._profile.tox_id[:64]) + + icon = self._get_accept_icon() + self.copyPublicKeyPushButton.setIcon(icon) + self.copyPublicKeyPushButton.setIconSize(QtCore.QSize(10, 10)) + + def _set_new_no_spam(self): + self.toxIdLabel.setText(self._profile.set_new_nospam()) + + def _reset_avatar(self): + self._profile.reset_avatar(self._settings['identicons']) + + def _set_avatar(self): + choose = util_ui.tr("Choose avatar") + name = util_ui.file_dialog(choose, 'Images (*.png)') + if not name[0]: + return + bitmap = QtGui.QPixmap(name[0]) + bitmap.scaled(QtCore.QSize(128, 128), QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation) + + byte_array = QtCore.QByteArray() + buffer = QtCore.QBuffer(byte_array) + buffer.open(QtCore.QIODevice.WriteOnly) + bitmap.save(buffer, 'PNG') + + self._profile.set_avatar(bytes(byte_array.data())) + + def _export_profile(self): + directory = util_ui.directory_dialog() + if not directory: + return + + reply = util_ui.question(util_ui.tr('Do you want to move your profile to this location?'), + util_ui.tr('Use new path')) + + self._settings.export(directory) + self._profile.export_db(directory) + self._profile_manager.export_profile(self._settings, directory, reply) + + @staticmethod + def _get_accept_icon(): + pixmap = QtGui.QPixmap(join_path(get_images_directory(), 'accept.png')) + + return QtGui.QIcon(pixmap) + diff --git a/toxygen/ui/self_peer_screen.py b/toxygen/ui/self_peer_screen.py new file mode 100644 index 0000000..cf252d3 --- /dev/null +++ b/toxygen/ui/self_peer_screen.py @@ -0,0 +1,66 @@ +from ui.widgets import CenteredWidget, LineEdit +from PyQt5 import uic +import utils.util as util +import utils.ui as util_ui +from ui.contact_items import * + + +class SelfPeerScreen(CenteredWidget): + + def __init__(self, contacts_manager, groups_service, group): + super().__init__() + self._contacts_manager = contacts_manager + self._groups_service = groups_service + self._group = group + self._peer = group.get_self_peer() + self._roles = { + TOX_GROUP_ROLE['FOUNDER']: util_ui.tr('Administrator'), + TOX_GROUP_ROLE['MODERATOR']: util_ui.tr('Moderator'), + TOX_GROUP_ROLE['USER']: util_ui.tr('User'), + TOX_GROUP_ROLE['OBSERVER']: util_ui.tr('Observer') + } + + uic.loadUi(util.get_views_path('self_peer_screen'), self) + self._update_ui() + + def _update_ui(self): + self.lineEdit = LineEdit(self) + self.lineEdit.setGeometry(140, 40, 400, 30) + self.lineEdit.setText(self._peer.name) + self.lineEdit.textChanged.connect(self._nick_changed) + + self.savePushButton.clicked.connect(self._save) + self.copyPublicKeyPushButton.clicked.connect(self._copy_public_key) + + self._retranslate_ui() + + self.statusComboBox.setCurrentIndex(self._peer.status) + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Change credentials in group')) + self.lineEdit.setPlaceholderText(util_ui.tr('Your nickname in group')) + self.nameLabel.setText(util_ui.tr('Name:')) + self.roleLabel.setText(util_ui.tr('Role:')) + self.statusLabel.setText(util_ui.tr('Status:')) + self.copyPublicKeyPushButton.setText(util_ui.tr('Copy public key')) + self.savePushButton.setText(util_ui.tr('Save')) + self.roleNameLabel.setText(self._get_role_name()) + self.statusComboBox.addItem(util_ui.tr('Online')) + self.statusComboBox.addItem(util_ui.tr('Away')) + self.statusComboBox.addItem(util_ui.tr('Busy')) + + def _get_role_name(self): + return self._roles[self._peer.role] + + def _nick_changed(self): + nick = self.lineEdit.text() + self.savePushButton.setEnabled(bool(nick)) + + def _save(self): + nick = self.lineEdit.text() + status = self.statusComboBox.currentIndex() + self._groups_service.set_self_info(self._group, nick, status) + self.close() + + def _copy_public_key(self): + util_ui.copy_to_clipboard(self._peer.public_key) diff --git a/toxygen/ui/tray.py b/toxygen/ui/tray.py new file mode 100644 index 0000000..3bfc7f3 --- /dev/null +++ b/toxygen/ui/tray.py @@ -0,0 +1,111 @@ +from PyQt5 import QtWidgets, QtGui, QtCore +from utils.ui import tr +from utils.util import * +from ui.password_screen import UnlockAppScreen +import os.path + + +class SystemTrayIcon(QtWidgets.QSystemTrayIcon): + + leftClicked = QtCore.pyqtSignal() + + def __init__(self, icon, parent=None): + super().__init__(icon, parent) + self.activated.connect(self.icon_activated) + + def icon_activated(self, reason): + if reason == QtWidgets.QSystemTrayIcon.Trigger: + self.leftClicked.emit() + + +class Menu(QtWidgets.QMenu): + + def __init__(self, settings, profile, *args): + super().__init__(*args) + self._settings = settings + self._profile = profile + + def new_status(self, status): + if not self._settings.locked: + self._profile.set_status(status) + self.about_to_show_handler() + self.hide() + + def about_to_show_handler(self): + status = self._profile.status + act = self.act + if status is None or self._settings.locked: + self.actions()[1].setVisible(False) + else: + self.actions()[1].setVisible(True) + act.actions()[0].setChecked(False) + act.actions()[1].setChecked(False) + act.actions()[2].setChecked(False) + act.actions()[status].setChecked(True) + self.actions()[2].setVisible(not self._settings.locked) + + def languageChange(self, *args, **kwargs): + self.actions()[0].setText(tr('Open Toxygen')) + self.actions()[1].setText(tr('Set status')) + self.actions()[2].setText(tr('Exit')) + self.act.actions()[0].setText(tr('Online')) + self.act.actions()[1].setText(tr('Away')) + self.act.actions()[2].setText(tr('Busy')) + + +def init_tray(profile, settings, main_screen, toxes): + icon = os.path.join(get_images_directory(), 'icon.png') + tray = SystemTrayIcon(QtGui.QIcon(icon)) + + menu = Menu(settings, profile) + show = menu.addAction(tr('Open Toxygen')) + sub = menu.addMenu(tr('Set status')) + online = sub.addAction(tr('Online')) + away = sub.addAction(tr('Away')) + busy = sub.addAction(tr('Busy')) + online.setCheckable(True) + away.setCheckable(True) + busy.setCheckable(True) + menu.act = sub + exit = menu.addAction(tr('Exit')) + + def show_window(): + def show(): + if not main_screen.isActiveWindow(): + main_screen.setWindowState( + main_screen.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) + main_screen.activateWindow() + main_screen.show() + if not settings.locked: + show() + else: + def correct_pass(): + show() + settings.locked = False + settings.unlockScreen = False + if not settings.unlockScreen: + settings.unlockScreen = True + show_window.screen = UnlockAppScreen(toxes, correct_pass) + show_window.screen.show() + + def tray_activated(reason): + if reason == QtWidgets.QSystemTrayIcon.DoubleClick: + show_window() + + def close_app(): + if not settings.locked: + settings.closing = True + main_screen.close() + + show.triggered.connect(show_window) + exit.triggered.connect(close_app) + menu.aboutToShow.connect(menu.about_to_show_handler) + online.triggered.connect(lambda: menu.new_status(0)) + away.triggered.connect(lambda: menu.new_status(1)) + busy.triggered.connect(lambda: menu.new_status(2)) + + tray.setContextMenu(menu) + tray.show() + tray.activated.connect(tray_activated) + + return tray diff --git a/toxygen/ui/widgets.py b/toxygen/ui/widgets.py new file mode 100644 index 0000000..e7fe623 --- /dev/null +++ b/toxygen/ui/widgets.py @@ -0,0 +1,197 @@ +from PyQt5 import QtCore, QtGui, QtWidgets +import utils.ui as util_ui + + +class DataLabel(QtWidgets.QLabel): + """ + Label with elided text + """ + def setText(self, text): + text = ''.join('\u25AF' if len(bytes(c, 'utf-8')) >= 4 else c for c in text) + metrics = QtGui.QFontMetrics(self.font()) + text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width()) + super().setText(text) + + +class ComboBox(QtWidgets.QComboBox): + + def __init__(self, *args): + super().__init__(*args) + self.view().setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) + + +class CenteredWidget(QtWidgets.QWidget): + + def __init__(self): + super().__init__() + self.setAttribute(QtCore.Qt.WA_DeleteOnClose) + self.center() + + def center(self): + qr = self.frameGeometry() + cp = QtWidgets.QDesktopWidget().availableGeometry().center() + qr.moveCenter(cp) + self.move(qr.topLeft()) + + +class DialogWithResult(QtWidgets.QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self._result = None + + def get_result(self): + return self._result + + result = property(get_result) + + def close_with_result(self, result): + self._result = result + self.close() + + +class LineEdit(QtWidgets.QLineEdit): + + def __init__(self, parent=None): + super().__init__(parent) + + def contextMenuEvent(self, event): + menu = create_menu(self.createStandardContextMenu()) + menu.exec_(event.globalPos()) + del menu + + +class QRightClickButton(QtWidgets.QPushButton): + """ + Button with right click support + """ + + rightClicked = QtCore.pyqtSignal() + + def __init__(self, parent=None): + super().__init__(parent) + + def mousePressEvent(self, event): + if event.button() == QtCore.Qt.RightButton: + self.rightClicked.emit() + else: + super().mousePressEvent(event) + + +class RubberBand(QtWidgets.QRubberBand): + + def __init__(self): + super().__init__(QtWidgets.QRubberBand.Rectangle, None) + self.setPalette(QtGui.QPalette(QtCore.Qt.transparent)) + self.pen = QtGui.QPen(QtCore.Qt.blue, 4) + self.pen.setStyle(QtCore.Qt.SolidLine) + self.painter = QtGui.QPainter() + + def paintEvent(self, event): + + self.painter.begin(self) + self.painter.setPen(self.pen) + self.painter.drawRect(event.rect()) + self.painter.end() + + +class RubberBandWindow(QtWidgets.QWidget): + + def __init__(self, parent): + super().__init__() + self.parent = parent + self.setMouseTracking(True) + self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) + self.showFullScreen() + self.setWindowOpacity(0.5) + self.rubberband = RubberBand() + self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint) + self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + def mousePressEvent(self, event): + self.origin = event.pos() + self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize())) + self.rubberband.show() + QtWidgets.QWidget.mousePressEvent(self, event) + + def mouseMoveEvent(self, event): + if self.rubberband.isVisible(): + self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized()) + left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height())) + right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height())) + top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y()) + bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height()) + self.setMask(left + right + top + bottom) + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Escape: + self.rubberband.setHidden(True) + self.close() + else: + super().keyPressEvent(event) + + +def create_menu(menu): + """ + :return translated menu + """ + for action in menu.actions(): + text = action.text() + if 'Link Location' in text: + text = text.replace('Copy &Link Location', + util_ui.tr("Copy link location")) + elif '&Copy' in text: + text = text.replace('&Copy', util_ui.tr("Copy")) + elif 'All' in text: + text = text.replace('Select All', util_ui.tr("Select all")) + elif 'Delete' in text: + text = text.replace('Delete', util_ui.tr("Delete")) + elif '&Paste' in text: + text = text.replace('&Paste', util_ui.tr("Paste")) + elif 'Cu&t' in text: + text = text.replace('Cu&t', util_ui.tr("Cut")) + elif '&Undo' in text: + text = text.replace('&Undo', util_ui.tr("Undo")) + elif '&Redo' in text: + text = text.replace('&Redo', util_ui.tr("Redo")) + else: + menu.removeAction(action) + continue + action.setText(text) + return menu + + +class MultilineEdit(CenteredWidget): + + def __init__(self, title, text, save): + super(MultilineEdit, self).__init__() + self.resize(350, 200) + self.setMinimumSize(QtCore.QSize(350, 200)) + self.setMaximumSize(QtCore.QSize(350, 200)) + self.setWindowTitle(title) + self.edit = QtWidgets.QTextEdit(self) + self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150)) + self.edit.setText(text) + self.button = QtWidgets.QPushButton(self) + self.button.setGeometry(QtCore.QRect(0, 150, 350, 50)) + self.button.setText(util_ui.tr("Save")) + self.button.clicked.connect(self.button_click) + self.center() + self.save = save + + def button_click(self): + self.save(self.edit.toPlainText()) + self.close() + + +class LineEditWithEnterSupport(LineEdit): + + def __init__(self, enter_action, parent=None): + super().__init__(parent) + self._action = enter_action + + def keyPressEvent(self, event): + if event.key() == QtCore.Qt.Key_Return: + self._action() + else: + super().keyPressEvent(event) diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py new file mode 100644 index 0000000..128e85e --- /dev/null +++ b/toxygen/ui/widgets_factory.py @@ -0,0 +1,97 @@ +from ui.main_screen_widgets import * +from ui.menu import * +from ui.groups_widgets import * +from ui.peer_screen import * +from ui.self_peer_screen import * +from ui.group_invites_widgets import * +from ui.group_settings_widgets import * +from ui.group_bans_widgets import * +from ui.profile_settings_screen import ProfileSettings + + +class WidgetsFactory: + + def __init__(self, settings, profile, profile_manager, contacts_manager, file_transfer_handler, smiley_loader, + plugin_loader, toxes, version, groups_service, history, contacts_provider): + self._settings = settings + self._profile = profile + self._profile_manager = profile_manager + self._contacts_manager = contacts_manager + self._file_transfer_handler = file_transfer_handler + self._smiley_loader = smiley_loader + self._plugin_loader = plugin_loader + self._toxes = toxes + self._version = version + self._groups_service = groups_service + self._history = history + self._contacts_provider = contacts_provider + + def create_screenshot_window(self, *args): + return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args) + + def create_welcome_window(self): + return WelcomeScreen(self._settings) + + def create_profile_settings_window(self): + return ProfileSettings(self._profile, self._profile_manager, self._settings, self._toxes) + + def create_network_settings_window(self): + return NetworkSettings(self._settings, self._profile.restart) + + def create_audio_settings_window(self): + return AudioSettings(self._settings) + + def create_video_settings_window(self): + return VideoSettings(self._settings) + + def create_update_settings_window(self): + return UpdateSettings(self._settings, self._version) + + def create_plugins_settings_window(self): + return PluginsSettings(self._plugin_loader) + + def create_add_contact_window(self, tox_id): + return AddContact(self._settings, self._contacts_manager, tox_id) + + def create_privacy_settings_window(self): + return PrivacySettings(self._contacts_manager, self._settings) + + def create_interface_settings_window(self): + return InterfaceSettings(self._settings, self._smiley_loader) + + def create_notification_settings_window(self): + return NotificationsSettings(self._settings) + + def create_smiley_window(self, parent): + return SmileyWindow(parent, self._smiley_loader) + + def create_sticker_window(self): + return StickerWindow(self._file_transfer_handler, self._contacts_manager) + + def create_group_screen_window(self): + return CreateGroupScreen(self._groups_service, self._profile) + + def create_join_group_screen_window(self): + return JoinGroupScreen(self._groups_service, self._profile) + + def create_search_screen(self, messages): + return SearchScreen(self._contacts_manager, self._history, messages, messages.parent()) + + def create_peer_screen_window(self, group, peer_id): + return PeerScreen(self._contacts_manager, self._groups_service, group, peer_id) + + def create_self_peer_screen_window(self, group): + return SelfPeerScreen(self._contacts_manager, self._groups_service, group) + + def create_group_invites_window(self): + return GroupInvitesScreen(self._groups_service, self._profile, self._contacts_provider) + + def create_group_management_screen(self, group): + return GroupManagementScreen(self._groups_service, group) + + @staticmethod + def create_group_settings_screen(group): + return GroupSettingsScreen(group) + + def create_groups_bans_screen(self, group): + return GroupBansScreen(self._groups_service, group) diff --git a/toxygen/updater/__init__.py b/toxygen/updater/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/updater/updater.py b/toxygen/updater/updater.py new file mode 100644 index 0000000..329353c --- /dev/null +++ b/toxygen/updater/updater.py @@ -0,0 +1,124 @@ +import utils.util as util +import utils.ui as util_ui +import os +import platform +import urllib +from PyQt5 import QtNetwork, QtCore +import subprocess + + +def connection_available(): + try: + urllib.request.urlopen('http://216.58.192.142', timeout=1) # google.com + return True + except: + return False + + +def updater_available(): + if is_from_sources(): + return os.path.exists(util.curr_directory() + '/toxygen_updater.py') + elif platform.system() == 'Windows': + return os.path.exists(util.curr_directory() + '/toxygen_updater.exe') + else: + return os.path.exists(util.curr_directory() + '/toxygen_updater') + + +def check_for_updates(current_version, settings): + major, minor, patch = list(map(lambda x: int(x), current_version.split('.'))) + versions = generate_versions(major, minor, patch) + for version in versions: + if send_request(version, settings): + return version + return None # no new version was found + + +def is_from_sources(): + return __file__.endswith('.py') + + +def test_url(version): + return 'https://github.com/toxygen-project/toxygen/releases/tag/v' + version + + +def get_url(version): + if is_from_sources(): + return 'https://github.com/toxygen-project/toxygen/archive/v' + version + '.zip' + else: + if platform.system() == 'Windows': + name = 'toxygen_windows.zip' + elif util.is_64_bit(): + name = 'toxygen_linux_64.tar.gz' + else: + name = 'toxygen_linux.tar.gz' + return 'https://github.com/toxygen-project/toxygen/releases/download/v{}/{}'.format(version, name) + + +def get_params(url, version): + if is_from_sources(): + if platform.system() == 'Windows': + return ['python', 'toxygen_updater.py', url, version] + else: + return ['python3', 'toxygen_updater.py', url, version] + elif platform.system() == 'Windows': + return [util.curr_directory() + '/toxygen_updater.exe', url, version] + else: + return ['./toxygen_updater', url, version] + + +def download(version): + os.chdir(util.curr_directory()) + url = get_url(version) + params = get_params(url, version) + print('Updating Toxygen') + util.log('Updating Toxygen') + try: + subprocess.Popen(params) + except Exception as ex: + util.log('Exception: running updater failed with ' + str(ex)) + + +def send_request(version, settings): + netman = QtNetwork.QNetworkAccessManager() + proxy = QtNetwork.QNetworkProxy() + if settings['proxy_type']: + proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) + proxy.setHostName(settings['proxy_host']) + proxy.setPort(settings['proxy_port']) + netman.setProxy(proxy) + url = test_url(version) + try: + request = QtNetwork.QNetworkRequest() + request.setUrl(QtCore.QUrl(url)) + reply = netman.get(request) + while not reply.isFinished(): + QtCore.QThread.msleep(1) + QtCore.QCoreApplication.processEvents() + attr = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute) + return attr is not None and 200 <= attr < 300 + except Exception as ex: + util.log('TOXYGEN UPDATER ERROR: ' + str(ex)) + return False + + +def generate_versions(major, minor, patch): + new_major = '.'.join([str(major + 1), '0', '0']) + new_minor = '.'.join([str(major), str(minor + 1), '0']) + new_patch = '.'.join([str(major), str(minor), str(patch + 1)]) + return new_major, new_minor, new_patch + + +def start_update_if_needed(version, settings): + updating = False + if settings['update'] and updater_available() and connection_available(): # auto update + version = check_for_updates(version, settings) + if version is not None: + if settings['update'] == 2: + download(version) + updating = True + else: + reply = util_ui.question(util_ui.tr('Update for Toxygen was found. Download and install it?')) + if reply: + download(version) + updating = True + return updating diff --git a/toxygen/user_data/__init__.py b/toxygen/user_data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/user_data/backup_service.py b/toxygen/user_data/backup_service.py new file mode 100644 index 0000000..bb0cef9 --- /dev/null +++ b/toxygen/user_data/backup_service.py @@ -0,0 +1,40 @@ +import os.path +from utils.util import get_profile_name_from_path, join_path + + +class BackupService: + + def __init__(self, settings, profile_manager): + self._settings = settings + self._profile_name = get_profile_name_from_path(profile_manager.get_path()) + + settings.settings_saved_event.add_callback(self._settings_saved) + profile_manager.profile_saved_event.add_callback(self._profile_saved) + + def _settings_saved(self, data): + if not self._check_if_should_save_backup(): + return + + file_path = join_path(self._get_backup_directory(), self._profile_name + '.json') + + with open(file_path, 'wt') as fl: + fl.write(data) + + def _profile_saved(self, data): + if not self._check_if_should_save_backup(): + return + + file_path = join_path(self._get_backup_directory(), self._profile_name + '.tox') + + with open(file_path, 'wb') as fl: + fl.write(data) + + def _check_if_should_save_backup(self): + backup_directory = self._get_backup_directory() + if backup_directory is None: + return False + + return os.path.exists(backup_directory) and os.path.isdir(backup_directory) + + def _get_backup_directory(self): + return self._settings['backup_directory'] diff --git a/toxygen/user_data/profile_manager.py b/toxygen/user_data/profile_manager.py new file mode 100644 index 0000000..05e2f2d --- /dev/null +++ b/toxygen/user_data/profile_manager.py @@ -0,0 +1,90 @@ +import utils.util as util +import os +from user_data.settings import Settings +from common.event import Event + + +class ProfileManager: + """ + Class with methods for search, load and save profiles + """ + def __init__(self, toxes, path): + self._toxes = toxes + self._path = path + self._directory = os.path.dirname(path) + self._profile_saved_event = Event() + # create /avatars if not exists: + avatars_directory = util.join_path(self._directory, 'avatars') + if not os.path.exists(avatars_directory): + os.makedirs(avatars_directory) + + # ----------------------------------------------------------------------------------------------------------------- + # Properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_profile_saved_event(self): + return self._profile_saved_event + + profile_saved_event = property(get_profile_saved_event) + + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + + def open_profile(self): + with open(self._path, 'rb') as fl: + data = fl.read() + if data: + return data + else: + raise IOError('Save file has zero size!') + + def get_dir(self): + return self._directory + + def get_path(self): + return self._path + + def save_profile(self, data): + if self._toxes.has_password(): + data = self._toxes.pass_encrypt(data) + with open(self._path, 'wb') as fl: + fl.write(data) + print('Profile saved successfully') + + self._profile_saved_event(data) + + def export_profile(self, settings, new_path, use_new_path): + path = new_path + os.path.basename(self._path) + with open(self._path, 'rb') as fin: + data = fin.read() + with open(path, 'wb') as fout: + fout.write(data) + print('Profile exported successfully') + util.copy(self._directory + 'avatars', new_path + 'avatars') + if use_new_path: + self._path = new_path + os.path.basename(self._path) + self._directory = new_path + settings.update_path(new_path) + + @staticmethod + def find_profiles(): + """ + Find available tox profiles + """ + path = Settings.get_default_path() + result = [] + # check default path + if not os.path.exists(path): + os.makedirs(path) + for fl in os.listdir(path): + if fl.endswith('.tox'): + name = fl[:-4] + result.append((path, name)) + path = util.get_base_directory(__file__) + # check current directory + for fl in os.listdir(path): + if fl.endswith('.tox'): + name = fl[:-4] + result.append((path + '/', name)) + return result diff --git a/toxygen/user_data/settings.py b/toxygen/user_data/settings.py new file mode 100644 index 0000000..71422c2 --- /dev/null +++ b/toxygen/user_data/settings.py @@ -0,0 +1,244 @@ +import json +from utils.util import * +import pyaudio +from common.event import Event + + +class Settings(dict): + """ + Settings of current profile + global app settings + """ + + def __init__(self, toxes, path): + self._path = path + self._profile_path = path.replace('.json', '.tox') + self._toxes = toxes + self._settings_saved_event = Event() + if os.path.isfile(path): + with open(path, 'rb') as fl: + data = fl.read() + try: + if toxes.is_data_encrypted(data): + data = toxes.pass_decrypt(data) + info = json.loads(str(data, 'utf-8')) + except Exception as ex: + info = Settings.get_default_settings() + log('Parsing settings error: ' + str(ex)) + super().__init__(info) + self._upgrade() + else: + super().__init__(Settings.get_default_settings()) + self.save() + self.locked = False + self.closing = False + self.unlockScreen = False + p = pyaudio.PyAudio() + input_devices = output_devices = 0 + for i in range(p.get_device_count()): + device = p.get_device_info_by_index(i) + if device["maxInputChannels"]: + input_devices += 1 + if device["maxOutputChannels"]: + output_devices += 1 + self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1, + 'output': p.get_default_output_device_info()['index'] if output_devices else -1, + 'enabled': input_devices and output_devices} + self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0} + + # ----------------------------------------------------------------------------------------------------------------- + # Properties + # ----------------------------------------------------------------------------------------------------------------- + + def get_settings_saved_event(self): + return self._settings_saved_event + + settings_saved_event = property(get_settings_saved_event) + + # ----------------------------------------------------------------------------------------------------------------- + # Public methods + # ----------------------------------------------------------------------------------------------------------------- + + def save(self): + text = json.dumps(self) + if self._toxes.has_password(): + text = bytes(self._toxes.pass_encrypt(bytes(text, 'utf-8'))) + else: + text = bytes(text, 'utf-8') + with open(self._path, 'wb') as fl: + fl.write(text) + + self._settings_saved_event(text) + + def close(self): + path = self._profile_path + '.lock' + if os.path.isfile(path): + os.remove(path) + + def set_active_profile(self): + """ + Mark current profile as active + """ + path = self._profile_path + '.lock' + with open(path, 'w') as fl: + fl.write('active') + + def export(self, path): + text = json.dumps(self) + name = os.path.basename(self._path) + with open(join_path(path, str(name)), 'w') as fl: + fl.write(text) + + def update_path(self, new_path): + self._path = new_path + self.save() + + # ----------------------------------------------------------------------------------------------------------------- + # Static methods + # ----------------------------------------------------------------------------------------------------------------- + + @staticmethod + def get_auto_profile(): + p = Settings.get_global_settings_path() + if not os.path.isfile(p): + return None + with open(p) as fl: + data = fl.read() + try: + auto = json.loads(data) + except Exception as ex: + log(str(ex)) + auto = {} + if 'profile_path' in auto: + path = str(auto['profile_path']) + if not os.path.isabs(path): + path = join_path(path, curr_directory(__file__)) + if os.path.isfile(path): + return path + + @staticmethod + def set_auto_profile(path): + p = Settings.get_global_settings_path() + if os.path.isfile(p): + with open(p) as fl: + data = fl.read() + data = json.loads(data) + else: + data = {} + data['profile_path'] = str(path) + with open(p, 'w') as fl: + fl.write(json.dumps(data)) + + @staticmethod + def reset_auto_profile(): + p = Settings.get_global_settings_path() + if os.path.isfile(p): + with open(p) as fl: + data = fl.read() + data = json.loads(data) + else: + data = {} + if 'profile_path' in data: + del data['profile_path'] + with open(p, 'w') as fl: + fl.write(json.dumps(data)) + + @staticmethod + def is_active_profile(profile_path): + return os.path.isfile(profile_path + '.lock') + + @staticmethod + def get_default_settings(): + """ + Default profile settings + """ + return { + 'theme': 'dark', + 'ipv6_enabled': False, + 'udp_enabled': True, + 'proxy_type': 0, + 'proxy_host': '127.0.0.1', + 'proxy_port': 9050, + 'start_port': 0, + 'end_port': 0, + 'tcp_port': 0, + 'notifications': True, + 'sound_notifications': False, + 'language': 'English', + 'save_history': False, + 'allow_inline': True, + 'allow_auto_accept': True, + 'auto_accept_path': None, + 'sorting': 0, + 'auto_accept_from_friends': [], + 'paused_file_transfers': {}, + 'resend_files': True, + 'friends_aliases': [], + 'show_avatars': False, + 'typing_notifications': False, + 'calls_sound': True, + 'blocked': [], + 'plugins': [], + 'notes': {}, + 'smileys': True, + 'smiley_pack': 'default', + 'mirror_mode': False, + 'width': 920, + 'height': 500, + 'x': 400, + 'y': 400, + 'message_font_size': 14, + 'unread_color': 'red', + 'save_unsent_only': False, + 'compact_mode': False, + 'identicons': True, + 'show_welcome_screen': True, + 'close_app': 0, + 'font': 'Times New Roman', + 'update': 1, + 'group_notifications': True, + 'download_nodes_list': False, + 'notify_all_gc': False, + 'lan_discovery': True, + 'backup_directory': None + } + + @staticmethod + def supported_languages(): + return { + 'English': 'en_EN', + 'French': 'fr_FR', + 'Russian': 'ru_RU', + 'Ukrainian': 'uk_UA' + } + + @staticmethod + def built_in_themes(): + return { + 'dark': 'dark_style.qss', + 'default': 'style.qss' + } + + @staticmethod + def get_global_settings_path(): + return os.path.join(get_base_directory(), 'toxygen.json') + + @staticmethod + def get_default_path(): + system = get_platform() + if system == 'Windows': + return os.getenv('APPDATA') + '/Tox/' + elif system == 'Darwin': + return os.getenv('HOME') + '/Library/Application Support/Tox/' + else: + return os.getenv('HOME') + '/.config/tox/' + + # ----------------------------------------------------------------------------------------------------------------- + # Private methods + # ----------------------------------------------------------------------------------------------------------------- + + def _upgrade(self): + default = Settings.get_default_settings() + for key in default: + if key not in self: + print(key) + self[key] = default[key] diff --git a/toxygen/user_data/toxes.py b/toxygen/user_data/toxes.py new file mode 100644 index 0000000..982f287 --- /dev/null +++ b/toxygen/user_data/toxes.py @@ -0,0 +1,24 @@ + +class ToxES: + + def __init__(self, tox_encrypt_save): + self._tox_encrypt_save = tox_encrypt_save + self._password = None + + def set_password(self, password): + self._password = password + + def has_password(self): + return bool(self._password) + + def is_password(self, password): + return self._password == password + + def is_data_encrypted(self, data): + return len(data) > 0 and self._tox_encrypt_save.is_data_encrypted(data) + + def pass_encrypt(self, data): + return self._tox_encrypt_save.pass_encrypt(data, self._password) + + def pass_decrypt(self, data): + return self._tox_encrypt_save.pass_decrypt(data, self._password) diff --git a/toxygen/utils/__init__.py b/toxygen/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/utils/ui.py b/toxygen/utils/ui.py new file mode 100644 index 0000000..d2d7122 --- /dev/null +++ b/toxygen/utils/ui.py @@ -0,0 +1,54 @@ +from PyQt5 import QtWidgets +import utils.util as util + + +def tr(s): + return QtWidgets.QApplication.translate('Toxygen', s) + + +def question(text, title=None): + reply = QtWidgets.QMessageBox.question(None, title or 'Toxygen', text, + QtWidgets.QMessageBox.Yes, + QtWidgets.QMessageBox.No) + return reply == QtWidgets.QMessageBox.Yes + + +def message_box(text, title=None): + m_box = QtWidgets.QMessageBox() + m_box.setText(tr(text)) + m_box.setWindowTitle(title or 'Toxygen') + m_box.exec_() + + +def text_dialog(text, title='', default_value=''): + text, ok = QtWidgets.QInputDialog.getText(None, title, text, QtWidgets.QLineEdit.Normal, default_value) + + return text, ok + + +def directory_dialog(caption=''): + return QtWidgets.QFileDialog.getExistingDirectory(None, caption, util.curr_directory(), + QtWidgets.QFileDialog.DontUseNativeDialog) + + +def file_dialog(caption, file_filter=None): + return QtWidgets.QFileDialog.getOpenFileName(None, caption, util.curr_directory(), file_filter, + options=QtWidgets.QFileDialog.DontUseNativeDialog) + + +def save_file_dialog(caption, filter=None): + return QtWidgets.QFileDialog.getSaveFileName(None, caption, util.curr_directory(), + filter=filter, + options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) + + +def close_all_windows(): + QtWidgets.QApplication.closeAllWindows() + + +def copy_to_clipboard(text): + clipboard = QtWidgets.QApplication.clipboard() + clipboard.setText(text) + + +# TODO: all dialogs diff --git a/toxygen/utils/util.py b/toxygen/utils/util.py new file mode 100644 index 0000000..5bd5c3a --- /dev/null +++ b/toxygen/utils/util.py @@ -0,0 +1,170 @@ +import os +import time +import shutil +import sys +import re +import platform +import datetime + + +def cached(func): + saved_result = None + + def wrapped_func(): + nonlocal saved_result + if saved_result is None: + saved_result = func() + + return saved_result + + return wrapped_func + + +def log(data): + try: + with open(join_path(curr_directory(), 'logs.log'), 'a') as fl: + fl.write(str(data) + '\n') + except Exception as ex: + print(ex) + + +def curr_directory(current_file=None): + return os.path.dirname(os.path.realpath(current_file or __file__)) + + +def get_base_directory(current_file=None): + return os.path.dirname(curr_directory(current_file or __file__)) + + +@cached +def get_images_directory(): + return get_app_directory('images') + + +@cached +def get_styles_directory(): + return get_app_directory('styles') + + +@cached +def get_sounds_directory(): + return get_app_directory('sounds') + + +@cached +def get_stickers_directory(): + return get_app_directory('stickers') + + +@cached +def get_smileys_directory(): + return get_app_directory('smileys') + + +@cached +def get_translations_directory(): + return get_app_directory('translations') + + +@cached +def get_plugins_directory(): + return get_app_directory('plugins') + + +@cached +def get_libs_directory(): + return get_app_directory('libs') + + +def get_app_directory(directory_name): + return os.path.join(get_base_directory(), directory_name) + + +def get_profile_name_from_path(path): + return os.path.basename(path)[:-4] + + +def get_views_path(view_name): + ui_folder = os.path.join(get_base_directory(), 'ui') + views_folder = os.path.join(ui_folder, 'views') + + return os.path.join(views_folder, view_name + '.ui') + + +def curr_time(): + return time.strftime('%H:%M') + + +def get_unix_time(): + return int(time.time()) + + +def join_path(a, b): + return os.path.join(a, b) + + +def file_exists(file_path): + return os.path.exists(file_path) + + +def copy(src, dest): + if not os.path.exists(dest): + os.makedirs(dest) + src_files = os.listdir(src) + for file_name in src_files: + full_file_name = os.path.join(src, file_name) + if os.path.isfile(full_file_name): + shutil.copy(full_file_name, dest) + else: + copy(full_file_name, os.path.join(dest, file_name)) + + +def remove(folder): + if os.path.isdir(folder): + shutil.rmtree(folder) + + +def convert_time(t): + offset = time.timezone + time_offset() * 60 + sec = int(t) - offset + m, s = divmod(sec, 60) + h, m = divmod(m, 60) + d, h = divmod(h, 24) + return '%02d:%02d' % (h, m) + + +@cached +def time_offset(): + hours = int(time.strftime('%H')) + minutes = int(time.strftime('%M')) + sec = int(time.time()) - time.timezone + m, s = divmod(sec, 60) + h, m = divmod(m, 60) + d, h = divmod(h, 24) + result = hours * 60 + minutes - h * 60 - m + return result + + +def unix_time_to_long_str(unix_time): + date_time = datetime.datetime.utcfromtimestamp(unix_time) + + return date_time.strftime('%Y-%m-%d %H:%M:%S') + + +@cached +def is_64_bit(): + return sys.maxsize > 2 ** 32 + + +def is_re_valid(regex): + try: + re.compile(regex) + except re.error: + return False + else: + return True + + +@cached +def get_platform(): + return platform.system() diff --git a/toxygen/wrapper/__init__.py b/toxygen/wrapper/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/toxygen/wrapper/libtox.py b/toxygen/wrapper/libtox.py new file mode 100644 index 0000000..01d41f1 --- /dev/null +++ b/toxygen/wrapper/libtox.py @@ -0,0 +1,61 @@ +from ctypes import CDLL +import utils.util as util + + +class LibToxCore: + + def __init__(self): + platform = util.get_platform() + if platform == 'Windows': + self._libtoxcore = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll')) + elif platform == 'Darwin': + self._libtoxcore = CDLL('libtoxcore.dylib') + else: + # libtoxcore and libsodium must be installed in your os + try: + self._libtoxcore = CDLL('libtoxcore.so') + except: + self._libtoxcore = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so')) + + def __getattr__(self, item): + return self._libtoxcore.__getattr__(item) + + +class LibToxAV: + + def __init__(self): + platform = util.get_platform() + if platform == 'Windows': + # on Windows av api is in libtox.dll + self._libtoxav = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll')) + elif platform == 'Darwin': + self._libtoxav = CDLL('libtoxcore.dylib') + else: + # /usr/lib/libtoxcore.so must exists + try: + self._libtoxav = CDLL('libtoxcore.so') + except: + self._libtoxav = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so')) + + def __getattr__(self, item): + return self._libtoxav.__getattr__(item) + + +class LibToxEncryptSave: + + def __init__(self): + platform = util.get_platform() + if platform == 'Windows': + # on Windows profile encryption api is in libtox.dll + self._lib_tox_encrypt_save = CDLL(util.join_path(util.get_libs_directory(), 'libtox.dll')) + elif platform == 'Darwin': + self._lib_tox_encrypt_save = CDLL('libtoxcore.dylib') + else: + # /usr/lib/libtoxcore.so must exists + try: + self._lib_tox_encrypt_save = CDLL('libtoxcore.so') + except: + self._lib_tox_encrypt_save = CDLL(util.join_path(util.get_libs_directory(), 'libtoxcore.so')) + + def __getattr__(self, item): + return self._lib_tox_encrypt_save.__getattr__(item) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py new file mode 100644 index 0000000..21b0ebc --- /dev/null +++ b/toxygen/wrapper/tox.py @@ -0,0 +1,2532 @@ +# -*- coding: utf-8 -*- +from ctypes import * +from wrapper.toxcore_enums_and_consts import * +from wrapper.toxav import ToxAV +from wrapper.libtox import LibToxCore + + +class ToxOptions(Structure): + _fields_ = [ + ('ipv6_enabled', c_bool), + ('udp_enabled', c_bool), + ('local_discovery_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), + ('hole_punching_enabled', c_bool), + ('savedata_type', c_int), + ('savedata_data', c_char_p), + ('savedata_length', c_size_t), + ('log_callback', c_void_p), + ('log_user_data', c_void_p) + ] + + +class GroupChatSelfPeerInfo(Structure): + _fields_ = [ + ('nick', c_char_p), + ('nick_length', c_uint8), + ('user_status', c_int) + ] + + +def string_to_bin(tox_id): + return c_char_p(bytes.fromhex(tox_id)) 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 range(length)) + return res.upper() + + +class Tox: + 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() + f = Tox.libtoxcore.tox_new + f.restype = POINTER(c_void_p) + self._tox_pointer = f(tox_options, byref(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 RuntimeError('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 + self.friend_lossy_packet_cb = None + self.friend_lossless_packet_cb = None + self.group_moderation_cb = None + self.group_join_fail_cb = None + self.group_self_join_cb = None + self.group_invite_cb = None + self.group_custom_packet_cb = None + self.group_private_message_cb = None + self.group_message_cb = None + self.group_password_cb = None + self.group_peer_limit_cb = None + self.group_privacy_state_cb = None + self.group_topic_cb = None + self.group_peer_status_cb = None + self.group_peer_name_cb = None + self.group_peer_exit_cb = None + self.group_peer_join_cb = None + + self.AV = ToxAV(self._tox_pointer) + + def kill(self): + del self.AV + 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(byref(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. + """ + address = bytes(address, 'utf-8') + 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), byref(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. + """ + address = bytes(address, 'utf-8') + tox_err_bootstrap = c_int() + result = Tox.libtoxcore.tox_add_tcp_relay(self._tox_pointer, c_char_p(address), c_uint16(port), + string_to_bin(public_key), byref(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): + """ + 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 + """ + 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) + + 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, user_data=None): + """ + The main loop that needs to be run in intervals of tox_iteration_interval() milliseconds. + """ + if user_data is not None: + user_data = c_char_p(user_data) + Tox.libtoxcore.tox_iterate(self._tox_pointer, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # 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() + name = bytes(name, 'utf-8') + result = Tox.libtoxcore.tox_self_set_name(self._tox_pointer, c_char_p(name), + c_size_t(len(name)), byref(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 str(name.value, 'utf-8') + + 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() + status_message = bytes(status_message, 'utf-8') + result = Tox.libtoxcore.tox_self_set_status_message(self._tox_pointer, c_char_p(status_message), + c_size_t(len(status_message)), byref(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 str(status_message.value, 'utf-8') + + 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)), byref(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), + byref(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), + byref(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), + byref(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, + byref(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), + byref(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), + byref(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 friend_number: number of friend + :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, + byref(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return str(name.value, 'utf-8') + 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): + """ + 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 + """ + 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) + + 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), + byref(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, + byref(tox_err_friend_query)) + tox_err_friend_query = tox_err_friend_query.value + if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: + return str(status_message.value, 'utf-8') + 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): + """ + 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 + """ + 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) + + 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), + byref(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): + """ + 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) + + 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), + byref(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): + """ + 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 + """ + 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) + + 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), + byref(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): + """ + 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 + """ + 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) + + # ----------------------------------------------------------------------------------------------------------------- + # 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), byref(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)), + byref(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 MemoryError('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): + """ + 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) + + # ----------------------------------------------------------------------------------------------------------------- + # Receiving private messages and friend requests + # ----------------------------------------------------------------------------------------------------------------- + + def callback_friend_request(self, callback): + """ + 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_uint8 array) 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, POINTER(c_uint8), 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) + + def callback_friend_message(self, callback): + """ + 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 + """ + 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) + + # ----------------------------------------------------------------------------------------------------------------- + # 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), 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), byref(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 ArgumentError('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 RuntimeError('A RESUME control was sent, but the file transfer is running normally.') + elif tox_err_file_control == TOX_ERR_FILE_CONTROL['DENIED']: + raise RuntimeError('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 RuntimeError('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): + """ + 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 + """ + 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) + + 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), byref(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 ArgumentError('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 IOError('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 RuntimeError('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_get_file_id(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number), file_id, + byref(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)), byref(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 ArgumentError('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)), + byref(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 RuntimeError('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): + """ + 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 + """ + 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) + + # ----------------------------------------------------------------------------------------------------------------- + # File transmission: receiving + # ----------------------------------------------------------------------------------------------------------------- + + def callback_file_recv(self, callback): + """ + 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 + """ + 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) + + def callback_file_recv_chunk(self, callback): + """ + 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 + """ + 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) + + # ----------------------------------------------------------------------------------------------------------------- + # Low-level custom packet sending and receiving + # ----------------------------------------------------------------------------------------------------------------- + + def friend_send_lossy_packet(self, friend_number, data): + """ + Send a custom lossy packet to a friend. + The first byte of data must be in the range 200-254. Maximum length of a + custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. + + Lossy packets behave like UDP packets, meaning they might never reach the + other side or might arrive more than once (if someone is messing with the + connection) or might arrive in the wrong order. + + Unless latency is an issue, it is recommended that you use lossless custom packets instead. + + :param friend_number: The friend number of the friend this lossy packet + :param data: python string containing the packet data + :return: True on success. + """ + tox_err_friend_custom_packet = c_int() + result = self.libtoxcore.tox_friend_send_lossy_packet(self._tox_pointer, c_uint32(friend_number), + c_char_p(data), c_size_t(len(data)), + byref(tox_err_friend_custom_packet)) + tox_err_friend_custom_packet = tox_err_friend_custom_packet.value + if tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['OK']: + return bool(result) + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend number did not designate a valid friend.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_CONNECTED']: + raise ArgumentError('This client is currently not connected to the friend.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['INVALID']: + raise ArgumentError('The first byte of data was not in the specified range for the packet type.' + 'This range is 200-254 for lossy, and 160-191 for lossless packets.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['EMPTY']: + raise ArgumentError('Attempted to send an empty packet.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['TOO_LONG']: + raise ArgumentError('Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']: + raise RuntimeError('Packet queue is full.') + + def friend_send_lossless_packet(self, friend_number, data): + """ + Send a custom lossless packet to a friend. + The first byte of data must be in the range 160-191. Maximum length of a + custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. + + Lossless packet behaviour is comparable to TCP (reliability, arrive in order) + but with packets instead of a stream. + + :param friend_number: The friend number of the friend this lossless packet + :param data: python string containing the packet data + :return: True on success. + """ + tox_err_friend_custom_packet = c_int() + result = self.libtoxcore.tox_friend_send_lossless_packet(self._tox_pointer, c_uint32(friend_number), + c_char_p(data), c_size_t(len(data)), + byref(tox_err_friend_custom_packet)) + tox_err_friend_custom_packet = tox_err_friend_custom_packet.value + if tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['OK']: + return bool(result) + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend number did not designate a valid friend.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_CONNECTED']: + raise ArgumentError('This client is currently not connected to the friend.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['INVALID']: + raise ArgumentError('The first byte of data was not in the specified range for the packet type.' + 'This range is 200-254 for lossy, and 160-191 for lossless packets.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['EMPTY']: + raise ArgumentError('Attempted to send an empty packet.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['TOO_LONG']: + raise ArgumentError('Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE.') + elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']: + raise RuntimeError('Packet queue is full.') + + def callback_friend_lossy_packet(self, callback): + """ + Set the callback for the `friend_lossy_packet` event. Pass NULL to unset. + + :param callback: Python function. + Should take pointer (c_void_p) to Tox object, + friend_number (c_uint32) - The friend number of the friend who sent a lossy packet, + A byte array (c_uint8 array) containing the received packet data, + length (c_size_t) - The length of the packet data byte array, + pointer (c_void_p) to user_data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p) + self.friend_lossy_packet_cb = c_callback(callback) + self.libtoxcore.tox_callback_friend_lossy_packet(self._tox_pointer, self.friend_lossy_packet_cb) + + def callback_friend_lossless_packet(self, callback): + """ + Set the callback for the `friend_lossless_packet` event. Pass NULL to unset. + + :param callback: Python function. + Should take pointer (c_void_p) to Tox object, + friend_number (c_uint32) - The friend number of the friend who sent a lossless packet, + A byte array (c_uint8 array) containing the received packet data, + length (c_size_t) - The length of the packet data byte array, + pointer (c_void_p) to user_data + """ + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, c_void_p) + self.friend_lossless_packet_cb = c_callback(callback) + self.libtoxcore.tox_callback_friend_lossless_packet(self._tox_pointer, self.friend_lossless_packet_cb) + + # ----------------------------------------------------------------------------------------------------------------- + # 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, byref(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, byref(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.') + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat instance management + # ----------------------------------------------------------------------------------------------------------------- + + def group_new(self, privacy_state, group_name, nick, status): + """ + Creates a new group chat. + + This function creates a new group chat object and adds it to the chats array. + + The client should initiate its peer list with self info after calling this function, as + the peer_join callback will not be triggered. + + :param privacy_state: The privacy state of the group. If this is set to TOX_GROUP_PRIVACY_STATE_PUBLIC, + the group will attempt to announce itself to the DHT and anyone with the Chat ID may join. + Otherwise a friend invite will be required to join the group. + :param group_name: The name of the group. The name must be non-NULL. + + :return group number on success, UINT32_MAX on failure. + """ + + error = c_int() + peer_info = self.group_self_peer_info_new() + nick = bytes(nick, 'utf-8') + group_name = group_name.encode('utf-8') + peer_info.contents.nick = c_char_p(nick) + peer_info.contents.nick_length = len(nick) + peer_info.contents.user_status = status + result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, + len(group_name), peer_info, byref(error)) + return result + + def group_join(self, chat_id, password, nick, status): + """ + Joins a group chat with specified Chat ID. + + This function creates a new group chat object, adds it to the chats array, and sends + a DHT announcement to find peers in the group associated with chat_id. Once a peer has been + found a join attempt will be initiated. + + :param chat_id: The Chat ID of the group you wish to join. This must be TOX_GROUP_CHAT_ID_SIZE bytes. + :param password: The password required to join the group. Set to NULL if no password is required. + + :return group_number on success, UINT32_MAX on failure. + """ + + error = c_int() + peer_info = self.group_self_peer_info_new() + nick = bytes(nick, 'utf-8') + peer_info.contents.nick = c_char_p(nick) + peer_info.contents.nick_length = len(nick) + peer_info.contents.user_status = status + result = Tox.libtoxcore.tox_group_join(self._tox_pointer, string_to_bin(chat_id), + password, + len(password) if password is not None else 0, + peer_info, + byref(error)) + return result + + def group_reconnect(self, group_number): + """ + Reconnects to a group. + + This function disconnects from all peers in the group, then attempts to reconnect with the group. + The caller's state is not changed (i.e. name, status, role, chat public key etc.) + + :param group_number: The group number of the group we wish to reconnect to. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_reconnect(self._tox_pointer, group_number, byref(error)) + return result + + def group_is_connected(self, group_number): + error = c_int() + result = Tox.libtoxcore.tox_group_is_connected(self._tox_pointer, group_number, byref(error)) + return result + + def group_disconnect(self, group_number): + error = c_int() + result = Tox.libtoxcore.tox_group_disconnect(self._tox_pointer, group_number, byref(error)) + return result + + def group_leave(self, group_number, message=''): + """ + Leaves a group. + + This function sends a parting packet containing a custom (non-obligatory) message to all + peers in a group, and deletes the group from the chat array. All group state information is permanently + lost, including keys and role credentials. + + :param group_number: The group number of the group we wish to leave. + :param message: The parting message to be sent to all the peers. Set to NULL if we do not wish to + send a parting message. + + :return True if the group chat instance was successfully deleted. + """ + + error = c_int() + f = Tox.libtoxcore.tox_group_leave + f.restype = c_bool + result = f(self._tox_pointer, group_number, message, + len(message) if message is not None else 0, byref(error)) + return result + + # ----------------------------------------------------------------------------------------------------------------- + # Group user-visible client information (nickname/status/role/public key) + # ----------------------------------------------------------------------------------------------------------------- + + def group_self_set_name(self, group_number, name): + """ + Set the client's nickname for the group instance designated by the given group number. + + Nickname length cannot exceed TOX_MAX_NAME_LENGTH. If length is equal to zero or name is a NULL + pointer, the function call will fail. + + :param name: A byte array containing the new nickname. + + :return True on success. + """ + + error = c_int() + name = bytes(name, 'utf-8') + result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, group_number, name, len(name), byref(error)) + return result + + def group_self_get_name_size(self, group_number): + """ + Return the length of the client's current nickname for the group instance designated + by group_number as passed to tox_group_self_set_name. + + If no nickname was set before calling this function, the name is empty, + and this function returns 0. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_name_size(self._tox_pointer, group_number, byref(error)) + return result + + def group_self_get_name(self, group_number): + """ + Write the nickname set by tox_group_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_group_self_get_name_size to find out how much memory to allocate for the result. + :return nickname + """ + + error = c_int() + size = self.group_self_get_name_size(group_number) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_self_get_name(self._tox_pointer, group_number, name, byref(error)) + return str(name[:size], 'utf-8') + + def group_self_set_status(self, group_number, status): + + """ + Set the client's status for the group instance. Status must be a TOX_USER_STATUS. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_set_status(self._tox_pointer, group_number, status, byref(error)) + return result + + def group_self_get_status(self, group_number): + """ + returns the client's status for the group instance on success. + return value is unspecified on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_status(self._tox_pointer, group_number, byref(error)) + return result + + def group_self_get_role(self, group_number): + """ + returns the client's role for the group instance on success. + return value is unspecified on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_role(self._tox_pointer, group_number, byref(error)) + return result + + def group_self_get_peer_id(self, group_number): + """ + returns the client's peer id for the group instance on success. + return value is unspecified on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_self_get_peer_id(self._tox_pointer, group_number, byref(error)) + return result + + def group_self_get_public_key(self, group_number): + """ + Write the client's group public key designated by the given group number to a byte array. + + This key will be permanently tied to the client's identity for this particular group until + the client explicitly leaves the group or gets kicked/banned. This key is the only way for + other peers to reliably identify the client across client restarts. + + `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes. + + :return public key + """ + + error = c_int() + key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + result = Tox.libtoxcore.tox_group_self_get_public_key(self._tox_pointer, group_number, + key, byref(error)) + return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + + # ----------------------------------------------------------------------------------------------------------------- + # Peer-specific group state queries. + # ----------------------------------------------------------------------------------------------------------------- + + def group_peer_get_name_size(self, group_number, peer_id): + """ + Return the length of the peer's name. If the group number or ID is invalid, the + return value is unspecified. + + The return value is equal to the `length` argument received by the last + `group_peer_name` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_peer_get_name_size(self._tox_pointer, group_number, peer_id, byref(error)) + return result + + def group_peer_get_name(self, group_number, peer_id): + """ + Write the name of the peer designated by the given ID to a byte + array. + + Call tox_group_peer_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 + `group_peer_name` callback. + + :param group_number: The group number of the group we wish to query. + :param peer_id: The ID of the peer whose name we want to retrieve. + + :return name. + """ + error = c_int() + size = self.group_peer_get_name_size(group_number, peer_id) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_peer_get_name(self._tox_pointer, group_number, peer_id, name, byref(error)) + return str(name[:], 'utf-8') + + def group_peer_get_status(self, group_number, peer_id): + """ + Return the peer's user status (away/busy/...). If the ID or group number is + invalid, the return value is unspecified. + + The status returned is equal to the last status received through the + `group_peer_status` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_peer_get_status(self._tox_pointer, group_number, peer_id, byref(error)) + return result + + def group_peer_get_role(self, group_number, peer_id): + """ + Return the peer's role (user/moderator/founder...). If the ID or group number is + invalid, the return value is unspecified. + + The role returned is equal to the last role received through the + `group_moderation` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_peer_get_role(self._tox_pointer, group_number, peer_id, byref(error)) + return result + + def group_peer_get_public_key(self, group_number, peer_id): + """ + Write the group public key with the designated peer_id for the designated group number to public_key. + + This key will be permanently tied to a particular peer until they explicitly leave the group or + get kicked/banned, and is the only way to reliably identify the same peer across client restarts. + + `public_key` should have room for at least TOX_GROUP_PEER_PUBLIC_KEY_SIZE bytes. + + :return public key + """ + + error = c_int() + key = create_string_buffer(TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + result = Tox.libtoxcore.tox_group_peer_get_public_key(self._tox_pointer, group_number, peer_id, + key, byref(error)) + return bin_to_string(key, TOX_GROUP_PEER_PUBLIC_KEY_SIZE) + + def callback_group_peer_name(self, callback, user_data): + """ + Set the callback for the `group_peer_name` event. Pass NULL to unset. + This event is triggered when a peer changes their nickname. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_peer_name_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_name(self._tox_pointer, self.group_peer_name_cb, user_data) + + def callback_group_peer_status(self, callback, user_data): + """ + Set the callback for the `group_peer_status` event. Pass NULL to unset. + This event is triggered when a peer changes their status. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_void_p) + self.group_peer_status_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_status(self._tox_pointer, self.group_peer_status_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat state queries and events. + # ----------------------------------------------------------------------------------------------------------------- + + def group_set_topic(self, group_number, topic): + """ + Set the group topic and broadcast it to the rest of the group. + + topic length cannot be longer than TOX_GROUP_MAX_TOPIC_LENGTH. If length is equal to zero or + topic is set to NULL, the topic will be unset. + + :return True on success. + """ + + error = c_int() + topic = bytes(topic, 'utf-8') + result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, group_number, topic, len(topic), byref(error)) + return result + + def group_get_topic_size(self, group_number): + """ + Return the length of the group topic. If the group number is invalid, the + return value is unspecified. + + The return value is equal to the `length` argument received by the last + `group_topic` callback. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_topic_size(self._tox_pointer, group_number, byref(error)) + return result + + def group_get_topic(self, group_number): + """ + Write the topic designated by the given group number to a byte array. + Call tox_group_get_topic_size to determine the allocation size for the `topic` parameter. + The data written to `topic` is equal to the data received by the last + `group_topic` callback. + + :return topic + """ + + error = c_int() + size = self.group_get_topic_size(group_number) + topic = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_topic(self._tox_pointer, group_number, topic, byref(error)) + return str(topic[:size], 'utf-8') + + def group_get_name_size(self, group_number): + """ + Return the length of the group name. If the group number is invalid, the + return value is unspecified. + """ + error = c_int() + result = Tox.libtoxcore.tox_group_get_name_size(self._tox_pointer, group_number, byref(error)) + return int(result) + + def group_get_name(self, group_number): + """ + Write the name of the group designated by the given group number to a byte array. + Call tox_group_get_name_size to determine the allocation size for the `name` parameter. + :return true on success. + """ + + error = c_int() + size = self.group_get_name_size(group_number) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_name(self._tox_pointer, group_number, + name, byref(error)) + return str(name[:size], 'utf-8') + + def group_get_chat_id(self, group_number): + """ + Write the Chat ID designated by the given group number to a byte array. + `chat_id` should have room for at least TOX_GROUP_CHAT_ID_SIZE bytes. + :return chat id. + """ + + error = c_int() + buff = create_string_buffer(TOX_GROUP_CHAT_ID_SIZE) + result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, group_number, + buff, byref(error)) + return bin_to_string(buff, TOX_GROUP_CHAT_ID_SIZE) + + def group_get_number_groups(self): + """ + Return the number of groups in the Tox chats array. + """ + + result = Tox.libtoxcore.tox_group_get_number_groups(self._tox_pointer) + return result + + def groups_get_list(self): + groups_list_size = self.group_get_number_groups() + groups_list = create_string_buffer(sizeof(c_uint32) * groups_list_size) + groups_list = POINTER(c_uint32)(groups_list) + Tox.libtoxcore.tox_groups_get_list(self._tox_pointer, groups_list) + return groups_list[0:groups_list_size] + + def group_get_privacy_state(self, group_number): + """ + Return the privacy state of the group designated by the given group number. If group number + is invalid, the return value is unspecified. + + The value returned is equal to the data received by the last + `group_privacy_state` callback. + + see the `Group chat founder controls` section for the respective set function. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_privacy_state(self._tox_pointer, group_number, byref(error)) + return result + + def group_get_peer_limit(self, group_number): + """ + Return the maximum number of peers allowed for the group designated by the given group number. + If the group number is invalid, the return value is unspecified. + + The value returned is equal to the data received by the last + `group_peer_limit` callback. + + see the `Group chat founder controls` section for the respective set function. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_peer_limit(self._tox_pointer, group_number, byref(error)) + return result + + def group_get_password_size(self, group_number): + """ + Return the length of the group password. If the group number is invalid, the + return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_password_size(self._tox_pointer, group_number, byref(error)) + return result + + def group_get_password(self, group_number): + """ + Write the password for the group designated by the given group number to a byte array. + + Call tox_group_get_password_size to determine the allocation size for the `password` parameter. + + The data received is equal to the data received by the last + `group_password` callback. + + see the `Group chat founder controls` section for the respective set function. + + :return password + """ + + error = c_int() + size = self.group_get_password_size(group_number) + password = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_password(self._tox_pointer, group_number, + password, byref(error)) + return str(password[:size], 'utf-8') + + def callback_group_topic(self, callback, user_data): + """ + Set the callback for the `group_topic` event. Pass NULL to unset. + This event is triggered when a peer changes the group topic. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_topic_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_topic(self._tox_pointer, self.group_topic_cb, user_data) + + def callback_group_privacy_state(self, callback, user_data): + """ + Set the callback for the `group_privacy_state` event. Pass NULL to unset. + This event is triggered when the group founder changes the privacy state. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) + self.group_privacy_state_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_privacy_state(self._tox_pointer, self.group_privacy_state_cb, user_data) + + def callback_group_peer_limit(self, callback, user_data): + """ + Set the callback for the `group_peer_limit` event. Pass NULL to unset. + This event is triggered when the group founder changes the maximum peer limit. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p) + self.group_peer_limit_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_limit(self._tox_pointer, self.group_peer_limit_cb, user_data) + + def callback_group_password(self, callback, user_data): + """ + Set the callback for the `group_password` event. Pass NULL to unset. + This event is triggered when the group founder changes the group password. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_password_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_password(self._tox_pointer, self.group_password_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group message sending + # ----------------------------------------------------------------------------------------------------------------- + + def group_send_custom_packet(self, group_number, lossless, data): + """ + Send a custom packet to the group. + + If lossless is true the packet will be lossless. Lossless packet behaviour is comparable + to TCP (reliability, arrive in order) but with packets instead of a stream. + + If lossless is false, the packet will be lossy. Lossy packets behave like UDP packets, + meaning they might never reach the other side or might arrive more than once (if someone + is messing with the connection) or might arrive in the wrong order. + + Unless latency is an issue or message reliability is not important, it is recommended that you use + lossless custom packets. + + :param group_number: The group number of the group the message is intended for. + :param lossless: True if the packet should be lossless. + :param data A byte array containing the packet data. + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_send_custom_packet(self._tox_pointer, group_number, lossless, data, + len(data), byref(error)) + return result + + def group_send_private_message(self, group_number, peer_id, message_type, message): + """ + Send a text chat message to the specified peer in the specified group. + + This function creates a group private 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. + + :param group_number: The group number of the group the message is intended for. + :param peer_id: The ID of the peer the message is intended for. + :param message: A non-NULL pointer to the first element of a byte array containing the message text. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_send_private_message(self._tox_pointer, group_number, peer_id, + message_type, message, + len(message), byref(error)) + return result + + def group_send_message(self, group_number, type, message): + """ + Send a text chat message to the group. + + This function creates a group 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. + + :param group_number: The group number of the group the message is intended for. + :param type: Message type (normal, action, ...). + :param message: A non-NULL pointer to the first element of a byte array containing the message text. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_send_message(self._tox_pointer, group_number, type, message, len(message), + byref(error)) + return result + + # ----------------------------------------------------------------------------------------------------------------- + # Group message receiving + # ----------------------------------------------------------------------------------------------------------------- + + def callback_group_message(self, callback, user_data): + """ + Set the callback for the `group_message` event. Pass NULL to unset. + This event is triggered when the client receives a group message. + + Callback: python function with params: + tox Tox* instance + group_number The group number of the group the message is intended for. + peer_id The ID of the peer who sent the message. + type The type of message (normal, action, ...). + message The message data. + length The length of the message. + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_int, c_char_p, c_size_t, c_void_p) + self.group_message_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data) + + def callback_group_private_message(self, callback, user_data): + """ + Set the callback for the `group_private_message` event. Pass NULL to unset. + This event is triggered when the client receives a private message. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint8, c_char_p, c_size_t, c_void_p) + self.group_private_message_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_private_message(self._tox_pointer, self.group_private_message_cb, user_data) + + def callback_group_custom_packet(self, callback, user_data): + """ + Set the callback for the `group_custom_packet` event. Pass NULL to unset. + + This event is triggered when the client receives a custom packet. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, POINTER(c_uint8), c_void_p) + self.group_custom_packet_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_custom_packet(self._tox_pointer, self.group_custom_packet_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat inviting and join/part events + # ----------------------------------------------------------------------------------------------------------------- + + def group_invite_friend(self, group_number, friend_number): + """ + Invite a friend to a group. + + This function creates an invite request packet and pushes it to the send queue. + + :param group_number: The group number of the group the message is intended for. + :param friend_number: The friend number of the friend the invite is intended for. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, group_number, friend_number, byref(error)) + return result + + @staticmethod + def group_self_peer_info_new(): + error = c_int() + f = Tox.libtoxcore.tox_group_self_peer_info_new + f.restype = POINTER(GroupChatSelfPeerInfo) + result = f(byref(error)) + + return result + + def group_invite_accept(self, invite_data, friend_number, nick, status, password=None): + """ + Accept an invite to a group chat that the client previously received from a friend. The invite + is only valid while the inviter is present in the group. + + :param invite_data: The invite data received from the `group_invite` event. + :param password: The password required to join the group. Set to NULL if no password is required. + :return the group_number on success, UINT32_MAX on failure. + """ + + error = c_int() + f = Tox.libtoxcore.tox_group_invite_accept + f.restype = c_uint32 + peer_info = self.group_self_peer_info_new() + nick = bytes(nick, 'utf-8') + peer_info.contents.nick = c_char_p(nick) + peer_info.contents.nick_length = len(nick) + peer_info.contents.user_status = status + result = f(self._tox_pointer, friend_number, invite_data, len(invite_data), password, + len(password) if password is not None else 0, peer_info, byref(error)) + print('Invite accept. Result:', result, 'Error:', error.value) + return result + + def callback_group_invite(self, callback, user_data): + """ + Set the callback for the `group_invite` event. Pass NULL to unset. + + This event is triggered when the client receives a group invite from a friend. The client must store + invite_data which is used to join the group via tox_group_invite_accept. + + Callback: python function with params: + tox - Tox* + friend_number The friend number of the contact who sent the invite. + invite_data The invite data. + length The length of invite_data. + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, POINTER(c_uint8), c_size_t, + POINTER(c_uint8), c_size_t, c_void_p) + self.group_invite_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data) + + def callback_group_peer_join(self, callback, user_data): + """ + Set the callback for the `group_peer_join` event. Pass NULL to unset. + + This event is triggered when a peer other than self joins the group. + Callback: python function with params: + tox - Tox* + group_number - group number + peer_id - peer id + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_void_p) + self.group_peer_join_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_join(self._tox_pointer, self.group_peer_join_cb, user_data) + + def callback_group_peer_exit(self, callback, user_data): + """ + Set the callback for the `group_peer_exit` event. Pass NULL to unset. + + This event is triggered when a peer other than self exits the group. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_char_p, c_size_t, c_void_p) + self.group_peer_exit_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_peer_exit(self._tox_pointer, self.group_peer_exit_cb, user_data) + + def callback_group_self_join(self, callback, user_data): + """ + Set the callback for the `group_self_join` event. Pass NULL to unset. + + This event is triggered when the client has successfully joined a group. Use this to initialize + any group information the client may need. + Callback: python fucntion with params: + tox - *Tox + group_number - group number + user_data - user data + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_void_p) + self.group_self_join_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_self_join(self._tox_pointer, self.group_self_join_cb, user_data) + + def callback_group_join_fail(self, callback, user_data): + """ + Set the callback for the `group_join_fail` event. Pass NULL to unset. + + This event is triggered when the client fails to join a group. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_int, c_void_p) + self.group_join_fail_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_join_fail(self._tox_pointer, self.group_join_fail_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat founder controls (these only work for the group founder) + # ----------------------------------------------------------------------------------------------------------------- + + def group_founder_set_password(self, group_number, password): + """ + Set or unset the group password. + + This function sets the groups password, creates a new group shared state including the change, + and distributes it to the rest of the group. + + :param group_number: The group number of the group for which we wish to set the password. + :param password: The password we want to set. Set password to NULL to unset the password. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_founder_set_password(self._tox_pointer, group_number, password, + len(password), byref(error)) + return result + + def group_founder_set_privacy_state(self, group_number, privacy_state): + """ + Set the group privacy state. + + This function sets the group's privacy state, creates a new group shared state + including the change, and distributes it to the rest of the group. + + If an attempt is made to set the privacy state to the same state that the group is already + in, the function call will be successful and no action will be taken. + + :param group_number: The group number of the group for which we wish to change the privacy state. + :param privacy_state: The privacy state we wish to set the group to. + + :return true on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_founder_set_privacy_state(self._tox_pointer, group_number, privacy_state, + byref(error)) + return result + + def group_founder_set_peer_limit(self, group_number, max_peers): + """ + Set the group peer limit. + + This function sets a limit for the number of peers who may be in the group, creates a new + group shared state including the change, and distributes it to the rest of the group. + + :param group_number: The group number of the group for which we wish to set the peer limit. + :param max_peers: The maximum number of peers to allow in the group. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_founder_set_peer_limit(self._tox_pointer, group_number, + max_peers, byref(error)) + return result + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat moderation + # ----------------------------------------------------------------------------------------------------------------- + + def group_toggle_ignore(self, group_number, peer_id, ignore): + """ + Ignore or unignore a peer. + + :param group_number: The group number of the group the in which you wish to ignore a peer. + :param peer_id: The ID of the peer who shall be ignored or unignored. + :param ignore: True to ignore the peer, false to unignore the peer. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_toggle_ignore(self._tox_pointer, group_number, peer_id, ignore, byref(error)) + return result + + def group_mod_set_role(self, group_number, peer_id, role): + """ + Set a peer's role. + + This function will first remove the peer's previous role and then assign them a new role. + It will also send a packet to the rest of the group, requesting that they perform + the role reassignment. Note: peers cannot be set to the founder role. + + :param group_number: The group number of the group the in which you wish set the peer's role. + :param peer_id: The ID of the peer whose role you wish to set. + :param role: The role you wish to set the peer to. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_set_role(self._tox_pointer, group_number, peer_id, role, byref(error)) + return result + + def group_mod_remove_peer(self, group_number, peer_id): + """ + Kick/ban a peer. + + This function will remove a peer from the caller's peer list and optionally add their IP address + to the ban list. It will also send a packet to all group members requesting them + to do the same. + + :param group_number: The group number of the group the ban is intended for. + :param peer_id: The ID of the peer who will be kicked and/or added to the ban list. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, group_number, peer_id, + byref(error)) + return result + + def group_mod_ban_peer(self, group_number, peer_id, ban_type): + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_ban_peer(self._tox_pointer, group_number, peer_id, + ban_type, byref(error)) + return result + + def group_mod_remove_ban(self, group_number, ban_id): + """ + Removes a ban. + + This function removes a ban entry from the ban list, and sends a packet to the rest of + the group requesting that they do the same. + + :param group_number: The group number of the group in which the ban is to be removed. + :param ban_id: The ID of the ban entry that shall be removed. + + :return True on success + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_remove_ban(self._tox_pointer, group_number, ban_id, byref(error)) + return result + + def callback_group_moderation(self, callback, user_data): + """ + Set the callback for the `group_moderation` event. Pass NULL to unset. + + This event is triggered when a moderator or founder executes a moderation event. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint32, c_int, c_void_p) + self.group_moderation_cb = c_callback(callback) + Tox.libtoxcore.tox_callback_group_moderation(self._tox_pointer, self.group_moderation_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat ban list queries + # ----------------------------------------------------------------------------------------------------------------- + + def group_ban_get_list_size(self, group_number): + """ + Return the number of entries in the ban list for the group designated by + the given group number. If the group number is invalid, the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_list_size(self._tox_pointer, group_number, byref(error)) + return result + + def group_ban_get_list(self, group_number): + """ + Copy a list of valid ban list ID's into an array. + + Call tox_group_ban_get_list_size to determine the number of elements to allocate. + return true on success. + """ + + error = c_int() + bans_list_size = self.group_ban_get_list_size(group_number) + bans_list = create_string_buffer(sizeof(c_uint32) * bans_list_size) + bans_list = POINTER(c_uint32)(bans_list) + result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, group_number, bans_list, byref(error)) + return bans_list[:bans_list_size] + + def group_ban_get_type(self, group_number, ban_id): + """ + Return the type for the ban list entry designated by ban_id, in the + group designated by the given group number. If either group_number or ban_id is invalid, + the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_type(self._tox_pointer, group_number, ban_id, byref(error)) + return result + + def group_ban_get_target_size(self, group_number, ban_id): + """ + Return the length of the name for the ban list entry designated by ban_id, in the + group designated by the given group number. If either group_number or ban_id is invalid, + the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_target_size(self._tox_pointer, group_number, ban_id, byref(error)) + return result + + def group_ban_get_target(self, group_number, ban_id): + """ + Write the name of the ban entry designated by ban_id in the group designated by the + given group number to a byte array. + + Call tox_group_ban_get_name_size to find out how much memory to allocate for the result. + + :return name + """ + + error = c_int() + size = self.group_ban_get_target_size(group_number, ban_id) + target = create_string_buffer(size) + target_type = self.group_ban_get_type(group_number, ban_id) + + result = Tox.libtoxcore.tox_group_ban_get_target(self._tox_pointer, group_number, ban_id, + target, byref(error)) + if target_type == TOX_GROUP_BAN_TYPE['PUBLIC_KEY']: + return bin_to_string(target, size) + return str(target[:size], 'utf-8') + + def group_ban_get_time_set(self, group_number, ban_id): + """ + Return a time stamp indicating the time the ban was set, for the ban list entry + designated by ban_id, in the group designated by the given group number. + If either group_number or ban_id is invalid, the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_time_set(self._tox_pointer, group_number, ban_id, byref(error)) + return result diff --git a/toxygen/wrapper/toxav.py b/toxygen/wrapper/toxav.py new file mode 100644 index 0000000..98e1c73 --- /dev/null +++ b/toxygen/wrapper/toxav.py @@ -0,0 +1,363 @@ +from ctypes import c_int, POINTER, c_void_p, byref, ArgumentError, c_uint32, CFUNCTYPE, c_size_t, c_uint8, c_uint16 +from ctypes import c_char_p, c_int32, c_bool, cast +from wrapper.libtox import LibToxAV +from wrapper.toxav_enums import * + + +class ToxAV: + """ + The ToxAV instance type. Each ToxAV instance can be bound to only one Tox instance, and Tox instance can have only + one ToxAV instance. One must make sure to close ToxAV instance prior closing Tox instance otherwise undefined + behaviour occurs. Upon closing of ToxAV instance, all active calls will be forcibly terminated without notifying + peers. + """ + + # ----------------------------------------------------------------------------------------------------------------- + # Creation and destruction + # ----------------------------------------------------------------------------------------------------------------- + + def __init__(self, tox_pointer): + """ + Start new A/V session. There can only be only one session per Tox instance. + + :param tox_pointer: pointer to Tox instance + """ + self.libtoxav = LibToxAV() + toxav_err_new = c_int() + f = self.libtoxav.toxav_new + f.restype = POINTER(c_void_p) + self._toxav_pointer = f(tox_pointer, byref(toxav_err_new)) + toxav_err_new = toxav_err_new.value + if toxav_err_new == TOXAV_ERR_NEW['NULL']: + raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') + elif toxav_err_new == TOXAV_ERR_NEW['MALLOC']: + raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V ' + 'session.') + elif toxav_err_new == TOXAV_ERR_NEW['MULTIPLE']: + raise RuntimeError('Attempted to create a second session for the same Tox instance.') + + self.call_state_cb = None + self.audio_receive_frame_cb = None + self.video_receive_frame_cb = None + self.call_cb = None + + def kill(self): + """ + Releases all resources associated with the A/V session. + + If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this + function, no other functions may be called and the av pointer becomes invalid. + """ + self.libtoxav.toxav_kill(self._toxav_pointer) + + def get_tox_pointer(self): + """ + Returns the Tox instance the A/V object was created for. + + :return: pointer to the Tox instance + """ + self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p) + return self.libtoxav.toxav_get_tox(self._toxav_pointer) + + # ----------------------------------------------------------------------------------------------------------------- + # A/V event loop + # ----------------------------------------------------------------------------------------------------------------- + + def iteration_interval(self): + """ + Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the + moment, this function returns 200. + + :return: interval in milliseconds + """ + return self.libtoxav.toxav_iteration_interval(self._toxav_pointer) + + def iterate(self): + """ + Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval() + milliseconds. It is best called in the separate thread from tox_iterate. + """ + self.libtoxav.toxav_iterate(self._toxav_pointer) + + # ----------------------------------------------------------------------------------------------------------------- + # Call setup + # ----------------------------------------------------------------------------------------------------------------- + + def call(self, friend_number, audio_bit_rate, video_bit_rate): + """ + Call a friend. This will start ringing the friend. + + It is the client's responsibility to stop ringing after a certain timeout, if such behaviour is desired. If the + client does not stop ringing, the library will not stop until the friend is disconnected. Audio and video + receiving are both enabled by default. + + :param friend_number: The friend number of the friend that should be called. + :param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending. + :param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending. + :return: True on success. + """ + toxav_err_call = c_int() + result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate), + c_uint32(video_bit_rate), byref(toxav_err_call)) + toxav_err_call = toxav_err_call.value + if toxav_err_call == TOXAV_ERR_CALL['OK']: + return bool(result) + elif toxav_err_call == TOXAV_ERR_CALL['MALLOC']: + raise MemoryError('A resource allocation error occurred while trying to create the structures required for ' + 'the call.') + elif toxav_err_call == TOXAV_ERR_CALL['SYNC']: + raise RuntimeError('Synchronization error occurred.') + elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend number did not designate a valid friend.') + elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']: + raise ArgumentError('The friend was valid, but not currently connected.') + elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']: + raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.') + elif toxav_err_call == TOXAV_ERR_CALL['INVALID_BIT_RATE']: + raise ArgumentError('Audio or video bit rate is invalid.') + + def callback_call(self, callback, user_data): + """ + Set the callback for the `call` event. Pass None to unset. + + :param callback: The function for the call callback. + + Should take pointer (c_void_p) to ToxAV object, + The friend number (c_uint32) from which the call is incoming. + True (c_bool) if friend is sending audio. + True (c_bool) if friend is sending video. + 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_bool, c_void_p) + self.call_cb = c_callback(callback) + self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data) + + def answer(self, friend_number, audio_bit_rate, video_bit_rate): + """ + Accept an incoming call. + + If answering fails for any reason, the call will still be pending and it is possible to try and answer it later. + Audio and video receiving are both enabled by default. + + :param friend_number: The friend number of the friend that is calling. + :param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending. + :param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending. + :return: True on success. + """ + toxav_err_answer = c_int() + result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate), + c_uint32(video_bit_rate), byref(toxav_err_answer)) + toxav_err_answer = toxav_err_answer.value + if toxav_err_answer == TOXAV_ERR_ANSWER['OK']: + return bool(result) + elif toxav_err_answer == TOXAV_ERR_ANSWER['SYNC']: + raise RuntimeError('Synchronization error occurred.') + elif toxav_err_answer == TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']: + raise RuntimeError('Failed to initialize codecs for call session. Note that codec initiation will fail if ' + 'there is no receive callback registered for either audio or video.') + elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend number did not designate a valid friend.') + elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']: + raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is ' + 'also returned if this client is already in a call with the friend.') + elif toxav_err_answer == TOXAV_ERR_ANSWER['INVALID_BIT_RATE']: + raise ArgumentError('Audio or video bit rate is invalid.') + + # ----------------------------------------------------------------------------------------------------------------- + # Call state graph + # ----------------------------------------------------------------------------------------------------------------- + + def callback_call_state(self, callback, user_data): + """ + Set the callback for the `call_state` event. Pass None to unset. + + :param callback: Python function. + The function for the call_state callback. + + Should take pointer (c_void_p) to ToxAV object, + The friend number (c_uint32) for which the call state changed. + The bitmask of the new call state which is guaranteed to be different than the previous state. The state is set + to 0 when the call is paused. The bitmask represents all the activities currently performed by the friend. + 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.call_state_cb = c_callback(callback) + self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data) + + # ----------------------------------------------------------------------------------------------------------------- + # Call control + # ----------------------------------------------------------------------------------------------------------------- + + def call_control(self, friend_number, control): + """ + Sends a call control command to a friend. + + :param friend_number: The friend number of the friend this client is in a call with. + :param control: The control command to send. + :return: True on success. + """ + toxav_err_call_control = c_int() + result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control), + byref(toxav_err_call_control)) + toxav_err_call_control = toxav_err_call_control.value + if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']: + return bool(result) + elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['SYNC']: + raise RuntimeError('Synchronization error occurred.') + elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number passed did not designate a valid friend.') + elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']: + raise RuntimeError('This client is currently not in a call with the friend. Before the call is answered, ' + 'only CANCEL is a valid control.') + elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']: + raise RuntimeError('Happens if user tried to pause an already paused call or if trying to resume a call ' + 'that is not paused.') + + # ----------------------------------------------------------------------------------------------------------------- + # TODO Controlling bit rates + # ----------------------------------------------------------------------------------------------------------------- + + # ----------------------------------------------------------------------------------------------------------------- + # A/V sending + # ----------------------------------------------------------------------------------------------------------------- + + def audio_send_frame(self, friend_number, pcm, sample_count, channels, sampling_rate): + """ + Send an audio frame to a friend. + + The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... + Meaning: sample 1 for channel 1, sample 1 for channel 2, ... + For mono audio, this has no meaning, every sample is subsequent. For stereo, this means the expected format is + LRLRLR... with samples for left and right alternating. + + :param friend_number: The friend number of the friend to which to send an audio frame. + :param pcm: An array of audio samples. The size of this array must be sample_count * channels. + :param sample_count: Number of samples in this frame. Valid numbers here are + ((sample rate) * (audio length) / 1000), where audio length can be 2.5, 5, 10, 20, 40 or 60 milliseconds. + :param channels: Number of audio channels. Sulpported values are 1 and 2. + :param sampling_rate: Audio sampling rate used in this frame. Valid sampling rates are 8000, 12000, 16000, + 24000, or 48000. + """ + toxav_err_send_frame = c_int() + result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number), + cast(pcm, c_void_p), + c_size_t(sample_count), c_uint8(channels), + c_uint32(sampling_rate), byref(toxav_err_send_frame)) + toxav_err_send_frame = toxav_err_send_frame.value + if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']: + return bool(result) + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']: + raise ArgumentError('The samples data pointer was NULL.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number passed did not designate a valid friend.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']: + raise RuntimeError('This client is currently not in a call with the friend.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']: + raise RuntimeError('Synchronization error occurred.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']: + raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too ' + 'large, or the audio sampling rate may be unsupported.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']: + raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said' + 'payload.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']: + RuntimeError('Failed to push frame through rtp interface.') + + def video_send_frame(self, friend_number, width, height, y, u, v): + """ + Send a video frame to a friend. + + Y - plane should be of size: height * width + U - plane should be of size: (height/2) * (width/2) + V - plane should be of size: (height/2) * (width/2) + + :param friend_number: The friend number of the friend to which to send a video frame. + :param width: Width of the frame in pixels. + :param height: Height of the frame in pixels. + :param y: Y (Luminance) plane data. + :param u: U (Chroma) plane data. + :param v: V (Chroma) plane data. + """ + toxav_err_send_frame = c_int() + result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width), + c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v), + byref(toxav_err_send_frame)) + toxav_err_send_frame = toxav_err_send_frame.value + if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']: + return bool(result) + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']: + raise ArgumentError('One of Y, U, or V was NULL.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']: + raise ArgumentError('The friend_number passed did not designate a valid friend.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']: + raise RuntimeError('This client is currently not in a call with the friend.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']: + raise RuntimeError('Synchronization error occurred.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']: + raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too ' + 'large, or the audio sampling rate may be unsupported.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']: + raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said' + 'payload.') + elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']: + RuntimeError('Failed to push frame through rtp interface.') + + # ----------------------------------------------------------------------------------------------------------------- + # A/V receiving + # ----------------------------------------------------------------------------------------------------------------- + + def callback_audio_receive_frame(self, callback, user_data): + """ + Set the callback for the `audio_receive_frame` event. Pass None to unset. + + :param callback: Python function. + Function for the audio_receive_frame callback. The callback can be called multiple times per single + iteration depending on the amount of queued frames in the buffer. The received format is the same as in send + function. + + Should take pointer (c_void_p) to ToxAV object, + The friend number (c_uint32) of the friend who sent an audio frame. + An array (c_uint8) of audio samples (sample_count * channels elements). + The number (c_size_t) of audio samples per channel in the PCM array. + Number (c_uint8) of audio channels. + Sampling rate (c_uint32) used in this frame. + 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, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p) + self.audio_receive_frame_cb = c_callback(callback) + self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data) + + def callback_video_receive_frame(self, callback, user_data): + """ + Set the callback for the `video_receive_frame` event. Pass None to unset. + + :param callback: Python function. + The function type for the video_receive_frame callback. + + Should take + toxAV pointer (c_void_p) to ToxAV object, + friend_number The friend number (c_uint32) of the friend who sent a video frame. + width Width (c_uint16) of the frame in pixels. + height Height (c_uint16) of the frame in pixels. + y + u + v Plane data (POINTER(c_uint8)). + The size of plane data is derived from width and height where + Y = MAX(width, abs(ystride)) * height, + U = MAX(width/2, abs(ustride)) * (height/2) and + V = MAX(width/2, abs(vstride)) * (height/2). + ystride + ustride + vstride Strides data (c_int32). Strides represent padding for each plane that may or may not be present. You must + handle strides in your image processing code. Strides are negative if the image is bottom-up + hence why you MUST abs() it when calculating plane buffer size. + user_data 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_uint16, c_uint16, POINTER(c_uint8), POINTER(c_uint8), + POINTER(c_uint8), c_int32, c_int32, c_int32, c_void_p) + self.video_receive_frame_cb = c_callback(callback) + self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data) diff --git a/toxygen/wrapper/toxav_enums.py b/toxygen/wrapper/toxav_enums.py new file mode 100644 index 0000000..3f3977a --- /dev/null +++ b/toxygen/wrapper/toxav_enums.py @@ -0,0 +1,131 @@ +TOXAV_ERR_NEW = { + # The function returned successfully. + 'OK': 0, + # One of the arguments to the function was NULL when it was not expected. + 'NULL': 1, + # Memory allocation failure while trying to allocate structures required for the A/V session. + 'MALLOC': 2, + # Attempted to create a second session for the same Tox instance. + 'MULTIPLE': 3, +} + +TOXAV_ERR_CALL = { + # The function returned successfully. + 'OK': 0, + # A resource allocation error occurred while trying to create the structures required for the call. + 'MALLOC': 1, + # Synchronization error occurred. + 'SYNC': 2, + # The friend number did not designate a valid friend. + 'FRIEND_NOT_FOUND': 3, + # The friend was valid, but not currently connected. + 'FRIEND_NOT_CONNECTED': 4, + # Attempted to call a friend while already in an audio or video call with them. + 'FRIEND_ALREADY_IN_CALL': 5, + # Audio or video bit rate is invalid. + 'INVALID_BIT_RATE': 6, +} + +TOXAV_ERR_ANSWER = { + # The function returned successfully. + 'OK': 0, + # Synchronization error occurred. + 'SYNC': 1, + # Failed to initialize codecs for call session. Note that codec initiation will fail if there is no receive callback + # registered for either audio or video. + 'CODEC_INITIALIZATION': 2, + # The friend number did not designate a valid friend. + 'FRIEND_NOT_FOUND': 3, + # The friend was valid, but they are not currently trying to initiate a call. This is also returned if this client + # is already in a call with the friend. + 'FRIEND_NOT_CALLING': 4, + # Audio or video bit rate is invalid. + 'INVALID_BIT_RATE': 5, +} + +TOXAV_FRIEND_CALL_STATE = { + # Set by the AV core if an error occurred on the remote end or if friend timed out. This is the final state after + # which no more state transitions can occur for the call. This call state will never be triggered in combination + # with other call states. + 'ERROR': 1, + # The call has finished. This is the final state after which no more state transitions can occur for the call. This + # call state will never be triggered in combination with other call states. + 'FINISHED': 2, + # The flag that marks that friend is sending audio. + 'SENDING_A': 4, + # The flag that marks that friend is sending video. + 'SENDING_V': 8, + # The flag that marks that friend is receiving audio. + 'ACCEPTING_A': 16, + # The flag that marks that friend is receiving video. + 'ACCEPTING_V': 32, +} + +TOXAV_CALL_CONTROL = { + # Resume a previously paused call. Only valid if the pause was caused by this client, if not, this control is + # ignored. Not valid before the call is accepted. + 'RESUME': 0, + # Put a call on hold. Not valid before the call is accepted. + 'PAUSE': 1, + # Reject a call if it was not answered, yet. Cancel a call after it was answered. + 'CANCEL': 2, + # Request that the friend stops sending audio. Regardless of the friend's compliance, this will cause the + # audio_receive_frame event to stop being triggered on receiving an audio frame from the friend. + 'MUTE_AUDIO': 3, + # Calling this control will notify client to start sending audio again. + 'UNMUTE_AUDIO': 4, + # Request that the friend stops sending video. Regardless of the friend's compliance, this will cause the + # video_receive_frame event to stop being triggered on receiving a video frame from the friend. + 'HIDE_VIDEO': 5, + # Calling this control will notify client to start sending video again. + 'SHOW_VIDEO': 6, +} + +TOXAV_ERR_CALL_CONTROL = { + # The function returned successfully. + 'OK': 0, + # Synchronization error occurred. + 'SYNC': 1, + # The friend_number passed did not designate a valid friend. + 'FRIEND_NOT_FOUND': 2, + # This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid + # control. + 'FRIEND_NOT_IN_CALL': 3, + # Happens if user tried to pause an already paused call or if trying to resume a call that is not paused. + 'INVALID_TRANSITION': 4, +} + +TOXAV_ERR_BIT_RATE_SET = { + # The function returned successfully. + 'OK': 0, + # Synchronization error occurred. + 'SYNC': 1, + # The audio bit rate passed was not one of the supported values. + 'INVALID_AUDIO_BIT_RATE': 2, + # The video bit rate passed was not one of the supported values. + 'INVALID_VIDEO_BIT_RATE': 3, + # The friend_number passed did not designate a valid friend. + 'FRIEND_NOT_FOUND': 4, + # This client is currently not in a call with the friend. + 'FRIEND_NOT_IN_CALL': 5, +} + +TOXAV_ERR_SEND_FRAME = { + # The function returned successfully. + 'OK': 0, + # In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL. + 'NULL': 1, + # The friend_number passed did not designate a valid friend. + 'FRIEND_NOT_FOUND': 2, + # This client is currently not in a call with the friend. + 'FRIEND_NOT_IN_CALL': 3, + # Synchronization error occurred. + 'SYNC': 4, + # One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling + # rate may be unsupported. + 'INVALID': 5, + # Either friend turned off audio or video receiving or we turned off sending for the said payload. + 'PAYLOAD_TYPE_DISABLED': 6, + # Failed to push frame through rtp interface. + 'RTP_FAILED': 7, +} diff --git a/toxygen/wrapper/toxcore_enums_and_consts.py b/toxygen/wrapper/toxcore_enums_and_consts.py new file mode 100644 index 0000000..b34e272 --- /dev/null +++ b/toxygen/wrapper/toxcore_enums_and_consts.py @@ -0,0 +1,954 @@ +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_GROUP_PRIVACY_STATE = { + + # + # The group is considered to be public. Anyone may join the group using the Chat ID. + # + # If the group is in this state, even if the Chat ID is never explicitly shared + # with someone outside of the group, information including the Chat ID, IP addresses, + # and peer ID's (but not Tox ID's) is visible to anyone with access to a node + # storing a DHT entry for the given group. + # + 'PUBLIC': 0, + + # + # The group is considered to be private. The only way to join the group is by having + # someone in your contact list send you an invite. + # + # If the group is in this state, no group information (mentioned above) is present in the DHT; + # the DHT is not used for any purpose at all. If a public group is set to private, + # all DHT information related to the group will expire shortly. + # + 'PRIVATE': 1 +} + +TOX_GROUP_ROLE = { + + # + # May kick and ban all other peers as well as set their role to anything (except founder). + # Founders may also set the group password, toggle the privacy state, and set the peer limit. + # + 'FOUNDER': 0, + + # + # May kick, ban and set the user and observer roles for peers below this role. + # May also set the group topic. + # + 'MODERATOR': 1, + + # + # May communicate with other peers normally. + # + 'USER': 2, + + # + # May observe the group and ignore peers; may not communicate with other peers or with the group. + # + 'OBSERVER': 3 +} + +TOX_ERR_GROUP_NEW = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_NEW_OK': 0, + + # + # The group name exceeded TOX_GROUP_MAX_GROUP_NAME_LENGTH. + # + 'TOX_ERR_GROUP_NEW_TOO_LONG': 1, + + # + # group_name is NULL or length is zero. + # + 'TOX_ERR_GROUP_NEW_EMPTY': 2, + + # + # TOX_GROUP_PRIVACY_STATE is an invalid type. + # + 'TOX_ERR_GROUP_NEW_PRIVACY': 3, + + # + # The group instance failed to initialize. + # + 'TOX_ERR_GROUP_NEW_INIT': 4, + + # + # The group state failed to initialize. This usually indicates that something went wrong + # related to cryptographic signing. + # + 'TOX_ERR_GROUP_NEW_STATE': 5, + + # + # The group failed to announce to the DHT. This indicates a network related error. + # + 'TOX_ERR_GROUP_NEW_ANNOUNCE': 6, +} + +TOX_ERR_GROUP_JOIN = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_JOIN_OK': 0, + + # + # The group instance failed to initialize. + # + 'TOX_ERR_GROUP_JOIN_INIT': 1, + + # + # The chat_id pointer is set to NULL or a group with chat_id already exists. This usually + # happens if the client attempts to create multiple sessions for the same group. + # + 'TOX_ERR_GROUP_JOIN_BAD_CHAT_ID': 2, + + # + # Password length exceeded TOX_GROUP_MAX_PASSWORD_SIZE. + # + 'TOX_ERR_GROUP_JOIN_TOO_LONG': 3, +} + +TOX_ERR_GROUP_RECONNECT = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_RECONNECT_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_RECONNECT_GROUP_NOT_FOUND': 1, +} + +TOX_ERR_GROUP_LEAVE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_LEAVE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_LEAVE_GROUP_NOT_FOUND': 1, + + # + # Message length exceeded 'TOX_GROUP_MAX_PART_LENGTH. + # + 'TOX_ERR_GROUP_LEAVE_TOO_LONG': 2, + + # + # The parting packet failed to send. + # + 'TOX_ERR_GROUP_LEAVE_FAIL_SEND': 3, + + # + # The group chat instance failed to be deleted. This may occur due to memory related errors. + # + 'TOX_ERR_GROUP_LEAVE_DELETE_FAIL': 4, +} + +TOX_ERR_GROUP_SELF_QUERY = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SELF_QUERY_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SELF_QUERY_GROUP_NOT_FOUND': 1, +} + + +TOX_ERR_GROUP_SELF_NAME_SET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_GROUP_NOT_FOUND': 1, + + # + # Name length exceeded 'TOX_MAX_NAME_LENGTH. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_TOO_LONG': 2, + + # + # The length given to the set function is zero or name is a NULL pointer. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_INVALID': 3, + + # + # The name is already taken by another peer in the group. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_TAKEN': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_SELF_NAME_SET_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_SELF_STATUS_SET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_GROUP_NOT_FOUND': 1, + + # + # An invalid type was passed to the set function. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_INVALID': 2, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_SELF_STATUS_SET_FAIL_SEND': 3 +} + +TOX_ERR_GROUP_PEER_QUERY = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_PEER_QUERY_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_PEER_QUERY_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_PEER_QUERY_PEER_NOT_FOUND': 2 +} + +TOX_ERR_GROUP_STATE_QUERIES = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_STATE_QUERIES_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_STATE_QUERIES_GROUP_NOT_FOUND': 1 +} + + +TOX_ERR_GROUP_TOPIC_SET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_TOPIC_SET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_TOPIC_SET_GROUP_NOT_FOUND': 1, + + # + # Topic length exceeded 'TOX_GROUP_MAX_TOPIC_LENGTH. + # + 'TOX_ERR_GROUP_TOPIC_SET_TOO_LONG': 2, + + # + # The caller does not have the required permissions to set the topic. + # + 'TOX_ERR_GROUP_TOPIC_SET_PERMISSIONS': 3, + + # + # The packet could not be created. This error is usually related to cryptographic signing. + # + 'TOX_ERR_GROUP_TOPIC_SET_FAIL_CREATE': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_TOPIC_SET_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_SEND_MESSAGE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_GROUP_NOT_FOUND': 1, + + # + # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_TOO_LONG': 2, + + # + # The message pointer is null or length is zero. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_EMPTY': 3, + + # + # The message type is invalid. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_BAD_TYPE': 4, + + # + # The caller does not have the required permissions to send group messages. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_PERMISSIONS': 5, + + # + # Packet failed to send. + # + 'TOX_ERR_GROUP_SEND_MESSAGE_FAIL_SEND': 6 +} + +TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PEER_NOT_FOUND': 2, + + # + # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_TOO_LONG': 3, + + # + # The message pointer is null or length is zero. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_EMPTY': 4, + + # + # The caller does not have the required permissions to send group messages. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_PERMISSIONS': 5, + + # + # Packet failed to send. + # + 'TOX_ERR_GROUP_SEND_PRIVATE_MESSAGE_FAIL_SEND': 6 +} + +TOX_ERR_GROUP_SEND_CUSTOM_PACKET = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_GROUP_NOT_FOUND': 1, + + # + # Message length exceeded 'TOX_MAX_MESSAGE_LENGTH. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_TOO_LONG': 2, + + # + # The message pointer is null or length is zero. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_EMPTY': 3, + + # + # The caller does not have the required permissions to send group messages. + # + 'TOX_ERR_GROUP_SEND_CUSTOM_PACKET_PERMISSIONS': 4 +} + +TOX_ERR_GROUP_INVITE_FRIEND = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_GROUP_NOT_FOUND': 1, + + # + # The friend number passed did not designate a valid friend. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_FRIEND_NOT_FOUND': 2, + + # + # Creation of the invite packet failed. This indicates a network related error. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_INVITE_FAIL': 3, + + # + # Packet failed to send. + # + 'TOX_ERR_GROUP_INVITE_FRIEND_FAIL_SEND': 4 +} + +TOX_ERR_GROUP_INVITE_ACCEPT = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_OK': 0, + + # + # The invite data is not in the expected format. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_BAD_INVITE': 1, + + # + # The group instance failed to initialize. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_INIT_FAILED': 2, + + # + # Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE. + # + 'TOX_ERR_GROUP_INVITE_ACCEPT_TOO_LONG': 3 +} + +TOX_GROUP_JOIN_FAIL = { + + # + # You are using the same nickname as someone who is already in the group. + # + 'TOX_GROUP_JOIN_FAIL_NAME_TAKEN': 0, + + # + # The group peer limit has been reached. + # + 'TOX_GROUP_JOIN_FAIL_PEER_LIMIT': 1, + + # + # You have supplied an invalid password. + # + 'TOX_GROUP_JOIN_FAIL_INVALID_PASSWORD': 2, + + # + # The join attempt failed due to an unspecified error. This often occurs when the group is + # not found in the DHT. + # + 'TOX_GROUP_JOIN_FAIL_UNKNOWN': 3 +} + +TOX_ERR_GROUP_FOUNDER_SET_PASSWORD = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_GROUP_NOT_FOUND': 1, + + # + # The caller does not have the required permissions to set the password. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_PERMISSIONS': 2, + + # + # Password length exceeded 'TOX_GROUP_MAX_PASSWORD_SIZE. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_TOO_LONG': 3, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PASSWORD_FAIL_SEND': 4 +} + +TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_GROUP_NOT_FOUND': 1, + + # + # 'TOX_GROUP_PRIVACY_STATE is an invalid type. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_INVALID': 2, + + # + # The caller does not have the required permissions to set the privacy state. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_PERMISSIONS': 3, + + # + # The privacy state could not be set. This may occur due to an error related to + # cryptographic signing of the new shared state. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SET': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PRIVACY_STATE_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_GROUP_NOT_FOUND': 1, + + # + # The caller does not have the required permissions to set the peer limit. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_PERMISSIONS': 2, + + # + # The peer limit could not be set. This may occur due to an error related to + # cryptographic signing of the new shared state. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SET': 3, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_FOUNDER_SET_PEER_LIMIT_FAIL_SEND': 4 +} + +TOX_ERR_GROUP_TOGGLE_IGNORE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_TOGGLE_IGNORE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_TOGGLE_IGNORE_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_TOGGLE_IGNORE_PEER_NOT_FOUND': 2 +} + +TOX_ERR_GROUP_MOD_SET_ROLE = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. Note: you cannot set your own role. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_PEER_NOT_FOUND': 2, + + # + # The caller does not have the required permissions for this action. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_PERMISSIONS': 3, + + # + # The role assignment is invalid. This will occur if you try to set a peer's role to + # the role they already have. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_ASSIGNMENT': 4, + + # + # The role was not successfully set. This may occur if something goes wrong with role setting': , + # or if the packet fails to send. + # + 'TOX_ERR_GROUP_MOD_SET_ROLE_FAIL_ACTION': 5 +} + +TOX_ERR_GROUP_MOD_REMOVE_PEER = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_GROUP_NOT_FOUND': 1, + + # + # The ID passed did not designate a valid peer. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_PEER_NOT_FOUND': 2, + + # + # The caller does not have the required permissions for this action. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_PERMISSIONS': 3, + + # + # The peer could not be removed from the group. + # + # If a ban was set': , this error indicates that the ban entry could not be created. + # This is usually due to the peer's IP address already occurring in the ban list. It may also + # be due to the entry containing invalid peer information': , or a failure to cryptographically + # authenticate the entry. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_ACTION': 4, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_MOD_REMOVE_PEER_FAIL_SEND': 5 +} + +TOX_ERR_GROUP_MOD_REMOVE_BAN = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_GROUP_NOT_FOUND': 1, + + # + # The caller does not have the required permissions for this action. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_PERMISSIONS': 2, + + # + # The ban entry could not be removed. This may occur if ban_id does not designate + # a valid ban entry. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_ACTION': 3, + + # + # The packet failed to send. + # + 'TOX_ERR_GROUP_MOD_REMOVE_BAN_FAIL_SEND': 4 +} + +TOX_GROUP_MOD_EVENT = { + + # + # A peer has been kicked from the group. + # + 'KICK': 0, + + # + # A peer has been banned from the group. + # + 'BAN': 1, + + # + # A peer as been given the observer role. + # + 'OBSERVER': 2, + + # + # A peer has been given the user role. + # + 'USER': 3, + + # + # A peer has been given the moderator role. + # + 'MODERATOR': 4, +} + +TOX_ERR_GROUP_BAN_QUERY = { + + # + # The function returned successfully. + # + 'TOX_ERR_GROUP_BAN_QUERY_OK': 0, + + # + # The group number passed did not designate a valid group. + # + 'TOX_ERR_GROUP_BAN_QUERY_GROUP_NOT_FOUND': 1, + + # + # The ban_id does not designate a valid ban list entry. + # + 'TOX_ERR_GROUP_BAN_QUERY_BAD_ID': 2, +} + + +TOX_GROUP_BAN_TYPE = { + + 'IP_PORT': 0, + + 'PUBLIC_KEY': 1, + + 'NICK': 2 +} + +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_GROUP_MAX_TOPIC_LENGTH = 512 + +TOX_GROUP_MAX_PART_LENGTH = 128 + +TOX_GROUP_MAX_GROUP_NAME_LENGTH = 48 + +TOX_GROUP_MAX_PASSWORD_SIZE = 32 + +TOX_GROUP_CHAT_ID_SIZE = 32 + +TOX_GROUP_PEER_PUBLIC_KEY_SIZE = 32 + +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/toxygen/wrapper/toxencryptsave.py b/toxygen/wrapper/toxencryptsave.py new file mode 100644 index 0000000..31de085 --- /dev/null +++ b/toxygen/wrapper/toxencryptsave.py @@ -0,0 +1,74 @@ +from wrapper import libtox +from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool +from wrapper.toxencryptsave_enums_and_consts import * + + +class ToxEncryptSave: + + def __init__(self): + self.libtoxencryptsave = libtox.LibToxEncryptSave() + + def is_data_encrypted(self, data): + """ + Checks if given data is encrypted + """ + func = self.libtoxencryptsave.tox_is_data_encrypted + func.restype = c_bool + result = func(c_char_p(bytes(data))) + return result + + def pass_encrypt(self, data, password): + """ + Encrypts the given data with the given password. + + :return: output array + """ + out = create_string_buffer(len(data) + TOX_PASS_ENCRYPTION_EXTRA_LENGTH) + tox_err_encryption = c_int() + self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data), + c_size_t(len(data)), + c_char_p(bytes(password, 'utf-8')), + c_size_t(len(password)), + out, + byref(tox_err_encryption)) + tox_err_encryption = tox_err_encryption.value + if tox_err_encryption == TOX_ERR_ENCRYPTION['OK']: + return out[:] + elif tox_err_encryption == TOX_ERR_ENCRYPTION['NULL']: + raise ArgumentError('Some input data, or maybe the output pointer, was null.') + elif tox_err_encryption == TOX_ERR_ENCRYPTION['KEY_DERIVATION_FAILED']: + raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a' + ' lack of memory issue. The functions accepting keys do not produce this error.') + elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']: + raise RuntimeError('The encryption itself failed.') + + def pass_decrypt(self, data, password): + """ + Decrypts the given data with the given password. + + :return: output array + """ + out = create_string_buffer(len(data) - TOX_PASS_ENCRYPTION_EXTRA_LENGTH) + tox_err_decryption = c_int() + self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)), + c_size_t(len(data)), + c_char_p(bytes(password, 'utf-8')), + c_size_t(len(password)), + out, + byref(tox_err_decryption)) + tox_err_decryption = tox_err_decryption.value + if tox_err_decryption == TOX_ERR_DECRYPTION['OK']: + return out[:] + elif tox_err_decryption == TOX_ERR_DECRYPTION['NULL']: + raise ArgumentError('Some input data, or maybe the output pointer, was null.') + elif tox_err_decryption == TOX_ERR_DECRYPTION['INVALID_LENGTH']: + raise ArgumentError('The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes') + elif tox_err_decryption == TOX_ERR_DECRYPTION['BAD_FORMAT']: + raise ArgumentError('The input data is missing the magic number (i.e. wasn\'t created by this module, or is' + ' corrupted)') + elif tox_err_decryption == TOX_ERR_DECRYPTION['KEY_DERIVATION_FAILED']: + raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a' + ' lack of memory issue. The functions accepting keys do not produce this error.') + elif tox_err_decryption == TOX_ERR_DECRYPTION['FAILED']: + raise RuntimeError('The encrypted byte array could not be decrypted. Either the data was corrupt or the ' + 'password/key was incorrect.') diff --git a/toxygen/wrapper/toxencryptsave_enums_and_consts.py b/toxygen/wrapper/toxencryptsave_enums_and_consts.py new file mode 100644 index 0000000..cf795f8 --- /dev/null +++ b/toxygen/wrapper/toxencryptsave_enums_and_consts.py @@ -0,0 +1,29 @@ +TOX_ERR_ENCRYPTION = { + # The function returned successfully. + 'OK': 0, + # Some input data, or maybe the output pointer, was null. + 'NULL': 1, + # The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The + # functions accepting keys do not produce this error. + 'KEY_DERIVATION_FAILED': 2, + # The encryption itself failed. + 'FAILED': 3 +} + +TOX_ERR_DECRYPTION = { + # The function returned successfully. + 'OK': 0, + # Some input data, or maybe the output pointer, was null. + 'NULL': 1, + # The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes + 'INVALID_LENGTH': 2, + # The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted) + 'BAD_FORMAT': 3, + # The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The + # functions accepting keys do not produce this error. + 'KEY_DERIVATION_FAILED': 4, + # The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect. + 'FAILED': 5, +} + +TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80 From 6f0c1a444edd5cada66a5ce1bc29dc5d688664dc Mon Sep 17 00:00:00 2001 From: emdee Date: Tue, 27 Sep 2022 12:41:23 +0000 Subject: [PATCH 095/163] merge in next_gen branch --- toxygen/avwidgets.py | 134 -- toxygen/basecontact.py | 118 -- toxygen/bootstrap.py | 75 - toxygen/callbacks.py | 469 ------ toxygen/calls.py | 339 ----- toxygen/contact.py | 288 ---- toxygen/file_transfers.py | 347 ----- toxygen/friend.py | 75 - toxygen/group_chat.py | 49 - toxygen/history.py | 215 --- toxygen/items_factory.py | 68 - toxygen/libtox.py | 59 - toxygen/list_items.py | 545 ------- toxygen/loginscreen.py | 103 -- toxygen/mainscreen.py | 757 --------- toxygen/mainscreen_widgets.py | 492 ------ toxygen/menu.py | 1095 ------------- toxygen/messages.py | 113 -- toxygen/nodes.json | 1 - toxygen/notifications.py | 71 - toxygen/passwordscreen.py | 154 -- toxygen/plugin_support.py | 176 --- toxygen/profile.py | 1466 ------------------ toxygen/screen_sharing.py | 22 - toxygen/settings.py | 293 ---- toxygen/smileys.py | 88 -- toxygen/tox.py | 1601 -------------------- toxygen/tox_dns.py | 59 - toxygen/toxav.py | 362 ----- toxygen/toxav_enums.py | 131 -- toxygen/toxcore_enums_and_consts.py | 220 --- toxygen/toxencryptsave.py | 74 - toxygen/toxencryptsave_enums_and_consts.py | 29 - toxygen/toxes.py | 28 - toxygen/updater.py | 110 -- toxygen/util.py | 107 -- toxygen/widgets.py | 166 -- 37 files changed, 10499 deletions(-) delete mode 100644 toxygen/avwidgets.py delete mode 100644 toxygen/basecontact.py delete mode 100644 toxygen/bootstrap.py delete mode 100644 toxygen/callbacks.py delete mode 100644 toxygen/calls.py delete mode 100644 toxygen/contact.py delete mode 100644 toxygen/file_transfers.py delete mode 100644 toxygen/friend.py delete mode 100644 toxygen/group_chat.py delete mode 100644 toxygen/history.py delete mode 100644 toxygen/items_factory.py delete mode 100644 toxygen/libtox.py delete mode 100644 toxygen/list_items.py delete mode 100644 toxygen/loginscreen.py delete mode 100644 toxygen/mainscreen.py delete mode 100644 toxygen/mainscreen_widgets.py delete mode 100644 toxygen/menu.py delete mode 100644 toxygen/messages.py delete mode 100644 toxygen/nodes.json delete mode 100644 toxygen/notifications.py delete mode 100644 toxygen/passwordscreen.py delete mode 100644 toxygen/plugin_support.py delete mode 100644 toxygen/profile.py delete mode 100644 toxygen/screen_sharing.py delete mode 100644 toxygen/settings.py delete mode 100644 toxygen/smileys.py delete mode 100644 toxygen/tox.py delete mode 100644 toxygen/tox_dns.py delete mode 100644 toxygen/toxav.py delete mode 100644 toxygen/toxav_enums.py delete mode 100644 toxygen/toxcore_enums_and_consts.py delete mode 100644 toxygen/toxencryptsave.py delete mode 100644 toxygen/toxencryptsave_enums_and_consts.py delete mode 100644 toxygen/toxes.py delete mode 100644 toxygen/updater.py delete mode 100644 toxygen/util.py delete mode 100644 toxygen/widgets.py diff --git a/toxygen/avwidgets.py b/toxygen/avwidgets.py deleted file mode 100644 index 8c81387..0000000 --- a/toxygen/avwidgets.py +++ /dev/null @@ -1,134 +0,0 @@ -from PyQt5 import QtCore, QtGui, QtWidgets -import widgets -import profile -import util -import pyaudio -import wave -import settings -from util import curr_directory - - -class IncomingCallWidget(widgets.CenteredWidget): - - def __init__(self, friend_number, text, name): - super(IncomingCallWidget, self).__init__() - self.setWindowFlags(QtCore.Qt.CustomizeWindowHint | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowStaysOnTopHint) - self.resize(QtCore.QSize(500, 270)) - self.avatar_label = QtWidgets.QLabel(self) - self.avatar_label.setGeometry(QtCore.QRect(10, 20, 64, 64)) - self.avatar_label.setScaledContents(False) - self.name = widgets.DataLabel(self) - self.name.setGeometry(QtCore.QRect(90, 20, 300, 25)) - self._friend_number = friend_number - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(16) - font.setBold(True) - self.name.setFont(font) - self.call_type = widgets.DataLabel(self) - self.call_type.setGeometry(QtCore.QRect(90, 55, 300, 25)) - self.call_type.setFont(font) - self.accept_audio = QtWidgets.QPushButton(self) - self.accept_audio.setGeometry(QtCore.QRect(20, 100, 150, 150)) - self.accept_video = QtWidgets.QPushButton(self) - self.accept_video.setGeometry(QtCore.QRect(170, 100, 150, 150)) - self.decline = QtWidgets.QPushButton(self) - self.decline.setGeometry(QtCore.QRect(320, 100, 150, 150)) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_audio.png') - icon = QtGui.QIcon(pixmap) - self.accept_audio.setIcon(icon) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/accept_video.png') - icon = QtGui.QIcon(pixmap) - self.accept_video.setIcon(icon) - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/decline_call.png') - icon = QtGui.QIcon(pixmap) - self.decline.setIcon(icon) - self.accept_audio.setIconSize(QtCore.QSize(150, 150)) - self.accept_video.setIconSize(QtCore.QSize(140, 140)) - self.decline.setIconSize(QtCore.QSize(140, 140)) - self.accept_audio.setStyleSheet("QPushButton { border: none }") - self.accept_video.setStyleSheet("QPushButton { border: none }") - self.decline.setStyleSheet("QPushButton { border: none }") - self.setWindowTitle(text) - self.name.setText(name) - self.call_type.setText(text) - self._processing = False - self.accept_audio.clicked.connect(self.accept_call_with_audio) - self.accept_video.clicked.connect(self.accept_call_with_video) - self.decline.clicked.connect(self.decline_call) - - class SoundPlay(QtCore.QThread): - - def __init__(self): - QtCore.QThread.__init__(self) - self.a = None - - def run(self): - class AudioFile: - chunk = 1024 - - def __init__(self, fl): - self.stop = False - self.fl = fl - self.wf = wave.open(self.fl, 'rb') - self.p = pyaudio.PyAudio() - self.stream = self.p.open( - format=self.p.get_format_from_width(self.wf.getsampwidth()), - channels=self.wf.getnchannels(), - rate=self.wf.getframerate(), - output=True) - - def play(self): - while not self.stop: - data = self.wf.readframes(self.chunk) - while data and not self.stop: - self.stream.write(data) - data = self.wf.readframes(self.chunk) - self.wf = wave.open(self.fl, 'rb') - - def close(self): - self.stream.close() - self.p.terminate() - - self.a = AudioFile(curr_directory() + '/sounds/call.wav') - self.a.play() - self.a.close() - - if settings.Settings.get_instance()['calls_sound']: - self.thread = SoundPlay() - self.thread.start() - else: - self.thread = None - - def stop(self): - if self.thread is not None: - self.thread.a.stop = True - self.thread.wait() - self.close() - - def accept_call_with_audio(self): - if self._processing: - return - self._processing = True - pr = profile.Profile.get_instance() - pr.accept_call(self._friend_number, True, False) - self.stop() - - def accept_call_with_video(self): - if self._processing: - return - self._processing = True - pr = profile.Profile.get_instance() - pr.accept_call(self._friend_number, True, True) - self.stop() - - def decline_call(self): - if self._processing: - return - self._processing = True - pr = profile.Profile.get_instance() - pr.stop_call(self._friend_number, False) - self.stop() - - def set_pixmap(self, pixmap): - self.avatar_label.setPixmap(pixmap) diff --git a/toxygen/basecontact.py b/toxygen/basecontact.py deleted file mode 100644 index e1243a4..0000000 --- a/toxygen/basecontact.py +++ /dev/null @@ -1,118 +0,0 @@ -from settings import * -from PyQt5 import QtCore, QtGui -from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE - - -class BaseContact: - """ - Class encapsulating TOX contact - Properties: name (alias of contact or name), status_message, status (connection status) - widget - widget for update, tox id (or public key) - Base class for all contacts. - """ - - def __init__(self, name, status_message, widget, tox_id): - """ - :param name: name, example: 'Toxygen user' - :param status_message: status message, example: 'Toxing on Toxygen' - :param widget: ContactItem instance - :param tox_id: tox id of contact - """ - self._name, self._status_message = name, status_message - self._status, self._widget = None, widget - self._tox_id = tox_id - self.init_widget() - - # ----------------------------------------------------------------------------------------------------------------- - # Name - current name or alias of user - # ----------------------------------------------------------------------------------------------------------------- - - def get_name(self): - return self._name - - def set_name(self, value): - self._name = str(value, 'utf-8') - self._widget.name.setText(self._name) - self._widget.name.repaint() - - name = property(get_name, set_name) - - # ----------------------------------------------------------------------------------------------------------------- - # Status message - # ----------------------------------------------------------------------------------------------------------------- - - def get_status_message(self): - return self._status_message - - def set_status_message(self, value): - self._status_message = str(value, 'utf-8') - self._widget.status_message.setText(self._status_message) - self._widget.status_message.repaint() - - status_message = property(get_status_message, set_status_message) - - # ----------------------------------------------------------------------------------------------------------------- - # Status - # ----------------------------------------------------------------------------------------------------------------- - - def get_status(self): - return self._status - - def set_status(self, value): - self._status = value - self._widget.connection_status.update(value) - - status = property(get_status, set_status) - - # ----------------------------------------------------------------------------------------------------------------- - # TOX ID. WARNING: for friend it will return public key, for profile - full address - # ----------------------------------------------------------------------------------------------------------------- - - def get_tox_id(self): - return self._tox_id - - tox_id = property(get_tox_id) - - # ----------------------------------------------------------------------------------------------------------------- - # Avatars - # ----------------------------------------------------------------------------------------------------------------- - - def load_avatar(self): - """ - Tries to load avatar of contact or uses default avatar - """ - prefix = ProfileHelper.get_path() + 'avatars/' - avatar_path = prefix + '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image - avatar_path = curr_directory() + '/images/avatar.png' - width = self._widget.avatar_label.width() - pixmap = QtGui.QPixmap(avatar_path) - self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation)) - self._widget.avatar_label.repaint() - - def reset_avatar(self): - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if os.path.isfile(avatar_path): - os.remove(avatar_path) - self.load_avatar() - - def set_avatar(self, avatar): - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - with open(avatar_path, 'wb') as f: - f.write(avatar) - self.load_avatar() - - def get_pixmap(self): - return self._widget.avatar_label.pixmap() - - # ----------------------------------------------------------------------------------------------------------------- - # Widgets - # ----------------------------------------------------------------------------------------------------------------- - - def init_widget(self): - if self._widget is not None: - self._widget.name.setText(self._name) - self._widget.status_message.setText(self._status_message) - self._widget.connection_status.update(self._status) - self.load_avatar() diff --git a/toxygen/bootstrap.py b/toxygen/bootstrap.py deleted file mode 100644 index 0589940..0000000 --- a/toxygen/bootstrap.py +++ /dev/null @@ -1,75 +0,0 @@ -import random -import urllib.request -from util import log, curr_directory -import settings -from PyQt5 import QtNetwork, QtCore -import json - - -class Node: - - def __init__(self, node): - self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key'] - self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0 - - def get_priority(self): - return self._priority - - priority = property(get_priority) - - def get_data(self): - return bytes(self._ip, 'utf-8'), self._port, self._tox_key - - -def generate_nodes(): - with open(curr_directory() + '/nodes.json', 'rt') as fl: - json_nodes = json.loads(fl.read())['nodes'] - nodes = map(lambda json_node: Node(json_node), json_nodes) - sorted_nodes = sorted(nodes, key=lambda x: x.priority)[-4:] - for node in sorted_nodes: - yield node.get_data() - - -def save_nodes(nodes): - if not nodes: - return - print('Saving nodes...') - with open(curr_directory() + '/nodes.json', 'wb') as fl: - fl.write(nodes) - - -def download_nodes_list(): - url = 'https://nodes.tox.chat/json' - s = settings.Settings.get_instance() - if not s['download_nodes_list']: - return - - if not s['proxy_type']: # no proxy - try: - req = urllib.request.Request(url) - req.add_header('Content-Type', 'application/json') - response = urllib.request.urlopen(req) - result = response.read() - save_nodes(result) - except Exception as ex: - log('TOX nodes loading error: ' + str(ex)) - else: # proxy - netman = QtNetwork.QNetworkAccessManager() - proxy = QtNetwork.QNetworkProxy() - proxy.setType( - QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) - proxy.setHostName(s['proxy_host']) - proxy.setPort(s['proxy_port']) - netman.setProxy(proxy) - try: - request = QtNetwork.QNetworkRequest() - request.setUrl(QtCore.QUrl(url)) - reply = netman.get(request) - - while not reply.isFinished(): - QtCore.QThread.msleep(1) - QtCore.QCoreApplication.processEvents() - data = bytes(reply.readAll().data()) - save_nodes(data) - except Exception as ex: - log('TOX nodes loading error: ' + str(ex)) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py deleted file mode 100644 index b59d17c..0000000 --- a/toxygen/callbacks.py +++ /dev/null @@ -1,469 +0,0 @@ -from PyQt5 import QtCore, QtGui, QtWidgets -from notifications import * -from settings import Settings -from profile import Profile -from toxcore_enums_and_consts import * -from toxav_enums import * -from tox import bin_to_string -from plugin_support import PluginLoader -import queue -import threading -import util -import cv2 -import numpy as np - -# ----------------------------------------------------------------------------------------------------------------- -# Threads -# ----------------------------------------------------------------------------------------------------------------- - - -class InvokeEvent(QtCore.QEvent): - EVENT_TYPE = QtCore.QEvent.Type(QtCore.QEvent.registerEventType()) - - def __init__(self, fn, *args, **kwargs): - QtCore.QEvent.__init__(self, InvokeEvent.EVENT_TYPE) - self.fn = fn - self.args = args - self.kwargs = kwargs - - -class Invoker(QtCore.QObject): - - def event(self, event): - event.fn(*event.args, **event.kwargs) - return True - - -_invoker = Invoker() - - -def invoke_in_main_thread(fn, *args, **kwargs): - QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs)) - - -class FileTransfersThread(threading.Thread): - - def __init__(self): - self._queue = queue.Queue() - self._timeout = 0.01 - self._continue = True - super().__init__() - - def execute(self, function, *args, **kwargs): - self._queue.put((function, args, kwargs)) - - def stop(self): - self._continue = False - - def run(self): - while self._continue: - try: - function, args, kwargs = self._queue.get(timeout=self._timeout) - function(*args, **kwargs) - except queue.Empty: - pass - except queue.Full: - util.log('Queue is Full in _thread') - except Exception as ex: - util.log('Exception in _thread: ' + str(ex)) - - -_thread = FileTransfersThread() - - -def start(): - _thread.start() - - -def stop(): - _thread.stop() - _thread.join() - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - current user -# ----------------------------------------------------------------------------------------------------------------- - - -def self_connection_status(tox_link): - """ - Current user changed connection status (offline, UDP, TCP) - """ - def wrapped(tox, connection, user_data): - print('Connection status: ', str(connection)) - profile = Profile.get_instance() - if profile.status is None: - status = tox_link.self_get_status() - invoke_in_main_thread(profile.set_status, status) - elif connection == TOX_CONNECTION['NONE']: - invoke_in_main_thread(profile.set_status, None) - return wrapped - - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - friends -# ----------------------------------------------------------------------------------------------------------------- - - -def friend_status(tox, friend_num, new_status, user_data): - """ - Check friend's status (none, busy, away) - """ - print("Friend's #{} status changed!".format(friend_num)) - profile = Profile.get_instance() - friend = profile.get_friend_by_number(friend_num) - if friend.status is None and Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: - sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS']) - invoke_in_main_thread(friend.set_status, new_status) - invoke_in_main_thread(QtCore.QTimer.singleShot, 5000, lambda: profile.send_files(friend_num)) - invoke_in_main_thread(profile.update_filtration) - - -def friend_connection_status(tox, friend_num, new_status, user_data): - """ - Check friend's connection status (offline, udp, tcp) - """ - print("Friend #{} connection status: {}".format(friend_num, new_status)) - profile = Profile.get_instance() - friend = profile.get_friend_by_number(friend_num) - if new_status == TOX_CONNECTION['NONE']: - invoke_in_main_thread(profile.friend_exit, friend_num) - invoke_in_main_thread(profile.update_filtration) - if Settings.get_instance()['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: - sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS']) - elif friend.status is None: - invoke_in_main_thread(profile.send_avatar, friend_num) - invoke_in_main_thread(PluginLoader.get_instance().friend_online, friend_num) - - -def friend_name(tox, friend_num, name, size, user_data): - """ - Friend changed his name - """ - profile = Profile.get_instance() - print('New name friend #' + str(friend_num)) - invoke_in_main_thread(profile.new_name, friend_num, name) - - -def friend_status_message(tox, friend_num, status_message, size, user_data): - """ - :return: function for callback friend_status_message. It updates friend's status message - and calls window repaint - """ - profile = Profile.get_instance() - friend = profile.get_friend_by_number(friend_num) - invoke_in_main_thread(friend.set_status_message, status_message) - print('User #{} has new status'.format(friend_num)) - invoke_in_main_thread(profile.send_messages, friend_num) - if profile.get_active_number() == friend_num: - invoke_in_main_thread(profile.set_active) - - -def friend_message(window, tray): - """ - New message from friend - """ - def wrapped(tox, friend_number, message_type, message, size, user_data): - profile = Profile.get_instance() - settings = Settings.get_instance() - message = str(message, 'utf-8') - invoke_in_main_thread(profile.new_message, friend_number, message_type, message) - if not window.isActiveWindow(): - friend = profile.get_friend_by_number(friend_number) - if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: - invoke_in_main_thread(tray_notification, friend.name, message, tray, window) - if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: - sound_notification(SOUND_NOTIFICATION['MESSAGE']) - invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) - return wrapped - - -def friend_request(tox, public_key, message, message_size, user_data): - """ - Called when user get new friend request - """ - print('Friend request') - profile = Profile.get_instance() - key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE]) - tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE) - if tox_id not in Settings.get_instance()['blocked']: - invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8')) - - -def friend_typing(tox, friend_number, typing, user_data): - invoke_in_main_thread(Profile.get_instance().friend_typing, friend_number, typing) - - -def friend_read_receipt(tox, friend_number, message_id, user_data): - profile = Profile.get_instance() - profile.get_friend_by_number(friend_number).dec_receipt() - if friend_number == profile.get_active_number(): - invoke_in_main_thread(profile.receipt) - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - file transfers -# ----------------------------------------------------------------------------------------------------------------- - - -def tox_file_recv(window, tray): - """ - New incoming file - """ - def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data): - profile = Profile.get_instance() - settings = Settings.get_instance() - if file_type == TOX_FILE_KIND['DATA']: - print('File') - try: - file_name = str(file_name[:file_name_size], 'utf-8') - except: - file_name = 'toxygen_file' - invoke_in_main_thread(profile.incoming_file_transfer, - friend_number, - file_number, - size, - file_name) - if not window.isActiveWindow(): - friend = profile.get_friend_by_number(friend_number) - if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: - file_from = QtWidgets.QApplication.translate("Callback", "File from") - invoke_in_main_thread(tray_notification, file_from + ' ' + friend.name, file_name, tray, window) - if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: - sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER']) - invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) - else: # AVATAR - print('Avatar') - invoke_in_main_thread(profile.incoming_avatar, - friend_number, - file_number, - size) - return wrapped - - -def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data): - """ - Incoming chunk - """ - _thread.execute(Profile.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 - """ - Profile.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']: - invoke_in_main_thread(Profile.get_instance().cancel_transfer, friend_number, file_number, True) - elif file_control == TOX_FILE_CONTROL['PAUSE']: - invoke_in_main_thread(Profile.get_instance().pause_transfer, friend_number, file_number, True) - elif file_control == TOX_FILE_CONTROL['RESUME']: - invoke_in_main_thread(Profile.get_instance().resume_transfer, friend_number, file_number, True) - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - custom packets -# ----------------------------------------------------------------------------------------------------------------- - - -def lossless_packet(tox, friend_number, data, length, user_data): - """ - Incoming lossless packet - """ - data = data[:length] - plugin = PluginLoader.get_instance() - invoke_in_main_thread(plugin.callback_lossless, friend_number, data) - - -def lossy_packet(tox, friend_number, data, length, user_data): - """ - Incoming lossy packet - """ - data = data[:length] - plugin = PluginLoader.get_instance() - invoke_in_main_thread(plugin.callback_lossy, friend_number, data) - - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - audio -# ----------------------------------------------------------------------------------------------------------------- - -def call_state(toxav, friend_number, mask, user_data): - """ - New call state - """ - print(friend_number, mask) - if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']: - invoke_in_main_thread(Profile.get_instance().stop_call, friend_number, True) - else: - Profile.get_instance().call.toxav_call_state_cb(friend_number, mask) - - -def call(toxav, friend_number, audio, video, user_data): - """ - Incoming call from friend - """ - print(friend_number, audio, video) - invoke_in_main_thread(Profile.get_instance().incoming_call, audio, video, friend_number) - - -def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, audio_channels_count, rate, user_data): - """ - New audio chunk - """ - Profile.get_instance().call.audio_chunk( - bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]), - audio_channels_count, - rate) - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - video -# ----------------------------------------------------------------------------------------------------------------- - - -def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, ustride, vstride, user_data): - """ - Creates yuv frame from y, u, v and shows it using OpenCV - For yuv => bgr we need this YUV420 frame: - - width - ------------------------- - | | - | Y | height - | | - ------------------------- - | | | - | U even | U odd | height // 4 - | | | - ------------------------- - | | | - | V even | V odd | height // 4 - | | | - ------------------------- - - width // 2 width // 2 - - It can be created from initial y, u, v using slices - """ - try: - y_size = abs(max(width, abs(ystride))) - u_size = abs(max(width // 2, abs(ustride))) - v_size = abs(max(width // 2, abs(vstride))) - - y = np.asarray(y[:y_size * height], dtype=np.uint8).reshape(height, y_size) - u = np.asarray(u[:u_size * height // 2], dtype=np.uint8).reshape(height // 2, u_size) - v = np.asarray(v[:v_size * height // 2], dtype=np.uint8).reshape(height // 2, v_size) - - width -= width % 4 - height -= height % 4 - - frame = np.zeros((int(height * 1.5), width), dtype=np.uint8) - - frame[:height, :] = y[:height, :width] - frame[height:height * 5 // 4, :width // 2] = u[:height // 2:2, :width // 2] - frame[height:height * 5 // 4, width // 2:] = u[1:height // 2:2, :width // 2] - - frame[height * 5 // 4:, :width // 2] = v[:height // 2:2, :width // 2] - frame[height * 5 // 4:, width // 2:] = v[1:height // 2:2, :width // 2] - - frame = cv2.cvtColor(frame, cv2.COLOR_YUV2BGR_I420) - - invoke_in_main_thread(cv2.imshow, str(friend_number), frame) - except Exception as ex: - print(ex) - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - groups -# ----------------------------------------------------------------------------------------------------------------- - - -def group_invite(tox, friend_number, gc_type, data, length, user_data): - invoke_in_main_thread(Profile.get_instance().group_invite, friend_number, gc_type, - bytes(data[:length])) - - -def show_gc_notification(window, tray, message, group_number, peer_number): - profile = Profile.get_instance() - settings = Settings.get_instance() - chat = profile.get_group_by_number(group_number) - peer_name = chat.get_peer_name(peer_number) - if not window.isActiveWindow() and (profile.name in message or settings['group_notifications']): - if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked: - invoke_in_main_thread(tray_notification, chat.name + ' ' + peer_name, message, tray, window) - if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']: - sound_notification(SOUND_NOTIFICATION['MESSAGE']) - invoke_in_main_thread(tray.setIcon, QtGui.QIcon(curr_directory() + '/images/icon_new_messages.png')) - - -def group_message(window, tray): - def wrapped(tox, group_number, peer_number, message, length, user_data): - message = str(message[:length], 'utf-8') - invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['NORMAL'], message) - show_gc_notification(window, tray, message, group_number, peer_number) - return wrapped - - -def group_action(window, tray): - def wrapped(tox, group_number, peer_number, message, length, user_data): - message = str(message[:length], 'utf-8') - invoke_in_main_thread(Profile.get_instance().new_gc_message, group_number, - peer_number, TOX_MESSAGE_TYPE['ACTION'], message) - show_gc_notification(window, tray, message, group_number, peer_number) - return wrapped - - -def group_title(tox, group_number, peer_number, title, length, user_data): - invoke_in_main_thread(Profile.get_instance().new_gc_title, group_number, - title[:length]) - - -def group_namelist_change(tox, group_number, peer_number, change, user_data): - invoke_in_main_thread(Profile.get_instance().update_gc, group_number) - -# ----------------------------------------------------------------------------------------------------------------- -# Callbacks - initialization -# ----------------------------------------------------------------------------------------------------------------- - - -def init_callbacks(tox, window, tray): - """ - Initialization of all callbacks. - :param tox: tox instance - :param window: main window - :param tray: tray (for notifications) - """ - tox.callback_self_connection_status(self_connection_status(tox), 0) - - tox.callback_friend_status(friend_status, 0) - tox.callback_friend_message(friend_message(window, tray), 0) - tox.callback_friend_connection_status(friend_connection_status, 0) - tox.callback_friend_name(friend_name, 0) - tox.callback_friend_status_message(friend_status_message, 0) - tox.callback_friend_request(friend_request, 0) - tox.callback_friend_typing(friend_typing, 0) - tox.callback_friend_read_receipt(friend_read_receipt, 0) - - tox.callback_file_recv(tox_file_recv(window, tray), 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) - - toxav = tox.AV - toxav.callback_call_state(call_state, 0) - toxav.callback_call(call, 0) - toxav.callback_audio_receive_frame(callback_audio, 0) - toxav.callback_video_receive_frame(video_receive_frame, 0) - - tox.callback_friend_lossless_packet(lossless_packet, 0) - tox.callback_friend_lossy_packet(lossy_packet, 0) - - tox.callback_group_invite(group_invite) - tox.callback_group_message(group_message(window, tray)) - tox.callback_group_action(group_action(window, tray)) - tox.callback_group_title(group_title) - tox.callback_group_namelist_change(group_namelist_change) diff --git a/toxygen/calls.py b/toxygen/calls.py deleted file mode 100644 index 3d02110..0000000 --- a/toxygen/calls.py +++ /dev/null @@ -1,339 +0,0 @@ -import pyaudio -import time -import threading -import settings -from toxav_enums import * -import cv2 -import itertools -import numpy as np -import screen_sharing -# TODO: play sound until outgoing call will be started or cancelled - - -class Call: - - def __init__(self, out_audio, out_video, in_audio=False, in_video=False): - self._in_audio = in_audio - self._in_video = in_video - self._out_audio = out_audio - self._out_video = out_video - self._is_active = False - - def get_is_active(self): - return self._is_active - - def set_is_active(self, value): - self._is_active = value - - is_active = property(get_is_active, set_is_active) - - # ----------------------------------------------------------------------------------------------------------------- - # Audio - # ----------------------------------------------------------------------------------------------------------------- - - def get_in_audio(self): - return self._in_audio - - def set_in_audio(self, value): - self._in_audio = value - - in_audio = property(get_in_audio, set_in_audio) - - def get_out_audio(self): - return self._out_audio - - def set_out_audio(self, value): - self._out_audio = value - - out_audio = property(get_out_audio, set_out_audio) - - # ----------------------------------------------------------------------------------------------------------------- - # Video - # ----------------------------------------------------------------------------------------------------------------- - - def get_in_video(self): - return self._in_video - - def set_in_video(self, value): - self._in_video = value - - in_video = property(get_in_video, set_in_video) - - def get_out_video(self): - return self._out_video - - def set_out_video(self, value): - self._out_video = value - - out_video = property(get_out_video, set_out_video) - - -class AV: - - def __init__(self, toxav): - self._toxav = toxav - self._running = True - - self._calls = {} # dict: key - friend number, value - Call instance - - self._audio = None - self._audio_stream = None - self._audio_thread = None - self._audio_running = False - self._out_stream = None - - self._audio_rate = 8000 - self._audio_channels = 1 - self._audio_duration = 60 - self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000 - - self._video = None - self._video_thread = None - self._video_running = False - - self._video_width = 640 - self._video_height = 480 - - def stop(self): - self._running = False - self.stop_audio_thread() - self.stop_video_thread() - - def __contains__(self, friend_number): - return friend_number in self._calls - - # ----------------------------------------------------------------------------------------------------------------- - # Calls - # ----------------------------------------------------------------------------------------------------------------- - - def __call__(self, friend_number, audio, video): - """Call friend with specified number""" - self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0) - self._calls[friend_number] = Call(audio, video) - threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start() - - def accept_call(self, friend_number, audio_enabled, video_enabled): - if self._running: - self._calls[friend_number] = Call(audio_enabled, video_enabled) - self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0) - if audio_enabled: - self.start_audio_thread() - if video_enabled: - self.start_video_thread() - - def finish_call(self, friend_number, by_friend=False): - if not by_friend: - self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL']) - if friend_number in self._calls: - del self._calls[friend_number] - if not len(list(filter(lambda c: c.out_audio, self._calls))): - self.stop_audio_thread() - if not len(list(filter(lambda c: c.out_video, self._calls))): - self.stop_video_thread() - - def finish_not_started_call(self, friend_number): - if friend_number in self: - call = self._calls[friend_number] - if not call.is_active: - self.finish_call(friend_number) - - def toxav_call_state_cb(self, friend_number, state): - """ - New call state - """ - call = self._calls[friend_number] - call.is_active = True - - call.in_audio = state | TOXAV_FRIEND_CALL_STATE['SENDING_A'] > 0 - call.in_video = state | TOXAV_FRIEND_CALL_STATE['SENDING_V'] > 0 - - if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_A'] and call.out_audio: - self.start_audio_thread() - - if state | TOXAV_FRIEND_CALL_STATE['ACCEPTING_V'] and call.out_video: - self.start_video_thread() - - def is_video_call(self, number): - return number in self and self._calls[number].in_video - - # ----------------------------------------------------------------------------------------------------------------- - # Threads - # ----------------------------------------------------------------------------------------------------------------- - - def start_audio_thread(self): - """ - Start audio sending - """ - if self._audio_thread is not None: - return - - self._audio_running = True - - self._audio = pyaudio.PyAudio() - self._audio_stream = self._audio.open(format=pyaudio.paInt16, - rate=self._audio_rate, - channels=self._audio_channels, - input=True, - input_device_index=settings.Settings.get_instance().audio['input'], - frames_per_buffer=self._audio_sample_count * 10) - - self._audio_thread = threading.Thread(target=self.send_audio) - self._audio_thread.start() - - def stop_audio_thread(self): - - if self._audio_thread is None: - return - - self._audio_running = False - - self._audio_thread.join() - - self._audio_thread = None - self._audio_stream = None - self._audio = None - - if self._out_stream is not None: - self._out_stream.stop_stream() - self._out_stream.close() - self._out_stream = None - - def start_video_thread(self): - if self._video_thread is not None: - return - - self._video_running = True - s = settings.Settings.get_instance() - self._video_width = s.video['width'] - self._video_height = s.video['height'] - - if s.video['device'] == -1: - self._video = screen_sharing.DesktopGrabber(s.video['x'], s.video['y'], - s.video['width'], s.video['height']) - else: - self._video = cv2.VideoCapture(s.video['device']) - self._video.set(cv2.CAP_PROP_FPS, 25) - self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width) - self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height) - - self._video_thread = threading.Thread(target=self.send_video) - self._video_thread.start() - - def stop_video_thread(self): - if self._video_thread is None: - return - - self._video_running = False - self._video_thread.join() - self._video_thread = None - self._video = None - - # ----------------------------------------------------------------------------------------------------------------- - # Incoming chunks - # ----------------------------------------------------------------------------------------------------------------- - - def audio_chunk(self, samples, channels_count, rate): - """ - Incoming chunk - """ - - if self._out_stream is None: - self._out_stream = self._audio.open(format=pyaudio.paInt16, - channels=channels_count, - rate=rate, - output_device_index=settings.Settings.get_instance().audio['output'], - output=True) - self._out_stream.write(samples) - - # ----------------------------------------------------------------------------------------------------------------- - # AV sending - # ----------------------------------------------------------------------------------------------------------------- - - def send_audio(self): - """ - This method sends audio to friends - """ - - while self._audio_running: - try: - pcm = self._audio_stream.read(self._audio_sample_count) - if pcm: - for friend_num in self._calls: - if self._calls[friend_num].out_audio: - try: - self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count, - self._audio_channels, self._audio_rate) - except: - pass - except: - pass - - time.sleep(0.01) - - def send_video(self): - """ - This method sends video to friends - """ - while self._video_running: - try: - result, frame = self._video.read() - if result: - height, width, channels = frame.shape - for friend_num in self._calls: - if self._calls[friend_num].out_video: - try: - y, u, v = self.convert_bgr_to_yuv(frame) - self._toxav.video_send_frame(friend_num, width, height, y, u, v) - except: - pass - except: - pass - - time.sleep(0.01) - - def convert_bgr_to_yuv(self, frame): - """ - :param frame: input bgr frame - :return y, u, v: y, u, v values of frame - - How this function works: - OpenCV creates YUV420 frame from BGR - This frame has following structure and size: - width, height - dim of input frame - width, height * 1.5 - dim of output frame - - width - ------------------------- - | | - | Y | height - | | - ------------------------- - | | | - | U even | U odd | height // 4 - | | | - ------------------------- - | | | - | V even | V odd | height // 4 - | | | - ------------------------- - - width // 2 width // 2 - - Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable() - Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes - """ - frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420) - - y = frame[:self._video_height, :] - y = list(itertools.chain.from_iterable(y)) - - u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int) - u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2] - u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:] - u = list(itertools.chain.from_iterable(u)) - v = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int) - v[::2, :] = frame[self._video_height * 5 // 4:, :self._video_width // 2] - v[1::2, :] = frame[self._video_height * 5 // 4:, self._video_width // 2:] - v = list(itertools.chain.from_iterable(v)) - - return bytes(y), bytes(u), bytes(v) diff --git a/toxygen/contact.py b/toxygen/contact.py deleted file mode 100644 index 9f27a1d..0000000 --- a/toxygen/contact.py +++ /dev/null @@ -1,288 +0,0 @@ -from PyQt5 import QtCore, QtGui -from history import * -import basecontact -import util -from messages import * -import file_transfers as ft -import re - - -class Contact(basecontact.BaseContact): - """ - Class encapsulating TOX contact - Properties: number, message getter, history etc. Base class for friend and gc classes - """ - - def __init__(self, message_getter, number, name, status_message, widget, tox_id): - """ - :param message_getter: gets messages from db - :param number: number of friend. - """ - super().__init__(name, status_message, widget, tox_id) - self._number = number - self._new_messages = False - self._visible = True - self._alias = False - self._message_getter = message_getter - self._corr = [] - self._unsaved_messages = 0 - self._history_loaded = self._new_actions = False - self._curr_text = self._search_string = '' - self._search_index = 0 - - def __del__(self): - self.set_visibility(False) - del self._widget - if hasattr(self, '_message_getter'): - del self._message_getter - - # ----------------------------------------------------------------------------------------------------------------- - # History support - # ----------------------------------------------------------------------------------------------------------------- - - def load_corr(self, first_time=True): - """ - :param first_time: friend became active, load first part of messages - """ - if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')): - return - if self._message_getter is None: - return - data = list(self._message_getter.get(PAGE_SIZE)) - if data is not None and len(data): - data.reverse() - else: - return - data = list(map(lambda tupl: TextMessage(*tupl), data)) - self._corr = data + self._corr - self._history_loaded = True - - def load_all_corr(self): - """ - Get all chat history from db for current friend - """ - if self._message_getter is None: - return - data = list(self._message_getter.get_all()) - if data is not None and len(data): - data.reverse() - data = list(map(lambda tupl: TextMessage(*tupl), data)) - self._corr = data + self._corr - self._history_loaded = True - - def get_corr_for_saving(self): - """ - Get data to save in db - :return: list of unsaved messages or [] - """ - messages = list(filter(lambda x: x.get_type() <= 1, self._corr)) - return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else [] - - def get_corr(self): - return self._corr[:] - - def append_message(self, message): - """ - :param message: text or file transfer message - """ - self._corr.append(message) - if message.get_type() <= 1: - self._unsaved_messages += 1 - - def get_last_message_text(self): - messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr)) - if messages: - return messages[-1].get_data()[0] - else: - return '' - - # ----------------------------------------------------------------------------------------------------------------- - # Unsent messages - # ----------------------------------------------------------------------------------------------------------------- - - def get_unsent_messages(self): - """ - :return list of unsent messages - """ - messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr) - return list(messages) - - def get_unsent_messages_for_saving(self): - """ - :return list of unsent messages for saving - """ - messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr) - return list(map(lambda x: x.get_data(), messages)) - - def mark_as_sent(self): - try: - message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0] - message.mark_as_sent() - except Exception as ex: - util.log('Mark as sent ex: ' + str(ex)) - - # ----------------------------------------------------------------------------------------------------------------- - # Message deletion - # ----------------------------------------------------------------------------------------------------------------- - - def delete_message(self, time): - elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0] - tmp = list(filter(lambda x: x.get_type() <= 1, self._corr)) - if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: - self._unsaved_messages -= 1 - self._corr.remove(elem) - self._message_getter.delete_one() - self._search_index = 0 - - def delete_old_messages(self): - """ - Delete old messages (reduces RAM usage if messages saving is not enabled) - """ - def save_message(x): - if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None): - return True - return x.get_owner() == MESSAGE_OWNER['NOT_SENT'] - - old = filter(save_message, self._corr[:-SAVE_MESSAGES]) - self._corr = list(old) + self._corr[-SAVE_MESSAGES:] - text_messages = filter(lambda x: x.get_type() <= 1, self._corr) - self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages))) - self._search_index = 0 - - def clear_corr(self, save_unsent=False): - """ - Clear messages list - """ - if hasattr(self, '_message_getter'): - del self._message_getter - self._search_index = 0 - # don't delete data about active file transfer - if not save_unsent: - self._corr = list(filter(lambda x: x.get_type() == 2 and - x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr)) - self._unsaved_messages = 0 - else: - self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS) - or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']), - self._corr)) - self._unsaved_messages = len(self.get_unsent_messages()) - - # ----------------------------------------------------------------------------------------------------------------- - # Chat history search - # ----------------------------------------------------------------------------------------------------------------- - - def search_string(self, search_string): - self._search_string, self._search_index = search_string, 0 - return self.search_prev() - - def search_prev(self): - while True: - l = len(self._corr) - for i in range(self._search_index - 1, -l - 1, -1): - if self._corr[i].get_type() > 1: - continue - message = self._corr[i].get_data()[0] - if re.search(self._search_string, message, re.IGNORECASE) is not None: - self._search_index = i - return i - self._search_index = -l - self.load_corr(False) - if len(self._corr) == l: - return None # not found - - def search_next(self): - if not self._search_index: - return None - for i in range(self._search_index + 1, 0): - if self._corr[i].get_type() > 1: - continue - message = self._corr[i].get_data()[0] - if re.search(self._search_string, message, re.IGNORECASE) is not None: - self._search_index = i - return i - return None # not found - - # ----------------------------------------------------------------------------------------------------------------- - # Current text - text from message area - # ----------------------------------------------------------------------------------------------------------------- - - def get_curr_text(self): - return self._curr_text - - def set_curr_text(self, value): - self._curr_text = value - - curr_text = property(get_curr_text, set_curr_text) - - # ----------------------------------------------------------------------------------------------------------------- - # Alias support - # ----------------------------------------------------------------------------------------------------------------- - - def set_name(self, value): - """ - Set new name or ignore if alias exists - :param value: new name - """ - if not self._alias: - super().set_name(value) - - def set_alias(self, alias): - self._alias = bool(alias) - - # ----------------------------------------------------------------------------------------------------------------- - # Visibility in friends' list - # ----------------------------------------------------------------------------------------------------------------- - - def get_visibility(self): - return self._visible - - def set_visibility(self, value): - self._visible = value - - visibility = property(get_visibility, set_visibility) - - def set_widget(self, widget): - self._widget = widget - self.init_widget() - - # ----------------------------------------------------------------------------------------------------------------- - # Unread messages and other actions from friend - # ----------------------------------------------------------------------------------------------------------------- - - def get_actions(self): - return self._new_actions - - def set_actions(self, value): - self._new_actions = value - self._widget.connection_status.update(self.status, value) - - actions = property(get_actions, set_actions) # unread messages, incoming files, av calls - - def get_messages(self): - return self._new_messages - - def inc_messages(self): - self._new_messages += 1 - self._new_actions = True - self._widget.connection_status.update(self.status, True) - self._widget.messages.update(self._new_messages) - - def reset_messages(self): - self._new_actions = False - self._new_messages = 0 - self._widget.messages.update(self._new_messages) - self._widget.connection_status.update(self.status, False) - - messages = property(get_messages) - - # ----------------------------------------------------------------------------------------------------------------- - # Friend's number (can be used in toxcore) - # ----------------------------------------------------------------------------------------------------------------- - - def get_number(self): - return self._number - - def set_number(self, value): - self._number = value - - number = property(get_number, set_number) diff --git a/toxygen/file_transfers.py b/toxygen/file_transfers.py deleted file mode 100644 index 1bbabe5..0000000 --- a/toxygen/file_transfers.py +++ /dev/null @@ -1,347 +0,0 @@ -from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL -from os.path import basename, getsize, exists, dirname -from os import remove, rename, chdir -from time import time, sleep -from tox import Tox -import settings -from PyQt5 import QtCore - - -TOX_FILE_TRANSFER_STATE = { - 'RUNNING': 0, - 'PAUSED_BY_USER': 1, - 'CANCELLED': 2, - 'FINISHED': 3, - 'PAUSED_BY_FRIEND': 4, - 'INCOMING_NOT_STARTED': 5, - 'OUTGOING_NOT_STARTED': 6 -} - -ACTIVE_FILE_TRANSFERS = (0, 1, 4, 5, 6) - -PAUSED_FILE_TRANSFERS = (1, 4, 5, 6) - -DO_NOT_SHOW_ACCEPT_BUTTON = (2, 3, 4, 6) - -SHOW_PROGRESS_BAR = (0, 1, 4) - -ALLOWED_FILES = ('toxygen_inline.png', 'utox-inline.png', 'sticker.png') - - -def is_inline(file_name): - return file_name in ALLOWED_FILES or file_name.startswith('qTox_Screenshot_') or file_name.startswith('qTox_Image_') - - -class StateSignal(QtCore.QObject): - - signal = QtCore.pyqtSignal(int, float, int) # state, progress, time in sec - - -class TransferFinishedSignal(QtCore.QObject): - - signal = QtCore.pyqtSignal(int, int) # friend number, file number - - -class FileTransfer(QtCore.QObject): - """ - Superclass for file transfers - """ - - def __init__(self, path, tox, friend_number, size, file_number=None): - QtCore.QObject.__init__(self) - 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 = None - self._size = float(size) - self._done = 0 - self._state_changed = StateSignal() - self._finished = TransferFinishedSignal() - self._file_id = None - - def set_tox(self, tox): - self._tox = tox - - def set_state_changed_handler(self, handler): - self._state_changed.signal.connect(handler) - - def set_transfer_finished_handler(self, handler): - self._finished.signal.connect(handler) - - def signal(self): - percentage = self._done / self._size if self._size else 0 - if self._creation_time is None or not percentage: - t = -1 - else: - t = ((time() - self._creation_time) / percentage) * (1 - percentage) - self._state_changed.signal.emit(self.state, percentage, int(t)) - - def finished(self): - self._finished.signal.emit(self._friend_number, self._file_number) - - def get_file_number(self): - return self._file_number - - def get_friend_number(self): - return self._friend_number - - def get_id(self): - return self._file_id - - def get_path(self): - return self._path - - def cancel(self): - self.send_control(TOX_FILE_CONTROL['CANCEL']) - if hasattr(self, '_file'): - self._file.close() - self.signal() - - def cancelled(self): - if hasattr(self, '_file'): - sleep(0.1) - self._file.close() - self.state = TOX_FILE_TRANSFER_STATE['CANCELLED'] - self.signal() - - def pause(self, by_friend): - if not by_friend: - self.send_control(TOX_FILE_CONTROL['PAUSE']) - else: - self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] - self.signal() - - def send_control(self, control): - if self._tox.file_control(self._friend_number, self._file_number, control): - self.state = control - self.signal() - - 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.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] - self._file_number = tox.file_send(friend_number, kind, size, file_id, - bytes(basename(path), 'utf-8') if path else b'') - self._file_id = self.get_file_id() - - def send_chunk(self, position, size): - """ - Send chunk - :param position: start position in file - :param size: chunk max size - """ - if self._creation_time is None: - self._creation_time = time() - 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: - if hasattr(self, '_file'): - self._file.close() - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() - self.signal() - - -class SendAvatar(SendTransfer): - """ - Send avatar to friend. Doesn't need file transfer item - """ - - def __init__(self, path, tox, friend_number): - if path is None: - hash = None - else: - with open(path, 'rb') as fl: - hash = Tox.hash(fl.read()) - super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], hash) - - -class SendFromBuffer(FileTransfer): - """ - Send inline image - """ - - def __init__(self, tox, friend_number, data, file_name): - super(SendFromBuffer, self).__init__(None, tox, friend_number, len(data)) - self.state = TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'] - self._data = data - self._file_number = tox.file_send(friend_number, TOX_FILE_KIND['DATA'], - len(data), None, bytes(file_name, 'utf-8')) - - def get_data(self): - return self._data - - def send_chunk(self, position, size): - if self._creation_time is None: - self._creation_time = time() - if size: - data = self._data[position:position + size] - self._tox.file_send_chunk(self._friend_number, self._file_number, position, data) - self._done += size - else: - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() - self.signal() - - -class SendFromFileBuffer(SendTransfer): - - def __init__(self, *args): - super(SendFromFileBuffer, self).__init__(*args) - - def send_chunk(self, position, size): - super(SendFromFileBuffer, self).send_chunk(position, size) - if not size: - chdir(dirname(self._path)) - remove(self._path) - -# ----------------------------------------------------------------------------------------------------------------- -# Receive file -# ----------------------------------------------------------------------------------------------------------------- - - -class ReceiveTransfer(FileTransfer): - - def __init__(self, path, tox, friend_number, size, file_number, position=0): - super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number) - self._file = open(self._path, 'wb') - self._file_size = position - self._file.truncate(position) - self._missed = set() - self._file_id = self.get_file_id() - self._done = position - - def cancel(self): - super(ReceiveTransfer, self).cancel() - remove(self._path) - - def total_size(self): - self._missed.add(self._file_size) - return min(self._missed) - - def write_chunk(self, position, data): - """ - Incoming chunk - :param position: position in file to save data - :param data: raw data (string) - """ - if self._creation_time is None: - self._creation_time = time() - if data is None: - self._file.close() - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() - else: - data = bytearray(data) - if self._file_size < position: - self._file.seek(0, 2) - self._file.write(b'\0' * (position - self._file_size)) - self._missed.add(self._file_size) - else: - self._missed.discard(position) - self._file.seek(position) - self._file.write(data) - l = len(data) - if position + l > self._file_size: - self._file_size = position + l - self._done += l - self.signal() - - -class ReceiveToBuffer(FileTransfer): - """ - Inline image - save in buffer not in file system - """ - - def __init__(self, tox, friend_number, size, file_number): - super(ReceiveToBuffer, self).__init__(None, tox, friend_number, size, file_number) - self._data = bytes() - self._data_size = 0 - - def get_data(self): - return self._data - - def write_chunk(self, position, data): - if self._creation_time is None: - self._creation_time = time() - if data is None: - self.state = TOX_FILE_TRANSFER_STATE['FINISHED'] - self.finished() - else: - data = bytes(data) - l = len(data) - if self._data_size < position: - self._data += (b'\0' * (position - self._data_size)) - self._data = self._data[:position] + data + self._data[position + l:] - if position + l > self._data_size: - self._data_size = position + l - self._done += l - self.signal() - - -class ReceiveAvatar(ReceiveTransfer): - """ - Get friend's avatar. Doesn't need file transfer item - """ - MAX_AVATAR_SIZE = 512 * 1024 - - def __init__(self, tox, friend_number, size, file_number): - path = settings.ProfileHelper.get_path() + 'avatars/{}.png'.format(tox.friend_get_public_key(friend_number)) - super(ReceiveAvatar, self).__init__(path + '.tmp', tox, friend_number, size, file_number) - if size > self.MAX_AVATAR_SIZE: - self.send_control(TOX_FILE_CONTROL['CANCEL']) - self._file.close() - remove(path + '.tmp') - elif not size: - self.send_control(TOX_FILE_CONTROL['CANCEL']) - self._file.close() - if exists(path): - remove(path) - self._file.close() - remove(path + '.tmp') - elif exists(path): - hash = self.get_file_id() - with open(path, 'rb') as fl: - data = fl.read() - existing_hash = Tox.hash(data) - if hash == existing_hash: - self.send_control(TOX_FILE_CONTROL['CANCEL']) - self._file.close() - remove(path + '.tmp') - else: - self.send_control(TOX_FILE_CONTROL['RESUME']) - else: - self.send_control(TOX_FILE_CONTROL['RESUME']) - - def write_chunk(self, position, data): - super(ReceiveAvatar, self).write_chunk(position, data) - if self.state: - avatar_path = self._path[:-4] - if exists(avatar_path): - chdir(dirname(avatar_path)) - remove(avatar_path) - rename(self._path, avatar_path) - self.finished(True) - - def finished(self, emit=False): - if emit: - super().finished() diff --git a/toxygen/friend.py b/toxygen/friend.py deleted file mode 100644 index d912708..0000000 --- a/toxygen/friend.py +++ /dev/null @@ -1,75 +0,0 @@ -import contact -from messages import * -import os - - -class Friend(contact.Contact): - """ - Friend in list of friends. - """ - - def __init__(self, message_getter, number, name, status_message, widget, tox_id): - super().__init__(message_getter, number, name, status_message, widget, tox_id) - self._receipts = 0 - - # ----------------------------------------------------------------------------------------------------------------- - # File transfers support - # ----------------------------------------------------------------------------------------------------------------- - - def update_transfer_data(self, file_number, status, inline=None): - """ - Update status of active transfer and load inline if needed - """ - try: - tr = list(filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number), - self._corr))[0] - tr.set_status(status) - i = self._corr.index(tr) - if inline: # inline was loaded - self._corr.insert(i, inline) - return i - len(self._corr) - except: - pass - - def get_unsent_files(self): - messages = filter(lambda x: type(x) is UnsentFile, self._corr) - return messages - - def clear_unsent_files(self): - self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr)) - - def remove_invalid_unsent_files(self): - def is_valid(message): - if type(message) is not UnsentFile: - return True - if message.get_data()[1] is not None: - return True - return os.path.exists(message.get_data()[0]) - self._corr = list(filter(is_valid, self._corr)) - - def delete_one_unsent_file(self, time): - self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr)) - - # ----------------------------------------------------------------------------------------------------------------- - # History support - # ----------------------------------------------------------------------------------------------------------------- - - def get_receipts(self): - return self._receipts - - receipts = property(get_receipts) # read receipts - - def inc_receipts(self): - self._receipts += 1 - - def dec_receipt(self): - if self._receipts: - self._receipts -= 1 - self.mark_as_sent() - - # ----------------------------------------------------------------------------------------------------------------- - # Full status - # ----------------------------------------------------------------------------------------------------------------- - - def get_full_status(self): - return self._status_message diff --git a/toxygen/group_chat.py b/toxygen/group_chat.py deleted file mode 100644 index f7921a1..0000000 --- a/toxygen/group_chat.py +++ /dev/null @@ -1,49 +0,0 @@ -import contact -import util -from PyQt5 import QtGui, QtCore -import toxcore_enums_and_consts as constants - - -class GroupChat(contact.Contact): - - def __init__(self, name, status_message, widget, tox, group_number): - super().__init__(None, group_number, name, status_message, widget, None) - self._tox = tox - self.set_status(constants.TOX_USER_STATUS['NONE']) - - def set_name(self, name): - self._tox.group_set_title(self._number, name) - super().set_name(name) - - def send_message(self, message): - self._tox.group_message_send(self._number, message.encode('utf-8')) - - def new_title(self, title): - super().set_name(title) - - def load_avatar(self): - path = util.curr_directory() + '/images/group.png' - width = self._widget.avatar_label.width() - pixmap = QtGui.QPixmap(path) - self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation)) - self._widget.avatar_label.repaint() - - def remove_invalid_unsent_files(self): - pass - - def get_names(self): - peers_count = self._tox.group_number_peers(self._number) - names = [] - for i in range(peers_count): - name = self._tox.group_peername(self._number, i) - names.append(name) - names = sorted(names, key=lambda n: n.lower()) - return names - - def get_full_status(self): - names = self.get_names() - return '\n'.join(names) - - def get_peer_name(self, peer_number): - return self._tox.group_peername(self._number, peer_number) diff --git a/toxygen/history.py b/toxygen/history.py deleted file mode 100644 index 586981a..0000000 --- a/toxygen/history.py +++ /dev/null @@ -1,215 +0,0 @@ -from sqlite3 import connect -import settings -from os import chdir -import os.path -from toxes import ToxES - - -PAGE_SIZE = 42 - -TIMEOUT = 11 - -SAVE_MESSAGES = 250 - -MESSAGE_OWNER = { - 'ME': 0, - 'FRIEND': 1, - 'NOT_SENT': 2 -} - - -class History: - - def __init__(self, name): - self._name = name - chdir(settings.ProfileHelper.get_path()) - path = settings.ProfileHelper.get_path() + self._name + '.hstr' - if os.path.exists(path): - decr = ToxES.get_instance() - try: - with open(path, 'rb') as fin: - data = fin.read() - if decr.is_data_encrypted(data): - data = decr.pass_decrypt(data) - with open(path, 'wb') as fout: - fout.write(data) - except: - os.remove(path) - db = connect(name + '.hstr', timeout=TIMEOUT) - cursor = db.cursor() - cursor.execute('CREATE TABLE IF NOT EXISTS friends(' - ' tox_id TEXT PRIMARY KEY' - ')') - db.close() - - def save(self): - encr = ToxES.get_instance() - if encr.has_password(): - path = settings.ProfileHelper.get_path() + self._name + '.hstr' - with open(path, 'rb') as fin: - data = fin.read() - data = encr.pass_encrypt(bytes(data)) - with open(path, 'wb') as fout: - fout.write(data) - - def export(self, directory): - path = settings.ProfileHelper.get_path() + self._name + '.hstr' - new_path = directory + self._name + '.hstr' - with open(path, 'rb') as fin: - data = fin.read() - encr = ToxES.get_instance() - if encr.has_password(): - data = encr.pass_encrypt(data) - with open(new_path, 'wb') as fout: - fout.write(data) - - def add_friend_to_db(self, tox_id): - chdir(settings.ProfileHelper.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) - try: - cursor = db.cursor() - cursor.execute('INSERT INTO friends VALUES (?);', (tox_id, )) - cursor.execute('CREATE TABLE id' + tox_id + '(' - ' id INTEGER PRIMARY KEY,' - ' message TEXT,' - ' owner INTEGER,' - ' unix_time REAL,' - ' message_type INTEGER' - ')') - db.commit() - except: - print('Database is locked!') - db.rollback() - finally: - db.close() - - def delete_friend_from_db(self, tox_id): - chdir(settings.ProfileHelper.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) - try: - cursor = db.cursor() - cursor.execute('DELETE FROM friends WHERE tox_id=?;', (tox_id, )) - cursor.execute('DROP TABLE id' + tox_id + ';') - db.commit() - except: - print('Database is locked!') - db.rollback() - finally: - db.close() - - def friend_exists_in_db(self, tox_id): - chdir(settings.ProfileHelper.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) - cursor = db.cursor() - cursor.execute('SELECT 0 FROM friends WHERE tox_id=?', (tox_id, )) - result = cursor.fetchone() - db.close() - return result is not None - - def save_messages_to_db(self, tox_id, messages_iter): - chdir(settings.ProfileHelper.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) - try: - cursor = db.cursor() - cursor.executemany('INSERT INTO id' + tox_id + '(message, owner, unix_time, message_type) ' - 'VALUES (?, ?, ?, ?);', messages_iter) - db.commit() - except: - print('Database is locked!') - db.rollback() - finally: - db.close() - - def update_messages(self, tox_id, unsent_time): - chdir(settings.ProfileHelper.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) - try: - cursor = db.cursor() - cursor.execute('UPDATE id' + tox_id + ' SET owner = 0 ' - 'WHERE unix_time < ' + str(unsent_time) + ' AND owner = 2;') - db.commit() - except: - print('Database is locked!') - db.rollback() - finally: - db.close() - - def delete_message(self, tox_id, time): - start, end = str(time - 0.01), str(time + 0.01) - chdir(settings.ProfileHelper.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) - try: - cursor = db.cursor() - cursor.execute('DELETE FROM id' + tox_id + ' WHERE unix_time < ' + end + ' AND unix_time > ' + - start + ';') - db.commit() - except: - print('Database is locked!') - db.rollback() - finally: - db.close() - - def delete_messages(self, tox_id): - chdir(settings.ProfileHelper.get_path()) - db = connect(self._name + '.hstr', timeout=TIMEOUT) - try: - cursor = db.cursor() - cursor.execute('DELETE FROM id' + tox_id + ';') - db.commit() - except: - print('Database is locked!') - db.rollback() - finally: - db.close() - - def messages_getter(self, tox_id): - return History.MessageGetter(self._name, tox_id) - - class MessageGetter: - - def __init__(self, name, tox_id): - self._count = 0 - self._name = name - self._tox_id = tox_id - self._db = self._cursor = None - - def connect(self): - chdir(settings.ProfileHelper.get_path()) - self._db = connect(self._name + '.hstr', timeout=TIMEOUT) - self._cursor = self._db.cursor() - self._cursor.execute('SELECT message, owner, unix_time, message_type FROM id' + self._tox_id + - ' ORDER BY unix_time DESC;') - - def disconnect(self): - self._db.close() - - def get_one(self): - self.connect() - self.skip() - data = self._cursor.fetchone() - self._count += 1 - self.disconnect() - return data - - def get_all(self): - self.connect() - data = self._cursor.fetchall() - self.disconnect() - self._count = len(data) - return data - - def get(self, count): - self.connect() - self.skip() - data = self._cursor.fetchmany(count) - self.disconnect() - self._count += len(data) - return data - - def skip(self): - if self._count: - self._cursor.fetchmany(self._count) - - def delete_one(self): - if self._count: - self._count -= 1 diff --git a/toxygen/items_factory.py b/toxygen/items_factory.py deleted file mode 100644 index 44a00ad..0000000 --- a/toxygen/items_factory.py +++ /dev/null @@ -1,68 +0,0 @@ -from PyQt5 import QtWidgets, QtCore -from list_items import * - - -class ItemsFactory: - - def __init__(self, friends_list, messages): - self._friends = friends_list - self._messages = messages - - def friend_item(self): - item = ContactItem() - elem = QtWidgets.QListWidgetItem(self._friends) - elem.setSizeHint(QtCore.QSize(250, item.height())) - self._friends.addItem(elem) - self._friends.setItemWidget(elem, item) - return item - - def message_item(self, text, time, name, sent, message_type, append, pixmap): - item = MessageItem(text, time, name, sent, message_type, self._messages) - if pixmap is not None: - item.set_avatar(pixmap) - elem = QtWidgets.QListWidgetItem() - elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) - if append: - self._messages.addItem(elem) - else: - self._messages.insertItem(0, elem) - self._messages.setItemWidget(elem, item) - return item - - def inline_item(self, data, append): - elem = QtWidgets.QListWidgetItem() - item = InlineImageItem(data, self._messages.width(), elem) - elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) - if append: - self._messages.addItem(elem) - else: - self._messages.insertItem(0, elem) - self._messages.setItemWidget(elem, item) - return item - - def unsent_file_item(self, file_name, size, name, time, append): - item = UnsentFileItem(file_name, - size, - name, - time, - self._messages.width()) - elem = QtWidgets.QListWidgetItem() - elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) - if append: - self._messages.addItem(elem) - else: - self._messages.insertItem(0, elem) - self._messages.setItemWidget(elem, item) - return item - - def file_transfer_item(self, data, append): - data.append(self._messages.width()) - item = FileTransferItem(*data) - elem = QtWidgets.QListWidgetItem() - elem.setSizeHint(QtCore.QSize(self._messages.width() - 30, 34)) - if append: - self._messages.addItem(elem) - else: - self._messages.insertItem(0, elem) - self._messages.setItemWidget(elem, item) - return item diff --git a/toxygen/libtox.py b/toxygen/libtox.py deleted file mode 100644 index 752798f..0000000 --- a/toxygen/libtox.py +++ /dev/null @@ -1,59 +0,0 @@ -from platform import system -from ctypes import CDLL -import util - - -class LibToxCore: - - def __init__(self): - if system() == 'Windows': - self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtox.dll') - elif system() == 'Darwin': - self._libtoxcore = CDLL('libtoxcore.dylib') - else: - # libtoxcore and libsodium must be installed in your os - try: - self._libtoxcore = CDLL('libtoxcore.so') - except: - self._libtoxcore = CDLL(util.curr_directory() + '/libs/libtoxcore.so') - - def __getattr__(self, item): - return self._libtoxcore.__getattr__(item) - - -class LibToxAV: - - def __init__(self): - if system() == 'Windows': - # on Windows av api is in libtox.dll - self._libtoxav = CDLL(util.curr_directory() + '/libs/libtox.dll') - elif system() == 'Darwin': - self._libtoxav = CDLL('libtoxav.dylib') - else: - # /usr/lib/libtoxav.so must exists - try: - self._libtoxav = CDLL('libtoxav.so') - except: - self._libtoxav = CDLL(util.curr_directory() + '/libs/libtoxav.so') - - def __getattr__(self, item): - return self._libtoxav.__getattr__(item) - - -class LibToxEncryptSave: - - def __init__(self): - if system() == 'Windows': - # on Windows profile encryption api is in libtox.dll - self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtox.dll') - elif system() == 'Darwin': - self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.dylib') - else: - # /usr/lib/libtoxencryptsave.so must exists - try: - self._lib_tox_encrypt_save = CDLL('libtoxencryptsave.so') - except: - self._lib_tox_encrypt_save = CDLL(util.curr_directory() + '/libs/libtoxencryptsave.so') - - def __getattr__(self, item): - return self._lib_tox_encrypt_save.__getattr__(item) diff --git a/toxygen/list_items.py b/toxygen/list_items.py deleted file mode 100644 index 9b92f2a..0000000 --- a/toxygen/list_items.py +++ /dev/null @@ -1,545 +0,0 @@ -from toxcore_enums_and_consts import * -from PyQt5 import QtCore, QtGui, QtWidgets -import profile -from file_transfers import TOX_FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR -from util import curr_directory, convert_time, curr_time -from widgets import DataLabel, create_menu -import html as h -import smileys -import settings -import re - - -class MessageEdit(QtWidgets.QTextBrowser): - - def __init__(self, text, width, message_type, parent=None): - super(MessageEdit, self).__init__(parent) - self.urls = {} - self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.setWordWrapMode(QtGui.QTextOption.WrapAtWordBoundaryOrAnywhere) - self.document().setTextWidth(width) - self.setOpenExternalLinks(True) - self.setAcceptRichText(True) - self.setOpenLinks(False) - path = smileys.SmileyLoader.get_instance().get_smileys_path() - if path is not None: - self.setSearchPaths([path]) - self.document().setDefaultStyleSheet('a { color: #306EFF; }') - text = self.decoratedText(text) - if message_type != TOX_MESSAGE_TYPE['NORMAL']: - self.setHtml('

' + text + '

') - else: - self.setHtml(text) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPixelSize(settings.Settings.get_instance()['message_font_size']) - font.setBold(False) - self.setFont(font) - self.resize(width, self.document().size().height()) - self.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.LinksAccessibleByMouse) - self.anchorClicked.connect(self.on_anchor_clicked) - - def contextMenuEvent(self, event): - menu = create_menu(self.createStandardContextMenu(event.pos())) - quote = menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Quote selected text')) - quote.triggered.connect(self.quote_text) - text = self.textCursor().selection().toPlainText() - if not text: - quote.setEnabled(False) - else: - import plugin_support - submenu = plugin_support.PluginLoader.get_instance().get_message_menu(menu, text) - if len(submenu): - plug = menu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) - plug.addActions(submenu) - menu.popup(event.globalPos()) - menu.exec_(event.globalPos()) - del menu - - def quote_text(self): - text = self.textCursor().selection().toPlainText() - if text: - import mainscreen - window = mainscreen.MainWindow.get_instance() - text = '>' + '\n>'.join(text.split('\n')) - if window.messageEdit.toPlainText(): - text = '\n' + text - window.messageEdit.appendPlainText(text) - - def on_anchor_clicked(self, url): - text = str(url.toString()) - if text.startswith('tox:'): - import menu - self.add_contact = menu.AddContact(text[4:]) - self.add_contact.show() - else: - QtGui.QDesktopServices.openUrl(url) - self.clearFocus() - - def addAnimation(self, url, fileName): - movie = QtGui.QMovie(self) - movie.setFileName(fileName) - self.urls[movie] = url - movie.frameChanged[int].connect(lambda x: self.animate(movie)) - movie.start() - - def animate(self, movie): - self.document().addResource(QtGui.QTextDocument.ImageResource, - self.urls[movie], - movie.currentPixmap()) - self.setLineWrapColumnOrWidth(self.lineWrapColumnOrWidth()) - - def decoratedText(self, text): - text = h.escape(text) # replace < and > - exp = QtCore.QRegExp( - '(' - '(?:\\b)((www\\.)|(http[s]?|ftp)://)' - '\\w+\\S+)' - '|(?:\\b)(file:///)([\\S| ]*)' - '|(?:\\b)(tox:[a-zA-Z\\d]{76}$)' - '|(?:\\b)(mailto:\\S+@\\S+\\.\\S+)' - '|(?:\\b)(tox:\\S+@\\S+)') - offset = exp.indexIn(text, 0) - while offset != -1: # add links - url = exp.cap() - if exp.cap(2) == 'www.': - html = '{0}'.format(url) - else: - html = '{0}'.format(url) - text = text[:offset] + html + text[offset + len(exp.cap()):] - offset += len(html) - offset = exp.indexIn(text, offset) - arr = text.split('\n') - for i in range(len(arr)): # quotes - if arr[i].startswith('>'): - arr[i] = '' + arr[i][4:] + '' - text = '
'.join(arr) - text = smileys.SmileyLoader.get_instance().add_smileys_to_text(text, self) # smileys - return text - - -class MessageItem(QtWidgets.QWidget): - """ - Message in messages list - """ - def __init__(self, text, time, user='', sent=True, message_type=TOX_MESSAGE_TYPE['NORMAL'], parent=None): - QtWidgets.QWidget.__init__(self, parent) - self.name = DataLabel(self) - self.name.setGeometry(QtCore.QRect(2, 2, 95, 23)) - self.name.setTextFormat(QtCore.Qt.PlainText) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(11) - font.setBold(True) - self.name.setFont(font) - self.name.setText(user) - - self.time = QtWidgets.QLabel(self) - self.time.setGeometry(QtCore.QRect(parent.width() - 60, 0, 50, 25)) - font.setPointSize(10) - font.setBold(False) - self.time.setFont(font) - self._time = time - if not sent: - movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif') - self.time.setMovie(movie) - movie.start() - self.t = True - else: - self.time.setText(convert_time(time)) - self.t = False - - self.message = MessageEdit(text, parent.width() - 160, message_type, self) - if message_type != TOX_MESSAGE_TYPE['NORMAL']: - self.name.setStyleSheet("QLabel { color: #5CB3FF; }") - self.message.setAlignment(QtCore.Qt.AlignCenter) - self.time.setStyleSheet("QLabel { color: #5CB3FF; }") - self.message.setGeometry(QtCore.QRect(100, 0, parent.width() - 160, self.message.height())) - self.setFixedHeight(self.message.height()) - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.RightButton and event.x() > self.time.x(): - self.listMenu = QtWidgets.QMenu() - delete_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Delete message')) - delete_item.triggered.connect(self.delete) - parent_position = self.time.mapToGlobal(QtCore.QPoint(0, 0)) - self.listMenu.move(parent_position) - self.listMenu.show() - - def delete(self): - pr = profile.Profile.get_instance() - pr.delete_message(self._time) - - def mark_as_sent(self): - if self.t: - self.time.setText(convert_time(self._time)) - self.t = False - return True - return False - - def set_avatar(self, pixmap): - self.name.setAlignment(QtCore.Qt.AlignCenter) - self.message.setAlignment(QtCore.Qt.AlignVCenter) - self.setFixedHeight(max(self.height(), 36)) - self.name.setFixedHeight(self.height()) - self.message.setFixedHeight(self.height()) - self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation)) - - def select_text(self, text): - tmp = self.message.toHtml() - text = h.escape(text) - strings = re.findall(text, tmp, flags=re.IGNORECASE) - for s in strings: - tmp = self.replace_all(tmp, s) - self.message.setHtml(tmp) - - @staticmethod - def replace_all(text, substring): - i, l = 0, len(substring) - while i < len(text) - l + 1: - index = text[i:].find(substring) - if index == -1: - break - i += index - lgt, rgt = text[i:].find('<'), text[i:].find('>') - if rgt < lgt: - i += rgt + 1 - continue - sub = '{}'.format(substring) - text = text[:i] + sub + text[i + l:] - i += len(sub) - return text - - -class ContactItem(QtWidgets.QWidget): - """ - Contact in friends list - """ - - def __init__(self, parent=None): - QtWidgets.QWidget.__init__(self, parent) - mode = settings.Settings.get_instance()['compact_mode'] - self.setBaseSize(QtCore.QSize(250, 40 if mode else 70)) - self.avatar_label = QtWidgets.QLabel(self) - size = 32 if mode else 64 - self.avatar_label.setGeometry(QtCore.QRect(3, 4, size, size)) - self.avatar_label.setScaledContents(False) - self.avatar_label.setAlignment(QtCore.Qt.AlignCenter) - self.name = DataLabel(self) - self.name.setGeometry(QtCore.QRect(50 if mode else 75, 3 if mode else 10, 150, 15 if mode else 25)) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(10 if mode else 12) - font.setBold(True) - self.name.setFont(font) - self.status_message = DataLabel(self) - self.status_message.setGeometry(QtCore.QRect(50 if mode else 75, 20 if mode else 30, 170, 15 if mode else 20)) - font.setPointSize(10) - font.setBold(False) - self.status_message.setFont(font) - self.connection_status = StatusCircle(self) - self.connection_status.setGeometry(QtCore.QRect(230, -2 if mode else 5, 32, 32)) - self.messages = UnreadMessagesCount(self) - self.messages.setGeometry(QtCore.QRect(20 if mode else 52, 20 if mode else 50, 30, 20)) - - -class StatusCircle(QtWidgets.QWidget): - """ - Connection status - """ - def __init__(self, parent): - QtWidgets.QWidget.__init__(self, parent) - self.setGeometry(0, 0, 32, 32) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) - self.unread = False - - def update(self, status, unread_messages=None): - if unread_messages is None: - unread_messages = self.unread - else: - self.unread = unread_messages - if status == TOX_USER_STATUS['NONE']: - name = 'online' - elif status == TOX_USER_STATUS['AWAY']: - name = 'idle' - elif status == TOX_USER_STATUS['BUSY']: - name = 'busy' - else: - name = 'offline' - if unread_messages: - name += '_notification' - self.label.setGeometry(QtCore.QRect(0, 0, 32, 32)) - else: - self.label.setGeometry(QtCore.QRect(2, 0, 32, 32)) - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(name)) - self.label.setPixmap(pixmap) - - -class UnreadMessagesCount(QtWidgets.QWidget): - - def __init__(self, parent=None): - super(UnreadMessagesCount, self).__init__(parent) - self.resize(30, 20) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(0, 0, 30, 20)) - self.label.setVisible(False) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(12) - font.setBold(True) - self.label.setFont(font) - self.label.setAlignment(QtCore.Qt.AlignVCenter | QtCore.Qt.AlignCenter) - color = settings.Settings.get_instance()['unread_color'] - self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') - - def update(self, messages_count): - color = settings.Settings.get_instance()['unread_color'] - self.label.setStyleSheet('QLabel { color: white; background-color: ' + color + '; border-radius: 10; }') - if messages_count: - self.label.setVisible(True) - self.label.setText(str(messages_count)) - else: - self.label.setVisible(False) - - -class FileTransferItem(QtWidgets.QListWidget): - - def __init__(self, file_name, size, time, user, friend_number, file_number, state, width, parent=None): - - QtWidgets.QListWidget.__init__(self, parent) - self.resize(QtCore.QSize(width, 34)) - if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - elif state in PAUSED_FILE_TRANSFERS: - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - else: - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - - self.name = DataLabel(self) - self.name.setGeometry(QtCore.QRect(3, 7, 95, 25)) - self.name.setTextFormat(QtCore.Qt.PlainText) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(11) - font.setBold(True) - self.name.setFont(font) - self.name.setText(user) - - self.time = QtWidgets.QLabel(self) - self.time.setGeometry(QtCore.QRect(width - 60, 7, 50, 25)) - font.setPointSize(10) - font.setBold(False) - self.time.setFont(font) - self.time.setText(convert_time(time)) - - self.cancel = QtWidgets.QPushButton(self) - self.cancel.setGeometry(QtCore.QRect(width - 125, 2, 30, 30)) - pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png') - icon = QtGui.QIcon(pixmap) - self.cancel.setIcon(icon) - self.cancel.setIconSize(QtCore.QSize(30, 30)) - self.cancel.setVisible(state in ACTIVE_FILE_TRANSFERS) - self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number)) - self.cancel.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none;}') - - self.accept_or_pause = QtWidgets.QPushButton(self) - self.accept_or_pause.setGeometry(QtCore.QRect(width - 170, 2, 30, 30)) - if state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - self.accept_or_pause.setVisible(True) - self.button_update('accept') - elif state in DO_NOT_SHOW_ACCEPT_BUTTON: - self.accept_or_pause.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # setup for continue - self.accept_or_pause.setVisible(True) - self.button_update('resume') - else: # pause - self.accept_or_pause.setVisible(True) - self.button_update('pause') - self.accept_or_pause.clicked.connect(lambda: self.accept_or_pause_transfer(friend_number, file_number, size)) - - self.accept_or_pause.setStyleSheet('QPushButton:hover { border: 1px solid #3A3939; background-color: none}') - - self.pb = QtWidgets.QProgressBar(self) - self.pb.setGeometry(QtCore.QRect(100, 7, 100, 20)) - self.pb.setValue(0) - self.pb.setStyleSheet('QProgressBar { background-color: #302F2F; }') - self.pb.setVisible(state in SHOW_PROGRESS_BAR) - - self.file_name = DataLabel(self) - self.file_name.setGeometry(QtCore.QRect(210, 7, width - 420, 20)) - font.setPointSize(12) - self.file_name.setFont(font) - file_size = size // 1024 - if not file_size: - file_size = '{}B'.format(size) - elif file_size >= 1024: - file_size = '{}MB'.format(file_size // 1024) - else: - file_size = '{}KB'.format(file_size) - file_data = '{} {}'.format(file_size, file_name) - self.file_name.setText(file_data) - self.file_name.setToolTip(file_name) - self.saved_name = file_name - self.time_left = QtWidgets.QLabel(self) - self.time_left.setGeometry(QtCore.QRect(width - 92, 7, 30, 20)) - font.setPointSize(10) - self.time_left.setFont(font) - self.time_left.setVisible(state == TOX_FILE_TRANSFER_STATE['RUNNING']) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self.paused = False - - def cancel_transfer(self, friend_number, file_number): - pr = profile.Profile.get_instance() - pr.cancel_transfer(friend_number, file_number) - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - self.cancel.setVisible(False) - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - - def accept_or_pause_transfer(self, friend_number, file_number, size): - if self.state == TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - self.pb.setVisible(True) - if directory: - pr = profile.Profile.get_instance() - pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size) - self.button_update('pause') - elif self.state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: # resume - self.paused = False - profile.Profile.get_instance().resume_transfer(friend_number, file_number) - self.button_update('pause') - self.state = TOX_FILE_TRANSFER_STATE['RUNNING'] - else: # pause - self.paused = True - self.state = TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] - profile.Profile.get_instance().pause_transfer(friend_number, file_number) - self.button_update('resume') - self.accept_or_pause.clearFocus() - - def button_update(self, path): - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(path)) - icon = QtGui.QIcon(pixmap) - self.accept_or_pause.setIcon(icon) - self.accept_or_pause.setIconSize(QtCore.QSize(30, 30)) - - def update_transfer_state(self, state, progress, time): - self.pb.setValue(int(progress * 100)) - if time + 1: - m, s = divmod(time, 60) - self.time_left.setText('{0:02d}:{1:02d}'.format(m, s)) - if self.state != state and self.state in ACTIVE_FILE_TRANSFERS: - if state == TOX_FILE_TRANSFER_STATE['CANCELLED']: - self.setStyleSheet('QListWidget { border: 1px solid #B40404; }') - self.cancel.setVisible(False) - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['FINISHED']: - self.accept_or_pause.setVisible(False) - self.pb.setVisible(False) - self.cancel.setVisible(False) - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND']: - self.accept_or_pause.setVisible(False) - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER']: - self.button_update('resume') # setup button continue - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(False) - elif state == TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED']: - self.setStyleSheet('QListWidget { border: 1px solid #FF8000; }') - self.accept_or_pause.setVisible(False) - self.time_left.setVisible(False) - self.pb.setVisible(False) - elif not self.paused: # active - self.pb.setVisible(True) - self.accept_or_pause.setVisible(True) # setup to pause - self.button_update('pause') - self.setStyleSheet('QListWidget { border: 1px solid green; }') - self.state = state - self.time_left.setVisible(True) - - def mark_as_sent(self): - return False - - -class UnsentFileItem(FileTransferItem): - - def __init__(self, file_name, size, user, time, width, parent=None): - super(UnsentFileItem, self).__init__(file_name, size, time, user, -1, -1, - TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'], width, parent) - self._time = time - self.pb.setVisible(False) - movie = QtGui.QMovie(curr_directory() + '/images/spinner.gif') - self.time.setMovie(movie) - movie.start() - - def cancel_transfer(self, *args): - pr = profile.Profile.get_instance() - pr.cancel_not_started_transfer(self._time) - - -class InlineImageItem(QtWidgets.QScrollArea): - - def __init__(self, data, width, elem): - - QtWidgets.QScrollArea.__init__(self) - self.setFocusPolicy(QtCore.Qt.NoFocus) - self._elem = elem - self._image_label = QtWidgets.QLabel(self) - self._image_label.raise_() - self.setWidget(self._image_label) - self._image_label.setScaledContents(False) - self._pixmap = QtGui.QPixmap() - self._pixmap.loadFromData(data, 'PNG') - self._max_size = width - 30 - self._resize_needed = not (self._pixmap.width() <= self._max_size) - self._full_size = not self._resize_needed - if not self._resize_needed: - self._image_label.setPixmap(self._pixmap) - self.resize(QtCore.QSize(self._max_size + 5, self._pixmap.height() + 5)) - self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) - else: - pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) - self._image_label.setPixmap(pixmap) - self.resize(QtCore.QSize(self._max_size + 5, pixmap.height())) - self._image_label.setGeometry(5, 0, self._max_size + 5, pixmap.height()) - self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) - - def mouseReleaseEvent(self, event): - if event.button() == QtCore.Qt.LeftButton and self._resize_needed: # scale inline - if self._full_size: - pixmap = self._pixmap.scaled(self._max_size, self._max_size, QtCore.Qt.KeepAspectRatio) - self._image_label.setPixmap(pixmap) - self.resize(QtCore.QSize(self._max_size, pixmap.height())) - self._image_label.setGeometry(5, 0, pixmap.width(), pixmap.height()) - else: - self._image_label.setPixmap(self._pixmap) - self.resize(QtCore.QSize(self._max_size, self._pixmap.height() + 17)) - self._image_label.setGeometry(5, 0, self._pixmap.width(), self._pixmap.height()) - self._full_size = not self._full_size - self._elem.setSizeHint(QtCore.QSize(self.width(), self.height())) - elif event.button() == QtCore.Qt.RightButton: # save inline - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - if directory: - fl = QtCore.QFile(directory + '/toxygen_inline_' + curr_time().replace(':', '_') + '.png') - self._pixmap.save(fl, 'PNG') - - def mark_as_sent(self): - return False diff --git a/toxygen/loginscreen.py b/toxygen/loginscreen.py deleted file mode 100644 index 77aa5ba..0000000 --- a/toxygen/loginscreen.py +++ /dev/null @@ -1,103 +0,0 @@ -from PyQt5 import QtWidgets, QtCore -from widgets import * - - -class NickEdit(LineEdit): - - def __init__(self, parent): - super(NickEdit, self).__init__(parent) - self.parent = parent - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Return: - self.parent.create_profile() - else: - super(NickEdit, self).keyPressEvent(event) - - -class LoginScreen(CenteredWidget): - - def __init__(self): - super(LoginScreen, self).__init__() - self.initUI() - self.center() - - def initUI(self): - self.resize(400, 200) - self.setMinimumSize(QtCore.QSize(400, 200)) - self.setMaximumSize(QtCore.QSize(400, 200)) - self.new_profile = QtWidgets.QPushButton(self) - self.new_profile.setGeometry(QtCore.QRect(20, 150, 171, 27)) - self.new_profile.clicked.connect(self.create_profile) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(20, 70, 101, 17)) - self.new_name = NickEdit(self) - self.new_name.setGeometry(QtCore.QRect(20, 100, 171, 31)) - self.load_profile = QtWidgets.QPushButton(self) - self.load_profile.setGeometry(QtCore.QRect(220, 150, 161, 27)) - self.load_profile.clicked.connect(self.load_ex_profile) - self.default = QtWidgets.QCheckBox(self) - self.default.setGeometry(QtCore.QRect(220, 110, 131, 22)) - self.groupBox = QtWidgets.QGroupBox(self) - self.groupBox.setGeometry(QtCore.QRect(210, 40, 181, 151)) - self.comboBox = QtWidgets.QComboBox(self.groupBox) - self.comboBox.setGeometry(QtCore.QRect(10, 30, 161, 27)) - self.groupBox_2 = QtWidgets.QGroupBox(self) - self.groupBox_2.setGeometry(QtCore.QRect(10, 40, 191, 151)) - self.toxygen = QtWidgets.QLabel(self) - self.groupBox.raise_() - self.groupBox_2.raise_() - self.comboBox.raise_() - self.default.raise_() - self.load_profile.raise_() - self.new_name.raise_() - self.new_profile.raise_() - self.toxygen.setGeometry(QtCore.QRect(160, 8, 90, 25)) - font = QtGui.QFont() - font.setFamily("Impact") - font.setPointSize(16) - self.toxygen.setFont(font) - self.toxygen.setObjectName("toxygen") - self.type = 0 - self.number = -1 - self.load_as_default = False - self.name = None - self.retranslateUi() - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.new_name.setPlaceholderText(QtWidgets.QApplication.translate("login", "Profile name")) - self.setWindowTitle(QtWidgets.QApplication.translate("login", "Log in")) - self.new_profile.setText(QtWidgets.QApplication.translate("login", "Create")) - self.label.setText(QtWidgets.QApplication.translate("login", "Profile name:")) - self.load_profile.setText(QtWidgets.QApplication.translate("login", "Load profile")) - self.default.setText(QtWidgets.QApplication.translate("login", "Use as default")) - self.groupBox.setTitle(QtWidgets.QApplication.translate("login", "Load existing profile")) - self.groupBox_2.setTitle(QtWidgets.QApplication.translate("login", "Create new profile")) - self.toxygen.setText(QtWidgets.QApplication.translate("login", "toxygen")) - - def create_profile(self): - self.type = 1 - self.name = self.new_name.text() - self.close() - - def load_ex_profile(self): - if not self.create_only: - self.type = 2 - self.number = self.comboBox.currentIndex() - self.load_as_default = self.default.isChecked() - self.close() - - def update_select(self, data): - list_of_profiles = [] - for elem in data: - list_of_profiles.append(elem) - self.comboBox.addItems(list_of_profiles) - self.create_only = not list_of_profiles - - def update_on_close(self, func): - self.onclose = func - - def closeEvent(self, event): - self.onclose(self.type, self.number, self.load_as_default, self.name) - event.accept() diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py deleted file mode 100644 index 93ec72d..0000000 --- a/toxygen/mainscreen.py +++ /dev/null @@ -1,757 +0,0 @@ -from menu import * -from profile import * -from list_items import * -from widgets import MultilineEdit, ComboBox -import plugin_support -from mainscreen_widgets import * -import settings -import toxes - - -class MainWindow(QtWidgets.QMainWindow, Singleton): - - def __init__(self, tox, reset, tray): - super().__init__() - Singleton.__init__(self) - self.reset = reset - self.tray = tray - self.setAcceptDrops(True) - self.initUI(tox) - self._saved = False - if settings.Settings.get_instance()['show_welcome_screen']: - self.ws = WelcomeScreen() - - def setup_menu(self, window): - self.menubar = QtWidgets.QMenuBar(window) - self.menubar.setObjectName("menubar") - self.menubar.setNativeMenuBar(False) - self.menubar.setMinimumSize(self.width(), 25) - self.menubar.setMaximumSize(self.width(), 25) - self.menubar.setBaseSize(self.width(), 25) - self.menuProfile = QtWidgets.QMenu(self.menubar) - - self.menuProfile = QtWidgets.QMenu(self.menubar) - self.menuProfile.setObjectName("menuProfile") - self.menuSettings = QtWidgets.QMenu(self.menubar) - self.menuSettings.setObjectName("menuSettings") - self.menuPlugins = QtWidgets.QMenu(self.menubar) - self.menuPlugins.setObjectName("menuPlugins") - self.menuAbout = QtWidgets.QMenu(self.menubar) - self.menuAbout.setObjectName("menuAbout") - - self.actionAdd_friend = QtWidgets.QAction(window) - self.actionAdd_gc = QtWidgets.QAction(window) - self.actionAdd_friend.setObjectName("actionAdd_friend") - self.actionprofilesettings = QtWidgets.QAction(window) - self.actionprofilesettings.setObjectName("actionprofilesettings") - self.actionPrivacy_settings = QtWidgets.QAction(window) - self.actionPrivacy_settings.setObjectName("actionPrivacy_settings") - self.actionInterface_settings = QtWidgets.QAction(window) - self.actionInterface_settings.setObjectName("actionInterface_settings") - self.actionNotifications = QtWidgets.QAction(window) - self.actionNotifications.setObjectName("actionNotifications") - self.actionNetwork = QtWidgets.QAction(window) - self.actionNetwork.setObjectName("actionNetwork") - self.actionAbout_program = QtWidgets.QAction(window) - self.actionAbout_program.setObjectName("actionAbout_program") - self.updateSettings = QtWidgets.QAction(window) - self.actionSettings = QtWidgets.QAction(window) - self.actionSettings.setObjectName("actionSettings") - self.audioSettings = QtWidgets.QAction(window) - self.videoSettings = QtWidgets.QAction(window) - self.pluginData = QtWidgets.QAction(window) - self.importPlugin = QtWidgets.QAction(window) - self.reloadPlugins = QtWidgets.QAction(window) - self.lockApp = QtWidgets.QAction(window) - self.menuProfile.addAction(self.actionAdd_friend) - self.menuProfile.addAction(self.actionAdd_gc) - self.menuProfile.addAction(self.actionSettings) - self.menuProfile.addAction(self.lockApp) - self.menuSettings.addAction(self.actionPrivacy_settings) - self.menuSettings.addAction(self.actionInterface_settings) - self.menuSettings.addAction(self.actionNotifications) - self.menuSettings.addAction(self.actionNetwork) - self.menuSettings.addAction(self.audioSettings) - self.menuSettings.addAction(self.videoSettings) - self.menuSettings.addAction(self.updateSettings) - self.menuPlugins.addAction(self.pluginData) - self.menuPlugins.addAction(self.importPlugin) - self.menuPlugins.addAction(self.reloadPlugins) - self.menuAbout.addAction(self.actionAbout_program) - - self.menubar.addAction(self.menuProfile.menuAction()) - self.menubar.addAction(self.menuSettings.menuAction()) - self.menubar.addAction(self.menuPlugins.menuAction()) - self.menubar.addAction(self.menuAbout.menuAction()) - - self.actionAbout_program.triggered.connect(self.about_program) - self.actionNetwork.triggered.connect(self.network_settings) - self.actionAdd_friend.triggered.connect(self.add_contact) - self.actionAdd_gc.triggered.connect(self.create_gc) - self.actionSettings.triggered.connect(self.profile_settings) - self.actionPrivacy_settings.triggered.connect(self.privacy_settings) - self.actionInterface_settings.triggered.connect(self.interface_settings) - self.actionNotifications.triggered.connect(self.notification_settings) - self.audioSettings.triggered.connect(self.audio_settings) - self.videoSettings.triggered.connect(self.video_settings) - self.updateSettings.triggered.connect(self.update_settings) - self.pluginData.triggered.connect(self.plugins_menu) - self.lockApp.triggered.connect(self.lock_app) - self.importPlugin.triggered.connect(self.import_plugin) - self.reloadPlugins.triggered.connect(self.reload_plugins) - - def languageChange(self, *args, **kwargs): - self.retranslateUi() - - def event(self, event): - if event.type() == QtCore.QEvent.WindowActivate: - self.tray.setIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) - self.messages.repaint() - return super(MainWindow, self).event(event) - - def retranslateUi(self): - self.lockApp.setText(QtWidgets.QApplication.translate("MainWindow", "Lock")) - self.menuPlugins.setTitle(QtWidgets.QApplication.translate("MainWindow", "Plugins")) - self.pluginData.setText(QtWidgets.QApplication.translate("MainWindow", "List of plugins")) - self.menuProfile.setTitle(QtWidgets.QApplication.translate("MainWindow", "Profile")) - self.menuSettings.setTitle(QtWidgets.QApplication.translate("MainWindow", "Settings")) - self.menuAbout.setTitle(QtWidgets.QApplication.translate("MainWindow", "About")) - self.actionAdd_friend.setText(QtWidgets.QApplication.translate("MainWindow", "Add contact")) - self.actionAdd_gc.setText(QtWidgets.QApplication.translate("MainWindow", "Create group chat")) - self.actionprofilesettings.setText(QtWidgets.QApplication.translate("MainWindow", "Profile")) - self.actionPrivacy_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Privacy")) - self.actionInterface_settings.setText(QtWidgets.QApplication.translate("MainWindow", "Interface")) - self.actionNotifications.setText(QtWidgets.QApplication.translate("MainWindow", "Notifications")) - self.actionNetwork.setText(QtWidgets.QApplication.translate("MainWindow", "Network")) - self.actionAbout_program.setText(QtWidgets.QApplication.translate("MainWindow", "About program")) - self.actionSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Settings")) - self.audioSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Audio")) - self.videoSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Video")) - self.updateSettings.setText(QtWidgets.QApplication.translate("MainWindow", "Updates")) - self.contact_name.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) - self.sendMessageButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Send message")) - self.callButton.setToolTip(QtWidgets.QApplication.translate("MainWindow", "Start audio call with friend")) - self.online_contacts.clear() - self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "All")) - self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online")) - self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first")) - self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Name")) - self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online and by name")) - self.online_contacts.addItem(QtWidgets.QApplication.translate("MainWindow", "Online first and by name")) - ind = Settings.get_instance()['sorting'] - d = {0: 0, 1: 1, 2: 2, 3: 4, 1 | 4: 4, 2 | 4: 5} - self.online_contacts.setCurrentIndex(d[ind]) - self.importPlugin.setText(QtWidgets.QApplication.translate("MainWindow", "Import plugin")) - self.reloadPlugins.setText(QtWidgets.QApplication.translate("MainWindow", "Reload plugins")) - - def setup_right_bottom(self, Form): - Form.resize(650, 60) - self.messageEdit = MessageArea(Form, self) - self.messageEdit.setGeometry(QtCore.QRect(0, 3, 450, 55)) - self.messageEdit.setObjectName("messageEdit") - font = QtGui.QFont() - font.setPointSize(11) - font.setFamily(settings.Settings.get_instance()['font']) - self.messageEdit.setFont(font) - - self.sendMessageButton = QtWidgets.QPushButton(Form) - self.sendMessageButton.setGeometry(QtCore.QRect(565, 3, 60, 55)) - self.sendMessageButton.setObjectName("sendMessageButton") - - self.menuButton = MenuButton(Form, self.show_menu) - self.menuButton.setGeometry(QtCore.QRect(QtCore.QRect(455, 3, 55, 55))) - - pixmap = QtGui.QPixmap('send.png') - icon = QtGui.QIcon(pixmap) - self.sendMessageButton.setIcon(icon) - self.sendMessageButton.setIconSize(QtCore.QSize(45, 60)) - - pixmap = QtGui.QPixmap('menu.png') - icon = QtGui.QIcon(pixmap) - self.menuButton.setIcon(icon) - self.menuButton.setIconSize(QtCore.QSize(40, 40)) - - self.sendMessageButton.clicked.connect(self.send_message) - - QtCore.QMetaObject.connectSlotsByName(Form) - - def setup_left_center_menu(self, Form): - Form.resize(270, 25) - self.search_label = QtWidgets.QLabel(Form) - self.search_label.setGeometry(QtCore.QRect(3, 2, 20, 20)) - pixmap = QtGui.QPixmap() - pixmap.load(curr_directory() + '/images/search.png') - self.search_label.setScaledContents(False) - self.search_label.setPixmap(pixmap) - - self.contact_name = LineEdit(Form) - self.contact_name.setGeometry(QtCore.QRect(0, 0, 150, 25)) - self.contact_name.setObjectName("contact_name") - self.contact_name.textChanged.connect(self.filtering) - - self.online_contacts = ComboBox(Form) - self.online_contacts.setGeometry(QtCore.QRect(150, 0, 120, 25)) - self.online_contacts.activated[int].connect(lambda x: self.filtering()) - self.search_label.raise_() - - QtCore.QMetaObject.connectSlotsByName(Form) - - def setup_left_top(self, Form): - Form.setCursor(QtCore.Qt.PointingHandCursor) - Form.setMinimumSize(QtCore.QSize(270, 75)) - Form.setMaximumSize(QtCore.QSize(270, 75)) - Form.setBaseSize(QtCore.QSize(270, 75)) - self.avatar_label = Form.avatar_label = QtWidgets.QLabel(Form) - self.avatar_label.setGeometry(QtCore.QRect(5, 5, 64, 64)) - self.avatar_label.setScaledContents(False) - self.avatar_label.setAlignment(QtCore.Qt.AlignCenter) - self.name = Form.name = DataLabel(Form) - Form.name.setGeometry(QtCore.QRect(75, 15, 150, 25)) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(14) - font.setBold(True) - Form.name.setFont(font) - Form.name.setObjectName("name") - self.status_message = Form.status_message = DataLabel(Form) - Form.status_message.setGeometry(QtCore.QRect(75, 35, 170, 25)) - font.setPointSize(12) - font.setBold(False) - Form.status_message.setFont(font) - Form.status_message.setObjectName("status_message") - self.connection_status = Form.connection_status = StatusCircle(Form) - Form.connection_status.setGeometry(QtCore.QRect(230, 10, 32, 32)) - self.avatar_label.mouseReleaseEvent = self.profile_settings - self.status_message.mouseReleaseEvent = self.profile_settings - self.name.mouseReleaseEvent = self.profile_settings - self.connection_status.raise_() - Form.connection_status.setObjectName("connection_status") - - def setup_right_top(self, Form): - Form.resize(650, 75) - self.account_avatar = QtWidgets.QLabel(Form) - self.account_avatar.setGeometry(QtCore.QRect(10, 5, 64, 64)) - self.account_avatar.setScaledContents(False) - self.account_name = DataLabel(Form) - self.account_name.setGeometry(QtCore.QRect(100, 0, 400, 25)) - self.account_name.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse) - font = QtGui.QFont() - font.setFamily(settings.Settings.get_instance()['font']) - font.setPointSize(14) - font.setBold(True) - self.account_name.setFont(font) - self.account_name.setObjectName("account_name") - self.account_status = DataLabel(Form) - self.account_status.setGeometry(QtCore.QRect(100, 20, 400, 25)) - self.account_status.setTextInteractionFlags(QtCore.Qt.LinksAccessibleByMouse) - font.setPointSize(12) - font.setBold(False) - self.account_status.setFont(font) - self.account_status.setObjectName("account_status") - self.callButton = QtWidgets.QPushButton(Form) - self.callButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) - self.callButton.setObjectName("callButton") - self.callButton.clicked.connect(lambda: self.profile.call_click(True)) - self.videocallButton = QtWidgets.QPushButton(Form) - self.videocallButton.setGeometry(QtCore.QRect(550, 5, 50, 50)) - self.videocallButton.setObjectName("videocallButton") - self.videocallButton.clicked.connect(lambda: self.profile.call_click(True, True)) - self.update_call_state('call') - self.typing = QtWidgets.QLabel(Form) - self.typing.setGeometry(QtCore.QRect(500, 25, 50, 30)) - pixmap = QtGui.QPixmap(QtCore.QSize(50, 30)) - pixmap.load(curr_directory() + '/images/typing.png') - self.typing.setScaledContents(False) - self.typing.setPixmap(pixmap.scaled(50, 30, QtCore.Qt.KeepAspectRatio)) - self.typing.setVisible(False) - QtCore.QMetaObject.connectSlotsByName(Form) - - def setup_left_center(self, widget): - self.friends_list = QtWidgets.QListWidget(widget) - self.friends_list.setObjectName("friends_list") - self.friends_list.setGeometry(0, 0, 270, 310) - self.friends_list.clicked.connect(self.friend_click) - self.friends_list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) - self.friends_list.customContextMenuRequested.connect(self.friend_right_click) - self.friends_list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) - self.friends_list.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - self.friends_list.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.friends_list.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu) - - def setup_right_center(self, widget): - self.messages = QtWidgets.QListWidget(widget) - self.messages.setGeometry(0, 0, 620, 310) - self.messages.setObjectName("messages") - self.messages.setSpacing(1) - self.messages.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) - self.messages.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) - self.messages.focusOutEvent = lambda event: self.messages.clearSelection() - self.messages.verticalScrollBar().setContextMenuPolicy(QtCore.Qt.NoContextMenu) - - def load(pos): - if not pos: - self.profile.load_history() - self.messages.verticalScrollBar().setValue(1) - self.messages.verticalScrollBar().valueChanged.connect(load) - self.messages.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) - self.messages.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) - - def initUI(self, tox): - self.setMinimumSize(920, 500) - s = Settings.get_instance() - self.setGeometry(s['x'], s['y'], s['width'], s['height']) - self.setWindowTitle('Toxygen') - os.chdir(curr_directory() + '/images/') - menu = QtWidgets.QWidget() - main = QtWidgets.QWidget() - grid = QtWidgets.QGridLayout() - search = QtWidgets.QWidget() - name = QtWidgets.QWidget() - info = QtWidgets.QWidget() - main_list = QtWidgets.QWidget() - messages = QtWidgets.QWidget() - message_buttons = QtWidgets.QWidget() - self.setup_left_center_menu(search) - self.setup_left_top(name) - self.setup_right_center(messages) - self.setup_right_top(info) - self.setup_right_bottom(message_buttons) - self.setup_left_center(main_list) - self.setup_menu(menu) - if not Settings.get_instance()['mirror_mode']: - grid.addWidget(search, 2, 0) - grid.addWidget(name, 1, 0) - grid.addWidget(messages, 2, 1, 2, 1) - grid.addWidget(info, 1, 1) - grid.addWidget(message_buttons, 4, 1) - grid.addWidget(main_list, 3, 0, 2, 1) - grid.setColumnMinimumWidth(1, 500) - grid.setColumnMinimumWidth(0, 270) - else: - grid.addWidget(search, 2, 1) - grid.addWidget(name, 1, 1) - grid.addWidget(messages, 2, 0, 2, 1) - grid.addWidget(info, 1, 0) - grid.addWidget(message_buttons, 4, 0) - grid.addWidget(main_list, 3, 1, 2, 1) - grid.setColumnMinimumWidth(0, 500) - grid.setColumnMinimumWidth(1, 270) - - grid.addWidget(menu, 0, 0, 1, 2) - grid.setSpacing(0) - grid.setContentsMargins(0, 0, 0, 0) - grid.setRowMinimumHeight(0, 25) - grid.setRowMinimumHeight(1, 75) - grid.setRowMinimumHeight(2, 25) - grid.setRowMinimumHeight(3, 320) - grid.setRowMinimumHeight(4, 55) - grid.setColumnStretch(1, 1) - grid.setRowStretch(3, 1) - main.setLayout(grid) - self.setCentralWidget(main) - self.messageEdit.setFocus() - self.user_info = name - self.friend_info = info - self.retranslateUi() - self.profile = Profile(tox, self) - - def closeEvent(self, event): - s = Settings.get_instance() - if not s['close_to_tray'] or s.closing: - if not self._saved: - self._saved = True - self.profile.save_history() - self.profile.close() - s['x'] = self.geometry().x() - s['y'] = self.geometry().y() - s['width'] = self.width() - s['height'] = self.height() - s.save() - QtWidgets.QApplication.closeAllWindows() - event.accept() - elif QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): - event.ignore() - self.hide() - - def close_window(self): - Settings.get_instance().closing = True - self.close() - - def resizeEvent(self, *args, **kwargs): - self.messages.setGeometry(0, 0, self.width() - 270, self.height() - 155) - self.friends_list.setGeometry(0, 0, 270, self.height() - 125) - - self.videocallButton.setGeometry(QtCore.QRect(self.width() - 330, 10, 50, 50)) - self.callButton.setGeometry(QtCore.QRect(self.width() - 390, 10, 50, 50)) - self.typing.setGeometry(QtCore.QRect(self.width() - 450, 20, 50, 30)) - - self.messageEdit.setGeometry(QtCore.QRect(55, 0, self.width() - 395, 55)) - self.menuButton.setGeometry(QtCore.QRect(0, 0, 55, 55)) - self.sendMessageButton.setGeometry(QtCore.QRect(self.width() - 340, 0, 70, 55)) - - self.account_name.setGeometry(QtCore.QRect(100, 15, self.width() - 560, 25)) - self.account_status.setGeometry(QtCore.QRect(100, 35, self.width() - 560, 25)) - self.messageEdit.setFocus() - self.profile.update() - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Escape and QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): - self.hide() - elif event.key() == QtCore.Qt.Key_C and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): - rows = list(map(lambda x: self.messages.row(x), self.messages.selectedItems())) - indexes = (rows[0] - self.messages.count(), rows[-1] - self.messages.count()) - s = self.profile.export_history(self.profile.active_friend, True, indexes) - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(s) - elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes(): - self.messages.clearSelection() - elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier: - self.show_search_field() - else: - super(MainWindow, self).keyPressEvent(event) - - # ----------------------------------------------------------------------------------------------------------------- - # Functions which called when user click in menu - # ----------------------------------------------------------------------------------------------------------------- - - def about_program(self): - import util - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "About")) - text = (QtWidgets.QApplication.translate("MainWindow", 'Toxygen is Tox client written on Python.
Version: ')) - github = '
Github' - submit_a_bug = '
Submit a bug' - msgBox.setText(text + util.program_version + github + submit_a_bug) - msgBox.exec_() - - def network_settings(self): - self.n_s = NetworkSettings(self.reset) - self.n_s.show() - - def plugins_menu(self): - self.p_s = PluginsSettings() - self.p_s.show() - - def add_contact(self, link=''): - self.a_c = AddContact(link or '') - self.a_c.show() - - def create_gc(self): - self.profile.create_group_chat() - - def profile_settings(self, *args): - self.p_s = ProfileSettings() - self.p_s.show() - - def privacy_settings(self): - self.priv_s = PrivacySettings() - self.priv_s.show() - - def notification_settings(self): - self.notif_s = NotificationsSettings() - self.notif_s.show() - - def interface_settings(self): - self.int_s = InterfaceSettings() - self.int_s.show() - - def audio_settings(self): - self.audio_s = AudioSettings() - self.audio_s.show() - - def video_settings(self): - self.video_s = VideoSettings() - self.video_s.show() - - def update_settings(self): - self.update_s = UpdateSettings() - self.update_s.show() - - def reload_plugins(self): - plugin_loader = plugin_support.PluginLoader.get_instance() - if plugin_loader is not None: - plugin_loader.reload() - - def import_plugin(self): - import util - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", 'Choose folder with plugin'), - util.curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - if directory: - src = directory + '/' - dest = curr_directory() + '/plugins/' - util.copy(src, dest) - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "Restart Toxygen")) - msgBox.setText( - QtWidgets.QApplication.translate("MainWindow", 'Plugin will be loaded after restart')) - msgBox.exec_() - - def lock_app(self): - if toxes.ToxES.get_instance().has_password(): - Settings.get_instance().locked = True - self.hide() - else: - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "Cannot lock app")) - msgBox.setText( - QtWidgets.QApplication.translate("MainWindow", 'Error. Profile password is not set.')) - msgBox.exec_() - - def show_menu(self): - if not hasattr(self, 'menu'): - self.menu = DropdownMenu(self) - self.menu.setGeometry(QtCore.QRect(0 if Settings.get_instance()['mirror_mode'] else 270, - self.height() - 120, - 180, - 120)) - self.menu.show() - - # ----------------------------------------------------------------------------------------------------------------- - # Messages, calls and file transfers - # ----------------------------------------------------------------------------------------------------------------- - - def send_message(self): - text = self.messageEdit.toPlainText() - self.profile.send_message(text) - - def send_file(self): - self.menu.hide() - if self.profile.active_friend + 1and self.profile.is_active_a_friend(): - choose = QtWidgets.QApplication.translate("MainWindow", 'Choose file') - name = QtWidgets.QFileDialog.getOpenFileName(self, choose, options=QtWidgets.QFileDialog.DontUseNativeDialog) - if name[0]: - self.profile.send_file(name[0]) - - def send_screenshot(self, hide=False): - self.menu.hide() - if self.profile.active_friend + 1 and self.profile.is_active_a_friend(): - self.sw = ScreenShotWindow(self) - self.sw.show() - if hide: - self.hide() - - def send_smiley(self): - self.menu.hide() - if self.profile.active_friend + 1: - self.smiley = SmileyWindow(self) - self.smiley.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(), - self.y() + self.height() - 200, - self.smiley.width(), - self.smiley.height())) - self.smiley.show() - - def send_sticker(self): - self.menu.hide() - if self.profile.active_friend + 1 and self.profile.is_active_a_friend(): - self.sticker = StickerWindow(self) - self.sticker.setGeometry(QtCore.QRect(self.x() if Settings.get_instance()['mirror_mode'] else 270 + self.x(), - self.y() + self.height() - 200, - self.sticker.width(), - self.sticker.height())) - self.sticker.show() - - def active_call(self): - self.update_call_state('finish_call') - - def incoming_call(self): - self.update_call_state('incoming_call') - - def call_finished(self): - self.update_call_state('call') - - def update_call_state(self, state): - os.chdir(curr_directory() + '/images/') - - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}.png'.format(state)) - icon = QtGui.QIcon(pixmap) - self.callButton.setIcon(icon) - self.callButton.setIconSize(QtCore.QSize(50, 50)) - - pixmap = QtGui.QPixmap(curr_directory() + '/images/{}_video.png'.format(state)) - icon = QtGui.QIcon(pixmap) - self.videocallButton.setIcon(icon) - self.videocallButton.setIconSize(QtCore.QSize(35, 35)) - - # ----------------------------------------------------------------------------------------------------------------- - # Functions which called when user open context menu in friends list - # ----------------------------------------------------------------------------------------------------------------- - - def friend_right_click(self, pos): - item = self.friends_list.itemAt(pos) - num = self.friends_list.indexFromItem(item).row() - friend = Profile.get_instance().get_friend(num) - if friend is None: - return - settings = Settings.get_instance() - allowed = friend.tox_id in settings['auto_accept_from_friends'] - auto = QtWidgets.QApplication.translate("MainWindow", 'Disallow auto accept') if allowed else QtWidgets.QApplication.translate("MainWindow", 'Allow auto accept') - if item is not None: - self.listMenu = QtWidgets.QMenu() - is_friend = type(friend) is Friend - if is_friend: - set_alias_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set alias')) - set_alias_item.triggered.connect(lambda: self.set_alias(num)) - - history_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Chat history')) - clear_history_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Clear history')) - export_to_text_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as text')) - export_to_html_item = history_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Export as HTML')) - - copy_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Copy')) - copy_name_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Name')) - copy_status_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Status message')) - if is_friend: - copy_key_item = copy_menu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Public key')) - - auto_accept_item = self.listMenu.addAction(auto) - remove_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Remove friend')) - block_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Block friend')) - notes_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Notes')) - - chats = self.profile.get_group_chats() - if len(chats) and self.profile.is_active_online(): - invite_menu = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Invite to group chat')) - for i in range(len(chats)): - name, number = chats[i] - item = invite_menu.addAction(name) - item.triggered.connect(lambda number=number: self.invite_friend_to_gc(num, number)) - - plugins_loader = plugin_support.PluginLoader.get_instance() - if plugins_loader is not None: - submenu = plugins_loader.get_menu(self.listMenu, num) - if len(submenu): - plug = self.listMenu.addMenu(QtWidgets.QApplication.translate("MainWindow", 'Plugins')) - plug.addActions(submenu) - copy_key_item.triggered.connect(lambda: self.copy_friend_key(num)) - remove_item.triggered.connect(lambda: self.remove_friend(num)) - block_item.triggered.connect(lambda: self.block_friend(num)) - auto_accept_item.triggered.connect(lambda: self.auto_accept(num, not allowed)) - notes_item.triggered.connect(lambda: self.show_note(friend)) - else: - leave_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Leave chat')) - set_title_item = self.listMenu.addAction(QtWidgets.QApplication.translate("MainWindow", 'Set title')) - leave_item.triggered.connect(lambda: self.leave_gc(num)) - set_title_item.triggered.connect(lambda: self.set_title(num)) - clear_history_item.triggered.connect(lambda: self.clear_history(num)) - copy_name_item.triggered.connect(lambda: self.copy_name(friend)) - copy_status_item.triggered.connect(lambda: self.copy_status(friend)) - export_to_text_item.triggered.connect(lambda: self.export_history(num)) - export_to_html_item.triggered.connect(lambda: self.export_history(num, False)) - parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) - self.listMenu.move(parent_position + pos) - self.listMenu.show() - - def show_note(self, friend): - s = Settings.get_instance() - note = s['notes'][friend.tox_id] if friend.tox_id in s['notes'] else '' - user = QtWidgets.QApplication.translate("MainWindow", 'Notes about user') - user = '{} {}'.format(user, friend.name) - - def save_note(text): - if friend.tox_id in s['notes']: - del s['notes'][friend.tox_id] - if text: - s['notes'][friend.tox_id] = text - s.save() - self.note = MultilineEdit(user, note, save_note) - self.note.show() - - def export_history(self, num, as_text=True): - s = self.profile.export_history(num, as_text) - extension = 'txt' if as_text else 'html' - file_name, _ = QtWidgets.QFileDialog.getSaveFileName(None, - QtWidgets.QApplication.translate("MainWindow", - 'Choose file name'), - curr_directory(), - filter=extension, - options=QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - - if file_name: - if not file_name.endswith('.' + extension): - file_name += '.' + extension - with open(file_name, 'wt') as fl: - fl.write(s) - - def set_alias(self, num): - self.profile.set_alias(num) - - def remove_friend(self, num): - self.profile.delete_friend(num) - - def block_friend(self, num): - friend = self.profile.get_friend(num) - self.profile.block_user(friend.tox_id) - - def copy_friend_key(self, num): - tox_id = self.profile.friend_public_key(num) - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(tox_id) - - def copy_name(self, friend): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(friend.name) - - def copy_status(self, friend): - clipboard = QtWidgets.QApplication.clipboard() - clipboard.setText(friend.status_message) - - def clear_history(self, num): - self.profile.clear_history(num) - - def leave_gc(self, num): - self.profile.leave_gc(num) - - def set_title(self, num): - self.profile.set_title(num) - - def auto_accept(self, num, value): - settings = Settings.get_instance() - tox_id = self.profile.friend_public_key(num) - if value: - settings['auto_accept_from_friends'].append(tox_id) - else: - settings['auto_accept_from_friends'].remove(tox_id) - settings.save() - - def invite_friend_to_gc(self, friend_number, group_number): - self.profile.invite_friend(friend_number, group_number) - - # ----------------------------------------------------------------------------------------------------------------- - # Functions which called when user click somewhere else - # ----------------------------------------------------------------------------------------------------------------- - - def friend_click(self, index): - num = index.row() - self.profile.set_active(num) - - def mouseReleaseEvent(self, event): - pos = self.connection_status.pos() - x, y = pos.x() + self.user_info.pos().x(), pos.y() + self.user_info.pos().y() - if (x < event.x() < x + 32) and (y < event.y() < y + 32): - self.profile.change_status() - else: - super(MainWindow, self).mouseReleaseEvent(event) - - def show(self): - super().show() - self.profile.update() - - def filtering(self): - ind = self.online_contacts.currentIndex() - d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4} - self.profile.filtration_and_sorting(d[ind], self.contact_name.text()) - - def show_search_field(self): - if hasattr(self, 'search_field') and self.search_field.isVisible(): - return - if self.profile.get_curr_friend() is None: - return - self.search_field = SearchScreen(self.messages, self.messages.width(), self.messages.parent()) - x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40 - self.search_field.setGeometry(x, y, self.messages.width(), 40) - self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40) - self.search_field.show() diff --git a/toxygen/mainscreen_widgets.py b/toxygen/mainscreen_widgets.py deleted file mode 100644 index dcbc075..0000000 --- a/toxygen/mainscreen_widgets.py +++ /dev/null @@ -1,492 +0,0 @@ -from PyQt5 import QtCore, QtGui, QtWidgets -from widgets import RubberBandWindow, create_menu, QRightClickButton, CenteredWidget, LineEdit -from profile import Profile -import smileys -import util -import platform - - -class MessageArea(QtWidgets.QPlainTextEdit): - """User types messages here""" - - def __init__(self, parent, form): - super(MessageArea, self).__init__(parent) - self.parent = form - self.setAcceptDrops(True) - self.timer = QtCore.QTimer(self) - self.timer.timeout.connect(lambda: self.parent.profile.send_typing(False)) - - def keyPressEvent(self, event): - if event.matches(QtGui.QKeySequence.Paste): - mimeData = QtWidgets.QApplication.clipboard().mimeData() - if mimeData.hasUrls(): - for url in mimeData.urls(): - self.pasteEvent(url.toString()) - else: - self.pasteEvent() - elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): - modifiers = event.modifiers() - if modifiers & QtCore.Qt.ControlModifier or modifiers & QtCore.Qt.ShiftModifier: - self.insertPlainText('\n') - else: - if self.timer.isActive(): - self.timer.stop() - self.parent.profile.send_typing(False) - self.parent.send_message() - elif event.key() == QtCore.Qt.Key_Up and not self.toPlainText(): - self.appendPlainText(Profile.get_instance().get_last_message()) - elif event.key() == QtCore.Qt.Key_Tab and not self.parent.profile.is_active_a_friend(): - text = self.toPlainText() - pos = self.textCursor().position() - self.insertPlainText(Profile.get_instance().get_gc_peer_name(text[:pos])) - else: - self.parent.profile.send_typing(True) - if self.timer.isActive(): - self.timer.stop() - self.timer.start(5000) - super(MessageArea, self).keyPressEvent(event) - - def contextMenuEvent(self, event): - menu = create_menu(self.createStandardContextMenu()) - menu.exec_(event.globalPos()) - del menu - - def dragEnterEvent(self, e): - e.accept() - - def dragMoveEvent(self, e): - e.accept() - - def dropEvent(self, e): - if e.mimeData().hasFormat('text/plain') or e.mimeData().hasFormat('text/html'): - e.accept() - self.pasteEvent(e.mimeData().text()) - elif e.mimeData().hasUrls(): - for url in e.mimeData().urls(): - self.pasteEvent(url.toString()) - e.accept() - else: - e.ignore() - - def pasteEvent(self, text=None): - text = text or QtWidgets.QApplication.clipboard().text() - if text.startswith('file://'): - file_name = self.parse_file_name(text) - self.parent.profile.send_file(file_name) - elif text: - self.insertPlainText(text) - else: - image = QtWidgets.QApplication.clipboard().image() - if image is not None: - byte_array = QtCore.QByteArray() - buffer = QtCore.QBuffer(byte_array) - buffer.open(QtCore.QIODevice.WriteOnly) - image.save(buffer, 'PNG') - self.parent.profile.send_screenshot(bytes(byte_array.data())) - - def parse_file_name(self, file_name): - import urllib - if file_name.endswith('\r\n'): - file_name = file_name[:-2] - file_name = urllib.parse.unquote(file_name) - return file_name[8 if platform.system() == 'Windows' else 7:] - - -class ScreenShotWindow(RubberBandWindow): - - def closeEvent(self, *args): - if self.parent.isHidden(): - self.parent.show() - - def mouseReleaseEvent(self, event): - if self.rubberband.isVisible(): - self.rubberband.hide() - rect = self.rubberband.geometry() - if rect.width() and rect.height(): - screen = QtWidgets.QApplication.primaryScreen() - p = screen.grabWindow(0, - rect.x() + 4, - rect.y() + 4, - rect.width() - 8, - rect.height() - 8) - byte_array = QtCore.QByteArray() - buffer = QtCore.QBuffer(byte_array) - buffer.open(QtCore.QIODevice.WriteOnly) - p.save(buffer, 'PNG') - Profile.get_instance().send_screenshot(bytes(byte_array.data())) - self.close() - - -class SmileyWindow(QtWidgets.QWidget): - """ - Smiley selection window - """ - - def __init__(self, parent): - super(SmileyWindow, self).__init__() - self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - inst = smileys.SmileyLoader.get_instance() - self.data = inst.get_smileys() - count = len(self.data) - if not count: - self.close() - self.page_size = int(pow(count / 8, 0.5) + 1) * 8 # smileys per page - if count % self.page_size == 0: - self.page_count = count // self.page_size - else: - self.page_count = round(count / self.page_size + 0.5) - self.page = -1 - self.radio = [] - self.parent = parent - for i in range(self.page_count): # buttons with smileys - elem = QtWidgets.QRadioButton(self) - elem.setGeometry(QtCore.QRect(i * 20 + 5, 180, 20, 20)) - elem.clicked.connect(lambda c, t=i: self.checked(t)) - self.radio.append(elem) - width = max(self.page_count * 20 + 30, (self.page_size + 5) * 8 // 10) - self.setMaximumSize(width, 200) - self.setMinimumSize(width, 200) - self.buttons = [] - for i in range(self.page_size): # pages - radio buttons - b = QtWidgets.QPushButton(self) - b.setGeometry(QtCore.QRect((i // 8) * 20 + 5, (i % 8) * 20, 20, 20)) - b.clicked.connect(lambda c, t=i: self.clicked(t)) - self.buttons.append(b) - self.checked(0) - - def checked(self, pos): # new page opened - self.radio[self.page].setChecked(False) - self.radio[pos].setChecked(True) - self.page = pos - start = self.page * self.page_size - for i in range(self.page_size): - try: - self.buttons[i].setVisible(True) - pixmap = QtGui.QPixmap(self.data[start + i][1]) - icon = QtGui.QIcon(pixmap) - self.buttons[i].setIcon(icon) - except: - self.buttons[i].setVisible(False) - - def clicked(self, pos): # smiley selected - pos += self.page * self.page_size - smiley = self.data[pos][0] - self.parent.messageEdit.insertPlainText(smiley) - self.close() - - def leaveEvent(self, event): - self.close() - - -class MenuButton(QtWidgets.QPushButton): - - def __init__(self, parent, enter): - super(MenuButton, self).__init__(parent) - self.enter = enter - - def enterEvent(self, event): - self.enter() - super(MenuButton, self).enterEvent(event) - - -class DropdownMenu(QtWidgets.QWidget): - - def __init__(self, parent): - super(DropdownMenu, self).__init__(parent) - self.installEventFilter(self) - self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - self.setMaximumSize(120, 120) - self.setMinimumSize(120, 120) - self.screenshotButton = QRightClickButton(self) - self.screenshotButton.setGeometry(QtCore.QRect(0, 60, 60, 60)) - self.screenshotButton.setObjectName("screenshotButton") - - self.fileTransferButton = QtWidgets.QPushButton(self) - self.fileTransferButton.setGeometry(QtCore.QRect(60, 60, 60, 60)) - self.fileTransferButton.setObjectName("fileTransferButton") - - self.smileyButton = QtWidgets.QPushButton(self) - self.smileyButton.setGeometry(QtCore.QRect(0, 0, 60, 60)) - - self.stickerButton = QtWidgets.QPushButton(self) - self.stickerButton.setGeometry(QtCore.QRect(60, 0, 60, 60)) - - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/file.png') - icon = QtGui.QIcon(pixmap) - self.fileTransferButton.setIcon(icon) - self.fileTransferButton.setIconSize(QtCore.QSize(50, 50)) - - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/screenshot.png') - icon = QtGui.QIcon(pixmap) - self.screenshotButton.setIcon(icon) - self.screenshotButton.setIconSize(QtCore.QSize(50, 60)) - - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/smiley.png') - icon = QtGui.QIcon(pixmap) - self.smileyButton.setIcon(icon) - self.smileyButton.setIconSize(QtCore.QSize(50, 50)) - - pixmap = QtGui.QPixmap(util.curr_directory() + '/images/sticker.png') - icon = QtGui.QIcon(pixmap) - self.stickerButton.setIcon(icon) - self.stickerButton.setIconSize(QtCore.QSize(55, 55)) - - self.screenshotButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send screenshot")) - self.fileTransferButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send file")) - self.smileyButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Add smiley")) - self.stickerButton.setToolTip(QtWidgets.QApplication.translate("MenuWindow", "Send sticker")) - - self.fileTransferButton.clicked.connect(parent.send_file) - self.screenshotButton.clicked.connect(parent.send_screenshot) - self.screenshotButton.rightClicked.connect(lambda: parent.send_screenshot(True)) - self.smileyButton.clicked.connect(parent.send_smiley) - self.stickerButton.clicked.connect(parent.send_sticker) - - def leaveEvent(self, event): - self.close() - - def eventFilter(self, obj, event): - if event.type() == QtCore.QEvent.WindowDeactivate: - self.close() - return False - - -class StickerItem(QtWidgets.QWidget): - - def __init__(self, fl): - super(StickerItem, self).__init__() - self._image_label = QtWidgets.QLabel(self) - self.path = fl - self.pixmap = QtGui.QPixmap() - self.pixmap.load(fl) - if self.pixmap.width() > 150: - self.pixmap = self.pixmap.scaled(150, 200, QtCore.Qt.KeepAspectRatio) - self.setFixedSize(150, self.pixmap.height()) - self._image_label.setPixmap(self.pixmap) - - -class StickerWindow(QtWidgets.QWidget): - """Sticker selection window""" - - def __init__(self, parent): - super(StickerWindow, self).__init__() - self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - self.setMaximumSize(250, 200) - self.setMinimumSize(250, 200) - self.list = QtWidgets.QListWidget(self) - self.list.setGeometry(QtCore.QRect(0, 0, 250, 200)) - self.arr = smileys.sticker_loader() - for sticker in self.arr: - item = StickerItem(sticker) - elem = QtWidgets.QListWidgetItem() - elem.setSizeHint(QtCore.QSize(250, item.height())) - self.list.addItem(elem) - self.list.setItemWidget(elem, item) - self.list.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) - self.list.setSpacing(3) - self.list.clicked.connect(self.click) - self.parent = parent - - def click(self, index): - num = index.row() - self.parent.profile.send_sticker(self.arr[num]) - self.close() - - def leaveEvent(self, event): - self.close() - - -class WelcomeScreen(CenteredWidget): - - def __init__(self): - super().__init__() - self.setMaximumSize(250, 200) - self.setMinimumSize(250, 200) - self.center() - self.setAttribute(QtCore.Qt.WA_DeleteOnClose) - self.text = QtWidgets.QTextBrowser(self) - self.text.setGeometry(QtCore.QRect(0, 0, 250, 170)) - self.text.setOpenExternalLinks(True) - self.checkbox = QtWidgets.QCheckBox(self) - self.checkbox.setGeometry(QtCore.QRect(5, 170, 240, 30)) - self.checkbox.setText(QtWidgets.QApplication.translate('WelcomeScreen', "Don't show again")) - self.setWindowTitle(QtWidgets.QApplication.translate('WelcomeScreen', 'Tip of the day')) - import random - num = random.randint(0, 10) - if num == 0: - text = QtWidgets.QApplication.translate('WelcomeScreen', 'Press Esc if you want hide app to tray.') - elif num == 1: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Right click on screenshot button hides app to tray during screenshot.') - elif num == 2: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'You can use Tox over Tor. For more info read this post') - elif num == 3: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use Settings -> Interface to customize interface.') - elif num == 4: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set profile password via Profile -> Settings. Password allows Toxygen encrypt your history and settings.') - elif num == 5: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Since v0.1.3 Toxygen supports plugins. Read more') - elif num == 6: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Toxygen supports faux offline messages and file transfers. Send message or file to offline friend and he will get it later.') - elif num == 7: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'New in Toxygen 0.4.1:
Downloading nodes from tox.chat
Bug fixes') - elif num == 8: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Delete single message in chat: make right click on spinner or message time and choose "Delete" in menu') - elif num == 9: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Use right click on inline image to save it') - else: - text = QtWidgets.QApplication.translate('WelcomeScreen', - 'Set new NoSpam to avoid spam friend requests: Profile -> Settings -> Set new NoSpam.') - self.text.setHtml(text) - self.checkbox.stateChanged.connect(self.not_show) - QtCore.QTimer.singleShot(1000, self.show) - - def not_show(self): - import settings - s = settings.Settings.get_instance() - s['show_welcome_screen'] = False - s.save() - - -class MainMenuButton(QtWidgets.QPushButton): - - def __init__(self, *args): - super().__init__(*args) - self.setObjectName("mainmenubutton") - - def setText(self, text): - metrics = QtGui.QFontMetrics(self.font()) - self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20) - super().setText(text) - - -class ClickableLabel(QtWidgets.QLabel): - - clicked = QtCore.pyqtSignal() - - def __init__(self, *args): - super().__init__(*args) - - def mouseReleaseEvent(self, ev): - self.clicked.emit() - - -class SearchScreen(QtWidgets.QWidget): - - def __init__(self, messages, width, *args): - super().__init__(*args) - self.setMaximumSize(width, 40) - self.setMinimumSize(width, 40) - self._messages = messages - - self.search_text = LineEdit(self) - self.search_text.setGeometry(0, 0, width - 160, 40) - - self.search_button = ClickableLabel(self) - self.search_button.setGeometry(width - 160, 0, 40, 40) - pixmap = QtGui.QPixmap() - pixmap.load(util.curr_directory() + '/images/search.png') - self.search_button.setScaledContents(False) - self.search_button.setAlignment(QtCore.Qt.AlignCenter) - self.search_button.setPixmap(pixmap) - self.search_button.clicked.connect(self.search) - - font = QtGui.QFont() - font.setPointSize(32) - font.setBold(True) - - self.prev_button = QtWidgets.QPushButton(self) - self.prev_button.setGeometry(width - 120, 0, 40, 40) - self.prev_button.clicked.connect(self.prev) - self.prev_button.setText('\u25B2') - - self.next_button = QtWidgets.QPushButton(self) - self.next_button.setGeometry(width - 80, 0, 40, 40) - self.next_button.clicked.connect(self.next) - self.next_button.setText('\u25BC') - - self.close_button = QtWidgets.QPushButton(self) - self.close_button.setGeometry(width - 40, 0, 40, 40) - self.close_button.clicked.connect(self.close) - self.close_button.setText('×') - self.close_button.setFont(font) - - font.setPointSize(18) - self.next_button.setFont(font) - self.prev_button.setFont(font) - - self.retranslateUi() - - def retranslateUi(self): - self.search_text.setPlaceholderText(QtWidgets.QApplication.translate("MainWindow", "Search")) - - def show(self): - super().show() - self.search_text.setFocus() - - def search(self): - Profile.get_instance().update() - text = self.search_text.text() - friend = Profile.get_instance().get_curr_friend() - if text and friend and util.is_re_valid(text): - index = friend.search_string(text) - self.load_messages(index) - - def prev(self): - friend = Profile.get_instance().get_curr_friend() - if friend is not None: - index = friend.search_prev() - self.load_messages(index) - - def next(self): - friend = Profile.get_instance().get_curr_friend() - text = self.search_text.text() - if friend is not None: - index = friend.search_next() - if index is not None: - count = self._messages.count() - index += count - item = self._messages.item(index) - self._messages.scrollToItem(item) - self._messages.itemWidget(item).select_text(text) - else: - self.not_found(text) - - def load_messages(self, index): - text = self.search_text.text() - if index is not None: - profile = Profile.get_instance() - count = self._messages.count() - while count + index < 0: - profile.load_history() - count = self._messages.count() - index += count - item = self._messages.item(index) - self._messages.scrollToItem(item) - self._messages.itemWidget(item).select_text(text) - else: - self.not_found(text) - - def closeEvent(self, *args): - Profile.get_instance().update() - self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40) - super().closeEvent(*args) - - @staticmethod - def not_found(text): - mbox = QtWidgets.QMessageBox() - mbox_text = QtWidgets.QApplication.translate("MainWindow", - 'Text "{}" was not found') - - mbox.setText(mbox_text.format(text)) - mbox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", - 'Not found')) - mbox.exec_() diff --git a/toxygen/menu.py b/toxygen/menu.py deleted file mode 100644 index 17f4e17..0000000 --- a/toxygen/menu.py +++ /dev/null @@ -1,1095 +0,0 @@ -from PyQt5 import QtCore, QtGui, QtWidgets -from settings import * -from profile import Profile -from util import curr_directory, copy -from widgets import CenteredWidget, DataLabel, LineEdit, RubberBandWindow -import pyaudio -import toxes -import plugin_support -import updater - - -class AddContact(CenteredWidget): - """Add contact form""" - - def __init__(self, tox_id=''): - super(AddContact, self).__init__() - self.initUI(tox_id) - self._adding = False - - def initUI(self, tox_id): - self.setObjectName('AddContact') - self.resize(568, 306) - self.sendRequestButton = QtWidgets.QPushButton(self) - self.sendRequestButton.setGeometry(QtCore.QRect(50, 270, 471, 31)) - self.sendRequestButton.setMinimumSize(QtCore.QSize(0, 0)) - self.sendRequestButton.setBaseSize(QtCore.QSize(0, 0)) - self.sendRequestButton.setObjectName("sendRequestButton") - self.sendRequestButton.clicked.connect(self.add_friend) - self.tox_id = LineEdit(self) - self.tox_id.setGeometry(QtCore.QRect(50, 40, 471, 27)) - self.tox_id.setObjectName("lineEdit") - self.tox_id.setText(tox_id) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(50, 10, 80, 20)) - self.error_label = DataLabel(self) - self.error_label.setGeometry(QtCore.QRect(120, 10, 420, 20)) - font = QtGui.QFont() - font.setFamily(Settings.get_instance()['font']) - font.setPointSize(10) - font.setWeight(30) - self.error_label.setFont(font) - self.error_label.setStyleSheet("QLabel { color: #BC1C1C; }") - self.label.setObjectName("label") - self.message_edit = QtWidgets.QTextEdit(self) - self.message_edit.setGeometry(QtCore.QRect(50, 110, 471, 151)) - self.message_edit.setObjectName("textEdit") - self.message = QtWidgets.QLabel(self) - self.message.setGeometry(QtCore.QRect(50, 70, 101, 31)) - self.message.setFont(font) - self.message.setObjectName("label_2") - self.retranslateUi() - self.message_edit.setText('Hello! Add me to your contact list please') - font.setPointSize(12) - font.setBold(True) - self.label.setFont(font) - self.message.setFont(font) - QtCore.QMetaObject.connectSlotsByName(self) - - def add_friend(self): - if self._adding: - return - self._adding = True - profile = Profile.get_instance() - send = profile.send_friend_request(self.tox_id.text().strip(), self.message_edit.toPlainText()) - self._adding = False - if send is True: - # request was successful - self.close() - else: # print error data - self.error_label.setText(send) - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate('AddContact', "Add contact")) - self.sendRequestButton.setText(QtWidgets.QApplication.translate("Form", "Send request")) - self.label.setText(QtWidgets.QApplication.translate('AddContact', "TOX ID:")) - self.message.setText(QtWidgets.QApplication.translate('AddContact', "Message:")) - self.tox_id.setPlaceholderText(QtWidgets.QApplication.translate('AddContact', "TOX ID or public key of contact")) - - -class ProfileSettings(CenteredWidget): - """Form with profile settings such as name, status, TOX ID""" - def __init__(self): - super(ProfileSettings, self).__init__() - self.initUI() - self.center() - - def initUI(self): - self.setObjectName("ProfileSettingsForm") - self.setMinimumSize(QtCore.QSize(700, 600)) - self.setMaximumSize(QtCore.QSize(700, 600)) - self.nick = LineEdit(self) - self.nick.setGeometry(QtCore.QRect(30, 60, 350, 27)) - profile = Profile.get_instance() - self.nick.setText(profile.name) - self.status = QtWidgets.QComboBox(self) - self.status.setGeometry(QtCore.QRect(400, 60, 200, 27)) - self.status_message = LineEdit(self) - self.status_message.setGeometry(QtCore.QRect(30, 130, 350, 27)) - self.status_message.setText(profile.status_message) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(40, 30, 91, 25)) - font = QtGui.QFont() - font.setFamily(Settings.get_instance()['font']) - font.setPointSize(18) - font.setWeight(75) - font.setBold(True) - self.label.setFont(font) - self.label_2 = QtWidgets.QLabel(self) - self.label_2.setGeometry(QtCore.QRect(40, 100, 100, 25)) - self.label_2.setFont(font) - self.label_3 = QtWidgets.QLabel(self) - self.label_3.setGeometry(QtCore.QRect(40, 180, 100, 25)) - self.label_3.setFont(font) - self.tox_id = QtWidgets.QLabel(self) - self.tox_id.setGeometry(QtCore.QRect(15, 210, 685, 21)) - font.setPointSize(10) - self.tox_id.setFont(font) - s = profile.tox_id - self.tox_id.setText(s) - self.copyId = QtWidgets.QPushButton(self) - self.copyId.setGeometry(QtCore.QRect(40, 250, 180, 30)) - self.copyId.clicked.connect(self.copy) - self.export = QtWidgets.QPushButton(self) - self.export.setGeometry(QtCore.QRect(230, 250, 180, 30)) - self.export.clicked.connect(self.export_profile) - self.new_nospam = QtWidgets.QPushButton(self) - self.new_nospam.setGeometry(QtCore.QRect(420, 250, 180, 30)) - self.new_nospam.clicked.connect(self.new_no_spam) - self.copy_pk = QtWidgets.QPushButton(self) - self.copy_pk.setGeometry(QtCore.QRect(40, 300, 180, 30)) - self.copy_pk.clicked.connect(self.copy_public_key) - self.new_avatar = QtWidgets.QPushButton(self) - self.new_avatar.setGeometry(QtCore.QRect(230, 300, 180, 30)) - self.delete_avatar = QtWidgets.QPushButton(self) - self.delete_avatar.setGeometry(QtCore.QRect(420, 300, 180, 30)) - self.delete_avatar.clicked.connect(self.reset_avatar) - self.new_avatar.clicked.connect(self.set_avatar) - self.profilepass = QtWidgets.QLabel(self) - self.profilepass.setGeometry(QtCore.QRect(40, 340, 300, 30)) - font.setPointSize(18) - self.profilepass.setFont(font) - self.password = LineEdit(self) - self.password.setGeometry(QtCore.QRect(40, 380, 300, 30)) - self.password.setEchoMode(QtWidgets.QLineEdit.Password) - self.leave_blank = QtWidgets.QLabel(self) - self.leave_blank.setGeometry(QtCore.QRect(350, 380, 300, 30)) - self.confirm_password = LineEdit(self) - self.confirm_password.setGeometry(QtCore.QRect(40, 420, 300, 30)) - self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password) - self.set_password = QtWidgets.QPushButton(self) - self.set_password.setGeometry(QtCore.QRect(40, 470, 300, 30)) - self.set_password.clicked.connect(self.new_password) - self.not_match = QtWidgets.QLabel(self) - self.not_match.setGeometry(QtCore.QRect(350, 420, 300, 30)) - self.not_match.setVisible(False) - self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }') - self.warning = QtWidgets.QLabel(self) - self.warning.setGeometry(QtCore.QRect(40, 510, 500, 30)) - self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') - self.default = QtWidgets.QPushButton(self) - self.default.setGeometry(QtCore.QRect(40, 550, 620, 30)) - path, name = Settings.get_auto_profile() - self.auto = path + name == ProfileHelper.get_path() + Settings.get_instance().name - self.default.clicked.connect(self.auto_profile) - self.retranslateUi() - if profile.status is not None: - self.status.setCurrentIndex(profile.status) - else: - self.status.setVisible(False) - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.export.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Export profile")) - self.setWindowTitle(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile settings")) - self.label.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Name:")) - self.label_2.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Status:")) - self.label_3.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "TOX ID:")) - self.copyId.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy TOX ID")) - self.new_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New avatar")) - self.delete_avatar.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Reset avatar")) - self.new_nospam.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "New NoSpam")) - self.profilepass.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Profile password")) - self.password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Password (at least 8 symbols)")) - self.confirm_password.setPlaceholderText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Confirm password")) - self.set_password.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Set password")) - self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) - self.leave_blank.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Leaving blank will reset current password")) - self.warning.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "There is no way to recover lost passwords")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Online")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Away")) - self.status.addItem(QtWidgets.QApplication.translate("ProfileSettingsForm", "Busy")) - self.copy_pk.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Copy public key")) - if self.auto: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) - else: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) - - def auto_profile(self): - if self.auto: - Settings.reset_auto_profile() - else: - Settings.set_auto_profile(ProfileHelper.get_path(), Settings.get_instance().name) - self.auto = not self.auto - if self.auto: - self.default.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as not default profile")) - else: - self.default.setText( - QtWidgets.QApplication.translate("ProfileSettingsForm", "Mark as default profile")) - - def new_password(self): - if self.password.text() == self.confirm_password.text(): - if not len(self.password.text()) or len(self.password.text()) >= 8: - e = toxes.ToxES.get_instance() - e.set_password(self.password.text()) - self.close() - else: - self.not_match.setText( - QtWidgets.QApplication.translate("ProfileSettingsForm", "Password must be at least 8 symbols")) - self.not_match.setVisible(True) - else: - self.not_match.setText(QtWidgets.QApplication.translate("ProfileSettingsForm", "Passwords do not match")) - self.not_match.setVisible(True) - - def copy(self): - clipboard = QtWidgets.QApplication.clipboard() - profile = Profile.get_instance() - clipboard.setText(profile.tox_id) - pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') - icon = QtGui.QIcon(pixmap) - self.copyId.setIcon(icon) - self.copyId.setIconSize(QtCore.QSize(10, 10)) - - def copy_public_key(self): - clipboard = QtWidgets.QApplication.clipboard() - profile = Profile.get_instance() - clipboard.setText(profile.tox_id[:64]) - pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png') - icon = QtGui.QIcon(pixmap) - self.copy_pk.setIcon(icon) - self.copy_pk.setIconSize(QtCore.QSize(10, 10)) - - def new_no_spam(self): - self.tox_id.setText(Profile.get_instance().new_nospam()) - - def reset_avatar(self): - Profile.get_instance().reset_avatar() - - def set_avatar(self): - choose = QtWidgets.QApplication.translate("ProfileSettingsForm", "Choose avatar") - name = QtWidgets.QFileDialog.getOpenFileName(self, choose, None, 'Images (*.png)', - options=QtWidgets.QFileDialog.DontUseNativeDialog) - if name[0]: - bitmap = QtGui.QPixmap(name[0]) - bitmap.scaled(QtCore.QSize(128, 128), aspectRatioMode=QtCore.Qt.KeepAspectRatio, - transformMode=QtCore.Qt.SmoothTransformation) - - byte_array = QtCore.QByteArray() - buffer = QtCore.QBuffer(byte_array) - buffer.open(QtCore.QIODevice.WriteOnly) - bitmap.save(buffer, 'PNG') - Profile.get_instance().set_avatar(bytes(byte_array.data())) - - def export_profile(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(self, '', curr_directory(), - QtWidgets.QFileDialog.DontUseNativeDialog) + '/' - if directory != '/': - reply = QtWidgets.QMessageBox.question(None, - QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Use new path'), - QtWidgets.QApplication.translate("ProfileSettingsForm", - 'Do you want to move your profile to this location?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - settings = Settings.get_instance() - settings.export(directory) - profile = Profile.get_instance() - profile.export_db(directory) - ProfileHelper.get_instance().export_profile(directory, reply == QtWidgets.QMessageBox.Yes) - - def closeEvent(self, event): - profile = Profile.get_instance() - profile.set_name(self.nick.text()) - profile.set_status_message(self.status_message.text().encode('utf-8')) - profile.set_status(self.status.currentIndex()) - - -class NetworkSettings(CenteredWidget): - """Network settings form: UDP, Ipv6 and proxy""" - def __init__(self, reset): - super(NetworkSettings, self).__init__() - self.reset = reset - self.initUI() - self.center() - - def initUI(self): - self.setObjectName("NetworkSettings") - self.resize(300, 400) - self.setMinimumSize(QtCore.QSize(300, 400)) - self.setMaximumSize(QtCore.QSize(300, 400)) - self.setBaseSize(QtCore.QSize(300, 400)) - self.ipv = QtWidgets.QCheckBox(self) - self.ipv.setGeometry(QtCore.QRect(20, 10, 97, 22)) - self.ipv.setObjectName("ipv") - self.udp = QtWidgets.QCheckBox(self) - self.udp.setGeometry(QtCore.QRect(150, 10, 97, 22)) - self.udp.setObjectName("udp") - self.proxy = QtWidgets.QCheckBox(self) - self.proxy.setGeometry(QtCore.QRect(20, 40, 97, 22)) - self.http = QtWidgets.QCheckBox(self) - self.http.setGeometry(QtCore.QRect(20, 70, 97, 22)) - self.proxy.setObjectName("proxy") - self.proxyip = LineEdit(self) - self.proxyip.setGeometry(QtCore.QRect(40, 130, 231, 27)) - self.proxyip.setObjectName("proxyip") - self.proxyport = LineEdit(self) - self.proxyport.setGeometry(QtCore.QRect(40, 190, 231, 27)) - self.proxyport.setObjectName("proxyport") - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(40, 100, 66, 17)) - self.label_2 = QtWidgets.QLabel(self) - self.label_2.setGeometry(QtCore.QRect(40, 165, 66, 17)) - self.reconnect = QtWidgets.QPushButton(self) - self.reconnect.setGeometry(QtCore.QRect(40, 230, 231, 30)) - self.reconnect.clicked.connect(self.restart_core) - settings = Settings.get_instance() - self.ipv.setChecked(settings['ipv6_enabled']) - self.udp.setChecked(settings['udp_enabled']) - self.proxy.setChecked(settings['proxy_type']) - self.proxyip.setText(settings['proxy_host']) - self.proxyport.setText(str(settings['proxy_port'])) - self.http.setChecked(settings['proxy_type'] == 1) - self.warning = QtWidgets.QLabel(self) - self.warning.setGeometry(QtCore.QRect(5, 270, 290, 60)) - self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') - self.nodes = QtWidgets.QCheckBox(self) - self.nodes.setGeometry(QtCore.QRect(20, 350, 270, 22)) - self.nodes.setChecked(settings['download_nodes_list']) - self.retranslateUi() - self.proxy.stateChanged.connect(lambda x: self.activate()) - self.activate() - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("NetworkSettings", "Network settings")) - self.ipv.setText(QtWidgets.QApplication.translate("Form", "IPv6")) - self.udp.setText(QtWidgets.QApplication.translate("Form", "UDP")) - self.proxy.setText(QtWidgets.QApplication.translate("Form", "Proxy")) - self.label.setText(QtWidgets.QApplication.translate("Form", "IP:")) - self.label_2.setText(QtWidgets.QApplication.translate("Form", "Port:")) - self.reconnect.setText(QtWidgets.QApplication.translate("NetworkSettings", "Restart TOX core")) - self.http.setText(QtWidgets.QApplication.translate("Form", "HTTP")) - self.nodes.setText(QtWidgets.QApplication.translate("Form", "Download nodes list from tox.chat")) - self.warning.setText(QtWidgets.QApplication.translate("Form", "WARNING:\nusing proxy with enabled UDP\ncan produce IP leak")) - - def activate(self): - bl = self.proxy.isChecked() - self.proxyip.setEnabled(bl) - self.http.setEnabled(bl) - self.proxyport.setEnabled(bl) - - def restart_core(self): - try: - settings = Settings.get_instance() - settings['ipv6_enabled'] = self.ipv.isChecked() - settings['udp_enabled'] = self.udp.isChecked() - settings['proxy_type'] = 2 - int(self.http.isChecked()) if self.proxy.isChecked() else 0 - settings['proxy_host'] = str(self.proxyip.text()) - settings['proxy_port'] = int(self.proxyport.text()) - settings['download_nodes_list'] = self.nodes.isChecked() - settings.save() - # recreate tox instance - Profile.get_instance().reset(self.reset) - self.close() - except Exception as ex: - log('Exception in restart: ' + str(ex)) - - -class PrivacySettings(CenteredWidget): - """Privacy settings form: history, typing notifications""" - - def __init__(self): - super(PrivacySettings, self).__init__() - self.initUI() - self.center() - - def initUI(self): - self.setObjectName("privacySettings") - self.resize(370, 600) - self.setMinimumSize(QtCore.QSize(370, 600)) - self.setMaximumSize(QtCore.QSize(370, 600)) - self.saveHistory = QtWidgets.QCheckBox(self) - self.saveHistory.setGeometry(QtCore.QRect(10, 20, 350, 22)) - self.saveUnsentOnly = QtWidgets.QCheckBox(self) - self.saveUnsentOnly.setGeometry(QtCore.QRect(10, 60, 350, 22)) - - self.fileautoaccept = QtWidgets.QCheckBox(self) - self.fileautoaccept.setGeometry(QtCore.QRect(10, 100, 350, 22)) - - self.typingNotifications = QtWidgets.QCheckBox(self) - self.typingNotifications.setGeometry(QtCore.QRect(10, 140, 350, 30)) - self.inlines = QtWidgets.QCheckBox(self) - self.inlines.setGeometry(QtCore.QRect(10, 180, 350, 30)) - self.auto_path = QtWidgets.QLabel(self) - self.auto_path.setGeometry(QtCore.QRect(10, 230, 350, 30)) - self.path = QtWidgets.QPlainTextEdit(self) - self.path.setGeometry(QtCore.QRect(10, 265, 350, 45)) - self.change_path = QtWidgets.QPushButton(self) - self.change_path.setGeometry(QtCore.QRect(10, 320, 350, 30)) - settings = Settings.get_instance() - self.typingNotifications.setChecked(settings['typing_notifications']) - self.fileautoaccept.setChecked(settings['allow_auto_accept']) - self.saveHistory.setChecked(settings['save_history']) - self.inlines.setChecked(settings['allow_inline']) - self.saveUnsentOnly.setChecked(settings['save_unsent_only']) - self.saveUnsentOnly.setEnabled(settings['save_history']) - self.saveHistory.stateChanged.connect(self.update) - self.path.setPlainText(settings['auto_accept_path'] or curr_directory()) - self.change_path.clicked.connect(self.new_path) - self.block_user_label = QtWidgets.QLabel(self) - self.block_user_label.setGeometry(QtCore.QRect(10, 360, 350, 30)) - self.block_id = QtWidgets.QPlainTextEdit(self) - self.block_id.setGeometry(QtCore.QRect(10, 390, 350, 30)) - self.block = QtWidgets.QPushButton(self) - self.block.setGeometry(QtCore.QRect(10, 430, 350, 30)) - self.block.clicked.connect(lambda: Profile.get_instance().block_user(self.block_id.toPlainText()) or self.close()) - self.blocked_users_label = QtWidgets.QLabel(self) - self.blocked_users_label.setGeometry(QtCore.QRect(10, 470, 350, 30)) - self.comboBox = QtWidgets.QComboBox(self) - self.comboBox.setGeometry(QtCore.QRect(10, 500, 350, 30)) - self.comboBox.addItems(settings['blocked']) - self.unblock = QtWidgets.QPushButton(self) - self.unblock.setGeometry(QtCore.QRect(10, 540, 350, 30)) - self.unblock.clicked.connect(lambda: self.unblock_user()) - self.retranslateUi() - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("privacySettings", "Privacy settings")) - self.saveHistory.setText(QtWidgets.QApplication.translate("privacySettings", "Save chat history")) - self.fileautoaccept.setText(QtWidgets.QApplication.translate("privacySettings", "Allow file auto accept")) - self.typingNotifications.setText(QtWidgets.QApplication.translate("privacySettings", "Send typing notifications")) - self.auto_path.setText(QtWidgets.QApplication.translate("privacySettings", "Auto accept default path:")) - self.change_path.setText(QtWidgets.QApplication.translate("privacySettings", "Change")) - self.inlines.setText(QtWidgets.QApplication.translate("privacySettings", "Allow inlines")) - self.block_user_label.setText(QtWidgets.QApplication.translate("privacySettings", "Block by public key:")) - self.blocked_users_label.setText(QtWidgets.QApplication.translate("privacySettings", "Blocked users:")) - self.unblock.setText(QtWidgets.QApplication.translate("privacySettings", "Unblock")) - self.block.setText(QtWidgets.QApplication.translate("privacySettings", "Block user")) - self.saveUnsentOnly.setText(QtWidgets.QApplication.translate("privacySettings", "Save unsent messages only")) - - def update(self, new_state): - self.saveUnsentOnly.setEnabled(new_state) - if not new_state: - self.saveUnsentOnly.setChecked(False) - - def unblock_user(self): - if not self.comboBox.count(): - return - title = QtWidgets.QApplication.translate("privacySettings", "Add to friend list") - info = QtWidgets.QApplication.translate("privacySettings", "Do you want to add this user to friend list?") - reply = QtWidgets.QMessageBox.question(None, title, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) - Profile.get_instance().unblock_user(self.comboBox.currentText(), reply == QtWidgets.QMessageBox.Yes) - self.close() - - def closeEvent(self, event): - settings = Settings.get_instance() - settings['typing_notifications'] = self.typingNotifications.isChecked() - settings['allow_auto_accept'] = self.fileautoaccept.isChecked() - - if settings['save_history'] and not self.saveHistory.isChecked(): # clear history - reply = QtWidgets.QMessageBox.question(None, - QtWidgets.QApplication.translate("privacySettings", - 'Chat history'), - QtWidgets.QApplication.translate("privacySettings", - 'History will be cleaned! Continue?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - Profile.get_instance().clear_history() - settings['save_history'] = self.saveHistory.isChecked() - else: - settings['save_history'] = self.saveHistory.isChecked() - if self.saveUnsentOnly.isChecked() and not settings['save_unsent_only']: - reply = QtWidgets.QMessageBox.question(None, - QtWidgets.QApplication.translate("privacySettings", - 'Chat history'), - QtWidgets.QApplication.translate("privacySettings", - 'History will be cleaned! Continue?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - Profile.get_instance().clear_history(None, True) - settings['save_unsent_only'] = self.saveUnsentOnly.isChecked() - else: - settings['save_unsent_only'] = self.saveUnsentOnly.isChecked() - settings['auto_accept_path'] = self.path.toPlainText() - settings['allow_inline'] = self.inlines.isChecked() - settings.save() - - def new_path(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(options=QtWidgets.QFileDialog.DontUseNativeDialog) + '/' - if directory != '/': - self.path.setPlainText(directory) - - -class NotificationsSettings(CenteredWidget): - """Notifications settings form""" - - def __init__(self): - super(NotificationsSettings, self).__init__() - self.initUI() - self.center() - - def initUI(self): - self.setObjectName("notificationsForm") - self.resize(350, 210) - self.setMinimumSize(QtCore.QSize(350, 210)) - self.setMaximumSize(QtCore.QSize(350, 210)) - self.enableNotifications = QtWidgets.QCheckBox(self) - self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18)) - self.callsSound = QtWidgets.QCheckBox(self) - self.callsSound.setGeometry(QtCore.QRect(10, 170, 340, 18)) - self.soundNotifications = QtWidgets.QCheckBox(self) - self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18)) - self.groupNotifications = QtWidgets.QCheckBox(self) - self.groupNotifications.setGeometry(QtCore.QRect(10, 120, 340, 18)) - font = QtGui.QFont() - s = Settings.get_instance() - font.setFamily(s['font']) - font.setPointSize(12) - self.callsSound.setFont(font) - self.soundNotifications.setFont(font) - self.enableNotifications.setFont(font) - self.groupNotifications.setFont(font) - self.enableNotifications.setChecked(s['notifications']) - self.soundNotifications.setChecked(s['sound_notifications']) - self.groupNotifications.setChecked(s['group_notifications']) - self.callsSound.setChecked(s['calls_sound']) - self.retranslateUi() - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("notificationsForm", "Notification settings")) - self.enableNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable notifications")) - self.groupNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Notify about all messages in groups")) - self.callsSound.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable call\'s sound")) - self.soundNotifications.setText(QtWidgets.QApplication.translate("notificationsForm", "Enable sound notifications")) - - def closeEvent(self, *args, **kwargs): - settings = Settings.get_instance() - settings['notifications'] = self.enableNotifications.isChecked() - settings['sound_notifications'] = self.soundNotifications.isChecked() - settings['group_notifications'] = self.groupNotifications.isChecked() - settings['calls_sound'] = self.callsSound.isChecked() - settings.save() - - -class InterfaceSettings(CenteredWidget): - """Interface settings form""" - def __init__(self): - super(InterfaceSettings, self).__init__() - self.initUI() - self.center() - - def initUI(self): - self.setObjectName("interfaceForm") - self.setMinimumSize(QtCore.QSize(400, 650)) - self.setMaximumSize(QtCore.QSize(400, 650)) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(30, 10, 370, 20)) - settings = Settings.get_instance() - font = QtGui.QFont() - font.setPointSize(14) - font.setBold(True) - font.setFamily(settings['font']) - self.label.setFont(font) - self.themeSelect = QtWidgets.QComboBox(self) - self.themeSelect.setGeometry(QtCore.QRect(30, 40, 120, 30)) - self.themeSelect.addItems(list(settings.built_in_themes().keys())) - theme = settings['theme'] - if theme in settings.built_in_themes().keys(): - index = list(settings.built_in_themes().keys()).index(theme) - else: - index = 0 - self.themeSelect.setCurrentIndex(index) - self.lang_choose = QtWidgets.QComboBox(self) - self.lang_choose.setGeometry(QtCore.QRect(30, 110, 120, 30)) - supported = sorted(Settings.supported_languages().keys(), reverse=True) - for key in supported: - self.lang_choose.insertItem(0, key) - if settings['language'] == key: - self.lang_choose.setCurrentIndex(0) - self.lang = QtWidgets.QLabel(self) - self.lang.setGeometry(QtCore.QRect(30, 80, 370, 20)) - self.lang.setFont(font) - self.mirror_mode = QtWidgets.QCheckBox(self) - self.mirror_mode.setGeometry(QtCore.QRect(30, 160, 370, 20)) - self.mirror_mode.setChecked(settings['mirror_mode']) - self.smileys = QtWidgets.QCheckBox(self) - self.smileys.setGeometry(QtCore.QRect(30, 190, 120, 20)) - self.smileys.setChecked(settings['smileys']) - self.smiley_pack_label = QtWidgets.QLabel(self) - self.smiley_pack_label.setGeometry(QtCore.QRect(30, 230, 370, 20)) - self.smiley_pack_label.setFont(font) - self.smiley_pack = QtWidgets.QComboBox(self) - self.smiley_pack.setGeometry(QtCore.QRect(30, 260, 160, 30)) - sm = smileys.SmileyLoader.get_instance() - self.smiley_pack.addItems(sm.get_packs_list()) - try: - ind = sm.get_packs_list().index(settings['smiley_pack']) - except: - ind = sm.get_packs_list().index('default') - self.smiley_pack.setCurrentIndex(ind) - self.messages_font_size_label = QtWidgets.QLabel(self) - self.messages_font_size_label.setGeometry(QtCore.QRect(30, 300, 370, 20)) - self.messages_font_size_label.setFont(font) - self.messages_font_size = QtWidgets.QComboBox(self) - self.messages_font_size.setGeometry(QtCore.QRect(30, 330, 160, 30)) - self.messages_font_size.addItems([str(x) for x in range(10, 25)]) - self.messages_font_size.setCurrentIndex(settings['message_font_size'] - 10) - - self.unread = QtWidgets.QPushButton(self) - self.unread.setGeometry(QtCore.QRect(30, 470, 340, 30)) - self.unread.clicked.connect(self.select_color) - - self.compact_mode = QtWidgets.QCheckBox(self) - self.compact_mode.setGeometry(QtCore.QRect(30, 380, 370, 20)) - self.compact_mode.setChecked(settings['compact_mode']) - - self.close_to_tray = QtWidgets.QCheckBox(self) - self.close_to_tray.setGeometry(QtCore.QRect(30, 410, 370, 20)) - self.close_to_tray.setChecked(settings['close_to_tray']) - - self.show_avatars = QtWidgets.QCheckBox(self) - self.show_avatars.setGeometry(QtCore.QRect(30, 440, 370, 20)) - self.show_avatars.setChecked(settings['show_avatars']) - - self.choose_font = QtWidgets.QPushButton(self) - self.choose_font.setGeometry(QtCore.QRect(30, 510, 340, 30)) - self.choose_font.clicked.connect(self.new_font) - - self.import_smileys = QtWidgets.QPushButton(self) - self.import_smileys.setGeometry(QtCore.QRect(30, 550, 340, 30)) - self.import_smileys.clicked.connect(self.import_sm) - - self.import_stickers = QtWidgets.QPushButton(self) - self.import_stickers.setGeometry(QtCore.QRect(30, 590, 340, 30)) - self.import_stickers.clicked.connect(self.import_st) - - self.retranslateUi() - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.show_avatars.setText(QtWidgets.QApplication.translate("interfaceForm", "Show avatars in chat")) - self.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", "Interface settings")) - self.label.setText(QtWidgets.QApplication.translate("interfaceForm", "Theme:")) - self.lang.setText(QtWidgets.QApplication.translate("interfaceForm", "Language:")) - self.smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Smileys")) - self.smiley_pack_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Smiley pack:")) - self.mirror_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Mirror mode")) - self.messages_font_size_label.setText(QtWidgets.QApplication.translate("interfaceForm", "Messages font size:")) - self.unread.setText(QtWidgets.QApplication.translate("interfaceForm", "Select unread messages notification color")) - self.compact_mode.setText(QtWidgets.QApplication.translate("interfaceForm", "Compact contact list")) - self.import_smileys.setText(QtWidgets.QApplication.translate("interfaceForm", "Import smiley pack")) - self.import_stickers.setText(QtWidgets.QApplication.translate("interfaceForm", "Import sticker pack")) - self.close_to_tray.setText(QtWidgets.QApplication.translate("interfaceForm", "Close to tray")) - self.choose_font.setText(QtWidgets.QApplication.translate("interfaceForm", "Select font")) - - def import_st(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder with sticker pack'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - - if directory: - src = directory + '/' - dest = curr_directory() + '/stickers/' + os.path.basename(directory) + '/' - copy(src, dest) - - def import_sm(self): - directory = QtWidgets.QFileDialog.getExistingDirectory(self, - QtWidgets.QApplication.translate("MainWindow", - 'Choose folder with smiley pack'), - curr_directory(), - QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontUseNativeDialog) - - if directory: - src = directory + '/' - dest = curr_directory() + '/smileys/' + os.path.basename(directory) + '/' - copy(src, dest) - - def new_font(self): - settings = Settings.get_instance() - font, ok = QtWidgets.QFontDialog.getFont(QtGui.QFont(settings['font'], 10), self) - if ok: - settings['font'] = font.family() - settings.save() - msgBox = QtWidgets.QMessageBox() - text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings') - msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required')) - msgBox.setText(text) - msgBox.exec_() - - def select_color(self): - settings = Settings.get_instance() - col = QtWidgets.QColorDialog.getColor(QtGui.QColor(settings['unread_color'])) - - if col.isValid(): - name = col.name() - settings['unread_color'] = name - settings.save() - - def closeEvent(self, event): - settings = Settings.get_instance() - settings['theme'] = str(self.themeSelect.currentText()) - try: - theme = settings['theme'] - app = QtWidgets.QApplication.instance() - with open(curr_directory() + settings.built_in_themes()[theme]) as fl: - style = fl.read() - app.setStyleSheet(style) - except IsADirectoryError: - app.setStyleSheet('') # for default style - settings['smileys'] = self.smileys.isChecked() - restart = False - if settings['mirror_mode'] != self.mirror_mode.isChecked(): - settings['mirror_mode'] = self.mirror_mode.isChecked() - restart = True - if settings['compact_mode'] != self.compact_mode.isChecked(): - settings['compact_mode'] = self.compact_mode.isChecked() - restart = True - if settings['show_avatars'] != self.show_avatars.isChecked(): - settings['show_avatars'] = self.show_avatars.isChecked() - restart = True - settings['smiley_pack'] = self.smiley_pack.currentText() - settings['close_to_tray'] = self.close_to_tray.isChecked() - smileys.SmileyLoader.get_instance().load_pack() - language = self.lang_choose.currentText() - if settings['language'] != language: - settings['language'] = language - text = self.lang_choose.currentText() - path = Settings.supported_languages()[text] - app = QtWidgets.QApplication.instance() - app.removeTranslator(app.translator) - app.translator.load(curr_directory() + '/translations/' + path) - app.installTranslator(app.translator) - settings['message_font_size'] = self.messages_font_size.currentIndex() + 10 - Profile.get_instance().update() - settings.save() - if restart: - msgBox = QtWidgets.QMessageBox() - text = QtWidgets.QApplication.translate("interfaceForm", 'Restart app to apply settings') - msgBox.setWindowTitle(QtWidgets.QApplication.translate("interfaceForm", 'Restart required')) - msgBox.setText(text) - msgBox.exec_() - - -class AudioSettings(CenteredWidget): - """ - Audio calls settings form - """ - - def __init__(self): - super(AudioSettings, self).__init__() - self.initUI() - self.retranslateUi() - self.center() - - def initUI(self): - self.setObjectName("audioSettingsForm") - self.resize(400, 150) - self.setMinimumSize(QtCore.QSize(400, 150)) - self.setMaximumSize(QtCore.QSize(400, 150)) - self.in_label = QtWidgets.QLabel(self) - self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - self.out_label = QtWidgets.QLabel(self) - self.out_label.setGeometry(QtCore.QRect(25, 65, 350, 20)) - settings = Settings.get_instance() - font = QtGui.QFont() - font.setPointSize(16) - font.setBold(True) - font.setFamily(settings['font']) - self.in_label.setFont(font) - self.out_label.setFont(font) - self.input = QtWidgets.QComboBox(self) - self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.output = QtWidgets.QComboBox(self) - self.output.setGeometry(QtCore.QRect(25, 90, 350, 30)) - p = pyaudio.PyAudio() - self.in_indexes, self.out_indexes = [], [] - for i in range(p.get_device_count()): - device = p.get_device_info_by_index(i) - if device["maxInputChannels"]: - self.input.addItem(str(device["name"])) - self.in_indexes.append(i) - if device["maxOutputChannels"]: - self.output.addItem(str(device["name"])) - self.out_indexes.append(i) - self.input.setCurrentIndex(self.in_indexes.index(settings.audio['input'])) - self.output.setCurrentIndex(self.out_indexes.index(settings.audio['output'])) - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("audioSettingsForm", "Audio settings")) - self.in_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Input device:")) - self.out_label.setText(QtWidgets.QApplication.translate("audioSettingsForm", "Output device:")) - - def closeEvent(self, event): - settings = Settings.get_instance() - settings.audio['input'] = self.in_indexes[self.input.currentIndex()] - settings.audio['output'] = self.out_indexes[self.output.currentIndex()] - settings.save() - - -class DesktopAreaSelectionWindow(RubberBandWindow): - - def mouseReleaseEvent(self, event): - if self.rubberband.isVisible(): - self.rubberband.hide() - rect = self.rubberband.geometry() - width, height = rect.width(), rect.height() - if width >= 8 and height >= 8: - self.parent.save(rect.x(), rect.y(), width - (width % 4), height - (height % 4)) - self.close() - - -class VideoSettings(CenteredWidget): - """ - Audio calls settings form - """ - - def __init__(self): - super().__init__() - self.initUI() - self.retranslateUi() - self.center() - self.desktopAreaSelection = None - - def initUI(self): - self.setObjectName("videoSettingsForm") - self.resize(400, 120) - self.setMinimumSize(QtCore.QSize(400, 120)) - self.setMaximumSize(QtCore.QSize(400, 120)) - self.in_label = QtWidgets.QLabel(self) - self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - settings = Settings.get_instance() - font = QtGui.QFont() - font.setPointSize(16) - font.setBold(True) - font.setFamily(settings['font']) - self.in_label.setFont(font) - self.video_size = QtWidgets.QComboBox(self) - self.video_size.setGeometry(QtCore.QRect(25, 70, 350, 30)) - self.input = QtWidgets.QComboBox(self) - self.input.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.input.currentIndexChanged.connect(self.selectionChanged) - self.button = QtWidgets.QPushButton(self) - self.button.clicked.connect(self.button_clicked) - self.button.setGeometry(QtCore.QRect(25, 70, 350, 30)) - import cv2 - self.devices = [-1] - screen = QtWidgets.QApplication.primaryScreen() - size = screen.size() - self.frame_max_sizes = [(size.width(), size.height())] - desktop = QtWidgets.QApplication.translate("videoSettingsForm", "Desktop") - self.input.addItem(desktop) - for i in range(10): - v = cv2.VideoCapture(i) - if v.isOpened(): - v.set(cv2.CAP_PROP_FRAME_WIDTH, 10000) - v.set(cv2.CAP_PROP_FRAME_HEIGHT, 10000) - - width = int(v.get(cv2.CAP_PROP_FRAME_WIDTH)) - height = int(v.get(cv2.CAP_PROP_FRAME_HEIGHT)) - del v - self.devices.append(i) - self.frame_max_sizes.append((width, height)) - self.input.addItem('Device #' + str(i)) - try: - index = self.devices.index(settings.video['device']) - self.input.setCurrentIndex(index) - except: - print('Video devices error!') - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("videoSettingsForm", "Video settings")) - self.in_label.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Device:")) - self.button.setText(QtWidgets.QApplication.translate("videoSettingsForm", "Select region")) - - def button_clicked(self): - self.desktopAreaSelection = DesktopAreaSelectionWindow(self) - - def closeEvent(self, event): - if self.input.currentIndex() == 0: - return - try: - settings = Settings.get_instance() - settings.video['device'] = self.devices[self.input.currentIndex()] - text = self.video_size.currentText() - settings.video['width'] = int(text.split(' ')[0]) - settings.video['height'] = int(text.split(' ')[-1]) - settings.save() - except Exception as ex: - print('Saving video settings error: ' + str(ex)) - - def save(self, x, y, width, height): - self.desktopAreaSelection = None - settings = Settings.get_instance() - settings.video['device'] = -1 - settings.video['width'] = width - settings.video['height'] = height - settings.video['x'] = x - settings.video['y'] = y - settings.save() - - def selectionChanged(self): - if self.input.currentIndex() == 0: - self.button.setVisible(True) - self.video_size.setVisible(False) - else: - self.button.setVisible(False) - self.video_size.setVisible(True) - width, height = self.frame_max_sizes[self.input.currentIndex()] - self.video_size.clear() - dims = [ - (320, 240), - (640, 360), - (640, 480), - (720, 480), - (1280, 720), - (1920, 1080), - (2560, 1440) - ] - for w, h in dims: - if w <= width and h <= height: - self.video_size.addItem(str(w) + ' * ' + str(h)) - - -class PluginsSettings(CenteredWidget): - """ - Plugins settings form - """ - - def __init__(self): - super(PluginsSettings, self).__init__() - self.initUI() - self.center() - self.retranslateUi() - - def initUI(self): - self.resize(400, 210) - self.setMinimumSize(QtCore.QSize(400, 210)) - self.setMaximumSize(QtCore.QSize(400, 210)) - self.comboBox = QtWidgets.QComboBox(self) - self.comboBox.setGeometry(QtCore.QRect(30, 10, 340, 30)) - self.label = QtWidgets.QLabel(self) - self.label.setGeometry(QtCore.QRect(30, 40, 340, 90)) - self.label.setWordWrap(True) - self.button = QtWidgets.QPushButton(self) - self.button.setGeometry(QtCore.QRect(30, 130, 340, 30)) - self.button.clicked.connect(self.button_click) - self.open = QtWidgets.QPushButton(self) - self.open.setGeometry(QtCore.QRect(30, 170, 340, 30)) - self.open.clicked.connect(self.open_plugin) - self.pl_loader = plugin_support.PluginLoader.get_instance() - self.update_list() - self.comboBox.currentIndexChanged.connect(self.show_data) - self.show_data() - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate('PluginsForm', "Plugins")) - self.open.setText(QtWidgets.QApplication.translate('PluginsForm', "Open selected plugin")) - - def open_plugin(self): - ind = self.comboBox.currentIndex() - plugin = self.data[ind] - window = self.pl_loader.plugin_window(plugin[-1]) - if window is not None: - self.window = window - self.window.show() - else: - msgBox = QtWidgets.QMessageBox() - text = QtWidgets.QApplication.translate("PluginsForm", 'No GUI found for this plugin') - msgBox.setWindowTitle(QtWidgets.QApplication.translate("PluginsForm", 'Error')) - msgBox.setText(text) - msgBox.exec_() - - def update_list(self): - self.comboBox.clear() - data = self.pl_loader.get_plugins_list() - self.comboBox.addItems(list(map(lambda x: x[0], data))) - self.data = data - - def show_data(self): - ind = self.comboBox.currentIndex() - if len(self.data): - plugin = self.data[ind] - descr = plugin[2] or QtWidgets.QApplication.translate("PluginsForm", "No description available") - self.label.setText(descr) - if plugin[1]: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin")) - else: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin")) - else: - self.open.setVisible(False) - self.button.setVisible(False) - self.label.setText(QtWidgets.QApplication.translate("PluginsForm", "No plugins found")) - - def button_click(self): - ind = self.comboBox.currentIndex() - plugin = self.data[ind] - self.pl_loader.toggle_plugin(plugin[-1]) - plugin[1] = not plugin[1] - if plugin[1]: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Disable plugin")) - else: - self.button.setText(QtWidgets.QApplication.translate("PluginsForm", "Enable plugin")) - - -class UpdateSettings(CenteredWidget): - """ - Updates settings form - """ - - def __init__(self): - super(UpdateSettings, self).__init__() - self.initUI() - self.center() - - def initUI(self): - self.setObjectName("updateSettingsForm") - self.resize(400, 150) - self.setMinimumSize(QtCore.QSize(400, 120)) - self.setMaximumSize(QtCore.QSize(400, 120)) - self.in_label = QtWidgets.QLabel(self) - self.in_label.setGeometry(QtCore.QRect(25, 5, 350, 20)) - settings = Settings.get_instance() - font = QtGui.QFont() - font.setPointSize(16) - font.setBold(True) - font.setFamily(settings['font']) - self.in_label.setFont(font) - self.autoupdate = QtWidgets.QComboBox(self) - self.autoupdate.setGeometry(QtCore.QRect(25, 30, 350, 30)) - self.button = QtWidgets.QPushButton(self) - self.button.setGeometry(QtCore.QRect(25, 70, 350, 30)) - self.button.setEnabled(settings['update']) - self.button.clicked.connect(self.update_client) - - self.retranslateUi() - self.autoupdate.setCurrentIndex(settings['update']) - QtCore.QMetaObject.connectSlotsByName(self) - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("updateSettingsForm", "Update settings")) - self.in_label.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Select update mode:")) - self.button.setText(QtWidgets.QApplication.translate("updateSettingsForm", "Update Toxygen")) - self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Disabled")) - self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Manual")) - self.autoupdate.addItem(QtWidgets.QApplication.translate("updateSettingsForm", "Auto")) - - def closeEvent(self, event): - settings = Settings.get_instance() - settings['update'] = self.autoupdate.currentIndex() - settings.save() - - def update_client(self): - if not updater.connection_available(): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("updateSettingsForm", "Error")) - text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Problems with internet connection')) - msgBox.setText(text) - msgBox.exec_() - return - if not updater.updater_available(): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("updateSettingsForm", "Error")) - text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Updater not found')) - msgBox.setText(text) - msgBox.exec_() - return - version = updater.check_for_updates() - if version is not None: - updater.download(version) - QtWidgets.QApplication.closeAllWindows() - else: - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("updateSettingsForm", "No updates found")) - text = (QtWidgets.QApplication.translate("updateSettingsForm", 'Toxygen is up to date')) - msgBox.setText(text) - msgBox.exec_() diff --git a/toxygen/messages.py b/toxygen/messages.py deleted file mode 100644 index 8d9f4a3..0000000 --- a/toxygen/messages.py +++ /dev/null @@ -1,113 +0,0 @@ - - -MESSAGE_TYPE = { - 'TEXT': 0, - 'ACTION': 1, - 'FILE_TRANSFER': 2, - 'INLINE': 3, - 'INFO_MESSAGE': 4, - 'GC_TEXT': 5, - 'GC_ACTION': 6 -} - - -class Message: - - def __init__(self, message_type, owner, time): - self._time = time - self._type = message_type - self._owner = owner - - def get_type(self): - return self._type - - def get_owner(self): - return self._owner - - def mark_as_sent(self): - self._owner = 0 - - -class TextMessage(Message): - """ - Plain text or action message - """ - - def __init__(self, message, owner, time, message_type): - super(TextMessage, self).__init__(message_type, owner, time) - self._message = message - - def get_data(self): - return self._message, self._owner, self._time, self._type - - -class GroupChatMessage(TextMessage): - - def __init__(self, message, owner, time, message_type, name): - super().__init__(message, owner, time, message_type) - self._user_name = name - - def get_data(self): - return self._message, self._owner, self._time, self._type, self._user_name - - -class TransferMessage(Message): - """ - Message with info about file transfer - """ - - def __init__(self, owner, time, status, size, name, friend_number, file_number): - super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time) - self._status = status - self._size = size - self._file_name = name - self._friend_number, self._file_number = friend_number, file_number - - def is_active(self, file_number): - return self._file_number == file_number and self._status not in (2, 3) - - def get_friend_number(self): - return self._friend_number - - def get_file_number(self): - return self._file_number - - def get_status(self): - return self._status - - def set_status(self, value): - self._status = value - - def get_data(self): - return self._file_name, self._size, self._time, self._owner, self._friend_number, self._file_number, self._status - - -class UnsentFile(Message): - def __init__(self, path, data, time): - super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) - self._data, self._path = data, path - - def get_data(self): - return self._path, self._data, self._time - - def get_status(self): - return None - - -class InlineImage(Message): - """ - Inline image - """ - - def __init__(self, data): - super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None) - self._data = data - - def get_data(self): - return self._data - - -class InfoMessage(TextMessage): - - def __init__(self, message, time): - super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) diff --git a/toxygen/nodes.json b/toxygen/nodes.json deleted file mode 100644 index 003bbc0..0000000 --- a/toxygen/nodes.json +++ /dev/null @@ -1 +0,0 @@ -{"last_scan":1516822981,"last_refresh":1516822982,"nodes":[{"ipv4":"node.tox.biribiri.org","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"F404ABAA1C99A9D37D61AB54898F56793E1DEF8BD46B1038B9D822E8460FAB67","maintainer":"nurupo","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Welcome, stranger #7985. I'm up for 5d 14h 34m 34s, running since Jan 19 05:08:27 UTC. If I get outdated, please ping my maintainer at nurupo.contributions@gmail.com","last_ping":1516822981},{"ipv4":"nodes.tox.chat","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"6FC41E2BD381D37E9748FC0E0328CE086AF9598BECC8FEB7DDF2E440475F300E","maintainer":"Impyy","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Straps boots like no other","last_ping":1516822981},{"ipv4":"130.133.110.14","ipv6":"2001:6f8:1c3c:babe::14:1","port":33445,"tcp_ports":[33445],"public_key":"461FA3776EF0FA655F1A05477DF1B3B614F7D6B124F7DB1DD4FE3C08B03B640F","maintainer":"Manolis","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Spline tox bootstrap node","last_ping":1516822981},{"ipv4":"205.185.116.116","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"A179B09749AC826FF01F37A9613F6B57118AE014D4196A0E1105A98F93A54702","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"198.98.51.198","ipv6":"2605:6400:1:fed5:22:45af:ec10:f329","port":33445,"tcp_ports":[33445,3389],"public_key":"1D5A5F2F5D6233058BF0259B09622FB40B482E4FA0931EB8FD3AB8E7BF7DAF6F","maintainer":"Busindre","location":"US","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"85.172.30.117","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"8E7D0B859922EF569298B4D261A8CCB5FEA14FB91ED412A7603A585A25698832","maintainer":"ray65536","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Ray's Tox Node","last_ping":1516822981},{"ipv4":"194.249.212.109","ipv6":"2001:1470:fbfe::109","port":33445,"tcp_ports":[33445,3389],"public_key":"3CEE1F054081E7A011234883BC4FC39F661A55B73637A5AC293DDF1251D9432B","maintainer":"fluke571","location":"SI","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"185.25.116.107","ipv6":"2a00:7a60:0:746b::3","port":33445,"tcp_ports":[33445,3389],"public_key":"DA4E4ED4B697F2E9B000EEFE3A34B554ACD3F45F5C96EAEA2516DD7FF9AF7B43","maintainer":"MAH69K","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Saluton! Mia Tox ID: B229B7BD68FC66C2716EAB8671A461906321C764782D7B3EDBB650A315F6C458EF744CE89F07. Scribu! ;)","last_ping":1516822981},{"ipv4":"5.189.176.217","ipv6":"2a02:c200:1:10:3:1:605:1337","port":5190,"tcp_ports":[3389,33445,5190],"public_key":"2B2137E094F743AC8BD44652C55F41DFACC502F125E99E4FE24D40537489E32F","maintainer":"tastytea","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"217.182.143.254","ipv6":"2001:41d0:302:1000::e111","port":2306,"tcp_ports":[33445,2306,443],"public_key":"7AED21F94D82B05774F697B209628CD5A9AD17E0C073D9329076A4C28ED28147","maintainer":"pucetox","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"by pucetox,\nipv4/ipv6 UDP:2306 TCP:21/80/443/2306/33445\nsync your nodes here tox.0x10k.com/bootstrapd-conf , \n for communication: 1D1C0B992DEB6D7F18561176F7F5E572BCC7F2BA5CFA7E9E437B9134122CE96D906A6119F9D2","last_ping":1516822981},{"ipv4":"104.223.122.15","ipv6":"2607:ff48:aa81:800::35eb:1","port":33445,"tcp_ports":[3389,33445],"public_key":"0FB96EEBFB1650DDB52E70CF773DDFCABE25A95CC3BB50FC251082E4B63EF82A","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"built on: Tue Feb 21st 2017, 10:52:30 UTC+3\nplease note: running on TokTox Toxcore!\nmore info on the matter: goo.gl/Gz5KhK \u0026 goo.gl/i2TZJr\n\ntox id for queries and general info: EBD2A7B649ABB10ED9F47E5113F04000F39D46F087CEB62FCCE1069471FD6915256D197F2A97","last_ping":1516822981},{"ipv4":"tox.verdict.gg","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"1C5293AEF2114717547B39DA8EA6F1E331E5E358B35F9B6B5F19317911C5F976","maintainer":"Deliran","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Praise The Sun!","last_ping":1516822981},{"ipv4":"d4rk4.ru","ipv6":"-","port":1813,"tcp_ports":[1813],"public_key":"53737F6D47FA6BD2808F378E339AF45BF86F39B64E79D6D491C53A1D522E7039","maintainer":"D4rk4","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"TOX ID: 35EDC07AEB18B163E07EE33F6CDDA63969F394FF6A617CEAB22A7EBBEAAAF854C0EDFBD46898","last_ping":1516822981},{"ipv4":"51.254.84.212","ipv6":"2001:41d0:a:1a3b::18","port":33445,"tcp_ports":[3389,33445],"public_key":"AEC204B9A4501412D5F0BB67D9C81B5DB3EE6ADA64122D32A3E9B093D544327D","maintainer":"a68366","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Since 26.12.2015","last_ping":1516822981},{"ipv4":"88.99.133.52","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"2D320F971EF2CA18004416C2AAE7BA52BF7949DB34EA8E2E21AF67BD367BE211","maintainer":"Skey","location":"FR","status_udp":true,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"92.54.84.70","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"5625A62618CB4FCA70E147A71B29695F38CC65FF0CBD68AD46254585BE564802","maintainer":"t3mp","location":"RU","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tox.uplinklabs.net","ipv6":"tox.uplinklabs.net","port":33445,"tcp_ports":[3389,33445],"public_key":"1A56EA3EDF5DF4C0AEABBF3C2E4E603890F87E983CAC8A0D532A335F2C6E3E1F","maintainer":"AbacusAvenger","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"i don't know what this is for","last_ping":1516822981},{"ipv4":"toxnode.nek0.net","ipv6":"toxnode.nek0.net","port":33445,"tcp_ports":[3389,33445],"public_key":"20965721D32CE50C3E837DD75B33908B33037E6225110BFF209277AEAF3F9639","maintainer":"Phsm","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"95.215.44.78","ipv6":"2a02:7aa0:1619::c6fe:d0cb","port":33445,"tcp_ports":[33445,3389],"public_key":"672DBE27B4ADB9D5FB105A6BB648B2F8FDB89B3323486A7A21968316E012023C","maintainer":"HooinKyoma","location":"SE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Thanx to Hooin Kyoma","last_ping":1516822981},{"ipv4":"163.172.136.118","ipv6":"2001:bc8:4400:2100::1c:50f","port":33445,"tcp_ports":[33445,3389],"public_key":"2C289F9F37C20D09DA83565588BF496FAB3764853FA38141817A72E3F18ACA0B","maintainer":"LittleVulpix","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"LittleTox - your friendly neighbourhood tox node!","last_ping":1516822981},{"ipv4":"sorunome.de","ipv6":"sorunome.de","port":33445,"tcp_ports":[3389,33445],"public_key":"02807CF4F8BB8FB390CC3794BDF1E8449E9A8392C5D3F2200019DA9F1E812E46","maintainer":"Sorunome","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Keep calm and pony on","last_ping":1516822981},{"ipv4":"37.97.185.116","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E59A0E71ADA20D35BD1B0957059D7EF7E7792B3D680AE25C6F4DBBA09114D165","maintainer":"Yani","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Yani's node of pleasure and leisure","last_ping":1516822981},{"ipv4":"80.87.193.193","ipv6":"2a01:230:2:6::46a8","port":33445,"tcp_ports":[3389,33445],"public_key":"B38255EE4B054924F6D79A5E6E5889EC94B6ADF6FE9906F97A3D01E3D083223A","maintainer":"linxon","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Tox DHT node by Linxon. Author ToxID: EC774ED05A7E71EEE2EBA939A27CD4FF403D7D79E1E685CFD0394B1770498217C6107E4D3C26","last_ping":1516822981},{"ipv4":"initramfs.io","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"3F0A45A268367C1BEA652F258C85F4A66DA76BCAA667A49E770BCC4917AB6A25","maintainer":"initramfs","location":"TW","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"initramfs' Tox DHT Node","last_ping":1516822981},{"ipv4":"hibiki.eve.moe","ipv6":"hibiki.eve.moe","port":33445,"tcp_ports":[33445],"public_key":"D3EB45181B343C2C222A5BCF72B760638E15ED87904625AAD351C594EEFAE03E","maintainer":"EveNeko","location":"FR","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd@hibiki.eve.moe","last_ping":1516822981},{"ipv4":"tox.deadteam.org","ipv6":"tox.deadteam.org","port":33445,"tcp_ports":[33445],"public_key":"C7D284129E83877D63591F14B3F658D77FF9BA9BA7293AEB2BDFBFE1A803AF47","maintainer":"DeadTeam","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Vive le TOX","last_ping":1516822981},{"ipv4":"46.229.52.198","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"813C8F4187833EF0655B10F7752141A352248462A567529A38B6BBF73E979307","maintainer":"Stranger","location":"UA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Freedom to parrots!","last_ping":1516822981},{"ipv4":"node.tox.ngc.network","ipv6":"node.tox.ngc.network","port":33445,"tcp_ports":[3389,33445],"public_key":"A856243058D1DE633379508ADCAFCF944E40E1672FF402750EF712E30C42012A","maintainer":"Nolz","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Unlike Others","last_ping":1516822981},{"ipv4":"149.56.140.5","ipv6":"2607:5300:0201:3100:0000:0000:0000:3ec2","port":33445,"tcp_ports":[3389,33445],"public_key":"7E5668E0EE09E19F320AD47902419331FFEE147BB3606769CFBE921A2A2FD34C","maintainer":"velusip","location":"CA","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Jera","last_ping":1516822981},{"ipv4":"185.14.30.213","ipv6":"2a00:1ca8:a7::e8b","port":443,"tcp_ports":[33445,3389,443],"public_key":"2555763C8C460495B14157D234DD56B86300A2395554BCAE4621AC345B8C1B1B","maintainer":"dvor","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Just another tox node.","last_ping":1516822981},{"ipv4":"tox.natalenko.name","ipv6":"tox.natalenko.name","port":33445,"tcp_ports":[33445],"public_key":"1CB6EBFD9D85448FA70D3CAE1220B76BF6FCE911B46ACDCF88054C190589650B","maintainer":"post-factum","location":"DE","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"136.243.141.187","ipv6":"2a01:4f8:212:2459::a:1337","port":443,"tcp_ports":[33445,3389,443],"public_key":"6EE1FADE9F55CC7938234CC07C864081FC606D8FE7B751EDA217F268F1078A39","maintainer":"CeBe","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"uTox is the future! - maintained by CeBe - contact: tox@cebe.cc - tox: 7F50119368DC8FD3B1ECAF5D18E3F8854F0484CEC5BBF625D420B8E38638733C02486E387AF8","last_ping":1516822981},{"ipv4":"tox.abilinski.com","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"0E9D7FEE2AA4B42A4C18FE81C038E32FFD8D907AAA7896F05AA76C8D31A20065","maintainer":"flobe","location":"CA","status_udp":true,"status_tcp":true,"version":"","motd":"","last_ping":1516822981},{"ipv4":"m.loskiq.it","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"88124F3C18C6CFA8778B7679B7329A333616BD27A4DFB562D476681315CF143D","maintainer":"loskiq","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/loskiq","last_ping":1516822981},{"ipv4":"192.99.232.158","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"7B6CB208C811DEA8782711CE0CAD456AAC0C7B165A0498A1AA7010D2F2EC996C","maintainer":"basiljose","location":"CA","status_udp":true,"status_tcp":false,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"tmux.ru","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"7467AFA626D3246343170B309BA5BDC975DF3924FC9D7A5917FBFA9F5CD5CD38","maintainer":"nrn","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"https://t.me/nyoroon","last_ping":1516822981},{"ipv4":"37.48.122.22","ipv6":"2001:1af8:4700:a115:6::b","port":33445,"tcp_ports":[33445,3389],"public_key":"1B5A8AB25FFFB66620A531C4646B47F0F32B74C547B30AF8BD8266CA50A3AB59","maintainer":"Pokemon","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"Those who would give up essential Liberty, to purchase a little temporary Safety, deserve neither Liberty nor Safety","last_ping":1516822981},{"ipv4":"tox.novg.net","ipv6":"-","port":33445,"tcp_ports":[33445,3389],"public_key":"D527E5847F8330D628DAB1814F0A422F6DC9D0A300E6C357634EE2DA88C35463","maintainer":"blind_oracle","location":"NL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"t0x-node1.weba.ru","ipv6":"-","port":33445,"tcp_ports":[3389,33445],"public_key":"5A59705F86B9FC0671FDF72ED9BB5E55015FF20B349985543DDD4B0656CA1C63","maintainer":"Amin","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"T0X-Node #1","last_ping":1516822981},{"ipv4":"109.195.99.39","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"EF937F61B4979B60BBF306752D8F32029A2A05CD2615B2E9FBFFEADD8E7D5032","maintainer":"NaCl","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"NaCl node respond","last_ping":1516822981},{"ipv4":"79.140.30.52","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"FFAC871E85B1E1487F87AE7C76726AE0E60318A85F6A1669E04C47EB8DC7C72D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"94.41.167.70","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"E519B2C1098999B60190012C7B53E8C43A73C535721036CD9DEC7CCA06741A7D","maintainer":"warlomak","location":"RU","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"tox-easy-bootstrap","last_ping":1516822981},{"ipv4":"104.223.122.204","ipv6":"-","port":33445,"tcp_ports":[3389],"public_key":"3925752E43BF2F8EB4E12B0E9414311064FF2D76707DC7D5D2CCB43F75081F6B","maintainer":"ru_maniac","location":"US","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"rmnc_third_node","last_ping":1516822981},{"ipv4":"77.55.211.53","ipv6":"-","port":53,"tcp_ports":[443,33445,3389],"public_key":"B9D109CC820C69A5D97A4A1A15708107C6BA85C13BC6188CC809D374AFF18E63","maintainer":"GDR!","location":"PL","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"GDR!'s tox-bootstrapd https://gdr.name/","last_ping":1516822922},{"ipv4":"boseburo.ddns.net","ipv6":"-","port":33445,"tcp_ports":[33445],"public_key":"AF3FC9FC3D121E82E362B4FA84A53E63F58C11C2BA61D988855289B8CABC9B18","maintainer":"LowEel","location":"DE","status_udp":true,"status_tcp":true,"version":"2016010100","motd":"This is the Bose Buro bootstrap daemon","last_ping":1516822981},{"ipv4":"46.101.197.175","ipv6":"2a03:b0c0:3:d0::ac:5001","port":443,"tcp_ports":[443,33445,3389],"public_key":"CD133B521159541FB1D326DE9850F5E56A6C724B5B8E5EB5CD8D950408E95707","maintainer":"clearmartin","location":"DE","status_udp":false,"status_tcp":true,"version":"2014101200","motd":"tox-bootstrapd","last_ping":1516822981},{"ipv4":"104.233.104.126","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"EDEE8F2E839A57820DE3DA4156D88350E53D4161447068A3457EE8F59F362414","maintainer":"wildermesser","location":"CA","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"195.93.190.6","ipv6":"2a01:d0:ffff:a8a::2","port":33445,"tcp_ports":[],"public_key":"FB4CE0DDEFEED45F26917053E5D24BDDA0FA0A3D83A672A9DA2375928B37023D","maintainer":"strngr","location":"UA","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"tox node at strngr.name","last_ping":1516816803},{"ipv4":"193.124.186.205","ipv6":"2a02:f680:1:1100::542a","port":5228,"tcp_ports":[],"public_key":"9906D65F2A4751068A59D30505C5FC8AE1A95E0843AE9372EAFA3BAB6AC16C2C","maintainer":"Cactus","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"85.21.144.224","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"8F738BBC8FA9394670BCAB146C67A507B9907C8E564E28C2B59BEBB2FF68711B","maintainer":"himura","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"37.187.122.30","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"BEB71F97ED9C99C04B8489BB75579EB4DC6AB6F441B603D63533122F1858B51D","maintainer":"dolohow","location":"FR","status_udp":false,"status_tcp":false,"version":"2016010100","motd":"#stay frosty 8218DB335926393789859EDF2D79AC4CC805ADF73472D08165FEA51555502A58AE84FCE7C3D4","last_ping":1515853621},{"ipv4":"95.215.46.114","ipv6":"2a02:7aa0:1619::bdbd:17b8","port":33445,"tcp_ports":[],"public_key":"5823FB947FF24CF83DDFAC3F3BAA18F96EA2018B16CC08429CB97FA502F40C23","maintainer":"isotoxin","location":"SE","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0},{"ipv4":"tox.dumalogiya.ru","ipv6":"-","port":33445,"tcp_ports":[],"public_key":"2DAE6EB8C16131761A675D7C723F618FBA9D29DD8B4E0A39E7E3E8D7055EF113","maintainer":"mikhailnov","location":"RU","status_udp":false,"status_tcp":false,"version":"","motd":"","last_ping":0}]} \ No newline at end of file diff --git a/toxygen/notifications.py b/toxygen/notifications.py deleted file mode 100644 index 26a29ec..0000000 --- a/toxygen/notifications.py +++ /dev/null @@ -1,71 +0,0 @@ -from PyQt5 import QtCore, QtWidgets -from util import curr_directory -import wave -import pyaudio - - -SOUND_NOTIFICATION = { - 'MESSAGE': 0, - 'FRIEND_CONNECTION_STATUS': 1, - 'FILE_TRANSFER': 2 -} - - -def tray_notification(title, text, tray, window): - """ - Show tray notification and activate window icon - NOTE: different behaviour on different OS - :param title: Name of user who sent message or file - :param text: text of message or file info - :param tray: ref to tray icon - :param window: main window - """ - if QtWidgets.QSystemTrayIcon.isSystemTrayAvailable(): - if len(text) > 30: - text = text[:27] + '...' - tray.showMessage(title, text, QtWidgets.QSystemTrayIcon.NoIcon, 3000) - QtWidgets.QApplication.alert(window, 0) - - def message_clicked(): - window.setWindowState(window.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) - window.activateWindow() - tray.messageClicked.connect(message_clicked) - - -class AudioFile: - chunk = 1024 - - def __init__(self, fl): - self.wf = wave.open(fl, 'rb') - self.p = pyaudio.PyAudio() - self.stream = self.p.open( - format=self.p.get_format_from_width(self.wf.getsampwidth()), - channels=self.wf.getnchannels(), - rate=self.wf.getframerate(), - output=True) - - def play(self): - data = self.wf.readframes(self.chunk) - while data: - self.stream.write(data) - data = self.wf.readframes(self.chunk) - - def close(self): - self.stream.close() - self.p.terminate() - - -def sound_notification(t): - """ - Plays sound notification - :param t: type of notification - """ - if t == SOUND_NOTIFICATION['MESSAGE']: - f = curr_directory() + '/sounds/message.wav' - elif t == SOUND_NOTIFICATION['FILE_TRANSFER']: - f = curr_directory() + '/sounds/file.wav' - else: - f = curr_directory() + '/sounds/contact.wav' - a = AudioFile(f) - a.play() - a.close() diff --git a/toxygen/passwordscreen.py b/toxygen/passwordscreen.py deleted file mode 100644 index ca721e5..0000000 --- a/toxygen/passwordscreen.py +++ /dev/null @@ -1,154 +0,0 @@ -from widgets import CenteredWidget, LineEdit -from PyQt5 import QtCore, QtWidgets - - -class PasswordArea(LineEdit): - - def __init__(self, parent): - super(PasswordArea, self).__init__(parent) - self.parent = parent - self.setEchoMode(QtWidgets.QLineEdit.Password) - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Return: - self.parent.button_click() - else: - super(PasswordArea, self).keyPressEvent(event) - - -class PasswordScreenBase(CenteredWidget): - - def __init__(self, encrypt): - super(PasswordScreenBase, self).__init__() - self._encrypt = encrypt - self.initUI() - - def initUI(self): - self.resize(360, 170) - self.setMinimumSize(QtCore.QSize(360, 170)) - self.setMaximumSize(QtCore.QSize(360, 170)) - - self.enter_pass = QtWidgets.QLabel(self) - self.enter_pass.setGeometry(QtCore.QRect(30, 10, 300, 30)) - - self.password = PasswordArea(self) - self.password.setGeometry(QtCore.QRect(30, 50, 300, 30)) - - self.button = QtWidgets.QPushButton(self) - self.button.setGeometry(QtCore.QRect(30, 90, 300, 30)) - self.button.setText('OK') - self.button.clicked.connect(self.button_click) - - self.warning = QtWidgets.QLabel(self) - self.warning.setGeometry(QtCore.QRect(30, 130, 300, 30)) - self.warning.setStyleSheet('QLabel { color: #F70D1A; }') - self.warning.setVisible(False) - - self.retranslateUi() - self.center() - QtCore.QMetaObject.connectSlotsByName(self) - - def button_click(self): - pass - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Enter: - self.button_click() - else: - super(PasswordScreenBase, self).keyPressEvent(event) - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("pass", "Enter password")) - self.enter_pass.setText(QtWidgets.QApplication.translate("pass", "Password:")) - self.warning.setText(QtWidgets.QApplication.translate("pass", "Incorrect password")) - - -class PasswordScreen(PasswordScreenBase): - - def __init__(self, encrypt, data): - super(PasswordScreen, self).__init__(encrypt) - self._data = data - - def button_click(self): - if self.password.text(): - try: - self._encrypt.set_password(self.password.text()) - new_data = self._encrypt.pass_decrypt(self._data[0]) - except Exception as ex: - self.warning.setVisible(True) - print('Decryption error:', ex) - else: - self._data[0] = new_data - self.close() - - -class UnlockAppScreen(PasswordScreenBase): - - def __init__(self, encrypt, callback): - super(UnlockAppScreen, self).__init__(encrypt) - self._callback = callback - self.setWindowFlags(QtCore.Qt.FramelessWindowHint) - - def button_click(self): - if self.password.text(): - if self._encrypt.is_password(self.password.text()): - self._callback() - self.close() - else: - self.warning.setVisible(True) - print('Wrong password!') - - -class SetProfilePasswordScreen(CenteredWidget): - - def __init__(self, encrypt): - super(SetProfilePasswordScreen, self).__init__() - self._encrypt = encrypt - self.initUI() - self.retranslateUi() - self.center() - - def initUI(self): - self.setMinimumSize(QtCore.QSize(700, 200)) - self.setMaximumSize(QtCore.QSize(700, 200)) - self.password = LineEdit(self) - self.password.setGeometry(QtCore.QRect(40, 10, 300, 30)) - self.password.setEchoMode(QtWidgets.QLineEdit.Password) - self.confirm_password = LineEdit(self) - self.confirm_password.setGeometry(QtCore.QRect(40, 50, 300, 30)) - self.confirm_password.setEchoMode(QtWidgets.QLineEdit.Password) - self.set_password = QtWidgets.QPushButton(self) - self.set_password.setGeometry(QtCore.QRect(40, 100, 300, 30)) - self.set_password.clicked.connect(self.new_password) - self.not_match = QtWidgets.QLabel(self) - self.not_match.setGeometry(QtCore.QRect(350, 50, 300, 30)) - self.not_match.setVisible(False) - self.not_match.setStyleSheet('QLabel { color: #BC1C1C; }') - self.warning = QtWidgets.QLabel(self) - self.warning.setGeometry(QtCore.QRect(40, 160, 500, 30)) - self.warning.setStyleSheet('QLabel { color: #BC1C1C; }') - - def retranslateUi(self): - self.setWindowTitle(QtWidgets.QApplication.translate("PasswordScreen", "Profile password")) - self.password.setPlaceholderText( - QtWidgets.QApplication.translate("PasswordScreen", "Password (at least 8 symbols)")) - self.confirm_password.setPlaceholderText( - QtWidgets.QApplication.translate("PasswordScreen", "Confirm password")) - self.set_password.setText( - QtWidgets.QApplication.translate("PasswordScreen", "Set password")) - self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match")) - self.warning.setText( - QtWidgets.QApplication.translate("PasswordScreen", "There is no way to recover lost passwords")) - - def new_password(self): - if self.password.text() == self.confirm_password.text(): - if len(self.password.text()) >= 8: - self._encrypt.set_password(self.password.text()) - self.close() - else: - self.not_match.setText( - QtWidgets.QApplication.translate("PasswordScreen", "Password must be at least 8 symbols")) - self.not_match.setVisible(True) - else: - self.not_match.setText(QtWidgets.QApplication.translate("PasswordScreen", "Passwords do not match")) - self.not_match.setVisible(True) diff --git a/toxygen/plugin_support.py b/toxygen/plugin_support.py deleted file mode 100644 index 0ff7421..0000000 --- a/toxygen/plugin_support.py +++ /dev/null @@ -1,176 +0,0 @@ -import util -import profile -import os -import importlib -import inspect -import plugins.plugin_super_class as pl -import toxes -import sys - - -class PluginLoader(util.Singleton): - - def __init__(self, tox, settings): - super().__init__() - self._profile = profile.Profile.get_instance() - self._settings = settings - self._plugins = {} # dict. key - plugin unique short name, value - tuple (plugin instance, is active) - self._tox = tox - self._encr = toxes.ToxES.get_instance() - - def set_tox(self, tox): - """ - New tox instance - """ - self._tox = tox - for value in self._plugins.values(): - value[0].set_tox(tox) - - def load(self): - """ - Load all plugins in plugins folder - """ - path = util.curr_directory() + '/plugins/' - if not os.path.exists(path): - util.log('Plugin dir not found') - return - else: - sys.path.append(path) - files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))] - for fl in files: - if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'): - continue - name = fl[:-3] # module name without .py - try: - module = importlib.import_module(name) # import plugin - except ImportError: - util.log('Import error in module ' + name) - continue - except Exception as ex: - util.log('Exception in module ' + name + ' Exception: ' + str(ex)) - continue - for elem in dir(module): - obj = getattr(module, elem) - # looking for plugin class in module - if inspect.isclass(obj) and hasattr(obj, 'is_plugin') and obj.is_plugin: - print('Plugin', elem) - try: # create instance of plugin class - inst = obj(self._tox, self._profile, self._settings, self._encr) - autostart = inst.get_short_name() in self._settings['plugins'] - if autostart: - inst.start() - except Exception as ex: - util.log('Exception in module ' + name + ' Exception: ' + str(ex)) - continue - self._plugins[inst.get_short_name()] = [inst, autostart] # (inst, is active) - break - - def callback_lossless(self, friend_number, data): - """ - New incoming custom lossless packet (callback) - """ - l = data[0] - pl.LOSSLESS_FIRST_BYTE - name = ''.join(chr(x) for x in data[1:l + 1]) - if name in self._plugins and self._plugins[name][1]: - self._plugins[name][0].lossless_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) - - def callback_lossy(self, friend_number, data): - """ - New incoming custom lossy packet (callback) - """ - l = data[0] - pl.LOSSY_FIRST_BYTE - name = ''.join(chr(x) for x in data[1:l + 1]) - if name in self._plugins and self._plugins[name][1]: - self._plugins[name][0].lossy_packet(''.join(chr(x) for x in data[l + 1:]), friend_number) - - def friend_online(self, friend_number): - """ - Friend with specified number is online - """ - for elem in self._plugins.values(): - if elem[1]: - elem[0].friend_connected(friend_number) - - def get_plugins_list(self): - """ - Returns list of all plugins - """ - result = [] - for data in self._plugins.values(): - try: - result.append([data[0].get_name(), # plugin full name - data[1], # is enabled - data[0].get_description(), # plugin description - data[0].get_short_name()]) # key - short unique name - except: - continue - return result - - def plugin_window(self, key): - """ - Return window or None for specified plugin - """ - return self._plugins[key][0].get_window() - - def toggle_plugin(self, key): - """ - Enable/disable plugin - :param key: plugin short name - """ - plugin = self._plugins[key] - if plugin[1]: - plugin[0].stop() - else: - plugin[0].start() - plugin[1] = not plugin[1] - if plugin[1]: - self._settings['plugins'].append(key) - else: - self._settings['plugins'].remove(key) - self._settings.save() - - def command(self, text): - """ - New command for plugin - """ - text = text.strip() - name = text.split()[0] - if name in self._plugins and self._plugins[name][1]: - self._plugins[name][0].command(text[len(name) + 1:]) - - def get_menu(self, menu, num): - """ - Return list of items for menu - """ - result = [] - for elem in self._plugins.values(): - if elem[1]: - try: - result.extend(elem[0].get_menu(menu, num)) - except: - continue - return result - - def get_message_menu(self, menu, selected_text): - result = [] - for elem in self._plugins.values(): - if elem[1]: - try: - result.extend(elem[0].get_message_menu(menu, selected_text)) - except: - continue - return result - - def stop(self): - """ - App is closing, stop all plugins - """ - for key in list(self._plugins.keys()): - if self._plugins[key][1]: - self._plugins[key][0].close() - del self._plugins[key] - - def reload(self): - print('Reloading plugins') - self.stop() - self.load() diff --git a/toxygen/profile.py b/toxygen/profile.py deleted file mode 100644 index a0d8cd4..0000000 --- a/toxygen/profile.py +++ /dev/null @@ -1,1466 +0,0 @@ -from list_items import * -from PyQt5 import QtGui, QtWidgets -from friend import * -from settings import * -from toxcore_enums_and_consts import * -from ctypes import * -from util import log, Singleton, curr_directory -from tox_dns import tox_dns -from history import * -from file_transfers import * -import time -import calls -import avwidgets -import plugin_support -import basecontact -import items_factory -import cv2 -import threading -from group_chat import * -import re - - -class Profile(basecontact.BaseContact, Singleton): - """ - Profile of current toxygen user. Contains friends list, tox instance - """ - def __init__(self, tox, screen): - """ - :param tox: tox instance - :param screen: ref to main screen - """ - basecontact.BaseContact.__init__(self, - tox.self_get_name(), - tox.self_get_status_message(), - screen.user_info, - tox.self_get_address()) - Singleton.__init__(self) - self._screen = screen - self._messages = screen.messages - self._tox = tox - self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number) - self._call = calls.AV(tox.AV) # object with data about calls - self._call_widgets = {} # dict of incoming call widgets - self._incoming_calls = set() - self._load_history = True - self._waiting_for_reconnection = False - self._factory = items_factory.ItemsFactory(self._screen.friends_list, self._messages) - settings = Settings.get_instance() - self._sorting = settings['sorting'] - self._show_avatars = settings['show_avatars'] - self._filter_string = '' - self._friend_item_height = 40 if settings['compact_mode'] else 70 - self._paused_file_transfers = dict(settings['paused_file_transfers']) - # key - file id, value: [path, friend number, is incoming, start position] - screen.online_contacts.setCurrentIndex(int(self._sorting)) - aliases = settings['friends_aliases'] - data = tox.self_get_friend_list() - self._history = History(tox.self_get_public_key()) # connection to db - self._contacts, self._active_friend = [], -1 - for i in data: # creates list of friends - tox_id = tox.friend_get_public_key(i) - try: - alias = list(filter(lambda x: x[0] == tox_id, aliases))[0][1] - except: - alias = '' - item = self.create_friend_item() - name = alias or tox.friend_get_name(i) or tox_id - status_message = tox.friend_get_status_message(i) - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - friend = Friend(message_getter, i, name, status_message, item, tox_id) - friend.set_alias(alias) - self._contacts.append(friend) - if len(self._contacts): - self.set_active(0) - self.filtration_and_sorting(self._sorting) - - # ----------------------------------------------------------------------------------------------------------------- - # Edit current user's data - # ----------------------------------------------------------------------------------------------------------------- - - def change_status(self): - """ - Changes status of user (online, away, busy) - """ - if self._status is not None: - self.set_status((self._status + 1) % 3) - - def set_status(self, status): - super(Profile, self).set_status(status) - if status is not None: - self._tox.self_set_status(status) - elif not self._waiting_for_reconnection: - self._waiting_for_reconnection = True - QtCore.QTimer.singleShot(50000, self.reconnect) - - def set_name(self, value): - if self.name == value: - return - tmp = self.name - super(Profile, self).set_name(value.encode('utf-8')) - self._tox.self_set_name(self._name.encode('utf-8')) - message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') - message = message.format(tmp, value) - for friend in self._contacts: - friend.append_message(InfoMessage(message, time.time())) - if self._active_friend + 1: - self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - - def set_status_message(self, value): - super(Profile, self).set_status_message(value) - self._tox.self_set_status_message(self._status_message.encode('utf-8')) - - def new_nospam(self): - """Sets new nospam part of tox id""" - import random - self._tox.self_set_nospam(random.randint(0, 4294967295)) # no spam - uint32 - self._tox_id = self._tox.self_get_address() - return self._tox_id - - # ----------------------------------------------------------------------------------------------------------------- - # Filtration - # ----------------------------------------------------------------------------------------------------------------- - - def filtration_and_sorting(self, sorting=0, filter_str=''): - """ - Filtration of friends list - :param sorting: 0 - no sort, 1 - online only, 2 - online first, 4 - by name - :param filter_str: show contacts which name contains this substring - """ - filter_str = filter_str.lower() - settings = Settings.get_instance() - number = self.get_active_number() - is_friend = self.is_active_a_friend() - if sorting > 1: - if sorting & 2: - self._contacts = sorted(self._contacts, key=lambda x: int(x.status is not None), reverse=True) - if sorting & 4: - if not sorting & 2: - self._contacts = sorted(self._contacts, key=lambda x: x.name.lower()) - else: # save results of prev sorting - online_friends = filter(lambda x: x.status is not None, self._contacts) - count = len(list(online_friends)) - part1 = self._contacts[:count] - part2 = self._contacts[count:] - part1 = sorted(part1, key=lambda x: x.name.lower()) - part2 = sorted(part2, key=lambda x: x.name.lower()) - self._contacts = part1 + part2 - else: # sort by number - online_friends = filter(lambda x: x.status is not None, self._contacts) - count = len(list(online_friends)) - part1 = self._contacts[:count] - part2 = self._contacts[count:] - part1 = sorted(part1, key=lambda x: x.number) - part2 = sorted(part2, key=lambda x: x.number) - self._contacts = part1 + part2 - self._screen.friends_list.clear() - for contact in self._contacts: - contact.set_widget(self.create_friend_item()) - for index, friend in enumerate(self._contacts): - friend.visibility = (friend.status is not None or not (sorting & 1)) and (filter_str in friend.name.lower()) - friend.visibility = friend.visibility or friend.messages or friend.actions - if friend.visibility: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, self._friend_item_height)) - else: - self._screen.friends_list.item(index).setSizeHint(QtCore.QSize(250, 0)) - self._sorting, self._filter_string = sorting, filter_str - settings['sorting'] = self._sorting - settings.save() - self.set_active_by_number_and_type(number, is_friend) - - def update_filtration(self): - """ - Update list of contacts when 1 of friends change connection status - """ - self.filtration_and_sorting(self._sorting, self._filter_string) - - # ----------------------------------------------------------------------------------------------------------------- - # Friend getters - # ----------------------------------------------------------------------------------------------------------------- - - def get_friend_by_number(self, num): - return list(filter(lambda x: x.number == num and type(x) is Friend, self._contacts))[0] - - def get_friend(self, num): - if num < 0 or num >= len(self._contacts): - return None - return self._contacts[num] - - def get_curr_friend(self): - return self._contacts[self._active_friend] if self._active_friend + 1 else None - - # ----------------------------------------------------------------------------------------------------------------- - # Work with active friend - # ----------------------------------------------------------------------------------------------------------------- - - def get_active(self): - return self._active_friend - - def set_active(self, value=None): - """ - Change current active friend or update info - :param value: number of new active friend in friend's list or None to update active user's data - """ - if value is None and self._active_friend == -1: # nothing to update - return - if value == -1: # all friends were deleted - self._screen.account_name.setText('') - self._screen.account_status.setText('') - self._screen.account_status.setToolTip('') - self._active_friend = -1 - self._screen.account_avatar.setHidden(True) - self._messages.clear() - self._screen.messageEdit.clear() - return - try: - self.send_typing(False) - self._screen.typing.setVisible(False) - if value is not None: - if self._active_friend + 1 and self._active_friend != value: - try: - self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText() - except: - pass - friend = self._contacts[value] - friend.remove_invalid_unsent_files() - if self._active_friend != value: - self._screen.messageEdit.setPlainText(friend.curr_text) - self._active_friend = value - friend.reset_messages() - if not Settings.get_instance()['save_history']: - friend.delete_old_messages() - self._messages.clear() - friend.load_corr() - messages = friend.get_corr()[-PAGE_SIZE:] - self._load_history = False - for message in messages: - if message.get_type() <= 1: - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3]) - elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: - if message.get_status() is None: - self.create_unsent_file_item(message) - continue - item = self.create_file_transfer_item(message) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') - elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline - self.create_inline_item(message.get_data()) - elif message.get_type() < 5: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3]) - else: - data = message.get_data() - self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3]) - self._messages.scrollToBottom() - self._load_history = True - if value in self._call: - self._screen.active_call() - elif value in self._incoming_calls: - self._screen.incoming_call() - else: - self._screen.call_finished() - else: - friend = self.get_curr_friend() - - self._screen.account_name.setText(friend.name) - self._screen.account_status.setText(friend.status_message) - self._screen.account_status.setToolTip(friend.get_full_status()) - if friend.tox_id is None: - avatar_path = curr_directory() + '/images/group.png' - else: - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if not os.path.isfile(avatar_path): # load default image - avatar_path = curr_directory() + '/images/avatar.png' - os.chdir(os.path.dirname(avatar_path)) - pixmap = QtGui.QPixmap(avatar_path) - self._screen.account_avatar.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio, - QtCore.Qt.SmoothTransformation)) - except Exception as ex: # no friend found. ignore - log('Friend value: ' + str(value)) - log('Error in set active: ' + str(ex)) - raise - - def set_active_by_number_and_type(self, number, is_friend): - for i in range(len(self._contacts)): - c = self._contacts[i] - if c.number == number and (type(c) is Friend == is_friend): - self._active_friend = i - break - - active_friend = property(get_active, set_active) - - def get_last_message(self): - if self._active_friend + 1: - return self.get_curr_friend().get_last_message_text() - else: - return '' - - def get_active_number(self): - return self.get_curr_friend().number if self._active_friend + 1 else -1 - - def get_active_name(self): - return self.get_curr_friend().name if self._active_friend + 1 else '' - - def is_active_online(self): - return self._active_friend + 1 and self.get_curr_friend().status is not None - - def new_name(self, number, name): - friend = self.get_friend_by_number(number) - tmp = friend.name - friend.set_name(name) - name = str(name, 'utf-8') - if friend.name == name and tmp != name: - message = QtWidgets.QApplication.translate("MainWindow", 'User {} is now known as {}') - message = message.format(tmp, name) - friend.append_message(InfoMessage(message, time.time())) - friend.actions = True - if number == self.get_active_number(): - self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - self.set_active(None) - - def update(self): - if self._active_friend + 1: - self.set_active(self._active_friend) - - # ----------------------------------------------------------------------------------------------------------------- - # Friend connection status callbacks - # ----------------------------------------------------------------------------------------------------------------- - - def send_files(self, friend_number): - friend = self.get_friend_by_number(friend_number) - friend.remove_invalid_unsent_files() - files = friend.get_unsent_files() - try: - for fl in files: - data = fl.get_data() - if data[1] is not None: - self.send_inline(data[1], data[0], friend_number, True) - else: - self.send_file(data[0], friend_number, True) - friend.clear_unsent_files() - for key in list(self._paused_file_transfers.keys()): - data = self._paused_file_transfers[key] - if not os.path.exists(data[0]): - del self._paused_file_transfers[key] - elif data[1] == friend_number and not data[2]: - self.send_file(data[0], friend_number, True, key) - del self._paused_file_transfers[key] - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self.update() - except Exception as ex: - print('Exception in file sending: ' + str(ex)) - - def friend_exit(self, friend_number): - """ - Friend with specified number quit - """ - self.get_friend_by_number(friend_number).status = None - self.friend_typing(friend_number, False) - if friend_number in self._call: - self._call.finish_call(friend_number, True) - for friend_num, file_num in list(self._file_transfers.keys()): - if friend_num == friend_number: - ft = self._file_transfers[(friend_num, file_num)] - if type(ft) is SendTransfer: - self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, False, -1] - elif type(ft) is ReceiveTransfer and ft.state != TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED']: - self._paused_file_transfers[ft.get_id()] = [ft.get_path(), friend_num, True, ft.total_size()] - self.cancel_transfer(friend_num, file_num, True) - - # ----------------------------------------------------------------------------------------------------------------- - # Typing notifications - # ----------------------------------------------------------------------------------------------------------------- - - def send_typing(self, typing): - """ - Send typing notification to a friend - """ - if Settings.get_instance()['typing_notifications'] and self._active_friend + 1: - try: - friend = self.get_curr_friend() - if friend.status is not None: - self._tox.self_set_typing(friend.number, typing) - except: - pass - - def friend_typing(self, friend_number, typing): - """ - Display incoming typing notification - """ - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self._screen.typing.setVisible(typing) - - # ----------------------------------------------------------------------------------------------------------------- - # Private messages - # ----------------------------------------------------------------------------------------------------------------- - - def receipt(self): - i = 0 - while i < self._messages.count() and not self._messages.itemWidget(self._messages.item(i)).mark_as_sent(): - i += 1 - - def send_messages(self, friend_number): - """ - Send 'offline' messages to friend - """ - friend = self.get_friend_by_number(friend_number) - friend.load_corr() - messages = friend.get_unsent_messages() - try: - for message in messages: - self.split_and_send(friend_number, message.get_data()[-1], message.get_data()[0].encode('utf-8')) - friend.inc_receipts() - except Exception as ex: - log('Sending pending messages failed with ' + str(ex)) - - def split_and_send(self, number, message_type, message): - """ - Message splitting. Message length cannot be > TOX_MAX_MESSAGE_LENGTH - :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 b' ' in last_part: - index = last_part.index(b' ') - elif b',' in last_part: - index = last_part.index(b',') - elif b'.' in last_part: - index = last_part.index(b'.') - else: - index = TOX_MAX_MESSAGE_LENGTH - size - 1 - 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_type, message): - """ - Current user gets new message - :param friend_num: friend_num of friend who sent message - :param message_type: message type - plain text or action message (/me) - :param message: text of message - """ - if friend_num == self.get_active_number()and self.is_active_a_friend(): # add message to list - t = time.time() - self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) - self._messages.scrollToBottom() - self.get_curr_friend().append_message( - TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type)) - else: - friend = self.get_friend_by_number(friend_num) - friend.inc_messages() - friend.append_message( - TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) - if not friend.visibility: - self.update_filtration() - - def send_message(self, text, friend_num=None): - """ - Send message - :param text: message text - :param friend_num: num of friend - """ - if not self.is_active_a_friend(): - self.send_gc_message(text) - return - if friend_num is None: - friend_num = self.get_active_number() - if text.startswith('/plugin '): - plugin_support.PluginLoader.get_instance().command(text[8:]) - self._screen.messageEdit.clear() - elif text and friend_num + 1: - if text.startswith('/me '): - message_type = TOX_MESSAGE_TYPE['ACTION'] - text = text[4:] - else: - message_type = TOX_MESSAGE_TYPE['NORMAL'] - friend = self.get_friend_by_number(friend_num) - friend.inc_receipts() - if friend.status is not None: - self.split_and_send(friend.number, message_type, text.encode('utf-8')) - t = time.time() - if friend.number == self.get_active_number() and self.is_active_a_friend(): - self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type) - self._screen.messageEdit.clear() - self._messages.scrollToBottom() - friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) - - def delete_message(self, time): - friend = self.get_curr_friend() - friend.delete_message(time) - self._history.delete_message(friend.tox_id, time) - self.update() - - # ----------------------------------------------------------------------------------------------------------------- - # History support - # ----------------------------------------------------------------------------------------------------------------- - - def save_history(self): - """ - Save history to db - """ - s = Settings.get_instance() - if hasattr(self, '_history'): - if s['save_history']: - for friend in filter(lambda x: type(x) is Friend, self._contacts): - if not self._history.friend_exists_in_db(friend.tox_id): - self._history.add_friend_to_db(friend.tox_id) - if not s['save_unsent_only']: - messages = friend.get_corr_for_saving() - else: - messages = friend.get_unsent_messages_for_saving() - self._history.delete_messages(friend.tox_id) - self._history.save_messages_to_db(friend.tox_id, messages) - unsent_messages = friend.get_unsent_messages() - unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 - self._history.update_messages(friend.tox_id, unsent_time) - self._history.save() - del self._history - - def clear_history(self, num=None, save_unsent=False): - """ - Clear chat history - """ - if num is not None: - friend = self._contacts[num] - friend.clear_corr(save_unsent) - if self._history.friend_exists_in_db(friend.tox_id): - self._history.delete_messages(friend.tox_id) - self._history.delete_friend_from_db(friend.tox_id) - else: # clear all history - for number in range(len(self._contacts)): - self.clear_history(number, save_unsent) - if num is None or num == self.get_active_number(): - self.update() - - def load_history(self): - """ - Tries to load next part of messages - """ - if not self._load_history: - return - self._load_history = False - friend = self.get_curr_friend() - friend.load_corr(False) - data = friend.get_corr() - if not data: - return - data.reverse() - data = data[self._messages.count():self._messages.count() + PAGE_SIZE] - for message in data: - if message.get_type() <= 1: # text message - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3], - False) - elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer - if message.get_status() is None: - self.create_unsent_file_item(message) - continue - item = self.create_file_transfer_item(message, False) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') - elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image - self.create_inline_item(message.get_data(), False) - elif message.get_type() < 5: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3], - False) - else: - data = message.get_data() - self.create_gc_message_item(data[0], data[2], data[1], data[4], data[3], False) - self._load_history = True - - def export_db(self, directory): - self._history.export(directory) - - def export_history(self, num, as_text=True, _range=None): - friend = self._contacts[num] - if _range is None: - friend.load_all_corr() - corr = friend.get_corr() - elif _range[1] + 1: - corr = friend.get_corr()[_range[0]:_range[1] + 1] - else: - corr = friend.get_corr()[_range[0]:] - arr = [] - new_line = '\n' if as_text else '
' - for message in corr: - if type(message) is TextMessage: - data = message.get_data() - if as_text: - x = '[{}] {}: {}\n' - else: - x = '[{}] {}: {}
' - arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', - friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, - data[0])) - s = new_line.join(arr) - if not as_text: - s = '{}{}'.format(friend.name, s) - return s - - # ----------------------------------------------------------------------------------------------------------------- - # Friend, message and file transfer items creation - # ----------------------------------------------------------------------------------------------------------------- - - def create_friend_item(self): - """ - Method-factory - :return: new widget for friend instance - """ - return self._factory.friend_item() - - def create_message_item(self, text, time, owner, message_type, append=True): - if message_type == MESSAGE_TYPE['INFO_MESSAGE']: - name = '' - elif owner == MESSAGE_OWNER['FRIEND']: - name = self.get_active_name() - else: - name = self._name - pixmap = None - if self._show_avatars: - if owner == MESSAGE_OWNER['FRIEND']: - pixmap = self.get_curr_friend().get_pixmap() - else: - pixmap = self.get_pixmap() - return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], - message_type, append, pixmap) - - def create_gc_message_item(self, text, time, owner, name, message_type, append=True): - pixmap = None - if self._show_avatars: - if owner == MESSAGE_OWNER['FRIEND']: - pixmap = self.get_curr_friend().get_pixmap() - else: - pixmap = self.get_pixmap() - return self._factory.message_item(text, time, name, True, - message_type - 5, append, pixmap) - - def create_file_transfer_item(self, tm, append=True): - data = list(tm.get_data()) - data[3] = self.get_friend_by_number(data[4]).name if data[3] else self._name - return self._factory.file_transfer_item(data, append) - - def create_unsent_file_item(self, message, append=True): - data = message.get_data() - return self._factory.unsent_file_item(os.path.basename(data[0]), - os.path.getsize(data[0]) if data[1] is None else len(data[1]), - self.name, - data[2], - append) - - def create_inline_item(self, data, append=True): - return self._factory.inline_item(data, append) - - # ----------------------------------------------------------------------------------------------------------------- - # Work with friends (remove, block, set alias, get public key) - # ----------------------------------------------------------------------------------------------------------------- - - def set_alias(self, num): - """ - Set new alias for friend - """ - friend = self._contacts[num] - name = friend.name - dialog = QtWidgets.QApplication.translate('MainWindow', - "Enter new alias for friend {} or leave empty to use friend's name:") - dialog = dialog.format(name) - title = QtWidgets.QApplication.translate('MainWindow', - 'Set alias') - text, ok = QtWidgets.QInputDialog.getText(None, - title, - dialog, - QtWidgets.QLineEdit.Normal, - name) - if ok: - settings = Settings.get_instance() - aliases = settings['friends_aliases'] - if text: - friend.name = bytes(text, 'utf-8') - try: - index = list(map(lambda x: x[0], aliases)).index(friend.tox_id) - aliases[index] = (friend.tox_id, text) - except: - aliases.append((friend.tox_id, text)) - friend.set_alias(text) - else: # use default name - friend.name = bytes(self._tox.friend_get_name(friend.number), 'utf-8') - friend.set_alias('') - try: - index = list(map(lambda x: x[0], aliases)).index(friend.tox_id) - del aliases[index] - except: - pass - settings.save() - if num == self.get_active_number() and self.is_active_a_friend(): - self.update() - - def friend_public_key(self, num): - return self._contacts[num].tox_id - - def delete_friend(self, num): - """ - Removes friend from contact list - :param num: number of friend in list - """ - friend = self._contacts[num] - settings = Settings.get_instance() - try: - index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id) - del settings['friends_aliases'][index] - except: - pass - if friend.tox_id in settings['notes']: - del settings['notes'][friend.tox_id] - settings.save() - self.clear_history(num) - if self._history.friend_exists_in_db(friend.tox_id): - self._history.delete_friend_from_db(friend.tox_id) - self._tox.friend_delete(friend.number) - del self._contacts[num] - self._screen.friends_list.takeItem(num) - if num == self._active_friend: # active friend was deleted - if not len(self._contacts): # last friend was deleted - self.set_active(-1) - else: - self.set_active(0) - data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - - def add_friend(self, tox_id): - """ - Adds friend to list - """ - num = self._tox.friend_add_norequest(tox_id) # num - friend number - item = self.create_friend_item() - try: - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - except Exception as ex: # something is wrong - log('Accept friend request failed! ' + str(ex)) - message_getter = None - friend = Friend(message_getter, num, tox_id, '', item, tox_id) - self._contacts.append(friend) - - def block_user(self, tox_id): - """ - Block user with specified tox id (or public key) - delete from friends list and ignore friend requests - """ - tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] - if tox_id == self.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]: - return - settings = Settings.get_instance() - if tox_id not in settings['blocked']: - settings['blocked'].append(tox_id) - settings.save() - try: - num = self._tox.friend_by_public_key(tox_id) - self.delete_friend(num) - data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - except: # not in friend list - pass - - def unblock_user(self, tox_id, add_to_friend_list): - """ - Unblock user - :param tox_id: tox id of contact - :param add_to_friend_list: add this contact to friend list or not - """ - s = Settings.get_instance() - s['blocked'].remove(tox_id) - s.save() - if add_to_friend_list: - self.add_friend(tox_id) - data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - - # ----------------------------------------------------------------------------------------------------------------- - # Friend requests - # ----------------------------------------------------------------------------------------------------------------- - - def send_friend_request(self, tox_id, message): - """ - Function tries to send request to contact with specified id - :param tox_id: id of new contact or tox dns 4 value - :param message: additional message - :return: True on success else error string - """ - try: - message = message or 'Hello! Add me to your contact list please' - if '@' in tox_id: # value like groupbot@toxme.io - tox_id = tox_dns(tox_id) - if tox_id is None: - raise Exception('TOX DNS lookup failed') - if len(tox_id) == TOX_PUBLIC_KEY_SIZE * 2: # public key - self.add_friend(tox_id) - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle(QtWidgets.QApplication.translate("MainWindow", "Friend added")) - text = (QtWidgets.QApplication.translate("MainWindow", 'Friend added without sending friend request')) - msgBox.setText(text) - msgBox.exec_() - else: - result = self._tox.friend_add(tox_id, message.encode('utf-8')) - tox_id = tox_id[:TOX_PUBLIC_KEY_SIZE * 2] - item = self.create_friend_item() - if not self._history.friend_exists_in_db(tox_id): - self._history.add_friend_to_db(tox_id) - message_getter = self._history.messages_getter(tox_id) - friend = Friend(message_getter, result, tox_id, '', item, tox_id) - self._contacts.append(friend) - data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - return True - except Exception as ex: # wrong data - log('Friend request failed with ' + str(ex)) - return str(ex) - - def process_friend_request(self, tox_id, message): - """ - Accept or ignore friend request - :param tox_id: tox id of contact - :param message: message - """ - try: - text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') - info = text.format(tox_id, message) - fr_req = QtWidgets.QApplication.translate('MainWindow', 'Friend request') - reply = QtWidgets.QMessageBox.question(None, fr_req, info, QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: # accepted - self.add_friend(tox_id) - data = self._tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - except Exception as ex: # something is wrong - log('Accept friend request failed! ' + str(ex)) - - # ----------------------------------------------------------------------------------------------------------------- - # Reset - # ----------------------------------------------------------------------------------------------------------------- - - def reset(self, restart): - """ - Recreate tox instance - :param restart: method which calls restart and returns new tox instance - """ - for contact in self._contacts: - if type(contact) is Friend: - self.friend_exit(contact.number) - else: - self.leave_gc(contact.number) - self._call.stop() - del self._call - del self._tox - self._tox = restart() - self._call = calls.AV(self._tox.AV) - self.status = None - for friend in self._contacts: - friend.number = self._tox.friend_by_public_key(friend.tox_id) # numbers update - self.update_filtration() - - def reconnect(self): - self._waiting_for_reconnection = False - if self.status is None or all(list(map(lambda x: x.status is None, self._contacts))) and len(self._contacts): - self._waiting_for_reconnection = True - self.reset(self._screen.reset) - QtCore.QTimer.singleShot(50000, self.reconnect) - - def close(self): - for friend in filter(lambda x: type(x) is Friend, self._contacts): - self.friend_exit(friend.number) - for i in range(len(self._contacts)): - del self._contacts[0] - if hasattr(self, '_call'): - self._call.stop() - del self._call - s = Settings.get_instance() - s['paused_file_transfers'] = dict(self._paused_file_transfers) if s['resend_files'] else {} - s.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 - """ - settings = Settings.get_instance() - friend = self.get_friend_by_number(friend_number) - auto = settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends'] - inline = is_inline(file_name) and settings['allow_inline'] - file_id = self._tox.file_get_file_id(friend_number, file_number) - accepted = True - if file_id in self._paused_file_transfers: - data = self._paused_file_transfers[file_id] - pos = data[-1] if os.path.exists(data[0]) else 0 - if pos >= size: - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) - return - self._tox.file_seek(friend_number, file_number, pos) - self.accept_transfer(None, data[0], friend_number, file_number, size, False, pos) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) - elif inline and size < 1024 * 1024: - self.accept_transfer(None, '', friend_number, file_number, size, True) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) - - elif auto: - path = settings['auto_accept_path'] - if not path or not os.path.exists(path): - path = curr_directory() - self.accept_transfer(None, path + '/' + file_name, friend_number, file_number, size) - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - size, - file_name, - friend_number, - file_number) - else: - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['INCOMING_NOT_STARTED'], - size, - file_name, - friend_number, - file_number) - accepted = False - if friend_number == self.get_active_number() and self.is_active_a_friend(): - item = self.create_file_transfer_item(tm) - if accepted: - self._file_transfers[(friend_number, file_number)].set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - else: - friend.actions = True - - friend.append_message(tm) - - 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 - """ - i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['CANCELLED']) - 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() - if (friend_number, file_number) in self._file_transfers: - del tr - del self._file_transfers[(friend_number, file_number)] - else: - if not already_cancelled: - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL']) - if friend_number == self.get_active_number() and self.is_active_a_friend(): - tmp = self._messages.count() + i - if tmp >= 0: - self._messages.itemWidget( - self._messages.item(tmp)).update_transfer_state(TOX_FILE_TRANSFER_STATE['CANCELLED'], - 0, -1) - - def cancel_not_started_transfer(self, cancel_time): - self.get_curr_friend().delete_one_unsent_file(cancel_time) - self.update() - - def pause_transfer(self, friend_number, file_number, by_friend=False): - """ - Pause transfer with specified data - """ - tr = self._file_transfers[(friend_number, file_number)] - tr.pause(by_friend) - t = TOX_FILE_TRANSFER_STATE['PAUSED_BY_FRIEND'] if by_friend else TOX_FILE_TRANSFER_STATE['PAUSED_BY_USER'] - self.get_friend_by_number(friend_number).update_transfer_data(file_number, t) - - def resume_transfer(self, friend_number, file_number, by_friend=False): - """ - Resume transfer with specified data - """ - self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['RUNNING']) - tr = self._file_transfers[(friend_number, file_number)] - if by_friend: - tr.state = TOX_FILE_TRANSFER_STATE['RUNNING'] - tr.signal() - else: - tr.send_control(TOX_FILE_CONTROL['RESUME']) - - def accept_transfer(self, item, path, friend_number, file_number, size, inline=False, from_position=0): - """ - :param item: transfer item. - :param path: path for saving - :param friend_number: friend number - :param file_number: file number - :param size: file size - :param inline: is inline image - :param from_position: position for start - """ - path, file_name = os.path.split(path) - new_file_name, i = file_name, 1 - if not from_position: - 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 - path = os.path.join(path, new_file_name) - if not inline: - rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position) - else: - rt = ReceiveToBuffer(self._tox, friend_number, size, file_number) - rt.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend_number, file_number)] = rt - self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME']) - if item is not None: - rt.set_state_changed_handler(item.update_transfer_state) - self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['RUNNING']) - - def send_screenshot(self, data): - """ - Send screenshot to current active friend - :param data: raw data - png - """ - self.send_inline(data, 'toxygen_inline.png') - self._messages.repaint() - - def send_sticker(self, path): - with open(path, 'rb') as fl: - data = fl.read() - self.send_inline(data, 'sticker.png') - - def send_inline(self, data, file_name, friend_number=None, is_resend=False): - friend_number = friend_number or self.get_active_number() - friend = self.get_friend_by_number(friend_number) - if friend.status is None and not is_resend: - m = UnsentFile(file_name, data, time.time()) - friend.append_message(m) - self.update() - return - elif friend.status is None and is_resend: - raise RuntimeError() - st = SendFromBuffer(self._tox, friend.number, data, file_name) - st.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend.number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], - time.time(), - TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], - len(data), - file_name, - friend.number, - st.get_file_number()) - friend.append_message(tm) - if friend_number == self.get_active_number(): - item = self.create_file_transfer_item(tm) - st.set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - - def send_file(self, path, number=None, is_resend=False, file_id=None): - """ - Send file to current active friend - :param path: file path - :param number: friend_number - :param is_resend: is 'offline' message - :param file_id: file id of transfer - """ - friend_number = self.get_active_number() if number is None else number - friend = self.get_friend_by_number(friend_number) - if friend.status is None and not is_resend: - m = UnsentFile(path, None, time.time()) - friend.append_message(m) - self.update() - return - elif friend.status is None and is_resend: - print('Error in sending') - raise RuntimeError() - st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id) - st.set_transfer_finished_handler(self.transfer_finished) - self._file_transfers[(friend_number, st.get_file_number())] = st - tm = TransferMessage(MESSAGE_OWNER['ME'], - time.time(), - TOX_FILE_TRANSFER_STATE['OUTGOING_NOT_STARTED'], - os.path.getsize(path), - os.path.basename(path), - friend_number, - st.get_file_number()) - if friend_number == self.get_active_number(): - item = self.create_file_transfer_item(tm) - st.set_state_changed_handler(item.update_transfer_state) - self._messages.scrollToBottom() - self._contacts[friend_number].append_message(tm) - - def incoming_chunk(self, friend_number, file_number, position, data): - """ - Incoming chunk - """ - self._file_transfers[(friend_number, file_number)].write_chunk(position, data) - - def outgoing_chunk(self, friend_number, file_number, position, size): - """ - Outgoing chunk - """ - self._file_transfers[(friend_number, file_number)].send_chunk(position, size) - - def transfer_finished(self, friend_number, file_number): - transfer = self._file_transfers[(friend_number, file_number)] - t = type(transfer) - if t is ReceiveAvatar: - self.get_friend_by_number(friend_number).load_avatar() - if friend_number == self.get_active_number() and self.is_active_a_friend(): - self.set_active(None) - elif t is ReceiveToBuffer or (t is SendFromBuffer and Settings.get_instance()['allow_inline']): # inline image - print('inline') - inline = InlineImage(transfer.get_data()) - i = self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['FINISHED'], - inline) - if friend_number == self.get_active_number() and self.is_active_a_friend(): - count = self._messages.count() - if count + i + 1 >= 0: - elem = QtWidgets.QListWidgetItem() - item = InlineImageItem(transfer.get_data(), self._messages.width(), elem) - elem.setSizeHint(QtCore.QSize(self._messages.width(), item.height())) - self._messages.insertItem(count + i + 1, elem) - self._messages.setItemWidget(elem, item) - self._messages.scrollToBottom() - elif t is not SendAvatar: - self.get_friend_by_number(friend_number).update_transfer_data(file_number, - TOX_FILE_TRANSFER_STATE['FINISHED']) - del self._file_transfers[(friend_number, file_number)] - del transfer - - # ----------------------------------------------------------------------------------------------------------------- - # Avatars support - # ----------------------------------------------------------------------------------------------------------------- - - def send_avatar(self, friend_number): - """ - :param friend_number: number of friend who should get new avatar - """ - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - if not os.path.isfile(avatar_path): # reset image - avatar_path = None - sa = SendAvatar(avatar_path, self._tox, friend_number) - self._file_transfers[(friend_number, sa.get_file_number())] = sa - - def incoming_avatar(self, friend_number, file_number, size): - """ - Friend changed avatar - :param friend_number: friend number - :param file_number: file number - :param size: size of avatar or 0 (default avatar) - """ - ra = ReceiveAvatar(self._tox, friend_number, size, file_number) - if ra.state != TOX_FILE_TRANSFER_STATE['CANCELLED']: - self._file_transfers[(friend_number, file_number)] = ra - ra.set_transfer_finished_handler(self.transfer_finished) - else: - self.get_friend_by_number(friend_number).load_avatar() - if self.get_active_number() == friend_number and self.is_active_a_friend(): - self.set_active(None) - - def reset_avatar(self): - super(Profile, self).reset_avatar() - for friend in filter(lambda x: x.status is not None, self._contacts): - self.send_avatar(friend.number) - - def set_avatar(self, data): - super(Profile, self).set_avatar(data) - for friend in filter(lambda x: x.status is not None, self._contacts): - self.send_avatar(friend.number) - - # ----------------------------------------------------------------------------------------------------------------- - # AV support - # ----------------------------------------------------------------------------------------------------------------- - - def get_call(self): - return self._call - - call = property(get_call) - - def call_click(self, audio=True, video=False): - """User clicked audio button in main window""" - num = self.get_active_number() - if not self.is_active_a_friend(): - return - if num not in self._call and self.is_active_online(): # start call - if not Settings.get_instance().audio['enabled']: - return - self._call(num, audio, video) - self._screen.active_call() - if video: - text = QtWidgets.QApplication.translate("incoming_call", "Outgoing video call") - else: - text = QtWidgets.QApplication.translate("incoming_call", "Outgoing audio call") - self.get_curr_friend().append_message(InfoMessage(text, time.time())) - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - elif num in self._call: # finish or cancel call if you call with active friend - self.stop_call(num, False) - - def incoming_call(self, audio, video, friend_number): - """ - Incoming call from friend. - """ - if not Settings.get_instance().audio['enabled']: - return - friend = self.get_friend_by_number(friend_number) - if video: - text = QtWidgets.QApplication.translate("incoming_call", "Incoming video call") - else: - text = QtWidgets.QApplication.translate("incoming_call", "Incoming audio call") - friend.append_message(InfoMessage(text, time.time())) - self._incoming_calls.add(friend_number) - if friend_number == self.get_active_number(): - self._screen.incoming_call() - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - else: - friend.actions = True - self._call_widgets[friend_number] = avwidgets.IncomingCallWidget(friend_number, text, friend.name) - self._call_widgets[friend_number].set_pixmap(friend.get_pixmap()) - self._call_widgets[friend_number].show() - - def accept_call(self, friend_number, audio, video): - """ - Accept incoming call with audio or video - """ - self._call.accept_call(friend_number, audio, video) - self._screen.active_call() - if friend_number in self._incoming_calls: - self._incoming_calls.remove(friend_number) - del self._call_widgets[friend_number] - - def stop_call(self, friend_number, by_friend): - """ - Stop call with friend - """ - if friend_number in self._incoming_calls: - self._incoming_calls.remove(friend_number) - text = QtWidgets.QApplication.translate("incoming_call", "Call declined") - else: - text = QtWidgets.QApplication.translate("incoming_call", "Call finished") - self._screen.call_finished() - is_video = self._call.is_video_call(friend_number) - self._call.finish_call(friend_number, by_friend) # finish or decline call - if hasattr(self, '_call_widget'): - self._call_widget[friend_number].close() - del self._call_widget[friend_number] - - def destroy_window(): - if is_video: - cv2.destroyWindow(str(friend_number)) - - threading.Timer(2.0, destroy_window).start() - friend = self.get_friend_by_number(friend_number) - friend.append_message(InfoMessage(text, time.time())) - if friend_number == self.get_active_number(): - self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) - self._messages.scrollToBottom() - - # ----------------------------------------------------------------------------------------------------------------- - # GC support - # ----------------------------------------------------------------------------------------------------------------- - - def is_active_a_friend(self): - return type(self.get_curr_friend()) is Friend - - def get_group_by_number(self, number): - groups = filter(lambda x: type(x) is GroupChat and x.number == number, self._contacts) - return list(groups)[0] - - def add_gc(self, number): - if number == -1: - return - widget = self.create_friend_item() - gc = GroupChat('Group chat #' + str(number), '', widget, self._tox, number) - self._contacts.append(gc) - - def create_group_chat(self): - number = self._tox.add_av_groupchat() - self.add_gc(number) - - def leave_gc(self, num): - gc = self._contacts[num] - self._tox.del_groupchat(gc.number) - del self._contacts[num] - self._screen.friends_list.takeItem(num) - if num == self._active_friend: # active friend was deleted - if not len(self._contacts): # last friend was deleted - self.set_active(-1) - else: - self.set_active(0) - - def group_invite(self, friend_number, gc_type, data): - text = QtWidgets.QApplication.translate('MainWindow', 'User {} invites you to group chat. Accept?') - title = QtWidgets.QApplication.translate('MainWindow', 'Group chat invite') - friend = self.get_friend_by_number(friend_number) - reply = QtWidgets.QMessageBox.question(None, title, text.format(friend.name), QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: # accepted - if gc_type == TOX_GROUPCHAT_TYPE['TEXT']: - number = self._tox.join_groupchat(friend_number, data) - else: - number = self._tox.join_av_groupchat(friend_number, data) - self.add_gc(number) - - def new_gc_message(self, group_number, peer_number, message_type, message): - name = self._tox.group_peername(group_number, peer_number) - message_type += 5 - if group_number == self.get_active_number() and not self.is_active_a_friend(): # add message to list - t = time.time() - self.create_gc_message_item(message, t, MESSAGE_OWNER['FRIEND'], name, message_type) - self._messages.scrollToBottom() - self.get_curr_friend().append_message( - GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type, name)) - else: - gc = self.get_group_by_number(group_number) - gc.inc_messages() - gc.append_message( - GroupChatMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type, name)) - if not gc.visibility: - self.update_filtration() - - def new_gc_title(self, group_number, title): - gc = self.get_group_by_number(group_number) - gc.new_title(title) - if not self.is_active_a_friend() and self.get_active_number() == group_number: - self.update() - - def update_gc(self, group_number): - count = self._tox.group_number_peers(group_number) - gc = self.get_group_by_number(group_number) - text = QtWidgets.QApplication.translate('MainWindow', '{} users in chat') - gc.status_message = text.format(str(count)).encode('utf-8') - if not self.is_active_a_friend() and self.get_active_number() == group_number: - self.update() - - def send_gc_message(self, text): - group_number = self.get_active_number() - if text.startswith('/me '): - text = text[4:] - self._tox.group_action_send(group_number, text.encode('utf-8')) - else: - self._tox.group_message_send(group_number, text.encode('utf-8')) - self._screen.messageEdit.clear() - - def set_title(self, num): - """ - Set new title for gc - """ - gc = self._contacts[num] - name = gc.name - dialog = QtWidgets.QApplication.translate('MainWindow', - "Enter new title for group {}:") - dialog = dialog.format(name) - title = QtWidgets.QApplication.translate('MainWindow', - 'Set title') - text, ok = QtWidgets.QInputDialog.getText(None, - title, - dialog, - QtWidgets.QLineEdit.Normal, - name) - if ok: - text = text.encode('utf-8') - self._tox.group_set_title(gc.number, text) - self.new_gc_title(gc.number, text) - - def get_group_chats(self): - chats = filter(lambda x: type(x) is GroupChat, self._contacts) - chats = map(lambda c: (c.name, c.number), chats) - return list(chats) - - def invite_friend(self, friend_num, group_number): - friend = self._contacts[friend_num] - self._tox.invite_friend(friend.number, group_number) - - def get_gc_peer_name(self, text): - gc = self.get_curr_friend() - if type(gc) is not GroupChat: - return '\t' - names = gc.get_names() - name = re.split("\s+", text)[-1] - suggested_names = list(filter(lambda x: x.startswith(name), names)) - if not len(suggested_names): - return '\t' - return suggested_names[0][len(name):] + ': ' - - -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 = Settings.get_default_settings() - tox_options = Tox.options_new() - # see lines 393-401 - tox_options.contents.ipv6_enabled = settings['ipv6_enabled'] - tox_options.contents.udp_enabled = settings['udp_enabled'] - tox_options.contents.proxy_type = settings['proxy_type'] - tox_options.contents.proxy_host = bytes(settings['proxy_host'], 'UTF-8') - 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/toxygen/screen_sharing.py b/toxygen/screen_sharing.py deleted file mode 100644 index 265658c..0000000 --- a/toxygen/screen_sharing.py +++ /dev/null @@ -1,22 +0,0 @@ -import numpy as np -from PyQt5 import QtWidgets - - -class DesktopGrabber: - - def __init__(self, x, y, width, height): - self._x = x - self._y = y - self._width = width - self._height = height - self._width -= width % 4 - self._height -= height % 4 - self._screen = QtWidgets.QApplication.primaryScreen() - - def read(self): - pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height) - image = pixmap.toImage() - s = image.bits().asstring(self._width * self._height * 4) - arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4)) - - return True, arr diff --git a/toxygen/settings.py b/toxygen/settings.py deleted file mode 100644 index 101f372..0000000 --- a/toxygen/settings.py +++ /dev/null @@ -1,293 +0,0 @@ -from platform import system -import json -import os -from util import Singleton, curr_directory, log, copy, append_slash -import pyaudio -from toxes import ToxES -import smileys - - -class Settings(dict, Singleton): - """ - Settings of current profile + global app settings - """ - - def __init__(self, name): - Singleton.__init__(self) - self.path = ProfileHelper.get_path() + str(name) + '.json' - self.name = name - if os.path.isfile(self.path): - with open(self.path, 'rb') as fl: - data = fl.read() - inst = ToxES.get_instance() - try: - if inst.is_data_encrypted(data): - data = inst.pass_decrypt(data) - info = json.loads(str(data, 'utf-8')) - except Exception as ex: - info = Settings.get_default_settings() - log('Parsing settings error: ' + str(ex)) - super(Settings, self).__init__(info) - self.upgrade() - else: - super(Settings, self).__init__(Settings.get_default_settings()) - self.save() - smileys.SmileyLoader(self) - self.locked = False - self.closing = False - self.unlockScreen = False - p = pyaudio.PyAudio() - input_devices = output_devices = 0 - for i in range(p.get_device_count()): - device = p.get_device_info_by_index(i) - if device["maxInputChannels"]: - input_devices += 1 - if device["maxOutputChannels"]: - output_devices += 1 - self.audio = {'input': p.get_default_input_device_info()['index'] if input_devices else -1, - 'output': p.get_default_output_device_info()['index'] if output_devices else -1, - 'enabled': input_devices and output_devices} - self.video = {'device': -1, 'width': 640, 'height': 480, 'x': 0, 'y': 0} - - @staticmethod - def get_auto_profile(): - p = Settings.get_global_settings_path() - if os.path.isfile(p): - with open(p) as fl: - data = fl.read() - auto = json.loads(data) - if 'path' in auto and 'name' in auto: - path = str(auto['path']) - name = str(auto['name']) - if os.path.isfile(append_slash(path) + name + '.tox'): - return path, name - return '', '' - - @staticmethod - def set_auto_profile(path, name): - p = Settings.get_global_settings_path() - if os.path.isfile(p): - with open(p) as fl: - data = fl.read() - data = json.loads(data) - else: - data = {} - data['path'] = str(path) - data['name'] = str(name) - with open(p, 'w') as fl: - fl.write(json.dumps(data)) - - @staticmethod - def reset_auto_profile(): - p = Settings.get_global_settings_path() - if os.path.isfile(p): - with open(p) as fl: - data = fl.read() - data = json.loads(data) - else: - data = {} - if 'path' in data: - del data['path'] - del data['name'] - with open(p, 'w') as fl: - fl.write(json.dumps(data)) - - @staticmethod - def is_active_profile(path, name): - path = path + name + '.lock' - return os.path.isfile(path) - - @staticmethod - def get_default_settings(): - """ - Default profile settings - """ - return { - 'theme': 'dark', - 'ipv6_enabled': False, - 'udp_enabled': True, - 'proxy_type': 0, - 'proxy_host': '127.0.0.1', - 'proxy_port': 9050, - 'start_port': 0, - 'end_port': 0, - 'tcp_port': 0, - 'notifications': True, - 'sound_notifications': False, - 'language': 'English', - 'save_history': False, - 'allow_inline': True, - 'allow_auto_accept': True, - 'auto_accept_path': None, - 'sorting': 0, - 'auto_accept_from_friends': [], - 'paused_file_transfers': {}, - 'resend_files': True, - 'friends_aliases': [], - 'show_avatars': False, - 'typing_notifications': False, - 'calls_sound': True, - 'blocked': [], - 'plugins': [], - 'notes': {}, - 'smileys': True, - 'smiley_pack': 'default', - 'mirror_mode': False, - 'width': 920, - 'height': 500, - 'x': 400, - 'y': 400, - 'message_font_size': 14, - 'unread_color': 'red', - 'save_unsent_only': False, - 'compact_mode': False, - 'show_welcome_screen': True, - 'close_to_tray': False, - 'font': 'Times New Roman', - 'update': 1, - 'group_notifications': True, - 'download_nodes_list': False - } - - @staticmethod - def supported_languages(): - return { - 'English': 'en_EN', - 'French': 'fr_FR', - 'Russian': 'ru_RU', - 'Ukrainian': 'uk_UA' - } - - @staticmethod - def built_in_themes(): - return { - 'dark': '/styles/dark_style.qss', - 'default': '/styles/style.qss' - } - - def upgrade(self): - default = Settings.get_default_settings() - for key in default: - if key not in self: - print(key) - self[key] = default[key] - self.save() - - def save(self): - text = json.dumps(self) - inst = ToxES.get_instance() - if inst.has_password(): - text = bytes(inst.pass_encrypt(bytes(text, 'utf-8'))) - else: - text = bytes(text, 'utf-8') - with open(self.path, 'wb') as fl: - fl.write(text) - - def close(self): - profile_path = ProfileHelper.get_path() - path = str(profile_path + str(self.name) + '.lock') - if os.path.isfile(path): - os.remove(path) - - def set_active_profile(self): - """ - Mark current profile as active - """ - profile_path = ProfileHelper.get_path() - path = str(profile_path + str(self.name) + '.lock') - with open(path, 'w') as fl: - fl.write('active') - - def export(self, path): - text = json.dumps(self) - with open(path + str(self.name) + '.json', 'w') as fl: - fl.write(text) - - def update_path(self): - self.path = ProfileHelper.get_path() + self.name + '.json' - - @staticmethod - def get_global_settings_path(): - return curr_directory() + '/toxygen.json' - - @staticmethod - def get_default_path(): - if system() == 'Windows': - return os.getenv('APPDATA') + '/Tox/' - elif system() == 'Darwin': - return os.getenv('HOME') + '/Library/Application Support/Tox/' - else: - return os.getenv('HOME') + '/.config/tox/' - - -class ProfileHelper(Singleton): - """ - Class with methods for search, load and save profiles - """ - def __init__(self, path, name): - Singleton.__init__(self) - path = append_slash(path) - self._path = path + name + '.tox' - self._directory = path - # create /avatars if not exists: - directory = path + 'avatars' - if not os.path.exists(directory): - os.makedirs(directory) - - def open_profile(self): - with open(self._path, 'rb') as fl: - data = fl.read() - if data: - return data - else: - raise IOError('Save file has zero size!') - - def get_dir(self): - return self._directory - - def save_profile(self, data): - inst = ToxES.get_instance() - if inst.has_password(): - data = inst.pass_encrypt(data) - with open(self._path, 'wb') as fl: - fl.write(data) - print('Profile saved successfully') - - def export_profile(self, new_path, use_new_path): - path = new_path + os.path.basename(self._path) - with open(self._path, 'rb') as fin: - data = fin.read() - with open(path, 'wb') as fout: - fout.write(data) - print('Profile exported successfully') - copy(self._directory + 'avatars', new_path + 'avatars') - if use_new_path: - self._path = new_path + os.path.basename(self._path) - self._directory = new_path - Settings.get_instance().update_path() - - @staticmethod - def find_profiles(): - """ - Find available tox profiles - """ - path = Settings.get_default_path() - result = [] - # check default path - if not os.path.exists(path): - os.makedirs(path) - for fl in os.listdir(path): - if fl.endswith('.tox'): - name = fl[:-4] - result.append((path, name)) - path = curr_directory() - # check current directory - for fl in os.listdir(path): - if fl.endswith('.tox'): - name = fl[:-4] - result.append((path + '/', name)) - return result - - @staticmethod - def get_path(): - return ProfileHelper.get_instance().get_dir() diff --git a/toxygen/smileys.py b/toxygen/smileys.py deleted file mode 100644 index 52cb603..0000000 --- a/toxygen/smileys.py +++ /dev/null @@ -1,88 +0,0 @@ -import util -import json -import os -from collections import OrderedDict -from PyQt5 import QtCore - - -class SmileyLoader(util.Singleton): - """ - Class which loads smileys packs and insert smileys into messages - """ - - def __init__(self, settings): - super().__init__() - self._settings = settings - self._curr_pack = None # current pack name - self._smileys = {} # smileys dict. key - smiley (str), value - path to image (str) - self._list = [] # smileys list without duplicates - self.load_pack() - - def load_pack(self): - """ - Loads smiley pack - """ - pack_name = self._settings['smiley_pack'] - if self._settings['smileys'] and self._curr_pack != pack_name: - self._curr_pack = pack_name - path = self.get_smileys_path() + 'config.json' - try: - with open(path, encoding='utf8') as fl: - self._smileys = json.loads(fl.read()) - fl.seek(0) - tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict) - print('Smiley pack {} loaded'.format(pack_name)) - keys, values, self._list = [], [], [] - for key, value in tmp.items(): - value = self.get_smileys_path() + value - if value not in values: - keys.append(key) - values.append(value) - self._list = list(zip(keys, values)) - except Exception as ex: - self._smileys = {} - self._list = [] - print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex)) - - def get_smileys_path(self): - return util.curr_directory() + '/smileys/' + self._curr_pack + '/' if self._curr_pack is not None else None - - def get_packs_list(self): - d = util.curr_directory() + '/smileys/' - return [x[1] for x in os.walk(d)][0] - - def get_smileys(self): - return list(self._list) - - def add_smileys_to_text(self, text, edit): - """ - Adds smileys to text - :param text: message - :param edit: MessageEdit instance - :return text with smileys - """ - if not self._settings['smileys'] or not len(self._smileys): - return text - arr = text.split(' ') - for i in range(len(arr)): - if arr[i] in self._smileys: - file_name = self._smileys[arr[i]] # image name - arr[i] = ''.format(arr[i], file_name) - if file_name.endswith('.gif'): # animated smiley - edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name) - return ' '.join(arr) - - -def sticker_loader(): - """ - :return list of stickers - """ - result = [] - d = util.curr_directory() + '/stickers/' - keys = [x[1] for x in os.walk(d)][0] - for key in keys: - path = d + key + '/' - files = filter(lambda f: f.endswith('.png'), os.listdir(path)) - files = map(lambda f: str(path + f), files) - result.extend(files) - return result diff --git a/toxygen/tox.py b/toxygen/tox.py deleted file mode 100644 index ef4e44c..0000000 --- a/toxygen/tox.py +++ /dev/null @@ -1,1601 +0,0 @@ -from ctypes import * -from toxcore_enums_and_consts import * -from toxav import ToxAV -from libtox import LibToxCore - - -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) - ] - - -def string_to_bin(tox_id): - return c_char_p(bytes.fromhex(tox_id)) 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 range(length)) - return res.upper() - - -class Tox: - - 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, byref(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 RuntimeError('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 - self.friend_lossy_packet_cb = None - self.friend_lossless_packet_cb = None - self.group_namelist_change_cb = None - self.group_title_cb = None - self.group_action_cb = None - self.group_message_cb = None - self.group_invite_cb = None - - self.AV = ToxAV(self._tox_pointer) - - def __del__(self): - del self.AV - 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(byref(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), byref(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), - string_to_bin(public_key), byref(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)), byref(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 str(name.value, 'utf-8') - - 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)), byref(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 str(status_message.value, 'utf-8') - - 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)), byref(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), - byref(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), - byref(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), - byref(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, - byref(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), - byref(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), - byref(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 friend_number: number of friend - :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, - byref(tox_err_friend_query)) - tox_err_friend_query = tox_err_friend_query.value - if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: - return str(name.value, 'utf-8') - 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), - byref(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, - byref(tox_err_friend_query)) - tox_err_friend_query = tox_err_friend_query.value - if tox_err_friend_query == TOX_ERR_FRIEND_QUERY['OK']: - return str(status_message.value, 'utf-8') - 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), - byref(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), - byref(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), - byref(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), byref(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)), - byref(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 MemoryError('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_uint8 array) 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, POINTER(c_uint8), 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), 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), byref(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 ArgumentError('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 RuntimeError('A RESUME control was sent, but the file transfer is running normally.') - elif tox_err_file_control == TOX_ERR_FILE_CONTROL['DENIED']: - raise RuntimeError('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 RuntimeError('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), byref(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 ArgumentError('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 IOError('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 RuntimeError('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_get_file_id(self._tox_pointer, c_uint32(friend_number), c_uint32(file_number), file_id, - byref(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)), byref(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 ArgumentError('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)), - byref(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 RuntimeError('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 custom packet sending and receiving - # ----------------------------------------------------------------------------------------------------------------- - - def friend_send_lossy_packet(self, friend_number, data): - """ - Send a custom lossy packet to a friend. - The first byte of data must be in the range 200-254. Maximum length of a - custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. - - Lossy packets behave like UDP packets, meaning they might never reach the - other side or might arrive more than once (if someone is messing with the - connection) or might arrive in the wrong order. - - Unless latency is an issue, it is recommended that you use lossless custom packets instead. - - :param friend_number: The friend number of the friend this lossy packet - :param data: python string containing the packet data - :return: True on success. - """ - tox_err_friend_custom_packet = c_int() - result = self.libtoxcore.tox_friend_send_lossy_packet(self._tox_pointer, c_uint32(friend_number), - c_char_p(data), c_size_t(len(data)), - byref(tox_err_friend_custom_packet)) - tox_err_friend_custom_packet = tox_err_friend_custom_packet.value - if tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['OK']: - return bool(result) - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['NULL']: - raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_FOUND']: - raise ArgumentError('The friend number did not designate a valid friend.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_CONNECTED']: - raise ArgumentError('This client is currently not connected to the friend.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['INVALID']: - raise ArgumentError('The first byte of data was not in the specified range for the packet type.' - 'This range is 200-254 for lossy, and 160-191 for lossless packets.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['EMPTY']: - raise ArgumentError('Attempted to send an empty packet.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['TOO_LONG']: - raise ArgumentError('Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']: - raise RuntimeError('Packet queue is full.') - - def friend_send_lossless_packet(self, friend_number, data): - """ - Send a custom lossless packet to a friend. - The first byte of data must be in the range 160-191. Maximum length of a - custom packet is TOX_MAX_CUSTOM_PACKET_SIZE. - - Lossless packet behaviour is comparable to TCP (reliability, arrive in order) - but with packets instead of a stream. - - :param friend_number: The friend number of the friend this lossless packet - :param data: python string containing the packet data - :return: True on success. - """ - tox_err_friend_custom_packet = c_int() - result = self.libtoxcore.tox_friend_send_lossless_packet(self._tox_pointer, c_uint32(friend_number), - c_char_p(data), c_size_t(len(data)), - byref(tox_err_friend_custom_packet)) - tox_err_friend_custom_packet = tox_err_friend_custom_packet.value - if tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['OK']: - return bool(result) - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['NULL']: - raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_FOUND']: - raise ArgumentError('The friend number did not designate a valid friend.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['FRIEND_NOT_CONNECTED']: - raise ArgumentError('This client is currently not connected to the friend.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['INVALID']: - raise ArgumentError('The first byte of data was not in the specified range for the packet type.' - 'This range is 200-254 for lossy, and 160-191 for lossless packets.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['EMPTY']: - raise ArgumentError('Attempted to send an empty packet.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['TOO_LONG']: - raise ArgumentError('Packet data length exceeded TOX_MAX_CUSTOM_PACKET_SIZE.') - elif tox_err_friend_custom_packet == TOX_ERR_FRIEND_CUSTOM_PACKET['SENDQ']: - raise RuntimeError('Packet queue is full.') - - def callback_friend_lossy_packet(self, callback, user_data): - """ - Set the callback for the `friend_lossy_packet` event. Pass NULL to unset. - - :param callback: Python function. - Should take pointer (c_void_p) to Tox object, - friend_number (c_uint32) - The friend number of the friend who sent a lossy packet, - A byte array (c_uint8 array) containing the received packet data, - length (c_size_t) - The length of the packet data 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, POINTER(c_uint8), c_size_t, c_void_p) - self.friend_lossy_packet_cb = c_callback(callback) - self.libtoxcore.tox_callback_friend_lossy_packet(self._tox_pointer, self.friend_lossy_packet_cb, user_data) - - def callback_friend_lossless_packet(self, callback, user_data): - """ - Set the callback for the `friend_lossless_packet` event. Pass NULL to unset. - - :param callback: Python function. - Should take pointer (c_void_p) to Tox object, - friend_number (c_uint32) - The friend number of the friend who sent a lossless packet, - A byte array (c_uint8 array) containing the received packet data, - length (c_size_t) - The length of the packet data 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, POINTER(c_uint8), c_size_t, c_void_p) - self.friend_lossless_packet_cb = c_callback(callback) - self.libtoxcore.tox_callback_friend_lossless_packet(self._tox_pointer, self.friend_lossless_packet_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, byref(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, byref(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.') - - # ----------------------------------------------------------------------------------------------------------------- - # Group chats - # ----------------------------------------------------------------------------------------------------------------- - - def del_groupchat(self, groupnumber): - result = Tox.libtoxcore.tox_del_groupchat(self._tox_pointer, c_int(groupnumber), None) - return result - - def group_peername(self, groupnumber, peernumber): - buffer = create_string_buffer(TOX_MAX_NAME_LENGTH) - result = Tox.libtoxcore.tox_group_peername(self._tox_pointer, c_int(groupnumber), c_int(peernumber), - buffer, None) - return str(buffer[:result], 'utf-8') - - def invite_friend(self, friendnumber, groupnumber): - result = Tox.libtoxcore.tox_invite_friend(self._tox_pointer, c_int(friendnumber), - c_int(groupnumber), None) - return result - - def join_groupchat(self, friendnumber, data): - result = Tox.libtoxcore.tox_join_groupchat(self._tox_pointer, - c_int(friendnumber), c_char_p(data), c_uint16(len(data)), None) - return result - - def group_message_send(self, groupnumber, message): - result = Tox.libtoxcore.tox_group_message_send(self._tox_pointer, c_int(groupnumber), c_char_p(message), - c_uint16(len(message)), None) - return result - - def group_action_send(self, groupnumber, action): - result = Tox.libtoxcore.tox_group_action_send(self._tox_pointer, - c_int(groupnumber), c_char_p(action), - c_uint16(len(action)), None) - return result - - def group_set_title(self, groupnumber, title): - result = Tox.libtoxcore.tox_group_set_title(self._tox_pointer, c_int(groupnumber), - c_char_p(title), c_uint8(len(title)), None) - return result - - def group_get_title(self, groupnumber): - buffer = create_string_buffer(TOX_MAX_NAME_LENGTH) - result = Tox.libtoxcore.tox_group_get_title(self._tox_pointer, - c_int(groupnumber), buffer, - c_uint32(TOX_MAX_NAME_LENGTH), None) - return str(buffer[:result], 'utf-8') - - def group_number_peers(self, groupnumber): - result = Tox.libtoxcore.tox_group_number_peers(self._tox_pointer, c_int(groupnumber), None) - return result - - def add_av_groupchat(self): - result = self.AV.libtoxav.toxav_add_av_groupchat(self._tox_pointer, None, None) - return result - - def join_av_groupchat(self, friendnumber, data): - result = self.AV.libtoxav.toxav_join_av_groupchat(self._tox_pointer, c_int32(friendnumber), - c_char_p(data), c_uint16(len(data)), - None, None) - return result - - def callback_group_invite(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int32, c_uint8, POINTER(c_uint8), c_uint16, c_void_p) - self.group_invite_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_invite(self._tox_pointer, self.group_invite_cb, user_data) - - def callback_group_message(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p) - self.group_message_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_message(self._tox_pointer, self.group_message_cb, user_data) - - def callback_group_action(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint16, c_void_p) - self.group_action_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_action(self._tox_pointer, self.group_action_cb, user_data) - - def callback_group_title(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_char_p, c_uint8, c_void_p) - self.group_title_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_title(self._tox_pointer, self.group_title_cb, user_data) - - def callback_group_namelist_change(self, callback, user_data=None): - c_callback = CFUNCTYPE(None, c_void_p, c_int, c_int, c_uint8, c_void_p) - self.group_namelist_change_cb = c_callback(callback) - Tox.libtoxcore.tox_callback_group_namelist_change(self._tox_pointer, self.group_namelist_change_cb, user_data) diff --git a/toxygen/tox_dns.py b/toxygen/tox_dns.py deleted file mode 100644 index 26b9619..0000000 --- a/toxygen/tox_dns.py +++ /dev/null @@ -1,59 +0,0 @@ -import json -import urllib.request -from util import log -import settings -from PyQt5 import QtNetwork, QtCore - - -def tox_dns(email): - """ - TOX DNS 4 - :param email: data like 'groupbot@toxme.io' - :return: tox id on success else None - """ - site = email.split('@')[1] - data = {"action": 3, "name": "{}".format(email)} - urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site)) - s = settings.Settings.get_instance() - if not s['proxy_type']: # no proxy - for url in urls: - try: - return send_request(url, data) - except Exception as ex: - log('TOX DNS ERROR: ' + str(ex)) - else: # proxy - netman = QtNetwork.QNetworkAccessManager() - proxy = QtNetwork.QNetworkProxy() - proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) - proxy.setHostName(s['proxy_host']) - proxy.setPort(s['proxy_port']) - netman.setProxy(proxy) - for url in urls: - try: - request = QtNetwork.QNetworkRequest() - request.setUrl(QtCore.QUrl(url)) - request.setHeader(QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json") - reply = netman.post(request, bytes(json.dumps(data), 'utf-8')) - - while not reply.isFinished(): - QtCore.QThread.msleep(1) - QtCore.QCoreApplication.processEvents() - data = bytes(reply.readAll().data()) - result = json.loads(str(data, 'utf-8')) - if not result['c']: - return result['tox_id'] - except Exception as ex: - log('TOX DNS ERROR: ' + str(ex)) - - return None # error - - -def send_request(url, data): - req = urllib.request.Request(url) - req.add_header('Content-Type', 'application/json') - response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8')) - res = json.loads(str(response.read(), 'utf-8')) - if not res['c']: - return res['tox_id'] - else: - raise LookupError() diff --git a/toxygen/toxav.py b/toxygen/toxav.py deleted file mode 100644 index 0ab891c..0000000 --- a/toxygen/toxav.py +++ /dev/null @@ -1,362 +0,0 @@ -from ctypes import c_int, POINTER, c_void_p, byref, ArgumentError, c_uint32, CFUNCTYPE, c_size_t, c_uint8, c_uint16 -from ctypes import c_char_p, c_int32, c_bool, cast -from libtox import LibToxAV -from toxav_enums import * - - -class ToxAV: - """ - The ToxAV instance type. Each ToxAV instance can be bound to only one Tox instance, and Tox instance can have only - one ToxAV instance. One must make sure to close ToxAV instance prior closing Tox instance otherwise undefined - behaviour occurs. Upon closing of ToxAV instance, all active calls will be forcibly terminated without notifying - peers. - """ - - # ----------------------------------------------------------------------------------------------------------------- - # Creation and destruction - # ----------------------------------------------------------------------------------------------------------------- - - def __init__(self, tox_pointer): - """ - Start new A/V session. There can only be only one session per Tox instance. - - :param tox_pointer: pointer to Tox instance - """ - self.libtoxav = LibToxAV() - toxav_err_new = c_int() - self.libtoxav.toxav_new.restype = POINTER(c_void_p) - self._toxav_pointer = self.libtoxav.toxav_new(tox_pointer, byref(toxav_err_new)) - toxav_err_new = toxav_err_new.value - if toxav_err_new == TOXAV_ERR_NEW['NULL']: - raise ArgumentError('One of the arguments to the function was NULL when it was not expected.') - elif toxav_err_new == TOXAV_ERR_NEW['MALLOC']: - raise MemoryError('Memory allocation failure while trying to allocate structures required for the A/V ' - 'session.') - elif toxav_err_new == TOXAV_ERR_NEW['MULTIPLE']: - raise RuntimeError('Attempted to create a second session for the same Tox instance.') - - self.call_state_cb = None - self.audio_receive_frame_cb = None - self.video_receive_frame_cb = None - self.call_cb = None - - def __del__(self): - """ - Releases all resources associated with the A/V session. - - If any calls were ongoing, these will be forcibly terminated without notifying peers. After calling this - function, no other functions may be called and the av pointer becomes invalid. - """ - self.libtoxav.toxav_kill(self._toxav_pointer) - - def get_tox_pointer(self): - """ - Returns the Tox instance the A/V object was created for. - - :return: pointer to the Tox instance - """ - self.libtoxav.toxav_get_tox.restype = POINTER(c_void_p) - return self.libtoxav.toxav_get_tox(self._toxav_pointer) - - # ----------------------------------------------------------------------------------------------------------------- - # A/V event loop - # ----------------------------------------------------------------------------------------------------------------- - - def iteration_interval(self): - """ - Returns the interval in milliseconds when the next toxav_iterate call should be. If no call is active at the - moment, this function returns 200. - - :return: interval in milliseconds - """ - return self.libtoxav.toxav_iteration_interval(self._toxav_pointer) - - def iterate(self): - """ - Main loop for the session. This function needs to be called in intervals of toxav_iteration_interval() - milliseconds. It is best called in the separate thread from tox_iterate. - """ - self.libtoxav.toxav_iterate(self._toxav_pointer) - - # ----------------------------------------------------------------------------------------------------------------- - # Call setup - # ----------------------------------------------------------------------------------------------------------------- - - def call(self, friend_number, audio_bit_rate, video_bit_rate): - """ - Call a friend. This will start ringing the friend. - - It is the client's responsibility to stop ringing after a certain timeout, if such behaviour is desired. If the - client does not stop ringing, the library will not stop until the friend is disconnected. Audio and video - receiving are both enabled by default. - - :param friend_number: The friend number of the friend that should be called. - :param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending. - :param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending. - :return: True on success. - """ - toxav_err_call = c_int() - result = self.libtoxav.toxav_call(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate), - c_uint32(video_bit_rate), byref(toxav_err_call)) - toxav_err_call = toxav_err_call.value - if toxav_err_call == TOXAV_ERR_CALL['OK']: - return bool(result) - elif toxav_err_call == TOXAV_ERR_CALL['MALLOC']: - raise MemoryError('A resource allocation error occurred while trying to create the structures required for ' - 'the call.') - elif toxav_err_call == TOXAV_ERR_CALL['SYNC']: - raise RuntimeError('Synchronization error occurred.') - elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_FOUND']: - raise ArgumentError('The friend number did not designate a valid friend.') - elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_NOT_CONNECTED']: - raise ArgumentError('The friend was valid, but not currently connected.') - elif toxav_err_call == TOXAV_ERR_CALL['FRIEND_ALREADY_IN_CALL']: - raise ArgumentError('Attempted to call a friend while already in an audio or video call with them.') - elif toxav_err_call == TOXAV_ERR_CALL['INVALID_BIT_RATE']: - raise ArgumentError('Audio or video bit rate is invalid.') - - def callback_call(self, callback, user_data): - """ - Set the callback for the `call` event. Pass None to unset. - - :param callback: The function for the call callback. - - Should take pointer (c_void_p) to ToxAV object, - The friend number (c_uint32) from which the call is incoming. - True (c_bool) if friend is sending audio. - True (c_bool) if friend is sending video. - 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_bool, c_void_p) - self.call_cb = c_callback(callback) - self.libtoxav.toxav_callback_call(self._toxav_pointer, self.call_cb, user_data) - - def answer(self, friend_number, audio_bit_rate, video_bit_rate): - """ - Accept an incoming call. - - If answering fails for any reason, the call will still be pending and it is possible to try and answer it later. - Audio and video receiving are both enabled by default. - - :param friend_number: The friend number of the friend that is calling. - :param audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending. - :param video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending. - :return: True on success. - """ - toxav_err_answer = c_int() - result = self.libtoxav.toxav_answer(self._toxav_pointer, c_uint32(friend_number), c_uint32(audio_bit_rate), - c_uint32(video_bit_rate), byref(toxav_err_answer)) - toxav_err_answer = toxav_err_answer.value - if toxav_err_answer == TOXAV_ERR_ANSWER['OK']: - return bool(result) - elif toxav_err_answer == TOXAV_ERR_ANSWER['SYNC']: - raise RuntimeError('Synchronization error occurred.') - elif toxav_err_answer == TOXAV_ERR_ANSWER['CODEC_INITIALIZATION']: - raise RuntimeError('Failed to initialize codecs for call session. Note that codec initiation will fail if ' - 'there is no receive callback registered for either audio or video.') - elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_FOUND']: - raise ArgumentError('The friend number did not designate a valid friend.') - elif toxav_err_answer == TOXAV_ERR_ANSWER['FRIEND_NOT_CALLING']: - raise ArgumentError('The friend was valid, but they are not currently trying to initiate a call. This is ' - 'also returned if this client is already in a call with the friend.') - elif toxav_err_answer == TOXAV_ERR_ANSWER['INVALID_BIT_RATE']: - raise ArgumentError('Audio or video bit rate is invalid.') - - # ----------------------------------------------------------------------------------------------------------------- - # Call state graph - # ----------------------------------------------------------------------------------------------------------------- - - def callback_call_state(self, callback, user_data): - """ - Set the callback for the `call_state` event. Pass None to unset. - - :param callback: Python function. - The function for the call_state callback. - - Should take pointer (c_void_p) to ToxAV object, - The friend number (c_uint32) for which the call state changed. - The bitmask of the new call state which is guaranteed to be different than the previous state. The state is set - to 0 when the call is paused. The bitmask represents all the activities currently performed by the friend. - 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.call_state_cb = c_callback(callback) - self.libtoxav.toxav_callback_call_state(self._toxav_pointer, self.call_state_cb, user_data) - - # ----------------------------------------------------------------------------------------------------------------- - # Call control - # ----------------------------------------------------------------------------------------------------------------- - - def call_control(self, friend_number, control): - """ - Sends a call control command to a friend. - - :param friend_number: The friend number of the friend this client is in a call with. - :param control: The control command to send. - :return: True on success. - """ - toxav_err_call_control = c_int() - result = self.libtoxav.toxav_call_control(self._toxav_pointer, c_uint32(friend_number), c_int(control), - byref(toxav_err_call_control)) - toxav_err_call_control = toxav_err_call_control.value - if toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['OK']: - return bool(result) - elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['SYNC']: - raise RuntimeError('Synchronization error occurred.') - elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_FOUND']: - raise ArgumentError('The friend_number passed did not designate a valid friend.') - elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['FRIEND_NOT_IN_CALL']: - raise RuntimeError('This client is currently not in a call with the friend. Before the call is answered, ' - 'only CANCEL is a valid control.') - elif toxav_err_call_control == TOXAV_ERR_CALL_CONTROL['INVALID_TRANSITION']: - raise RuntimeError('Happens if user tried to pause an already paused call or if trying to resume a call ' - 'that is not paused.') - - # ----------------------------------------------------------------------------------------------------------------- - # TODO Controlling bit rates - # ----------------------------------------------------------------------------------------------------------------- - - # ----------------------------------------------------------------------------------------------------------------- - # A/V sending - # ----------------------------------------------------------------------------------------------------------------- - - def audio_send_frame(self, friend_number, pcm, sample_count, channels, sampling_rate): - """ - Send an audio frame to a friend. - - The expected format of the PCM data is: [s1c1][s1c2][...][s2c1][s2c2][...]... - Meaning: sample 1 for channel 1, sample 1 for channel 2, ... - For mono audio, this has no meaning, every sample is subsequent. For stereo, this means the expected format is - LRLRLR... with samples for left and right alternating. - - :param friend_number: The friend number of the friend to which to send an audio frame. - :param pcm: An array of audio samples. The size of this array must be sample_count * channels. - :param sample_count: Number of samples in this frame. Valid numbers here are - ((sample rate) * (audio length) / 1000), where audio length can be 2.5, 5, 10, 20, 40 or 60 milliseconds. - :param channels: Number of audio channels. Sulpported values are 1 and 2. - :param sampling_rate: Audio sampling rate used in this frame. Valid sampling rates are 8000, 12000, 16000, - 24000, or 48000. - """ - toxav_err_send_frame = c_int() - result = self.libtoxav.toxav_audio_send_frame(self._toxav_pointer, c_uint32(friend_number), - cast(pcm, c_void_p), - c_size_t(sample_count), c_uint8(channels), - c_uint32(sampling_rate), byref(toxav_err_send_frame)) - toxav_err_send_frame = toxav_err_send_frame.value - if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']: - return bool(result) - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']: - raise ArgumentError('The samples data pointer was NULL.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']: - raise ArgumentError('The friend_number passed did not designate a valid friend.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']: - raise RuntimeError('This client is currently not in a call with the friend.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']: - raise RuntimeError('Synchronization error occurred.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']: - raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too ' - 'large, or the audio sampling rate may be unsupported.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']: - raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said' - 'payload.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']: - RuntimeError('Failed to push frame through rtp interface.') - - def video_send_frame(self, friend_number, width, height, y, u, v): - """ - Send a video frame to a friend. - - Y - plane should be of size: height * width - U - plane should be of size: (height/2) * (width/2) - V - plane should be of size: (height/2) * (width/2) - - :param friend_number: The friend number of the friend to which to send a video frame. - :param width: Width of the frame in pixels. - :param height: Height of the frame in pixels. - :param y: Y (Luminance) plane data. - :param u: U (Chroma) plane data. - :param v: V (Chroma) plane data. - """ - toxav_err_send_frame = c_int() - result = self.libtoxav.toxav_video_send_frame(self._toxav_pointer, c_uint32(friend_number), c_uint16(width), - c_uint16(height), c_char_p(y), c_char_p(u), c_char_p(v), - byref(toxav_err_send_frame)) - toxav_err_send_frame = toxav_err_send_frame.value - if toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['OK']: - return bool(result) - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['NULL']: - raise ArgumentError('One of Y, U, or V was NULL.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_FOUND']: - raise ArgumentError('The friend_number passed did not designate a valid friend.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['FRIEND_NOT_IN_CALL']: - raise RuntimeError('This client is currently not in a call with the friend.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['SYNC']: - raise RuntimeError('Synchronization error occurred.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['INVALID']: - raise ArgumentError('One of the frame parameters was invalid. E.g. the resolution may be too small or too ' - 'large, or the audio sampling rate may be unsupported.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['PAYLOAD_TYPE_DISABLED']: - raise RuntimeError('Either friend turned off audio or video receiving or we turned off sending for the said' - 'payload.') - elif toxav_err_send_frame == TOXAV_ERR_SEND_FRAME['RTP_FAILED']: - RuntimeError('Failed to push frame through rtp interface.') - - # ----------------------------------------------------------------------------------------------------------------- - # A/V receiving - # ----------------------------------------------------------------------------------------------------------------- - - def callback_audio_receive_frame(self, callback, user_data): - """ - Set the callback for the `audio_receive_frame` event. Pass None to unset. - - :param callback: Python function. - Function for the audio_receive_frame callback. The callback can be called multiple times per single - iteration depending on the amount of queued frames in the buffer. The received format is the same as in send - function. - - Should take pointer (c_void_p) to ToxAV object, - The friend number (c_uint32) of the friend who sent an audio frame. - An array (c_uint8) of audio samples (sample_count * channels elements). - The number (c_size_t) of audio samples per channel in the PCM array. - Number (c_uint8) of audio channels. - Sampling rate (c_uint32) used in this frame. - 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, POINTER(c_uint8), c_size_t, c_uint8, c_uint32, c_void_p) - self.audio_receive_frame_cb = c_callback(callback) - self.libtoxav.toxav_callback_audio_receive_frame(self._toxav_pointer, self.audio_receive_frame_cb, user_data) - - def callback_video_receive_frame(self, callback, user_data): - """ - Set the callback for the `video_receive_frame` event. Pass None to unset. - - :param callback: Python function. - The function type for the video_receive_frame callback. - - Should take - toxAV pointer (c_void_p) to ToxAV object, - friend_number The friend number (c_uint32) of the friend who sent a video frame. - width Width (c_uint16) of the frame in pixels. - height Height (c_uint16) of the frame in pixels. - y - u - v Plane data (POINTER(c_uint8)). - The size of plane data is derived from width and height where - Y = MAX(width, abs(ystride)) * height, - U = MAX(width/2, abs(ustride)) * (height/2) and - V = MAX(width/2, abs(vstride)) * (height/2). - ystride - ustride - vstride Strides data (c_int32). Strides represent padding for each plane that may or may not be present. You must - handle strides in your image processing code. Strides are negative if the image is bottom-up - hence why you MUST abs() it when calculating plane buffer size. - user_data 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_uint16, c_uint16, POINTER(c_uint8), POINTER(c_uint8), - POINTER(c_uint8), c_int32, c_int32, c_int32, c_void_p) - self.video_receive_frame_cb = c_callback(callback) - self.libtoxav.toxav_callback_video_receive_frame(self._toxav_pointer, self.video_receive_frame_cb, user_data) diff --git a/toxygen/toxav_enums.py b/toxygen/toxav_enums.py deleted file mode 100644 index 3f3977a..0000000 --- a/toxygen/toxav_enums.py +++ /dev/null @@ -1,131 +0,0 @@ -TOXAV_ERR_NEW = { - # The function returned successfully. - 'OK': 0, - # One of the arguments to the function was NULL when it was not expected. - 'NULL': 1, - # Memory allocation failure while trying to allocate structures required for the A/V session. - 'MALLOC': 2, - # Attempted to create a second session for the same Tox instance. - 'MULTIPLE': 3, -} - -TOXAV_ERR_CALL = { - # The function returned successfully. - 'OK': 0, - # A resource allocation error occurred while trying to create the structures required for the call. - 'MALLOC': 1, - # Synchronization error occurred. - 'SYNC': 2, - # The friend number did not designate a valid friend. - 'FRIEND_NOT_FOUND': 3, - # The friend was valid, but not currently connected. - 'FRIEND_NOT_CONNECTED': 4, - # Attempted to call a friend while already in an audio or video call with them. - 'FRIEND_ALREADY_IN_CALL': 5, - # Audio or video bit rate is invalid. - 'INVALID_BIT_RATE': 6, -} - -TOXAV_ERR_ANSWER = { - # The function returned successfully. - 'OK': 0, - # Synchronization error occurred. - 'SYNC': 1, - # Failed to initialize codecs for call session. Note that codec initiation will fail if there is no receive callback - # registered for either audio or video. - 'CODEC_INITIALIZATION': 2, - # The friend number did not designate a valid friend. - 'FRIEND_NOT_FOUND': 3, - # The friend was valid, but they are not currently trying to initiate a call. This is also returned if this client - # is already in a call with the friend. - 'FRIEND_NOT_CALLING': 4, - # Audio or video bit rate is invalid. - 'INVALID_BIT_RATE': 5, -} - -TOXAV_FRIEND_CALL_STATE = { - # Set by the AV core if an error occurred on the remote end or if friend timed out. This is the final state after - # which no more state transitions can occur for the call. This call state will never be triggered in combination - # with other call states. - 'ERROR': 1, - # The call has finished. This is the final state after which no more state transitions can occur for the call. This - # call state will never be triggered in combination with other call states. - 'FINISHED': 2, - # The flag that marks that friend is sending audio. - 'SENDING_A': 4, - # The flag that marks that friend is sending video. - 'SENDING_V': 8, - # The flag that marks that friend is receiving audio. - 'ACCEPTING_A': 16, - # The flag that marks that friend is receiving video. - 'ACCEPTING_V': 32, -} - -TOXAV_CALL_CONTROL = { - # Resume a previously paused call. Only valid if the pause was caused by this client, if not, this control is - # ignored. Not valid before the call is accepted. - 'RESUME': 0, - # Put a call on hold. Not valid before the call is accepted. - 'PAUSE': 1, - # Reject a call if it was not answered, yet. Cancel a call after it was answered. - 'CANCEL': 2, - # Request that the friend stops sending audio. Regardless of the friend's compliance, this will cause the - # audio_receive_frame event to stop being triggered on receiving an audio frame from the friend. - 'MUTE_AUDIO': 3, - # Calling this control will notify client to start sending audio again. - 'UNMUTE_AUDIO': 4, - # Request that the friend stops sending video. Regardless of the friend's compliance, this will cause the - # video_receive_frame event to stop being triggered on receiving a video frame from the friend. - 'HIDE_VIDEO': 5, - # Calling this control will notify client to start sending video again. - 'SHOW_VIDEO': 6, -} - -TOXAV_ERR_CALL_CONTROL = { - # The function returned successfully. - 'OK': 0, - # Synchronization error occurred. - 'SYNC': 1, - # The friend_number passed did not designate a valid friend. - 'FRIEND_NOT_FOUND': 2, - # This client is currently not in a call with the friend. Before the call is answered, only CANCEL is a valid - # control. - 'FRIEND_NOT_IN_CALL': 3, - # Happens if user tried to pause an already paused call or if trying to resume a call that is not paused. - 'INVALID_TRANSITION': 4, -} - -TOXAV_ERR_BIT_RATE_SET = { - # The function returned successfully. - 'OK': 0, - # Synchronization error occurred. - 'SYNC': 1, - # The audio bit rate passed was not one of the supported values. - 'INVALID_AUDIO_BIT_RATE': 2, - # The video bit rate passed was not one of the supported values. - 'INVALID_VIDEO_BIT_RATE': 3, - # The friend_number passed did not designate a valid friend. - 'FRIEND_NOT_FOUND': 4, - # This client is currently not in a call with the friend. - 'FRIEND_NOT_IN_CALL': 5, -} - -TOXAV_ERR_SEND_FRAME = { - # The function returned successfully. - 'OK': 0, - # In case of video, one of Y, U, or V was NULL. In case of audio, the samples data pointer was NULL. - 'NULL': 1, - # The friend_number passed did not designate a valid friend. - 'FRIEND_NOT_FOUND': 2, - # This client is currently not in a call with the friend. - 'FRIEND_NOT_IN_CALL': 3, - # Synchronization error occurred. - 'SYNC': 4, - # One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling - # rate may be unsupported. - 'INVALID': 5, - # Either friend turned off audio or video receiving or we turned off sending for the said payload. - 'PAYLOAD_TYPE_DISABLED': 6, - # Failed to push frame through rtp interface. - 'RTP_FAILED': 7, -} diff --git a/toxygen/toxcore_enums_and_consts.py b/toxygen/toxcore_enums_and_consts.py deleted file mode 100644 index a17d93e..0000000 --- a/toxygen/toxcore_enums_and_consts.py +++ /dev/null @@ -1,220 +0,0 @@ -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_CHAT_CHANGE = { - 'PEER_ADD': 0, - 'PEER_DEL': 1, - 'PEER_NAME': 2 -} - -TOX_GROUPCHAT_TYPE = { - 'TEXT': 0, - 'AV': 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/toxygen/toxencryptsave.py b/toxygen/toxencryptsave.py deleted file mode 100644 index b579e21..0000000 --- a/toxygen/toxencryptsave.py +++ /dev/null @@ -1,74 +0,0 @@ -import libtox -from ctypes import c_size_t, create_string_buffer, byref, c_int, ArgumentError, c_char_p, c_bool -from toxencryptsave_enums_and_consts import * - - -class ToxEncryptSave: - - def __init__(self): - self.libtoxencryptsave = libtox.LibToxEncryptSave() - - def is_data_encrypted(self, data): - """ - Checks if given data is encrypted - """ - func = self.libtoxencryptsave.tox_is_data_encrypted - func.restype = c_bool - result = func(c_char_p(bytes(data))) - return result - - def pass_encrypt(self, data, password): - """ - Encrypts the given data with the given password. - - :return: output array - """ - out = create_string_buffer(len(data) + TOX_PASS_ENCRYPTION_EXTRA_LENGTH) - tox_err_encryption = c_int() - self.libtoxencryptsave.tox_pass_encrypt(c_char_p(data), - c_size_t(len(data)), - c_char_p(bytes(password, 'utf-8')), - c_size_t(len(password)), - out, - byref(tox_err_encryption)) - tox_err_encryption = tox_err_encryption.value - if tox_err_encryption == TOX_ERR_ENCRYPTION['OK']: - return out[:] - elif tox_err_encryption == TOX_ERR_ENCRYPTION['NULL']: - raise ArgumentError('Some input data, or maybe the output pointer, was null.') - elif tox_err_encryption == TOX_ERR_ENCRYPTION['KEY_DERIVATION_FAILED']: - raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a' - ' lack of memory issue. The functions accepting keys do not produce this error.') - elif tox_err_encryption == TOX_ERR_ENCRYPTION['FAILED']: - raise RuntimeError('The encryption itself failed.') - - def pass_decrypt(self, data, password): - """ - Decrypts the given data with the given password. - - :return: output array - """ - out = create_string_buffer(len(data) - TOX_PASS_ENCRYPTION_EXTRA_LENGTH) - tox_err_decryption = c_int() - self.libtoxencryptsave.tox_pass_decrypt(c_char_p(bytes(data)), - c_size_t(len(data)), - c_char_p(bytes(password, 'utf-8')), - c_size_t(len(password)), - out, - byref(tox_err_decryption)) - tox_err_decryption = tox_err_decryption.value - if tox_err_decryption == TOX_ERR_DECRYPTION['OK']: - return out[:] - elif tox_err_decryption == TOX_ERR_DECRYPTION['NULL']: - raise ArgumentError('Some input data, or maybe the output pointer, was null.') - elif tox_err_decryption == TOX_ERR_DECRYPTION['INVALID_LENGTH']: - raise ArgumentError('The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes') - elif tox_err_decryption == TOX_ERR_DECRYPTION['BAD_FORMAT']: - raise ArgumentError('The input data is missing the magic number (i.e. wasn\'t created by this module, or is' - ' corrupted)') - elif tox_err_decryption == TOX_ERR_DECRYPTION['KEY_DERIVATION_FAILED']: - raise RuntimeError('The crypto lib was unable to derive a key from the given passphrase, which is usually a' - ' lack of memory issue. The functions accepting keys do not produce this error.') - elif tox_err_decryption == TOX_ERR_DECRYPTION['FAILED']: - raise RuntimeError('The encrypted byte array could not be decrypted. Either the data was corrupt or the ' - 'password/key was incorrect.') diff --git a/toxygen/toxencryptsave_enums_and_consts.py b/toxygen/toxencryptsave_enums_and_consts.py deleted file mode 100644 index cf795f8..0000000 --- a/toxygen/toxencryptsave_enums_and_consts.py +++ /dev/null @@ -1,29 +0,0 @@ -TOX_ERR_ENCRYPTION = { - # The function returned successfully. - 'OK': 0, - # Some input data, or maybe the output pointer, was null. - 'NULL': 1, - # The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The - # functions accepting keys do not produce this error. - 'KEY_DERIVATION_FAILED': 2, - # The encryption itself failed. - 'FAILED': 3 -} - -TOX_ERR_DECRYPTION = { - # The function returned successfully. - 'OK': 0, - # Some input data, or maybe the output pointer, was null. - 'NULL': 1, - # The input data was shorter than TOX_PASS_ENCRYPTION_EXTRA_LENGTH bytes - 'INVALID_LENGTH': 2, - # The input data is missing the magic number (i.e. wasn't created by this module, or is corrupted) - 'BAD_FORMAT': 3, - # The crypto lib was unable to derive a key from the given passphrase, which is usually a lack of memory issue. The - # functions accepting keys do not produce this error. - 'KEY_DERIVATION_FAILED': 4, - # The encrypted byte array could not be decrypted. Either the data was corrupt or the password/key was incorrect. - 'FAILED': 5, -} - -TOX_PASS_ENCRYPTION_EXTRA_LENGTH = 80 diff --git a/toxygen/toxes.py b/toxygen/toxes.py deleted file mode 100644 index 5b7282f..0000000 --- a/toxygen/toxes.py +++ /dev/null @@ -1,28 +0,0 @@ -import util -import toxencryptsave - - -class ToxES(util.Singleton): - - def __init__(self): - super().__init__() - self._toxencryptsave = toxencryptsave.ToxEncryptSave() - self._passphrase = None - - def set_password(self, passphrase): - self._passphrase = passphrase - - def has_password(self): - return bool(self._passphrase) - - def is_password(self, password): - return self._passphrase == password - - def is_data_encrypted(self, data): - return len(data) > 0 and self._toxencryptsave.is_data_encrypted(data) - - def pass_encrypt(self, data): - return self._toxencryptsave.pass_encrypt(data, self._passphrase) - - def pass_decrypt(self, data): - return self._toxencryptsave.pass_decrypt(data, self._passphrase) diff --git a/toxygen/updater.py b/toxygen/updater.py deleted file mode 100644 index 762892a..0000000 --- a/toxygen/updater.py +++ /dev/null @@ -1,110 +0,0 @@ -import util -import os -import settings -import platform -import urllib -from PyQt5 import QtNetwork, QtCore -import subprocess - - -def connection_available(): - try: - urllib.request.urlopen('http://216.58.192.142', timeout=1) # google.com - return True - except: - return False - - -def updater_available(): - if is_from_sources(): - return os.path.exists(util.curr_directory() + '/toxygen_updater.py') - elif platform.system() == 'Windows': - return os.path.exists(util.curr_directory() + '/toxygen_updater.exe') - else: - return os.path.exists(util.curr_directory() + '/toxygen_updater') - - -def check_for_updates(): - current_version = util.program_version - major, minor, patch = list(map(lambda x: int(x), current_version.split('.'))) - versions = generate_versions(major, minor, patch) - for version in versions: - if send_request(version): - return version - return None # no new version was found - - -def is_from_sources(): - return __file__.endswith('.py') - - -def test_url(version): - return 'https://github.com/toxygen-project/toxygen/releases/tag/v' + version - - -def get_url(version): - if is_from_sources(): - return 'https://github.com/toxygen-project/toxygen/archive/v' + version + '.zip' - else: - if platform.system() == 'Windows': - name = 'toxygen_windows.zip' - elif util.is_64_bit(): - name = 'toxygen_linux_64.tar.gz' - else: - name = 'toxygen_linux.tar.gz' - return 'https://github.com/toxygen-project/toxygen/releases/download/v{}/{}'.format(version, name) - - -def get_params(url, version): - if is_from_sources(): - if platform.system() == 'Windows': - return ['python', 'toxygen_updater.py', url, version] - else: - return ['python3', 'toxygen_updater.py', url, version] - elif platform.system() == 'Windows': - return [util.curr_directory() + '/toxygen_updater.exe', url, version] - else: - return ['./toxygen_updater', url, version] - - -def download(version): - os.chdir(util.curr_directory()) - url = get_url(version) - params = get_params(url, version) - print('Updating Toxygen') - util.log('Updating Toxygen') - try: - subprocess.Popen(params) - except Exception as ex: - util.log('Exception: running updater failed with ' + str(ex)) - - -def send_request(version): - s = settings.Settings.get_instance() - netman = QtNetwork.QNetworkAccessManager() - proxy = QtNetwork.QNetworkProxy() - if s['proxy_type']: - proxy.setType(QtNetwork.QNetworkProxy.Socks5Proxy if s['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy) - proxy.setHostName(s['proxy_host']) - proxy.setPort(s['proxy_port']) - netman.setProxy(proxy) - url = test_url(version) - try: - request = QtNetwork.QNetworkRequest() - request.setUrl(QtCore.QUrl(url)) - reply = netman.get(request) - while not reply.isFinished(): - QtCore.QThread.msleep(1) - QtCore.QCoreApplication.processEvents() - attr = reply.attribute(QtNetwork.QNetworkRequest.HttpStatusCodeAttribute) - return attr is not None and 200 <= attr < 300 - except Exception as ex: - util.log('TOXYGEN UPDATER ERROR: ' + str(ex)) - return False - - -def generate_versions(major, minor, patch): - new_major = '.'.join([str(major + 1), '0', '0']) - new_minor = '.'.join([str(major), str(minor + 1), '0']) - new_patch = '.'.join([str(major), str(minor), str(patch + 1)]) - return new_major, new_minor, new_patch diff --git a/toxygen/util.py b/toxygen/util.py deleted file mode 100644 index 6157775..0000000 --- a/toxygen/util.py +++ /dev/null @@ -1,107 +0,0 @@ -import os -import time -import shutil -import sys -import re - - -program_version = '0.4.4' - - -def cached(func): - saved_result = None - - def wrapped_func(): - nonlocal saved_result - if saved_result is None: - saved_result = func() - - return saved_result - - return wrapped_func - - -def log(data): - try: - with open(curr_directory() + '/logs.log', 'a') as fl: - fl.write(str(data) + '\n') - except: - pass - - -@cached -def curr_directory(): - return os.path.dirname(os.path.realpath(__file__)) - - -def curr_time(): - return time.strftime('%H:%M') - - -def copy(src, dest): - if not os.path.exists(dest): - os.makedirs(dest) - src_files = os.listdir(src) - for file_name in src_files: - full_file_name = os.path.join(src, file_name) - if os.path.isfile(full_file_name): - shutil.copy(full_file_name, dest) - else: - copy(full_file_name, os.path.join(dest, file_name)) - - -def remove(folder): - if os.path.isdir(folder): - shutil.rmtree(folder) - - -def convert_time(t): - offset = time.timezone + time_offset() * 60 - sec = int(t) - offset - m, s = divmod(sec, 60) - h, m = divmod(m, 60) - d, h = divmod(h, 24) - return '%02d:%02d' % (h, m) - - -@cached -def time_offset(): - hours = int(time.strftime('%H')) - minutes = int(time.strftime('%M')) - sec = int(time.time()) - time.timezone - m, s = divmod(sec, 60) - h, m = divmod(m, 60) - d, h = divmod(h, 24) - result = hours * 60 + minutes - h * 60 - m - return result - - -def append_slash(s): - if len(s) and s[-1] not in ('\\', '/'): - s += '/' - return s - - -@cached -def is_64_bit(): - return sys.maxsize > 2 ** 32 - - -def is_re_valid(regex): - try: - re.compile(regex) - except re.error: - return False - else: - return True - - -class Singleton: - _instance = None - - def __init__(self): - self.__class__._instance = self - - @classmethod - def get_instance(cls): - return cls._instance diff --git a/toxygen/widgets.py b/toxygen/widgets.py deleted file mode 100644 index b63deb0..0000000 --- a/toxygen/widgets.py +++ /dev/null @@ -1,166 +0,0 @@ -from PyQt5 import QtCore, QtGui, QtWidgets - - -class DataLabel(QtWidgets.QLabel): - """ - Label with elided text - """ - def setText(self, text): - text = ''.join('\u25AF' if len(bytes(c, 'utf-8')) >= 4 else c for c in text) - metrics = QtGui.QFontMetrics(self.font()) - text = metrics.elidedText(text, QtCore.Qt.ElideRight, self.width()) - super().setText(text) - - -class ComboBox(QtWidgets.QComboBox): - - def __init__(self, *args): - super().__init__(*args) - self.view().setSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.Expanding) - - -class CenteredWidget(QtWidgets.QWidget): - - def __init__(self): - super(CenteredWidget, self).__init__() - self.center() - - def center(self): - qr = self.frameGeometry() - cp = QtWidgets.QDesktopWidget().availableGeometry().center() - qr.moveCenter(cp) - self.move(qr.topLeft()) - - -class LineEdit(QtWidgets.QLineEdit): - - def __init__(self, parent=None): - super(LineEdit, self).__init__(parent) - - def contextMenuEvent(self, event): - menu = create_menu(self.createStandardContextMenu()) - menu.exec_(event.globalPos()) - del menu - - -class QRightClickButton(QtWidgets.QPushButton): - """ - Button with right click support - """ - - rightClicked = QtCore.pyqtSignal() - - def __init__(self, parent): - super(QRightClickButton, self).__init__(parent) - - def mousePressEvent(self, event): - if event.button() == QtCore.Qt.RightButton: - self.rightClicked.emit() - else: - super(QRightClickButton, self).mousePressEvent(event) - - -class RubberBand(QtWidgets.QRubberBand): - - def __init__(self): - super(RubberBand, self).__init__(QtWidgets.QRubberBand.Rectangle, None) - self.setPalette(QtGui.QPalette(QtCore.Qt.transparent)) - self.pen = QtGui.QPen(QtCore.Qt.blue, 4) - self.pen.setStyle(QtCore.Qt.SolidLine) - self.painter = QtGui.QPainter() - - def paintEvent(self, event): - - self.painter.begin(self) - self.painter.setPen(self.pen) - self.painter.drawRect(event.rect()) - self.painter.end() - - -class RubberBandWindow(QtWidgets.QWidget): - - def __init__(self, parent): - super().__init__() - self.parent = parent - self.setMouseTracking(True) - self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint | QtCore.Qt.WindowStaysOnTopHint) - self.showFullScreen() - self.setWindowOpacity(0.5) - self.rubberband = RubberBand() - self.rubberband.setWindowFlags(self.rubberband.windowFlags() | QtCore.Qt.FramelessWindowHint) - self.rubberband.setAttribute(QtCore.Qt.WA_TranslucentBackground) - - def mousePressEvent(self, event): - self.origin = event.pos() - self.rubberband.setGeometry(QtCore.QRect(self.origin, QtCore.QSize())) - self.rubberband.show() - QtWidgets.QWidget.mousePressEvent(self, event) - - def mouseMoveEvent(self, event): - if self.rubberband.isVisible(): - self.rubberband.setGeometry(QtCore.QRect(self.origin, event.pos()).normalized()) - left = QtGui.QRegion(QtCore.QRect(0, 0, self.rubberband.x(), self.height())) - right = QtGui.QRegion(QtCore.QRect(self.rubberband.x() + self.rubberband.width(), 0, self.width(), self.height())) - top = QtGui.QRegion(0, 0, self.width(), self.rubberband.y()) - bottom = QtGui.QRegion(0, self.rubberband.y() + self.rubberband.height(), self.width(), self.height()) - self.setMask(left + right + top + bottom) - - def keyPressEvent(self, event): - if event.key() == QtCore.Qt.Key_Escape: - self.rubberband.setHidden(True) - self.close() - else: - super().keyPressEvent(event) - - -def create_menu(menu): - """ - :return translated menu - """ - for action in menu.actions(): - text = action.text() - if 'Link Location' in text: - text = text.replace('Copy &Link Location', - QtWidgets.QApplication.translate("MainWindow", "Copy link location")) - elif '&Copy' in text: - text = text.replace('&Copy', QtWidgets.QApplication.translate("MainWindow", "Copy")) - elif 'All' in text: - text = text.replace('Select All', QtWidgets.QApplication.translate("MainWindow", "Select all")) - elif 'Delete' in text: - text = text.replace('Delete', QtWidgets.QApplication.translate("MainWindow", "Delete")) - elif '&Paste' in text: - text = text.replace('&Paste', QtWidgets.QApplication.translate("MainWindow", "Paste")) - elif 'Cu&t' in text: - text = text.replace('Cu&t', QtWidgets.QApplication.translate("MainWindow", "Cut")) - elif '&Undo' in text: - text = text.replace('&Undo', QtWidgets.QApplication.translate("MainWindow", "Undo")) - elif '&Redo' in text: - text = text.replace('&Redo', QtWidgets.QApplication.translate("MainWindow", "Redo")) - else: - menu.removeAction(action) - continue - action.setText(text) - return menu - - -class MultilineEdit(CenteredWidget): - - def __init__(self, title, text, save): - super(MultilineEdit, self).__init__() - self.resize(350, 200) - self.setMinimumSize(QtCore.QSize(350, 200)) - self.setMaximumSize(QtCore.QSize(350, 200)) - self.setWindowTitle(title) - self.edit = QtWidgets.QTextEdit(self) - self.edit.setGeometry(QtCore.QRect(0, 0, 350, 150)) - self.edit.setText(text) - self.button = QtWidgets.QPushButton(self) - self.button.setGeometry(QtCore.QRect(0, 150, 350, 50)) - self.button.setText(QtWidgets.QApplication.translate("MainWindow", "Save")) - self.button.clicked.connect(self.button_click) - self.center() - self.save = save - - def button_click(self): - self.save(self.edit.toPlainText()) - self.close() From 39f263893143a73aea6a69b25d2bcfd6550d963a Mon Sep 17 00:00:00 2001 From: emdee Date: Tue, 27 Sep 2022 12:52:32 +0000 Subject: [PATCH 096/163] next_gen branch README --- MANIFEST.in | 2 +- README.md | 21 +- docs/compile.md | 18 +- docs/contact.md | 2 +- setup.py | 27 +- tests/tests.py | 158 +------ toxygen/main.py | 504 ++-------------------- toxygen/smileys/default/003020E3.png | Bin 1305 -> 1317 bytes toxygen/smileys/default/003120E3.png | Bin 1151 -> 1198 bytes toxygen/smileys/default/003220E3.png | Bin 1291 -> 1317 bytes toxygen/smileys/default/003320E3.png | Bin 1281 -> 1308 bytes toxygen/smileys/default/003420E3.png | Bin 1238 -> 1255 bytes toxygen/smileys/default/003520E3.png | Bin 1286 -> 1319 bytes toxygen/smileys/default/003620E3.png | Bin 1328 -> 1348 bytes toxygen/smileys/default/003720E3.png | Bin 1227 -> 1274 bytes toxygen/smileys/default/003820E3.png | Bin 1328 -> 1342 bytes toxygen/smileys/default/003920E3.png | Bin 1329 -> 1345 bytes toxygen/smileys/default/00A9.png | Bin 1257 -> 1146 bytes toxygen/smileys/default/00AE.png | Bin 1262 -> 1152 bytes toxygen/smileys/default/203C.png | Bin 1199 -> 1311 bytes toxygen/smileys/default/2049.png | Bin 1304 -> 1513 bytes toxygen/smileys/default/2122.png | Bin 1126 -> 1054 bytes toxygen/smileys/default/2139.png | Bin 1314 -> 1330 bytes toxygen/smileys/default/2194.png | Bin 1277 -> 1268 bytes toxygen/smileys/default/2195.png | Bin 1283 -> 1282 bytes toxygen/smileys/default/2196.png | Bin 1306 -> 1315 bytes toxygen/smileys/default/2197.png | Bin 1289 -> 1322 bytes toxygen/smileys/default/2198.png | Bin 1308 -> 1319 bytes toxygen/smileys/default/2199.png | Bin 1287 -> 1325 bytes toxygen/smileys/default/21A9.png | Bin 1340 -> 1336 bytes toxygen/smileys/default/21AA.png | Bin 1325 -> 1337 bytes toxygen/smileys/default/231A.png | Bin 1466 -> 1344 bytes toxygen/smileys/default/231B.png | Bin 856 -> 1100 bytes toxygen/smileys/default/23E9.png | Bin 1324 -> 1338 bytes toxygen/smileys/default/23EA.png | Bin 1269 -> 1281 bytes toxygen/smileys/default/23EB.png | Bin 1340 -> 1333 bytes toxygen/smileys/default/23EC.png | Bin 1355 -> 1325 bytes toxygen/smileys/default/23F0.png | Bin 1688 -> 1600 bytes toxygen/smileys/default/23F3.png | Bin 1465 -> 1397 bytes toxygen/smileys/default/24C2.png | Bin 1492 -> 1570 bytes toxygen/smileys/default/25AA.png | Bin 1118 -> 1034 bytes toxygen/smileys/default/25AB.png | Bin 1100 -> 1026 bytes toxygen/smileys/default/25B6.png | Bin 1277 -> 1296 bytes toxygen/smileys/default/25C0.png | Bin 1274 -> 1305 bytes toxygen/smileys/default/25FB.png | Bin 515 -> 840 bytes toxygen/smileys/default/25FC.png | Bin 560 -> 519 bytes toxygen/smileys/default/25FD.png | Bin 1143 -> 1051 bytes toxygen/smileys/default/25FE.png | Bin 1195 -> 1080 bytes toxygen/smileys/default/2600.png | Bin 1410 -> 1312 bytes toxygen/smileys/default/2601.png | Bin 1373 -> 1418 bytes toxygen/smileys/default/260E.png | Bin 1462 -> 1334 bytes toxygen/smileys/default/2611.png | Bin 1338 -> 1166 bytes toxygen/smileys/default/2614.png | Bin 1592 -> 1582 bytes toxygen/smileys/default/2615.png | Bin 1607 -> 1581 bytes toxygen/smileys/default/261D.png | Bin 1466 -> 1442 bytes toxygen/smileys/default/263A.png | Bin 1779 -> 1852 bytes toxygen/smileys/default/2648.png | Bin 1389 -> 1396 bytes toxygen/smileys/default/2649.png | Bin 1421 -> 1401 bytes toxygen/smileys/default/264A.png | Bin 1341 -> 1321 bytes toxygen/smileys/default/264B.png | Bin 1525 -> 1535 bytes toxygen/smileys/default/264C.png | Bin 1422 -> 1454 bytes toxygen/smileys/default/264D.png | Bin 1362 -> 1355 bytes toxygen/smileys/default/264E.png | Bin 1341 -> 1308 bytes toxygen/smileys/default/264F.png | Bin 1290 -> 1287 bytes toxygen/smileys/default/2650.png | Bin 1380 -> 1330 bytes toxygen/smileys/default/2651.png | Bin 1390 -> 1413 bytes toxygen/smileys/default/2652.png | Bin 1274 -> 1305 bytes toxygen/smileys/default/2653.png | Bin 1381 -> 1376 bytes toxygen/smileys/default/2660.png | Bin 1382 -> 1216 bytes toxygen/smileys/default/2663.png | Bin 1400 -> 1228 bytes toxygen/smileys/default/2665.png | Bin 1444 -> 1486 bytes toxygen/smileys/default/2666.png | Bin 1506 -> 1460 bytes toxygen/smileys/default/2668.png | Bin 1582 -> 1559 bytes toxygen/smileys/default/267B.png | Bin 1521 -> 1597 bytes toxygen/smileys/default/267F.png | Bin 1336 -> 1348 bytes toxygen/smileys/default/2693.png | Bin 1692 -> 1742 bytes toxygen/smileys/default/26A0.png | Bin 1482 -> 1430 bytes toxygen/smileys/default/26A1.png | Bin 1387 -> 1368 bytes toxygen/smileys/default/26AA.png | Bin 1410 -> 1228 bytes toxygen/smileys/default/26AB.png | Bin 1431 -> 1235 bytes toxygen/smileys/default/26BD.png | Bin 1825 -> 1850 bytes toxygen/smileys/default/26BE.png | Bin 1712 -> 1815 bytes toxygen/smileys/default/26C4.png | Bin 1639 -> 1562 bytes toxygen/smileys/default/26C5.png | Bin 1569 -> 1585 bytes toxygen/smileys/default/26CE.png | Bin 1396 -> 1377 bytes toxygen/smileys/default/26D4.png | Bin 1542 -> 1536 bytes toxygen/smileys/default/26EA.png | Bin 1557 -> 1470 bytes toxygen/smileys/default/26F2.png | Bin 1533 -> 1587 bytes toxygen/smileys/default/26F3.png | Bin 1408 -> 1493 bytes toxygen/smileys/default/26F5.png | Bin 1470 -> 1423 bytes toxygen/smileys/default/26FA.png | Bin 1449 -> 1505 bytes toxygen/smileys/default/26FD.png | Bin 1423 -> 1327 bytes toxygen/smileys/default/2702.png | Bin 1572 -> 1539 bytes toxygen/smileys/default/2705.png | Bin 1258 -> 1273 bytes toxygen/smileys/default/2708.png | Bin 1433 -> 1347 bytes toxygen/smileys/default/2709.png | Bin 1332 -> 1295 bytes toxygen/smileys/default/270A.png | Bin 1692 -> 1735 bytes toxygen/smileys/default/270B.png | Bin 1521 -> 1514 bytes toxygen/smileys/default/270C.png | Bin 1600 -> 1607 bytes toxygen/smileys/default/270F.png | Bin 1581 -> 1528 bytes toxygen/smileys/default/2712.png | Bin 1609 -> 1517 bytes toxygen/smileys/default/2714.png | Bin 1262 -> 1400 bytes toxygen/smileys/default/2716.png | Bin 1207 -> 1209 bytes toxygen/smileys/default/2728.png | Bin 1347 -> 1385 bytes toxygen/smileys/default/2733.png | Bin 1416 -> 1388 bytes toxygen/smileys/default/2734.png | Bin 1322 -> 1297 bytes toxygen/smileys/default/2744.png | Bin 1833 -> 2099 bytes toxygen/smileys/default/2747.png | Bin 1493 -> 1462 bytes toxygen/smileys/default/274C.png | Bin 1169 -> 1396 bytes toxygen/smileys/default/274E.png | Bin 1319 -> 1324 bytes toxygen/smileys/default/2753.png | Bin 1262 -> 1379 bytes toxygen/smileys/default/2754.png | Bin 1234 -> 1156 bytes toxygen/smileys/default/2755.png | Bin 1134 -> 1075 bytes toxygen/smileys/default/2757.png | Bin 1169 -> 1291 bytes toxygen/smileys/default/2764.png | Bin 1583 -> 1616 bytes toxygen/smileys/default/2795.png | Bin 1081 -> 1050 bytes toxygen/smileys/default/2796.png | Bin 1031 -> 992 bytes toxygen/smileys/default/2797.png | Bin 1060 -> 1021 bytes toxygen/smileys/default/27A1.png | Bin 1226 -> 1257 bytes toxygen/smileys/default/27B0.png | Bin 1274 -> 1434 bytes toxygen/smileys/default/27BF.png | Bin 1376 -> 1335 bytes toxygen/smileys/default/2934.png | Bin 1261 -> 1288 bytes toxygen/smileys/default/2935.png | Bin 1295 -> 1293 bytes toxygen/smileys/default/2B05.png | Bin 1220 -> 1257 bytes toxygen/smileys/default/2B06.png | Bin 1238 -> 1260 bytes toxygen/smileys/default/2B07.png | Bin 1229 -> 1261 bytes toxygen/smileys/default/2B1B.png | Bin 1250 -> 1115 bytes toxygen/smileys/default/2B1C.png | Bin 1170 -> 1071 bytes toxygen/smileys/default/2B50.png | Bin 1461 -> 1450 bytes toxygen/smileys/default/2B55.png | Bin 1307 -> 1466 bytes toxygen/smileys/default/3030.png | Bin 1106 -> 1036 bytes toxygen/smileys/default/303D.png | Bin 1500 -> 1503 bytes toxygen/smileys/default/D83CDC04.png | Bin 1516 -> 1501 bytes toxygen/smileys/default/D83CDCCF.png | Bin 1559 -> 1507 bytes toxygen/smileys/default/D83CDD70.png | Bin 1261 -> 1282 bytes toxygen/smileys/default/D83CDD71.png | Bin 1221 -> 1262 bytes toxygen/smileys/default/D83CDD7E.png | Bin 1272 -> 1296 bytes toxygen/smileys/default/D83CDD7F.png | Bin 1222 -> 1269 bytes toxygen/smileys/default/D83CDD8E.png | Bin 1309 -> 1336 bytes toxygen/smileys/default/D83CDD91.png | Bin 1276 -> 1297 bytes toxygen/smileys/default/D83CDD92.png | Bin 1277 -> 1289 bytes toxygen/smileys/default/D83CDD93.png | Bin 1207 -> 1242 bytes toxygen/smileys/default/D83CDD94.png | Bin 1254 -> 1263 bytes toxygen/smileys/default/D83CDD95.png | Bin 1270 -> 1283 bytes toxygen/smileys/default/D83CDD96.png | Bin 1335 -> 1338 bytes toxygen/smileys/default/D83CDD97.png | Bin 1366 -> 1382 bytes toxygen/smileys/default/D83CDD98.png | Bin 1335 -> 1366 bytes toxygen/smileys/default/D83CDD99.png | Bin 1236 -> 1266 bytes toxygen/smileys/default/D83CDD9A.png | Bin 1362 -> 1402 bytes toxygen/smileys/default/D83CDE01.png | Bin 1169 -> 1199 bytes toxygen/smileys/default/D83CDF00.png | Bin 1397 -> 1794 bytes toxygen/smileys/default/D83CDF01.png | Bin 1472 -> 1615 bytes toxygen/smileys/default/D83CDF02.png | Bin 1527 -> 1601 bytes toxygen/smileys/default/D83CDF03.png | Bin 1372 -> 1286 bytes toxygen/smileys/default/D83CDF04.png | Bin 1623 -> 1776 bytes toxygen/smileys/default/D83CDF05.png | Bin 1559 -> 1782 bytes toxygen/smileys/default/D83CDF06.png | Bin 1375 -> 1350 bytes toxygen/smileys/default/D83CDF07.png | Bin 1514 -> 1554 bytes toxygen/smileys/default/D83CDF08.png | Bin 1482 -> 1560 bytes toxygen/smileys/default/D83CDF09.png | Bin 1514 -> 1470 bytes toxygen/smileys/default/D83CDF0A.png | Bin 1646 -> 1628 bytes toxygen/smileys/default/D83CDF0B.png | Bin 1597 -> 1697 bytes toxygen/smileys/default/D83CDF0C.png | Bin 1582 -> 1731 bytes toxygen/smileys/default/D83CDF0D.png | Bin 1786 -> 1865 bytes toxygen/smileys/default/D83CDF0E.png | Bin 1777 -> 1867 bytes toxygen/smileys/default/D83CDF0F.png | Bin 1769 -> 1878 bytes toxygen/smileys/default/D83CDF10.png | Bin 1403 -> 1771 bytes toxygen/smileys/default/D83CDF11.png | Bin 1687 -> 1753 bytes toxygen/smileys/default/D83CDF12.png | Bin 1734 -> 1828 bytes toxygen/smileys/default/D83CDF13.png | Bin 1717 -> 1820 bytes toxygen/smileys/default/D83CDF14.png | Bin 1756 -> 1844 bytes toxygen/smileys/default/D83CDF15.png | Bin 1688 -> 1821 bytes toxygen/smileys/default/D83CDF16.png | Bin 1768 -> 1849 bytes toxygen/smileys/default/D83CDF17.png | Bin 1702 -> 1836 bytes toxygen/smileys/default/D83CDF18.png | Bin 1716 -> 1821 bytes toxygen/smileys/default/D83CDF19.png | Bin 1457 -> 1504 bytes toxygen/smileys/default/D83CDF1A.png | Bin 1748 -> 1801 bytes toxygen/smileys/default/D83CDF1B.png | Bin 1501 -> 1510 bytes toxygen/smileys/default/D83CDF1C.png | Bin 1496 -> 1518 bytes toxygen/smileys/default/D83CDF1D.png | Bin 1691 -> 1833 bytes toxygen/smileys/default/D83CDF1E.png | Bin 1650 -> 1631 bytes toxygen/smileys/default/D83CDF1F.png | Bin 1671 -> 1652 bytes toxygen/smileys/default/D83CDF20.png | Bin 1463 -> 1605 bytes toxygen/smileys/default/D83CDF30.png | Bin 1713 -> 1875 bytes toxygen/smileys/default/D83CDF31.png | Bin 1371 -> 1353 bytes toxygen/smileys/default/D83CDF32.png | Bin 1549 -> 1607 bytes toxygen/smileys/default/D83CDF33.png | Bin 1667 -> 1740 bytes toxygen/smileys/default/D83CDF34.png | Bin 1606 -> 1676 bytes toxygen/smileys/default/D83CDF35.png | Bin 1542 -> 1617 bytes toxygen/smileys/default/D83CDF37.png | Bin 1636 -> 1559 bytes toxygen/smileys/default/D83CDF38.png | Bin 1744 -> 1805 bytes toxygen/smileys/default/D83CDF39.png | Bin 1510 -> 1468 bytes toxygen/smileys/default/D83CDF3A.png | Bin 1755 -> 1971 bytes toxygen/smileys/default/D83CDF3B.png | Bin 1678 -> 1695 bytes toxygen/smileys/default/D83CDF3C.png | Bin 1640 -> 1685 bytes toxygen/smileys/default/D83CDF3D.png | Bin 1770 -> 1937 bytes toxygen/smileys/default/D83CDF3E.png | Bin 1632 -> 1888 bytes toxygen/smileys/default/D83CDF3F.png | Bin 1679 -> 1655 bytes toxygen/smileys/default/D83CDF40.png | Bin 1601 -> 1752 bytes toxygen/smileys/default/D83CDF41.png | Bin 1624 -> 1662 bytes toxygen/smileys/default/D83CDF42.png | Bin 1614 -> 1628 bytes toxygen/smileys/default/D83CDF43.png | Bin 1665 -> 1780 bytes toxygen/smileys/default/D83CDF44.png | Bin 1667 -> 1725 bytes toxygen/smileys/default/D83CDF45.png | Bin 1716 -> 1911 bytes toxygen/smileys/default/D83CDF46.png | Bin 1729 -> 1938 bytes toxygen/smileys/default/D83CDF47.png | Bin 1668 -> 1681 bytes toxygen/smileys/default/D83CDF48.png | Bin 1665 -> 1831 bytes toxygen/smileys/default/D83CDF49.png | Bin 1669 -> 1763 bytes toxygen/smileys/default/D83CDF4A.png | Bin 1690 -> 1936 bytes toxygen/smileys/default/D83CDF4B.png | Bin 1644 -> 1924 bytes toxygen/smileys/default/D83CDF4C.png | Bin 1635 -> 1727 bytes toxygen/smileys/default/D83CDF4D.png | Bin 1570 -> 1699 bytes toxygen/smileys/default/D83CDF4E.png | Bin 1666 -> 1875 bytes toxygen/smileys/default/D83CDF4F.png | Bin 1690 -> 1882 bytes toxygen/smileys/default/D83CDF50.png | Bin 1722 -> 1950 bytes toxygen/smileys/default/D83CDF51.png | Bin 1731 -> 1999 bytes toxygen/smileys/default/D83CDF52.png | Bin 1665 -> 1725 bytes toxygen/smileys/default/D83CDF53.png | Bin 1830 -> 1989 bytes toxygen/smileys/default/D83CDF54.png | Bin 1681 -> 1720 bytes toxygen/smileys/default/D83CDF55.png | Bin 1578 -> 1620 bytes toxygen/smileys/default/D83CDF56.png | Bin 1772 -> 1981 bytes toxygen/smileys/default/D83CDF57.png | Bin 1574 -> 1608 bytes toxygen/smileys/default/D83CDF58.png | Bin 876 -> 1289 bytes toxygen/smileys/default/D83CDF59.png | Bin 929 -> 1258 bytes toxygen/smileys/default/D83CDF5A.png | Bin 1722 -> 1741 bytes toxygen/smileys/default/D83CDF5B.png | Bin 1708 -> 1742 bytes toxygen/smileys/default/D83CDF5C.png | Bin 1778 -> 1845 bytes toxygen/smileys/default/D83CDF5D.png | Bin 1776 -> 1848 bytes toxygen/smileys/default/D83CDF5E.png | Bin 1686 -> 1754 bytes toxygen/smileys/default/D83CDF5F.png | Bin 1681 -> 1743 bytes toxygen/smileys/default/D83CDF60.png | Bin 1672 -> 1715 bytes toxygen/smileys/default/D83CDF61.png | Bin 1549 -> 1506 bytes toxygen/smileys/default/D83CDF62.png | Bin 1605 -> 1564 bytes toxygen/smileys/default/D83CDF63.png | Bin 1608 -> 1636 bytes toxygen/smileys/default/D83CDF64.png | Bin 1773 -> 1953 bytes toxygen/smileys/default/D83CDF65.png | Bin 1678 -> 1755 bytes toxygen/smileys/default/D83CDF66.png | Bin 1521 -> 1527 bytes toxygen/smileys/default/D83CDF67.png | Bin 1646 -> 1646 bytes toxygen/smileys/default/D83CDF68.png | Bin 1614 -> 1646 bytes toxygen/smileys/default/D83CDF69.png | Bin 1626 -> 1671 bytes toxygen/smileys/default/D83CDF6A.png | Bin 1867 -> 2023 bytes toxygen/smileys/default/D83CDF6B.png | Bin 1690 -> 1680 bytes toxygen/smileys/default/D83CDF6C.png | Bin 1527 -> 1558 bytes toxygen/smileys/default/D83CDF6D.png | Bin 1600 -> 1584 bytes toxygen/smileys/default/D83CDF6E.png | Bin 1741 -> 1748 bytes toxygen/smileys/default/D83CDF6F.png | Bin 1714 -> 1882 bytes toxygen/smileys/default/D83CDF70.png | Bin 1549 -> 1652 bytes toxygen/smileys/default/D83CDF71.png | Bin 1504 -> 1418 bytes toxygen/smileys/default/D83CDF72.png | Bin 1623 -> 1685 bytes toxygen/smileys/default/D83CDF73.png | Bin 1663 -> 1772 bytes toxygen/smileys/default/D83CDF74.png | Bin 1376 -> 1347 bytes toxygen/smileys/default/D83CDF75.png | Bin 1778 -> 1792 bytes toxygen/smileys/default/D83CDF76.png | Bin 1598 -> 1618 bytes toxygen/smileys/default/D83CDF77.png | Bin 1616 -> 1609 bytes toxygen/smileys/default/D83CDF78.png | Bin 1403 -> 1342 bytes toxygen/smileys/default/D83CDF79.png | Bin 1599 -> 1587 bytes toxygen/smileys/default/D83CDF7A.png | Bin 1630 -> 1642 bytes toxygen/smileys/default/D83CDF7B.png | Bin 1574 -> 1523 bytes toxygen/smileys/default/D83CDF7C.png | Bin 1593 -> 1631 bytes toxygen/smileys/default/D83CDF80.png | Bin 1639 -> 1656 bytes toxygen/smileys/default/D83CDF81.png | Bin 1690 -> 1657 bytes toxygen/smileys/default/D83CDF82.png | Bin 1718 -> 1648 bytes toxygen/smileys/default/D83CDF83.png | Bin 1724 -> 1780 bytes toxygen/smileys/default/D83CDF84.png | Bin 1502 -> 1490 bytes toxygen/smileys/default/D83CDF85.png | Bin 1771 -> 1786 bytes toxygen/smileys/default/D83CDF86.png | Bin 1602 -> 1618 bytes toxygen/smileys/default/D83CDF87.png | Bin 1578 -> 1599 bytes toxygen/smileys/default/D83CDF88.png | Bin 1352 -> 1308 bytes toxygen/smileys/default/D83CDF89.png | Bin 1817 -> 1812 bytes toxygen/smileys/default/D83CDF8A.png | Bin 1720 -> 1574 bytes toxygen/smileys/default/D83CDF8B.png | Bin 1571 -> 1535 bytes toxygen/smileys/default/D83CDF8C.png | Bin 1523 -> 1534 bytes toxygen/smileys/default/D83CDF8D.png | Bin 1438 -> 1378 bytes toxygen/smileys/default/D83CDF8E.png | Bin 1629 -> 1569 bytes toxygen/smileys/default/D83CDF8F.png | Bin 1658 -> 1696 bytes toxygen/smileys/default/D83CDF90.png | Bin 1526 -> 1503 bytes toxygen/smileys/default/D83CDF91.png | Bin 1536 -> 1552 bytes toxygen/smileys/default/D83CDF92.png | Bin 1767 -> 1896 bytes toxygen/smileys/default/D83CDF93.png | Bin 1463 -> 1411 bytes toxygen/smileys/default/D83CDFA0.png | Bin 1492 -> 1467 bytes toxygen/smileys/default/D83CDFA1.png | Bin 1581 -> 1534 bytes toxygen/smileys/default/D83CDFA2.png | Bin 1436 -> 1401 bytes toxygen/smileys/default/D83CDFA3.png | Bin 1482 -> 1577 bytes toxygen/smileys/default/D83CDFA4.png | Bin 1590 -> 1539 bytes toxygen/smileys/default/D83CDFA5.png | Bin 1559 -> 1619 bytes toxygen/smileys/default/D83CDFA6.png | Bin 1318 -> 1322 bytes toxygen/smileys/default/D83CDFA7.png | Bin 1463 -> 1399 bytes toxygen/smileys/default/D83CDFA8.png | Bin 1704 -> 1719 bytes toxygen/smileys/default/D83CDFA9.png | Bin 1528 -> 1617 bytes toxygen/smileys/default/D83CDFAA.png | Bin 1669 -> 1738 bytes toxygen/smileys/default/D83CDFAB.png | Bin 1315 -> 1179 bytes toxygen/smileys/default/D83CDFAC.png | Bin 1522 -> 1506 bytes toxygen/smileys/default/D83CDFAD.png | Bin 1778 -> 1809 bytes toxygen/smileys/default/D83CDFAE.png | Bin 1665 -> 1749 bytes toxygen/smileys/default/D83CDFAF.png | Bin 1634 -> 1722 bytes toxygen/smileys/default/D83CDFB0.png | Bin 1359 -> 1218 bytes toxygen/smileys/default/D83CDFB1.png | Bin 1723 -> 1808 bytes toxygen/smileys/default/D83CDFB2.png | Bin 1683 -> 1713 bytes toxygen/smileys/default/D83CDFB3.png | Bin 1679 -> 1713 bytes toxygen/smileys/default/D83CDFB4.png | Bin 1495 -> 1450 bytes toxygen/smileys/default/D83CDFB5.png | Bin 1559 -> 1796 bytes toxygen/smileys/default/D83CDFB6.png | Bin 1276 -> 1152 bytes toxygen/smileys/default/D83CDFB7.png | Bin 1497 -> 1606 bytes toxygen/smileys/default/D83CDFB8.png | Bin 1543 -> 1551 bytes toxygen/smileys/default/D83CDFB9.png | Bin 1153 -> 1103 bytes toxygen/smileys/default/D83CDFBA.png | Bin 1540 -> 1583 bytes toxygen/smileys/default/D83CDFBB.png | Bin 1655 -> 1689 bytes toxygen/smileys/default/D83CDFBC.png | Bin 1413 -> 1488 bytes toxygen/smileys/default/D83CDFBD.png | Bin 1605 -> 1704 bytes toxygen/smileys/default/D83CDFBE.png | Bin 1691 -> 1825 bytes toxygen/smileys/default/D83CDFBF.png | Bin 1635 -> 1659 bytes toxygen/smileys/default/D83CDFC0.png | Bin 1785 -> 1926 bytes toxygen/smileys/default/D83CDFC1.png | Bin 1539 -> 1396 bytes toxygen/smileys/default/D83CDFC2.png | Bin 1731 -> 1759 bytes toxygen/smileys/default/D83CDFC3.png | Bin 1482 -> 1441 bytes toxygen/smileys/default/D83CDFC4.png | Bin 1689 -> 1708 bytes toxygen/smileys/default/D83CDFC6.png | Bin 1653 -> 1564 bytes toxygen/smileys/default/D83CDFC7.png | Bin 1648 -> 1651 bytes toxygen/smileys/default/D83CDFC8.png | Bin 1843 -> 1852 bytes toxygen/smileys/default/D83CDFC9.png | Bin 1785 -> 1852 bytes toxygen/smileys/default/D83CDFCA.png | Bin 1473 -> 1482 bytes toxygen/smileys/default/D83CDFE0.png | Bin 1481 -> 1365 bytes toxygen/smileys/default/D83CDFE1.png | Bin 1649 -> 1620 bytes toxygen/smileys/default/D83CDFE2.png | Bin 1246 -> 1169 bytes toxygen/smileys/default/D83CDFE3.png | Bin 1542 -> 1339 bytes toxygen/smileys/default/D83CDFE4.png | Bin 1557 -> 1399 bytes toxygen/smileys/default/D83CDFE5.png | Bin 1442 -> 1314 bytes toxygen/smileys/default/D83CDFE7.png | Bin 1285 -> 1289 bytes toxygen/smileys/default/D83CDFE8.png | Bin 1459 -> 1294 bytes toxygen/smileys/default/D83CDFE9.png | Bin 1519 -> 1401 bytes toxygen/smileys/default/D83CDFEA.png | Bin 1409 -> 1259 bytes toxygen/smileys/default/D83CDFEB.png | Bin 1497 -> 1370 bytes toxygen/smileys/default/D83CDFEC.png | Bin 1384 -> 1242 bytes toxygen/smileys/default/D83CDFED.png | Bin 1348 -> 1287 bytes toxygen/smileys/default/D83CDFEE.png | Bin 1485 -> 1496 bytes toxygen/smileys/default/D83CDFEF.png | Bin 1403 -> 1330 bytes toxygen/smileys/default/D83CDFF0.png | Bin 1353 -> 1313 bytes toxygen/smileys/default/D83DDC00.png | Bin 1685 -> 1731 bytes toxygen/smileys/default/D83DDC01.png | Bin 1582 -> 1606 bytes toxygen/smileys/default/D83DDC02.png | Bin 1576 -> 1625 bytes toxygen/smileys/default/D83DDC03.png | Bin 1694 -> 1625 bytes toxygen/smileys/default/D83DDC04.png | Bin 1599 -> 1548 bytes toxygen/smileys/default/D83DDC05.png | Bin 1701 -> 1656 bytes toxygen/smileys/default/D83DDC06.png | Bin 1647 -> 1767 bytes toxygen/smileys/default/D83DDC07.png | Bin 1575 -> 1544 bytes toxygen/smileys/default/D83DDC08.png | Bin 1595 -> 1626 bytes toxygen/smileys/default/D83DDC09.png | Bin 1802 -> 1856 bytes toxygen/smileys/default/D83DDC0A.png | Bin 1712 -> 1800 bytes toxygen/smileys/default/D83DDC0B.png | Bin 1649 -> 1699 bytes toxygen/smileys/default/D83DDC0C.png | Bin 1710 -> 1883 bytes toxygen/smileys/default/D83DDC0D.png | Bin 1584 -> 1693 bytes toxygen/smileys/default/D83DDC0E.png | Bin 1613 -> 1527 bytes toxygen/smileys/default/D83DDC0F.png | Bin 1648 -> 1560 bytes toxygen/smileys/default/D83DDC10.png | Bin 1547 -> 1513 bytes toxygen/smileys/default/D83DDC11.png | Bin 1634 -> 1521 bytes toxygen/smileys/default/D83DDC12.png | Bin 1698 -> 1731 bytes toxygen/smileys/default/D83DDC13.png | Bin 1650 -> 1597 bytes toxygen/smileys/default/D83DDC14.png | Bin 1668 -> 1654 bytes toxygen/smileys/default/D83DDC15.png | Bin 1633 -> 1643 bytes toxygen/smileys/default/D83DDC16.png | Bin 1530 -> 1555 bytes toxygen/smileys/default/D83DDC17.png | Bin 1606 -> 1743 bytes toxygen/smileys/default/D83DDC18.png | Bin 1571 -> 1309 bytes toxygen/smileys/default/D83DDC19.png | Bin 1637 -> 1618 bytes toxygen/smileys/default/D83DDC1A.png | Bin 1720 -> 1862 bytes toxygen/smileys/default/D83DDC1B.png | Bin 1556 -> 1611 bytes toxygen/smileys/default/D83DDC1C.png | Bin 1695 -> 1738 bytes toxygen/smileys/default/D83DDC1D.png | Bin 1842 -> 1899 bytes toxygen/smileys/default/D83DDC1E.png | Bin 1603 -> 1548 bytes toxygen/smileys/default/D83DDC1F.png | Bin 1571 -> 1672 bytes toxygen/smileys/default/D83DDC20.png | Bin 1657 -> 1820 bytes toxygen/smileys/default/D83DDC21.png | Bin 1487 -> 1458 bytes toxygen/smileys/default/D83DDC22.png | Bin 1614 -> 1629 bytes toxygen/smileys/default/D83DDC23.png | Bin 1680 -> 1698 bytes toxygen/smileys/default/D83DDC24.png | Bin 1595 -> 1808 bytes toxygen/smileys/default/D83DDC25.png | Bin 1629 -> 1622 bytes toxygen/smileys/default/D83DDC26.png | Bin 1676 -> 1718 bytes toxygen/smileys/default/D83DDC27.png | Bin 1674 -> 1716 bytes toxygen/smileys/default/D83DDC28.png | Bin 1657 -> 1584 bytes toxygen/smileys/default/D83DDC2A.png | Bin 1706 -> 1737 bytes toxygen/smileys/default/D83DDC2B.png | Bin 1613 -> 1561 bytes toxygen/smileys/default/D83DDC2C.png | Bin 1570 -> 1654 bytes toxygen/smileys/default/D83DDC2D.png | Bin 1744 -> 1692 bytes toxygen/smileys/default/D83DDC2E.png | Bin 1609 -> 1599 bytes toxygen/smileys/default/D83DDC2F.png | Bin 1797 -> 1753 bytes toxygen/smileys/default/D83DDC30.png | Bin 1609 -> 1634 bytes toxygen/smileys/default/D83DDC31.png | Bin 1621 -> 1528 bytes toxygen/smileys/default/D83DDC32.png | Bin 1601 -> 1719 bytes toxygen/smileys/default/D83DDC33.png | Bin 1619 -> 1721 bytes toxygen/smileys/default/D83DDC34.png | Bin 1639 -> 1616 bytes toxygen/smileys/default/D83DDC35.png | Bin 1696 -> 1713 bytes toxygen/smileys/default/D83DDC36.png | Bin 1697 -> 1752 bytes toxygen/smileys/default/D83DDC37.png | Bin 1685 -> 1636 bytes toxygen/smileys/default/D83DDC38.png | Bin 1558 -> 1640 bytes toxygen/smileys/default/D83DDC39.png | Bin 1835 -> 1844 bytes toxygen/smileys/default/D83DDC3A.png | Bin 1722 -> 1676 bytes toxygen/smileys/default/D83DDC3B.png | Bin 1685 -> 1642 bytes toxygen/smileys/default/D83DDC3C.png | Bin 1780 -> 1735 bytes toxygen/smileys/default/D83DDC3D.png | Bin 1592 -> 1544 bytes toxygen/smileys/default/D83DDC3E.png | Bin 1289 -> 1541 bytes toxygen/smileys/default/D83DDC40.png | Bin 1367 -> 1349 bytes toxygen/smileys/default/D83DDC42.png | Bin 1565 -> 1558 bytes toxygen/smileys/default/D83DDC43.png | Bin 1451 -> 1406 bytes toxygen/smileys/default/D83DDC44.png | Bin 1599 -> 1530 bytes toxygen/smileys/default/D83DDC45.png | Bin 1609 -> 1632 bytes toxygen/smileys/default/D83DDC46.png | Bin 1388 -> 1328 bytes toxygen/smileys/default/D83DDC47.png | Bin 1374 -> 1329 bytes toxygen/smileys/default/D83DDC48.png | Bin 1358 -> 1318 bytes toxygen/smileys/default/D83DDC49.png | Bin 1339 -> 1319 bytes toxygen/smileys/default/D83DDC4A.png | Bin 1546 -> 1625 bytes toxygen/smileys/default/D83DDC4B.png | Bin 1701 -> 1764 bytes toxygen/smileys/default/D83DDC4C.png | Bin 1607 -> 1618 bytes toxygen/smileys/default/D83DDC4D.png | Bin 1567 -> 1570 bytes toxygen/smileys/default/D83DDC4E.png | Bin 1590 -> 1566 bytes toxygen/smileys/default/D83DDC4F.png | Bin 1701 -> 1730 bytes toxygen/smileys/default/D83DDC50.png | Bin 1564 -> 1591 bytes toxygen/smileys/default/D83DDC51.png | Bin 1711 -> 1832 bytes toxygen/smileys/default/D83DDC52.png | Bin 1816 -> 1942 bytes toxygen/smileys/default/D83DDC53.png | Bin 1325 -> 1297 bytes toxygen/smileys/default/D83DDC54.png | Bin 1685 -> 1605 bytes toxygen/smileys/default/D83DDC55.png | Bin 1664 -> 1673 bytes toxygen/smileys/default/D83DDC56.png | Bin 1366 -> 1356 bytes toxygen/smileys/default/D83DDC57.png | Bin 1467 -> 1534 bytes toxygen/smileys/default/D83DDC58.png | Bin 1608 -> 1629 bytes toxygen/smileys/default/D83DDC59.png | Bin 1542 -> 1504 bytes toxygen/smileys/default/D83DDC5A.png | Bin 1626 -> 1656 bytes toxygen/smileys/default/D83DDC5B.png | Bin 1608 -> 1760 bytes toxygen/smileys/default/D83DDC5C.png | Bin 1687 -> 1842 bytes toxygen/smileys/default/D83DDC5D.png | Bin 1487 -> 1618 bytes toxygen/smileys/default/D83DDC5E.png | Bin 1686 -> 1736 bytes toxygen/smileys/default/D83DDC5F.png | Bin 1733 -> 1779 bytes toxygen/smileys/default/D83DDC60.png | Bin 1558 -> 1592 bytes toxygen/smileys/default/D83DDC61.png | Bin 1489 -> 1485 bytes toxygen/smileys/default/D83DDC62.png | Bin 1448 -> 1453 bytes toxygen/smileys/default/D83DDC63.png | Bin 1313 -> 1569 bytes toxygen/smileys/default/D83DDC64.png | Bin 1203 -> 1272 bytes toxygen/smileys/default/D83DDC65.png | Bin 1338 -> 1551 bytes toxygen/smileys/default/D83DDC66.png | Bin 1766 -> 1856 bytes toxygen/smileys/default/D83DDC67.png | Bin 1794 -> 1764 bytes toxygen/smileys/default/D83DDC68.png | Bin 1797 -> 1863 bytes toxygen/smileys/default/D83DDC69.png | Bin 1806 -> 1924 bytes toxygen/smileys/default/D83DDC6A.png | Bin 1763 -> 1834 bytes toxygen/smileys/default/D83DDC6B.png | Bin 1719 -> 1600 bytes toxygen/smileys/default/D83DDC6C.png | Bin 1621 -> 1551 bytes toxygen/smileys/default/D83DDC6D.png | Bin 1737 -> 1664 bytes toxygen/smileys/default/D83DDC6E.png | Bin 1790 -> 1786 bytes toxygen/smileys/default/D83DDC6F.png | Bin 1518 -> 1485 bytes toxygen/smileys/default/D83DDC70.png | Bin 1829 -> 1880 bytes toxygen/smileys/default/D83DDC71.png | Bin 1800 -> 1806 bytes toxygen/smileys/default/D83DDC72.png | Bin 1770 -> 1694 bytes toxygen/smileys/default/D83DDC73.png | Bin 1759 -> 1769 bytes toxygen/smileys/default/D83DDC74.png | Bin 1684 -> 1654 bytes toxygen/smileys/default/D83DDC75.png | Bin 1692 -> 1725 bytes toxygen/smileys/default/D83DDC76.png | Bin 1650 -> 1644 bytes toxygen/smileys/default/D83DDC77.png | Bin 1733 -> 1763 bytes toxygen/smileys/default/D83DDC78.png | Bin 1748 -> 1851 bytes toxygen/smileys/default/D83DDC79.png | Bin 1780 -> 1680 bytes toxygen/smileys/default/D83DDC7A.png | Bin 1708 -> 1635 bytes toxygen/smileys/default/D83DDC7B.png | Bin 1687 -> 1723 bytes toxygen/smileys/default/D83DDC7C.png | Bin 1661 -> 1611 bytes toxygen/smileys/default/D83DDC7D.png | Bin 1796 -> 1737 bytes toxygen/smileys/default/D83DDC7E.png | Bin 1373 -> 1234 bytes toxygen/smileys/default/D83DDC7F.png | Bin 1847 -> 1826 bytes toxygen/smileys/default/D83DDC80.png | Bin 1697 -> 1665 bytes toxygen/smileys/default/D83DDC81.png | Bin 1741 -> 1779 bytes toxygen/smileys/default/D83DDC82.png | Bin 1614 -> 1673 bytes toxygen/smileys/default/D83DDC83.png | Bin 1579 -> 1556 bytes toxygen/smileys/default/D83DDC84.png | Bin 1393 -> 1374 bytes toxygen/smileys/default/D83DDC85.png | Bin 1584 -> 1585 bytes toxygen/smileys/default/D83DDC86.png | Bin 1795 -> 1793 bytes toxygen/smileys/default/D83DDC87.png | Bin 1804 -> 1846 bytes toxygen/smileys/default/D83DDC88.png | Bin 1487 -> 1294 bytes toxygen/smileys/default/D83DDC89.png | Bin 1490 -> 1447 bytes toxygen/smileys/default/D83DDC8A.png | Bin 1666 -> 1718 bytes toxygen/smileys/default/D83DDC8B.png | Bin 1698 -> 1838 bytes toxygen/smileys/default/D83DDC8C.png | Bin 1460 -> 1415 bytes toxygen/smileys/default/D83DDC8D.png | Bin 1010 -> 1341 bytes toxygen/smileys/default/D83DDC8E.png | Bin 1442 -> 1335 bytes toxygen/smileys/default/D83DDC8F.png | Bin 1746 -> 1781 bytes toxygen/smileys/default/D83DDC90.png | Bin 1729 -> 1785 bytes toxygen/smileys/default/D83DDC91.png | Bin 1793 -> 1825 bytes toxygen/smileys/default/D83DDC92.png | Bin 1697 -> 1612 bytes toxygen/smileys/default/D83DDC93.png | Bin 553 -> 749 bytes toxygen/smileys/default/D83DDC94.png | Bin 741 -> 1008 bytes toxygen/smileys/default/D83DDC95.png | Bin 1450 -> 1444 bytes toxygen/smileys/default/D83DDC96.png | Bin 762 -> 1016 bytes toxygen/smileys/default/D83DDC97.png | Bin 841 -> 1022 bytes toxygen/smileys/default/D83DDC98.png | Bin 1673 -> 1738 bytes toxygen/smileys/default/D83DDC99.png | Bin 595 -> 910 bytes toxygen/smileys/default/D83DDC9A.png | Bin 616 -> 947 bytes toxygen/smileys/default/D83DDC9B.png | Bin 608 -> 922 bytes toxygen/smileys/default/D83DDC9C.png | Bin 705 -> 938 bytes toxygen/smileys/default/D83DDC9D.png | Bin 799 -> 1063 bytes toxygen/smileys/default/D83DDC9E.png | Bin 770 -> 1100 bytes toxygen/smileys/default/D83DDC9F.png | Bin 1322 -> 1341 bytes toxygen/smileys/default/D83DDCA0.png | Bin 1669 -> 1688 bytes toxygen/smileys/default/D83DDCA1.png | Bin 1474 -> 1436 bytes toxygen/smileys/default/D83DDCA2.png | Bin 1598 -> 1573 bytes toxygen/smileys/default/D83DDCA3.png | Bin 1628 -> 1652 bytes toxygen/smileys/default/D83DDCA4.png | Bin 1162 -> 1267 bytes toxygen/smileys/default/D83DDCA5.png | Bin 1544 -> 1838 bytes toxygen/smileys/default/D83DDCA6.png | Bin 1577 -> 1681 bytes toxygen/smileys/default/D83DDCA7.png | Bin 1356 -> 1340 bytes toxygen/smileys/default/D83DDCA8.png | Bin 1727 -> 1862 bytes toxygen/smileys/default/D83DDCA9.png | Bin 1622 -> 1588 bytes toxygen/smileys/default/D83DDCAA.png | Bin 1526 -> 1573 bytes toxygen/smileys/default/D83DDCAB.png | Bin 1510 -> 1705 bytes toxygen/smileys/default/D83DDCAC.png | Bin 1571 -> 1543 bytes toxygen/smileys/default/D83DDCAD.png | Bin 1636 -> 1634 bytes toxygen/smileys/default/D83DDCAE.png | Bin 1027 -> 1534 bytes toxygen/smileys/default/D83DDCAF.png | Bin 1502 -> 1801 bytes toxygen/smileys/default/D83DDCB0.png | Bin 1657 -> 1705 bytes toxygen/smileys/default/D83DDCB1.png | Bin 1342 -> 1650 bytes toxygen/smileys/default/D83DDCB2.png | Bin 1246 -> 1384 bytes toxygen/smileys/default/D83DDCB3.png | Bin 1325 -> 1294 bytes toxygen/smileys/default/D83DDCB4.png | Bin 1565 -> 1724 bytes toxygen/smileys/default/D83DDCB5.png | Bin 1555 -> 1654 bytes toxygen/smileys/default/D83DDCB6.png | Bin 1579 -> 1730 bytes toxygen/smileys/default/D83DDCB7.png | Bin 1583 -> 1730 bytes toxygen/smileys/default/D83DDCB8.png | Bin 1876 -> 1888 bytes toxygen/smileys/default/D83DDCB9.png | Bin 1355 -> 1461 bytes toxygen/smileys/default/D83DDCBA.png | Bin 1639 -> 1588 bytes toxygen/smileys/default/D83DDCBB.png | Bin 1332 -> 1279 bytes toxygen/smileys/default/D83DDCBC.png | Bin 1399 -> 1266 bytes toxygen/smileys/default/D83DDCBD.png | Bin 1674 -> 1716 bytes toxygen/smileys/default/D83DDCBE.png | Bin 1460 -> 1393 bytes toxygen/smileys/default/D83DDCBF.png | Bin 1788 -> 1853 bytes toxygen/smileys/default/D83DDCC0.png | Bin 1792 -> 1892 bytes toxygen/smileys/default/D83DDCC1.png | Bin 1603 -> 1826 bytes toxygen/smileys/default/D83DDCC2.png | Bin 1645 -> 1874 bytes toxygen/smileys/default/D83DDCC3.png | Bin 1331 -> 1290 bytes toxygen/smileys/default/D83DDCC4.png | Bin 1308 -> 1290 bytes toxygen/smileys/default/D83DDCC5.png | Bin 1383 -> 1298 bytes toxygen/smileys/default/D83DDCC6.png | Bin 1407 -> 1288 bytes toxygen/smileys/default/D83DDCC7.png | Bin 1465 -> 1384 bytes toxygen/smileys/default/D83DDCC8.png | Bin 1468 -> 1406 bytes toxygen/smileys/default/D83DDCC9.png | Bin 1462 -> 1400 bytes toxygen/smileys/default/D83DDCCA.png | Bin 1368 -> 1357 bytes toxygen/smileys/default/D83DDCCB.png | Bin 1499 -> 1421 bytes toxygen/smileys/default/D83DDCCC.png | Bin 1567 -> 1532 bytes toxygen/smileys/default/D83DDCCD.png | Bin 1339 -> 1294 bytes toxygen/smileys/default/D83DDCCE.png | Bin 1221 -> 1212 bytes toxygen/smileys/default/D83DDCCF.png | Bin 1336 -> 1185 bytes toxygen/smileys/default/D83DDCD0.png | Bin 1404 -> 1575 bytes toxygen/smileys/default/D83DDCD1.png | Bin 1455 -> 1313 bytes toxygen/smileys/default/D83DDCD2.png | Bin 1247 -> 1205 bytes toxygen/smileys/default/D83DDCD3.png | Bin 1593 -> 1513 bytes toxygen/smileys/default/D83DDCD4.png | Bin 1220 -> 1180 bytes toxygen/smileys/default/D83DDCD5.png | Bin 1260 -> 1245 bytes toxygen/smileys/default/D83DDCD6.png | Bin 1424 -> 1346 bytes toxygen/smileys/default/D83DDCD7.png | Bin 1240 -> 1242 bytes toxygen/smileys/default/D83DDCD8.png | Bin 1211 -> 1243 bytes toxygen/smileys/default/D83DDCD9.png | Bin 1250 -> 1242 bytes toxygen/smileys/default/D83DDCDA.png | Bin 1647 -> 1652 bytes toxygen/smileys/default/D83DDCDB.png | Bin 1607 -> 1624 bytes toxygen/smileys/default/D83DDCDC.png | Bin 1297 -> 1184 bytes toxygen/smileys/default/D83DDCDD.png | Bin 1562 -> 1559 bytes toxygen/smileys/default/D83DDCDE.png | Bin 1438 -> 1642 bytes toxygen/smileys/default/D83DDCDF.png | Bin 1376 -> 1271 bytes toxygen/smileys/default/D83DDCE0.png | Bin 1389 -> 1326 bytes toxygen/smileys/default/D83DDCE1.png | Bin 1720 -> 1813 bytes toxygen/smileys/default/D83DDCE2.png | Bin 1683 -> 1727 bytes toxygen/smileys/default/D83DDCE3.png | Bin 1593 -> 1684 bytes toxygen/smileys/default/D83DDCE4.png | Bin 1466 -> 1467 bytes toxygen/smileys/default/D83DDCE5.png | Bin 1473 -> 1460 bytes toxygen/smileys/default/D83DDCE6.png | Bin 1692 -> 1858 bytes toxygen/smileys/default/D83DDCE7.png | Bin 1398 -> 1340 bytes toxygen/smileys/default/D83DDCE8.png | Bin 1372 -> 1366 bytes toxygen/smileys/default/D83DDCE9.png | Bin 1406 -> 1356 bytes toxygen/smileys/default/D83DDCEA.png | Bin 1410 -> 1482 bytes toxygen/smileys/default/D83DDCEB.png | Bin 1444 -> 1529 bytes toxygen/smileys/default/D83DDCEC.png | Bin 1495 -> 1540 bytes toxygen/smileys/default/D83DDCED.png | Bin 1435 -> 1455 bytes toxygen/smileys/default/D83DDCEE.png | Bin 1466 -> 1430 bytes toxygen/smileys/default/D83DDCEF.png | Bin 1624 -> 1616 bytes toxygen/smileys/default/D83DDCF0.png | Bin 1306 -> 1284 bytes toxygen/smileys/default/D83DDCF1.png | Bin 1526 -> 1631 bytes toxygen/smileys/default/D83DDCF2.png | Bin 1592 -> 1586 bytes toxygen/smileys/default/D83DDCF3.png | Bin 1460 -> 1421 bytes toxygen/smileys/default/D83DDCF4.png | Bin 1281 -> 1292 bytes toxygen/smileys/default/D83DDCF5.png | Bin 1584 -> 1665 bytes toxygen/smileys/default/D83DDCF6.png | Bin 1173 -> 1197 bytes toxygen/smileys/default/D83DDCF7.png | Bin 1516 -> 1513 bytes toxygen/smileys/default/D83DDCF9.png | Bin 1582 -> 1541 bytes toxygen/smileys/default/D83DDCFA.png | Bin 1492 -> 1404 bytes toxygen/smileys/default/D83DDCFB.png | Bin 1490 -> 1393 bytes toxygen/smileys/default/D83DDCFC.png | Bin 1285 -> 1173 bytes toxygen/smileys/default/D83DDD00.png | Bin 1391 -> 1356 bytes toxygen/smileys/default/D83DDD01.png | Bin 1396 -> 1387 bytes toxygen/smileys/default/D83DDD02.png | Bin 1424 -> 1407 bytes toxygen/smileys/default/D83DDD03.png | Bin 1326 -> 1562 bytes toxygen/smileys/default/D83DDD04.png | Bin 1452 -> 1392 bytes toxygen/smileys/default/D83DDD05.png | Bin 1330 -> 1253 bytes toxygen/smileys/default/D83DDD06.png | Bin 1526 -> 1443 bytes toxygen/smileys/default/D83DDD07.png | Bin 1902 -> 1961 bytes toxygen/smileys/default/D83DDD09.png | Bin 1915 -> 2009 bytes toxygen/smileys/default/D83DDD0B.png | Bin 1342 -> 1204 bytes toxygen/smileys/default/D83DDD0C.png | Bin 1570 -> 1591 bytes toxygen/smileys/default/D83DDD0D.png | Bin 1574 -> 1583 bytes toxygen/smileys/default/D83DDD0E.png | Bin 1559 -> 1588 bytes toxygen/smileys/default/D83DDD0F.png | Bin 1626 -> 1596 bytes toxygen/smileys/default/D83DDD10.png | Bin 1572 -> 1622 bytes toxygen/smileys/default/D83DDD11.png | Bin 1570 -> 1574 bytes toxygen/smileys/default/D83DDD12.png | Bin 1371 -> 1290 bytes toxygen/smileys/default/D83DDD13.png | Bin 1398 -> 1323 bytes toxygen/smileys/default/D83DDD14.png | Bin 1637 -> 1761 bytes toxygen/smileys/default/D83DDD15.png | Bin 1703 -> 1787 bytes toxygen/smileys/default/D83DDD16.png | Bin 1568 -> 1560 bytes toxygen/smileys/default/D83DDD17.png | Bin 1712 -> 1694 bytes toxygen/smileys/default/D83DDD18.png | Bin 1567 -> 1316 bytes toxygen/smileys/default/D83DDD19.png | Bin 1286 -> 1499 bytes toxygen/smileys/default/D83DDD1A.png | Bin 1246 -> 1379 bytes toxygen/smileys/default/D83DDD1B.png | Bin 1313 -> 1499 bytes toxygen/smileys/default/D83DDD1C.png | Bin 1263 -> 1465 bytes toxygen/smileys/default/D83DDD1D.png | Bin 1244 -> 1424 bytes toxygen/smileys/default/D83DDD1E.png | Bin 1583 -> 1653 bytes toxygen/smileys/default/D83DDD1F.png | Bin 1344 -> 1347 bytes toxygen/smileys/default/D83DDD20.png | Bin 1489 -> 1435 bytes toxygen/smileys/default/D83DDD21.png | Bin 1368 -> 1383 bytes toxygen/smileys/default/D83DDD22.png | Bin 1372 -> 1340 bytes toxygen/smileys/default/D83DDD23.png | Bin 1278 -> 1535 bytes toxygen/smileys/default/D83DDD24.png | Bin 1302 -> 1305 bytes toxygen/smileys/default/D83DDD25.png | Bin 1534 -> 1832 bytes toxygen/smileys/default/D83DDD26.png | Bin 1597 -> 1555 bytes toxygen/smileys/default/D83DDD27.png | Bin 1611 -> 1573 bytes toxygen/smileys/default/D83DDD28.png | Bin 1440 -> 1444 bytes toxygen/smileys/default/D83DDD29.png | Bin 1590 -> 1503 bytes toxygen/smileys/default/D83DDD2A.png | Bin 1384 -> 1211 bytes toxygen/smileys/default/D83DDD2B.png | Bin 1422 -> 1403 bytes toxygen/smileys/default/D83DDD2C.png | Bin 1530 -> 1421 bytes toxygen/smileys/default/D83DDD2D.png | Bin 1496 -> 1469 bytes toxygen/smileys/default/D83DDD2E.png | Bin 1824 -> 1863 bytes toxygen/smileys/default/D83DDD31.png | Bin 1598 -> 1513 bytes toxygen/smileys/default/D83DDD32.png | Bin 1327 -> 1146 bytes toxygen/smileys/default/D83DDD33.png | Bin 1249 -> 1122 bytes toxygen/smileys/default/D83DDD34.png | Bin 1453 -> 1541 bytes toxygen/smileys/default/D83DDD35.png | Bin 1444 -> 1516 bytes toxygen/smileys/default/D83DDD36.png | Bin 1490 -> 1468 bytes toxygen/smileys/default/D83DDD37.png | Bin 1531 -> 1462 bytes toxygen/smileys/default/D83DDD38.png | Bin 1257 -> 1198 bytes toxygen/smileys/default/D83DDD39.png | Bin 1272 -> 1192 bytes toxygen/smileys/default/D83DDD3A.png | Bin 1177 -> 1127 bytes toxygen/smileys/default/D83DDD3B.png | Bin 1179 -> 1130 bytes toxygen/smileys/default/D83DDD3C.png | Bin 1259 -> 1291 bytes toxygen/smileys/default/D83DDD3D.png | Bin 1279 -> 1290 bytes toxygen/smileys/default/D83DDDFB.png | Bin 1372 -> 1424 bytes toxygen/smileys/default/D83DDDFC.png | Bin 1389 -> 1359 bytes toxygen/smileys/default/D83DDDFD.png | Bin 1608 -> 1690 bytes toxygen/smileys/default/D83DDDFE.png | Bin 1297 -> 1283 bytes toxygen/smileys/default/D83DDDFF.png | Bin 1499 -> 1252 bytes toxygen/smileys/default/D83DDE00.png | Bin 1639 -> 1645 bytes toxygen/smileys/default/D83DDE01.png | Bin 1607 -> 1649 bytes toxygen/smileys/default/D83DDE02.png | Bin 1774 -> 1767 bytes toxygen/smileys/default/D83DDE03.png | Bin 1699 -> 1718 bytes toxygen/smileys/default/D83DDE04.png | Bin 1660 -> 1676 bytes toxygen/smileys/default/D83DDE05.png | Bin 1698 -> 1721 bytes toxygen/smileys/default/D83DDE06.png | Bin 1721 -> 1734 bytes toxygen/smileys/default/D83DDE07.png | Bin 1685 -> 1761 bytes toxygen/smileys/default/D83DDE08.png | Bin 1860 -> 1856 bytes toxygen/smileys/default/D83DDE09.png | Bin 1630 -> 1703 bytes toxygen/smileys/default/D83DDE0A.png | Bin 1681 -> 1763 bytes toxygen/smileys/default/D83DDE0B.png | Bin 1633 -> 1713 bytes toxygen/smileys/default/D83DDE0C.png | Bin 1643 -> 1677 bytes toxygen/smileys/default/D83DDE0D.png | Bin 1676 -> 1746 bytes toxygen/smileys/default/D83DDE0E.png | Bin 1641 -> 1652 bytes toxygen/smileys/default/D83DDE0F.png | Bin 1604 -> 1658 bytes toxygen/smileys/default/D83DDE10.png | Bin 1549 -> 1597 bytes toxygen/smileys/default/D83DDE11.png | Bin 1557 -> 1612 bytes toxygen/smileys/default/D83DDE12.png | Bin 1715 -> 1724 bytes toxygen/smileys/default/D83DDE13.png | Bin 1656 -> 1720 bytes toxygen/smileys/default/D83DDE14.png | Bin 1623 -> 1661 bytes toxygen/smileys/default/D83DDE15.png | Bin 1585 -> 1655 bytes toxygen/smileys/default/D83DDE16.png | Bin 1685 -> 1717 bytes toxygen/smileys/default/D83DDE17.png | Bin 1528 -> 1599 bytes toxygen/smileys/default/D83DDE18.png | Bin 1659 -> 1758 bytes toxygen/smileys/default/D83DDE19.png | Bin 1561 -> 1631 bytes toxygen/smileys/default/D83DDE1A.png | Bin 1712 -> 1760 bytes toxygen/smileys/default/D83DDE1B.png | Bin 1578 -> 1593 bytes toxygen/smileys/default/D83DDE1C.png | Bin 1663 -> 1693 bytes toxygen/smileys/default/D83DDE1D.png | Bin 1659 -> 1693 bytes toxygen/smileys/default/D83DDE1E.png | Bin 1563 -> 1614 bytes toxygen/smileys/default/D83DDE1F.png | Bin 1625 -> 1649 bytes toxygen/smileys/default/D83DDE20.png | Bin 1625 -> 1654 bytes toxygen/smileys/default/D83DDE21.png | Bin 1712 -> 1726 bytes toxygen/smileys/default/D83DDE22.png | Bin 1657 -> 1644 bytes toxygen/smileys/default/D83DDE23.png | Bin 1636 -> 1677 bytes toxygen/smileys/default/D83DDE24.png | Bin 1716 -> 1736 bytes toxygen/smileys/default/D83DDE25.png | Bin 1619 -> 1663 bytes toxygen/smileys/default/D83DDE26.png | Bin 1559 -> 1607 bytes toxygen/smileys/default/D83DDE27.png | Bin 1600 -> 1644 bytes toxygen/smileys/default/D83DDE28.png | Bin 1700 -> 1726 bytes toxygen/smileys/default/D83DDE29.png | Bin 1694 -> 1749 bytes toxygen/smileys/default/D83DDE2A.png | Bin 1685 -> 1722 bytes toxygen/smileys/default/D83DDE2B.png | Bin 1722 -> 1741 bytes toxygen/smileys/default/D83DDE2C.png | Bin 1585 -> 1622 bytes toxygen/smileys/default/D83DDE2D.png | Bin 1719 -> 1785 bytes toxygen/smileys/default/D83DDE2E.png | Bin 1552 -> 1597 bytes toxygen/smileys/default/D83DDE2F.png | Bin 1572 -> 1620 bytes toxygen/smileys/default/D83DDE30.png | Bin 1741 -> 1702 bytes toxygen/smileys/default/D83DDE31.png | Bin 1795 -> 1796 bytes toxygen/smileys/default/D83DDE32.png | Bin 1658 -> 1678 bytes toxygen/smileys/default/D83DDE33.png | Bin 1759 -> 1805 bytes toxygen/smileys/default/D83DDE34.png | Bin 1599 -> 1654 bytes toxygen/smileys/default/D83DDE35.png | Bin 1651 -> 1685 bytes toxygen/smileys/default/D83DDE36.png | Bin 1509 -> 1579 bytes toxygen/smileys/default/D83DDE37.png | Bin 1679 -> 1667 bytes toxygen/smileys/default/D83DDE38.png | Bin 1695 -> 1740 bytes toxygen/smileys/default/D83DDE39.png | Bin 1755 -> 1801 bytes toxygen/smileys/default/D83DDE3A.png | Bin 1644 -> 1711 bytes toxygen/smileys/default/D83DDE3B.png | Bin 1668 -> 1721 bytes toxygen/smileys/default/D83DDE3C.png | Bin 1654 -> 1696 bytes toxygen/smileys/default/D83DDE3D.png | Bin 1716 -> 1782 bytes toxygen/smileys/default/D83DDE3E.png | Bin 1583 -> 1727 bytes toxygen/smileys/default/D83DDE3F.png | Bin 1679 -> 1733 bytes toxygen/smileys/default/D83DDE40.png | Bin 1747 -> 1792 bytes toxygen/smileys/default/D83DDE45.png | Bin 1783 -> 1757 bytes toxygen/smileys/default/D83DDE46.png | Bin 1858 -> 1806 bytes toxygen/smileys/default/D83DDE47.png | Bin 1659 -> 1661 bytes toxygen/smileys/default/D83DDE48.png | Bin 1785 -> 1778 bytes toxygen/smileys/default/D83DDE49.png | Bin 1747 -> 1728 bytes toxygen/smileys/default/D83DDE4A.png | Bin 1750 -> 1789 bytes toxygen/smileys/default/D83DDE4B.png | Bin 1826 -> 1886 bytes toxygen/smileys/default/D83DDE4C.png | Bin 1629 -> 1557 bytes toxygen/smileys/default/D83DDE4D.png | Bin 1645 -> 1667 bytes toxygen/smileys/default/D83DDE4E.png | Bin 1699 -> 1710 bytes toxygen/smileys/default/D83DDE4F.png | Bin 1590 -> 1567 bytes toxygen/smileys/default/D83DDE80.png | Bin 1679 -> 1695 bytes toxygen/smileys/default/D83DDE81.png | Bin 1571 -> 1552 bytes toxygen/smileys/default/D83DDE82.png | Bin 1516 -> 1430 bytes toxygen/smileys/default/D83DDE83.png | Bin 1277 -> 1240 bytes toxygen/smileys/default/D83DDE84.png | Bin 1561 -> 1633 bytes toxygen/smileys/default/D83DDE85.png | Bin 1474 -> 1488 bytes toxygen/smileys/default/D83DDE86.png | Bin 1698 -> 1611 bytes toxygen/smileys/default/D83DDE87.png | Bin 1600 -> 1459 bytes toxygen/smileys/default/D83DDE88.png | Bin 1531 -> 1540 bytes toxygen/smileys/default/D83DDE89.png | Bin 1681 -> 1550 bytes toxygen/smileys/default/D83DDE8A.png | Bin 1615 -> 1470 bytes toxygen/smileys/default/D83DDE8B.png | Bin 1317 -> 1257 bytes toxygen/smileys/default/D83DDE8C.png | Bin 1350 -> 1289 bytes toxygen/smileys/default/D83DDE8D.png | Bin 1657 -> 1577 bytes toxygen/smileys/default/D83DDE8E.png | Bin 1399 -> 1359 bytes toxygen/smileys/default/D83DDE8F.png | Bin 1310 -> 1233 bytes toxygen/smileys/default/D83DDE90.png | Bin 1463 -> 1462 bytes toxygen/smileys/default/D83DDE91.png | Bin 1528 -> 1547 bytes toxygen/smileys/default/D83DDE92.png | Bin 1562 -> 1490 bytes toxygen/smileys/default/D83DDE93.png | Bin 1559 -> 1531 bytes toxygen/smileys/default/D83DDE94.png | Bin 1628 -> 1594 bytes toxygen/smileys/default/D83DDE95.png | Bin 1491 -> 1477 bytes toxygen/smileys/default/D83DDE96.png | Bin 1618 -> 1615 bytes toxygen/smileys/default/D83DDE97.png | Bin 1502 -> 1568 bytes toxygen/smileys/default/D83DDE98.png | Bin 1577 -> 1602 bytes toxygen/smileys/default/D83DDE99.png | Bin 1513 -> 1546 bytes toxygen/smileys/default/D83DDE9A.png | Bin 1438 -> 1385 bytes toxygen/smileys/default/D83DDE9B.png | Bin 1398 -> 1335 bytes toxygen/smileys/default/D83DDE9C.png | Bin 1658 -> 1628 bytes toxygen/smileys/default/D83DDE9D.png | Bin 1453 -> 1431 bytes toxygen/smileys/default/D83DDE9E.png | Bin 1441 -> 1441 bytes toxygen/smileys/default/D83DDE9F.png | Bin 1318 -> 1261 bytes toxygen/smileys/default/D83DDEA0.png | Bin 1515 -> 1394 bytes toxygen/smileys/default/D83DDEA1.png | Bin 1513 -> 1457 bytes toxygen/smileys/default/D83DDEA2.png | Bin 1314 -> 1301 bytes toxygen/smileys/default/D83DDEA3.png | Bin 1458 -> 1408 bytes toxygen/smileys/default/D83DDEA4.png | Bin 1413 -> 1431 bytes toxygen/smileys/default/D83DDEA5.png | Bin 1462 -> 1385 bytes toxygen/smileys/default/D83DDEA6.png | Bin 1452 -> 1425 bytes toxygen/smileys/default/D83DDEA7.png | Bin 1351 -> 1298 bytes toxygen/smileys/default/D83DDEA8.png | Bin 1811 -> 1872 bytes toxygen/smileys/default/D83DDEA9.png | Bin 1256 -> 1253 bytes toxygen/smileys/default/D83DDEAA.png | Bin 1363 -> 1334 bytes toxygen/smileys/default/D83DDEAB.png | Bin 1798 -> 1869 bytes toxygen/smileys/default/D83DDEAC.png | Bin 1558 -> 1528 bytes toxygen/smileys/default/D83DDEAD.png | Bin 1435 -> 1415 bytes toxygen/smileys/default/D83DDEAE.png | Bin 1358 -> 1334 bytes toxygen/smileys/default/D83DDEAF.png | Bin 1621 -> 1693 bytes toxygen/smileys/default/D83DDEB0.png | Bin 1310 -> 1322 bytes toxygen/smileys/default/D83DDEB1.png | Bin 1637 -> 1706 bytes toxygen/smileys/default/D83DDEB2.png | Bin 1429 -> 1513 bytes toxygen/smileys/default/D83DDEB3.png | Bin 1647 -> 1721 bytes toxygen/smileys/default/D83DDEB4.png | Bin 1636 -> 1685 bytes toxygen/smileys/default/D83DDEB5.png | Bin 1766 -> 1716 bytes toxygen/smileys/default/D83DDEB6.png | Bin 1312 -> 1303 bytes toxygen/smileys/default/D83DDEB7.png | Bin 1643 -> 1730 bytes toxygen/smileys/default/D83DDEB8.png | Bin 1802 -> 1786 bytes toxygen/smileys/default/D83DDEB9.png | Bin 1269 -> 1285 bytes toxygen/smileys/default/D83DDEBA.png | Bin 1318 -> 1309 bytes toxygen/smileys/default/D83DDEBB.png | Bin 1348 -> 1319 bytes toxygen/smileys/default/D83DDEBC.png | Bin 1298 -> 1314 bytes toxygen/smileys/default/D83DDEBD.png | Bin 1435 -> 1431 bytes toxygen/smileys/default/D83DDEBE.png | Bin 1367 -> 1398 bytes toxygen/smileys/default/D83DDEBF.png | Bin 1624 -> 1838 bytes toxygen/smileys/default/D83DDEC0.png | Bin 1405 -> 1385 bytes toxygen/smileys/default/D83DDEC1.png | Bin 1360 -> 1346 bytes toxygen/smileys/default/D83DDEC2.png | Bin 1380 -> 1366 bytes toxygen/smileys/default/D83DDEC3.png | Bin 1317 -> 1329 bytes toxygen/smileys/default/D83DDEC4.png | Bin 1251 -> 1244 bytes toxygen/smileys/default/D83DDEC5.png | Bin 1337 -> 1371 bytes toxygen/smileys/default/ad.png | Bin 643 -> 977 bytes toxygen/smileys/default/ae.png | Bin 408 -> 781 bytes toxygen/smileys/default/af.png | Bin 604 -> 945 bytes toxygen/smileys/default/ag.png | Bin 591 -> 920 bytes toxygen/smileys/default/ai.png | Bin 643 -> 974 bytes toxygen/smileys/default/al.png | Bin 600 -> 949 bytes toxygen/smileys/default/am.png | Bin 497 -> 957 bytes toxygen/smileys/default/an.png | Bin 488 -> 811 bytes toxygen/smileys/default/ao.png | Bin 428 -> 828 bytes toxygen/smileys/default/ar.png | Bin 506 -> 937 bytes toxygen/smileys/default/as.png | Bin 647 -> 978 bytes toxygen/smileys/default/at.png | Bin 403 -> 856 bytes toxygen/smileys/default/au.png | Bin 673 -> 995 bytes toxygen/smileys/default/aw.png | Bin 524 -> 1004 bytes toxygen/smileys/default/ax.png | Bin 663 -> 1013 bytes toxygen/smileys/default/az.png | Bin 589 -> 1001 bytes toxygen/smileys/default/ba.png | Bin 593 -> 960 bytes toxygen/smileys/default/bb.png | Bin 585 -> 975 bytes toxygen/smileys/default/bd.png | Bin 504 -> 856 bytes toxygen/smileys/default/be.png | Bin 449 -> 886 bytes toxygen/smileys/default/bf.png | Bin 497 -> 933 bytes toxygen/smileys/default/bg.png | Bin 462 -> 911 bytes toxygen/smileys/default/bh.png | Bin 457 -> 836 bytes toxygen/smileys/default/bi.png | Bin 675 -> 962 bytes toxygen/smileys/default/bj.png | Bin 486 -> 906 bytes toxygen/smileys/default/bm.png | Bin 611 -> 969 bytes toxygen/smileys/default/bn.png | Bin 639 -> 958 bytes toxygen/smileys/default/bo.png | Bin 500 -> 957 bytes toxygen/smileys/default/br.png | Bin 593 -> 930 bytes toxygen/smileys/default/bs.png | Bin 526 -> 942 bytes toxygen/smileys/default/bt.png | Bin 631 -> 946 bytes toxygen/smileys/default/bv.png | Bin 512 -> 860 bytes toxygen/smileys/default/bw.png | Bin 443 -> 895 bytes toxygen/smileys/default/by.png | Bin 514 -> 927 bytes toxygen/smileys/default/bz.png | Bin 600 -> 961 bytes toxygen/smileys/default/ca.png | Bin 628 -> 926 bytes toxygen/smileys/default/catalonia.png | Bin 398 -> 890 bytes toxygen/smileys/default/cc.png | Bin 625 -> 945 bytes toxygen/smileys/default/cd.png | Bin 528 -> 826 bytes toxygen/smileys/default/cf.png | Bin 614 -> 969 bytes toxygen/smileys/default/cg.png | Bin 521 -> 861 bytes toxygen/smileys/default/ch.png | Bin 367 -> 615 bytes toxygen/smileys/default/ci.png | Bin 453 -> 832 bytes toxygen/smileys/default/ck.png | Bin 586 -> 930 bytes toxygen/smileys/default/cl.png | Bin 450 -> 836 bytes toxygen/smileys/default/cm.png | Bin 525 -> 957 bytes toxygen/smileys/default/cn.png | Bin 472 -> 890 bytes toxygen/smileys/default/co.png | Bin 483 -> 964 bytes toxygen/smileys/default/cr.png | Bin 477 -> 974 bytes toxygen/smileys/default/cs.png | Bin 439 -> 892 bytes toxygen/smileys/default/cu.png | Bin 563 -> 973 bytes toxygen/smileys/default/cv.png | Bin 529 -> 899 bytes toxygen/smileys/default/cx.png | Bin 608 -> 998 bytes toxygen/smileys/default/cy.png | Bin 428 -> 687 bytes toxygen/smileys/default/cz.png | Bin 476 -> 850 bytes toxygen/smileys/default/de.png | Bin 545 -> 966 bytes toxygen/smileys/default/dj.png | Bin 572 -> 951 bytes toxygen/smileys/default/dk.png | Bin 495 -> 853 bytes toxygen/smileys/default/dm.png | Bin 620 -> 928 bytes toxygen/smileys/default/do.png | Bin 508 -> 911 bytes toxygen/smileys/default/dz.png | Bin 582 -> 900 bytes toxygen/smileys/default/ec.png | Bin 500 -> 952 bytes toxygen/smileys/default/ee.png | Bin 429 -> 819 bytes toxygen/smileys/default/eg.png | Bin 465 -> 852 bytes toxygen/smileys/default/eh.png | Bin 508 -> 916 bytes toxygen/smileys/default/england.png | Bin 496 -> 790 bytes toxygen/smileys/default/er.png | Bin 653 -> 995 bytes toxygen/smileys/default/es.png | Bin 469 -> 889 bytes toxygen/smileys/default/et.png | Bin 592 -> 971 bytes toxygen/smileys/default/europeanunion.png | Bin 479 -> 902 bytes toxygen/smileys/default/fam.png | Bin 532 -> 971 bytes toxygen/smileys/default/fi.png | Bin 489 -> 859 bytes toxygen/smileys/default/fj.png | Bin 610 -> 989 bytes toxygen/smileys/default/fk.png | Bin 648 -> 1001 bytes toxygen/smileys/default/fm.png | Bin 552 -> 905 bytes toxygen/smileys/default/fo.png | Bin 474 -> 780 bytes toxygen/smileys/default/fr.png | Bin 545 -> 873 bytes toxygen/smileys/default/ga.png | Bin 489 -> 960 bytes toxygen/smileys/default/gb.png | Bin 599 -> 893 bytes toxygen/smileys/default/gd.png | Bin 637 -> 960 bytes toxygen/smileys/default/ge.png | Bin 594 -> 865 bytes toxygen/smileys/default/gf.png | Bin 545 -> 873 bytes toxygen/smileys/default/gh.png | Bin 490 -> 930 bytes toxygen/smileys/default/gi.png | Bin 463 -> 816 bytes toxygen/smileys/default/gl.png | Bin 470 -> 847 bytes toxygen/smileys/default/gm.png | Bin 493 -> 990 bytes toxygen/smileys/default/gn.png | Bin 480 -> 924 bytes toxygen/smileys/default/gp.png | Bin 488 -> 930 bytes toxygen/smileys/default/gq.png | Bin 537 -> 963 bytes toxygen/smileys/default/gr.png | Bin 487 -> 942 bytes toxygen/smileys/default/gs.png | Bin 630 -> 932 bytes toxygen/smileys/default/gt.png | Bin 493 -> 896 bytes toxygen/smileys/default/gu.png | Bin 509 -> 893 bytes toxygen/smileys/default/gw.png | Bin 516 -> 946 bytes toxygen/smileys/default/gy.png | Bin 645 -> 989 bytes toxygen/smileys/default/hk.png | Bin 527 -> 898 bytes toxygen/smileys/default/hm.png | Bin 673 -> 995 bytes toxygen/smileys/default/hn.png | Bin 537 -> 949 bytes toxygen/smileys/default/hr.png | Bin 524 -> 875 bytes toxygen/smileys/default/ht.png | Bin 487 -> 965 bytes toxygen/smileys/default/hu.png | Bin 432 -> 827 bytes toxygen/smileys/default/id.png | Bin 430 -> 803 bytes toxygen/smileys/default/ie.png | Bin 481 -> 887 bytes toxygen/smileys/default/il.png | Bin 431 -> 744 bytes toxygen/smileys/default/in.png | Bin 503 -> 955 bytes toxygen/smileys/default/io.png | Bin 658 -> 1004 bytes toxygen/smileys/default/iq.png | Bin 515 -> 897 bytes toxygen/smileys/default/ir.png | Bin 512 -> 931 bytes toxygen/smileys/default/is.png | Bin 532 -> 873 bytes toxygen/smileys/default/it.png | Bin 420 -> 808 bytes toxygen/smileys/default/jm.png | Bin 637 -> 955 bytes toxygen/smileys/default/jo.png | Bin 473 -> 821 bytes toxygen/smileys/default/jp.png | Bin 420 -> 703 bytes toxygen/smileys/default/ke.png | Bin 569 -> 937 bytes toxygen/smileys/default/kg.png | Bin 510 -> 882 bytes toxygen/smileys/default/kh.png | Bin 549 -> 972 bytes toxygen/smileys/default/ki.png | Bin 656 -> 984 bytes toxygen/smileys/default/km.png | Bin 577 -> 935 bytes toxygen/smileys/default/kn.png | Bin 604 -> 927 bytes toxygen/smileys/default/kp.png | Bin 561 -> 965 bytes toxygen/smileys/default/kr.png | Bin 592 -> 848 bytes toxygen/smileys/default/kw.png | Bin 486 -> 901 bytes toxygen/smileys/default/ky.png | Bin 643 -> 942 bytes toxygen/smileys/default/kz.png | Bin 616 -> 980 bytes toxygen/smileys/default/la.png | Bin 563 -> 943 bytes toxygen/smileys/default/lb.png | Bin 517 -> 900 bytes toxygen/smileys/default/lc.png | Bin 520 -> 951 bytes toxygen/smileys/default/li.png | Bin 537 -> 977 bytes toxygen/smileys/default/lk.png | Bin 627 -> 969 bytes toxygen/smileys/default/lr.png | Bin 466 -> 794 bytes toxygen/smileys/default/ls.png | Bin 628 -> 911 bytes toxygen/smileys/default/lt.png | Bin 508 -> 975 bytes toxygen/smileys/default/lu.png | Bin 481 -> 913 bytes toxygen/smileys/default/lv.png | Bin 465 -> 962 bytes toxygen/smileys/default/ly.png | Bin 419 -> 882 bytes toxygen/smileys/default/ma.png | Bin 432 -> 849 bytes toxygen/smileys/default/mc.png | Bin 380 -> 783 bytes toxygen/smileys/default/md.png | Bin 566 -> 959 bytes toxygen/smileys/default/me.png | Bin 448 -> 930 bytes toxygen/smileys/default/mg.png | Bin 453 -> 882 bytes toxygen/smileys/default/mh.png | Bin 628 -> 965 bytes toxygen/smileys/default/mk.png | Bin 664 -> 986 bytes toxygen/smileys/default/ml.png | Bin 474 -> 935 bytes toxygen/smileys/default/mm.png | Bin 483 -> 873 bytes toxygen/smileys/default/mn.png | Bin 492 -> 911 bytes toxygen/smileys/default/mo.png | Bin 588 -> 959 bytes toxygen/smileys/default/mp.png | Bin 597 -> 947 bytes toxygen/smileys/default/mq.png | Bin 655 -> 941 bytes toxygen/smileys/default/mr.png | Bin 569 -> 960 bytes toxygen/smileys/default/ms.png | Bin 614 -> 973 bytes toxygen/smileys/default/mt.png | Bin 420 -> 814 bytes toxygen/smileys/default/mu.png | Bin 496 -> 973 bytes toxygen/smileys/default/mv.png | Bin 542 -> 935 bytes toxygen/smileys/default/mw.png | Bin 529 -> 926 bytes toxygen/smileys/default/mx.png | Bin 574 -> 953 bytes toxygen/smileys/default/my.png | Bin 571 -> 966 bytes toxygen/smileys/default/mz.png | Bin 584 -> 1007 bytes toxygen/smileys/default/na.png | Bin 647 -> 969 bytes toxygen/smileys/default/nc.png | Bin 591 -> 953 bytes toxygen/smileys/default/ne.png | Bin 537 -> 954 bytes toxygen/smileys/default/nf.png | Bin 602 -> 927 bytes toxygen/smileys/default/ng.png | Bin 482 -> 881 bytes toxygen/smileys/default/ni.png | Bin 508 -> 928 bytes toxygen/smileys/default/nl.png | Bin 453 -> 884 bytes toxygen/smileys/default/no.png | Bin 512 -> 860 bytes toxygen/smileys/default/np.png | Bin 443 -> 617 bytes toxygen/smileys/default/nr.png | Bin 527 -> 963 bytes toxygen/smileys/default/nu.png | Bin 572 -> 934 bytes toxygen/smileys/default/nz.png | Bin 639 -> 986 bytes toxygen/smileys/default/om.png | Bin 478 -> 883 bytes toxygen/smileys/default/pa.png | Bin 519 -> 875 bytes toxygen/smileys/default/pe.png | Bin 397 -> 762 bytes toxygen/smileys/default/pf.png | Bin 498 -> 887 bytes toxygen/smileys/default/pg.png | Bin 593 -> 898 bytes toxygen/smileys/default/ph.png | Bin 538 -> 960 bytes toxygen/smileys/default/pk.png | Bin 569 -> 923 bytes toxygen/smileys/default/pl.png | Bin 374 -> 777 bytes toxygen/smileys/default/pm.png | Bin 689 -> 1004 bytes toxygen/smileys/default/pn.png | Bin 657 -> 976 bytes toxygen/smileys/default/pr.png | Bin 556 -> 962 bytes toxygen/smileys/default/ps.png | Bin 472 -> 800 bytes toxygen/smileys/default/pt.png | Bin 554 -> 923 bytes toxygen/smileys/default/pw.png | Bin 550 -> 942 bytes toxygen/smileys/default/py.png | Bin 473 -> 926 bytes toxygen/smileys/default/qa.png | Bin 450 -> 794 bytes toxygen/smileys/default/re.png | Bin 545 -> 873 bytes toxygen/smileys/default/ro.png | Bin 495 -> 948 bytes toxygen/smileys/default/rs.png | Bin 423 -> 829 bytes toxygen/smileys/default/ru.png | Bin 420 -> 885 bytes toxygen/smileys/default/rw.png | Bin 533 -> 964 bytes toxygen/smileys/default/sa.png | Bin 551 -> 895 bytes toxygen/smileys/default/sb.png | Bin 624 -> 914 bytes toxygen/smileys/default/sc.png | Bin 608 -> 963 bytes toxygen/smileys/default/scotland.png | Bin 649 -> 915 bytes toxygen/smileys/default/sd.png | Bin 492 -> 878 bytes toxygen/smileys/default/se.png | Bin 542 -> 917 bytes toxygen/smileys/default/sg.png | Bin 468 -> 833 bytes toxygen/smileys/default/sh.png | Bin 645 -> 984 bytes toxygen/smileys/default/si.png | Bin 510 -> 930 bytes toxygen/smileys/default/sj.png | Bin 512 -> 860 bytes toxygen/smileys/default/sk.png | Bin 562 -> 942 bytes toxygen/smileys/default/sl.png | Bin 436 -> 887 bytes toxygen/smileys/default/sm.png | Bin 502 -> 881 bytes toxygen/smileys/default/sn.png | Bin 532 -> 949 bytes toxygen/smileys/default/so.png | Bin 527 -> 962 bytes toxygen/smileys/default/sr.png | Bin 513 -> 974 bytes toxygen/smileys/default/st.png | Bin 584 -> 998 bytes toxygen/smileys/default/sv.png | Bin 501 -> 914 bytes toxygen/smileys/default/sy.png | Bin 422 -> 789 bytes toxygen/smileys/default/sz.png | Bin 643 -> 1001 bytes toxygen/smileys/default/tc.png | Bin 624 -> 980 bytes toxygen/smileys/default/td.png | Bin 570 -> 983 bytes toxygen/smileys/default/tf.png | Bin 527 -> 919 bytes toxygen/smileys/default/tg.png | Bin 562 -> 958 bytes toxygen/smileys/default/th.png | Bin 452 -> 908 bytes toxygen/smileys/default/tj.png | Bin 496 -> 822 bytes toxygen/smileys/default/tk.png | Bin 638 -> 959 bytes toxygen/smileys/default/tl.png | Bin 514 -> 887 bytes toxygen/smileys/default/tm.png | Bin 593 -> 913 bytes toxygen/smileys/default/tn.png | Bin 495 -> 895 bytes toxygen/smileys/default/to.png | Bin 426 -> 826 bytes toxygen/smileys/default/tox.png | Bin 3339 -> 942 bytes toxygen/smileys/default/tr.png | Bin 492 -> 886 bytes toxygen/smileys/default/tt.png | Bin 617 -> 927 bytes toxygen/smileys/default/tv.png | Bin 536 -> 903 bytes toxygen/smileys/default/tw.png | Bin 465 -> 875 bytes toxygen/smileys/default/tz.png | Bin 642 -> 966 bytes toxygen/smileys/default/ua.png | Bin 446 -> 931 bytes toxygen/smileys/default/ug.png | Bin 531 -> 932 bytes toxygen/smileys/default/um.png | Bin 571 -> 877 bytes toxygen/smileys/default/us.png | Bin 609 -> 894 bytes toxygen/smileys/default/uy.png | Bin 532 -> 977 bytes toxygen/smileys/default/uz.png | Bin 515 -> 897 bytes toxygen/smileys/default/va.png | Bin 553 -> 919 bytes toxygen/smileys/default/vc.png | Bin 577 -> 987 bytes toxygen/smileys/default/ve.png | Bin 528 -> 969 bytes toxygen/smileys/default/vg.png | Bin 630 -> 989 bytes toxygen/smileys/default/vi.png | Bin 616 -> 916 bytes toxygen/smileys/default/vn.png | Bin 474 -> 861 bytes toxygen/smileys/default/vu.png | Bin 604 -> 958 bytes toxygen/smileys/default/wales.png | Bin 652 -> 977 bytes toxygen/smileys/default/wf.png | Bin 554 -> 897 bytes toxygen/smileys/default/ws.png | Bin 476 -> 879 bytes toxygen/smileys/default/wtox.png | Bin 3075 -> 587 bytes toxygen/smileys/default/ye.png | Bin 413 -> 820 bytes toxygen/smileys/default/yt.png | Bin 593 -> 884 bytes toxygen/smileys/default/za.png | Bin 642 -> 1001 bytes toxygen/smileys/default/zm.png | Bin 500 -> 919 bytes toxygen/smileys/default/zw.png | Bin 574 -> 995 bytes 1044 files changed, 80 insertions(+), 652 deletions(-) mode change 100755 => 100644 toxygen/smileys/default/ad.png mode change 100755 => 100644 toxygen/smileys/default/ae.png mode change 100755 => 100644 toxygen/smileys/default/af.png mode change 100755 => 100644 toxygen/smileys/default/ag.png mode change 100755 => 100644 toxygen/smileys/default/ai.png mode change 100755 => 100644 toxygen/smileys/default/al.png mode change 100755 => 100644 toxygen/smileys/default/am.png mode change 100755 => 100644 toxygen/smileys/default/an.png mode change 100755 => 100644 toxygen/smileys/default/ar.png mode change 100755 => 100644 toxygen/smileys/default/as.png mode change 100755 => 100644 toxygen/smileys/default/at.png mode change 100755 => 100644 toxygen/smileys/default/au.png mode change 100755 => 100644 toxygen/smileys/default/aw.png mode change 100755 => 100644 toxygen/smileys/default/ax.png mode change 100755 => 100644 toxygen/smileys/default/az.png mode change 100755 => 100644 toxygen/smileys/default/ba.png mode change 100755 => 100644 toxygen/smileys/default/bb.png mode change 100755 => 100644 toxygen/smileys/default/bd.png mode change 100755 => 100644 toxygen/smileys/default/be.png mode change 100755 => 100644 toxygen/smileys/default/bf.png mode change 100755 => 100644 toxygen/smileys/default/bg.png mode change 100755 => 100644 toxygen/smileys/default/bh.png mode change 100755 => 100644 toxygen/smileys/default/bi.png mode change 100755 => 100644 toxygen/smileys/default/bj.png mode change 100755 => 100644 toxygen/smileys/default/bm.png mode change 100755 => 100644 toxygen/smileys/default/bn.png mode change 100755 => 100644 toxygen/smileys/default/bo.png mode change 100755 => 100644 toxygen/smileys/default/br.png mode change 100755 => 100644 toxygen/smileys/default/bs.png mode change 100755 => 100644 toxygen/smileys/default/bt.png mode change 100755 => 100644 toxygen/smileys/default/bv.png mode change 100755 => 100644 toxygen/smileys/default/bw.png mode change 100755 => 100644 toxygen/smileys/default/by.png mode change 100755 => 100644 toxygen/smileys/default/bz.png mode change 100755 => 100644 toxygen/smileys/default/ca.png mode change 100755 => 100644 toxygen/smileys/default/cc.png mode change 100755 => 100644 toxygen/smileys/default/cf.png mode change 100755 => 100644 toxygen/smileys/default/cg.png mode change 100755 => 100644 toxygen/smileys/default/ch.png mode change 100755 => 100644 toxygen/smileys/default/ci.png mode change 100755 => 100644 toxygen/smileys/default/ck.png mode change 100755 => 100644 toxygen/smileys/default/cl.png mode change 100755 => 100644 toxygen/smileys/default/cm.png mode change 100755 => 100644 toxygen/smileys/default/cn.png mode change 100755 => 100644 toxygen/smileys/default/co.png mode change 100755 => 100644 toxygen/smileys/default/cr.png mode change 100755 => 100644 toxygen/smileys/default/cs.png mode change 100755 => 100644 toxygen/smileys/default/cu.png mode change 100755 => 100644 toxygen/smileys/default/cv.png mode change 100755 => 100644 toxygen/smileys/default/cx.png mode change 100755 => 100644 toxygen/smileys/default/cy.png mode change 100755 => 100644 toxygen/smileys/default/cz.png mode change 100755 => 100644 toxygen/smileys/default/de.png mode change 100755 => 100644 toxygen/smileys/default/dj.png mode change 100755 => 100644 toxygen/smileys/default/dk.png mode change 100755 => 100644 toxygen/smileys/default/dm.png mode change 100755 => 100644 toxygen/smileys/default/do.png mode change 100755 => 100644 toxygen/smileys/default/dz.png mode change 100755 => 100644 toxygen/smileys/default/ec.png mode change 100755 => 100644 toxygen/smileys/default/ee.png mode change 100755 => 100644 toxygen/smileys/default/eg.png mode change 100755 => 100644 toxygen/smileys/default/eh.png mode change 100755 => 100644 toxygen/smileys/default/england.png mode change 100755 => 100644 toxygen/smileys/default/er.png mode change 100755 => 100644 toxygen/smileys/default/es.png mode change 100755 => 100644 toxygen/smileys/default/et.png mode change 100755 => 100644 toxygen/smileys/default/fam.png mode change 100755 => 100644 toxygen/smileys/default/fi.png mode change 100755 => 100644 toxygen/smileys/default/fj.png mode change 100755 => 100644 toxygen/smileys/default/fk.png mode change 100755 => 100644 toxygen/smileys/default/fm.png mode change 100755 => 100644 toxygen/smileys/default/fo.png mode change 100755 => 100644 toxygen/smileys/default/fr.png mode change 100755 => 100644 toxygen/smileys/default/ga.png mode change 100755 => 100644 toxygen/smileys/default/gd.png mode change 100755 => 100644 toxygen/smileys/default/ge.png mode change 100755 => 100644 toxygen/smileys/default/gf.png mode change 100755 => 100644 toxygen/smileys/default/gh.png mode change 100755 => 100644 toxygen/smileys/default/gi.png mode change 100755 => 100644 toxygen/smileys/default/gl.png mode change 100755 => 100644 toxygen/smileys/default/gm.png mode change 100755 => 100644 toxygen/smileys/default/gn.png mode change 100755 => 100644 toxygen/smileys/default/gp.png mode change 100755 => 100644 toxygen/smileys/default/gq.png mode change 100755 => 100644 toxygen/smileys/default/gr.png mode change 100755 => 100644 toxygen/smileys/default/gs.png mode change 100755 => 100644 toxygen/smileys/default/gt.png mode change 100755 => 100644 toxygen/smileys/default/gu.png mode change 100755 => 100644 toxygen/smileys/default/gw.png mode change 100755 => 100644 toxygen/smileys/default/gy.png mode change 100755 => 100644 toxygen/smileys/default/hk.png mode change 100755 => 100644 toxygen/smileys/default/hm.png mode change 100755 => 100644 toxygen/smileys/default/hn.png mode change 100755 => 100644 toxygen/smileys/default/hr.png mode change 100755 => 100644 toxygen/smileys/default/ht.png mode change 100755 => 100644 toxygen/smileys/default/hu.png mode change 100755 => 100644 toxygen/smileys/default/id.png mode change 100755 => 100644 toxygen/smileys/default/ie.png mode change 100755 => 100644 toxygen/smileys/default/il.png mode change 100755 => 100644 toxygen/smileys/default/in.png mode change 100755 => 100644 toxygen/smileys/default/io.png mode change 100755 => 100644 toxygen/smileys/default/iq.png mode change 100755 => 100644 toxygen/smileys/default/ir.png mode change 100755 => 100644 toxygen/smileys/default/is.png mode change 100755 => 100644 toxygen/smileys/default/it.png mode change 100755 => 100644 toxygen/smileys/default/jm.png mode change 100755 => 100644 toxygen/smileys/default/jo.png mode change 100755 => 100644 toxygen/smileys/default/jp.png mode change 100755 => 100644 toxygen/smileys/default/ke.png mode change 100755 => 100644 toxygen/smileys/default/kg.png mode change 100755 => 100644 toxygen/smileys/default/kh.png mode change 100755 => 100644 toxygen/smileys/default/ki.png mode change 100755 => 100644 toxygen/smileys/default/km.png mode change 100755 => 100644 toxygen/smileys/default/kn.png mode change 100755 => 100644 toxygen/smileys/default/kp.png mode change 100755 => 100644 toxygen/smileys/default/kr.png mode change 100755 => 100644 toxygen/smileys/default/kw.png mode change 100755 => 100644 toxygen/smileys/default/ky.png mode change 100755 => 100644 toxygen/smileys/default/kz.png mode change 100755 => 100644 toxygen/smileys/default/la.png mode change 100755 => 100644 toxygen/smileys/default/lb.png mode change 100755 => 100644 toxygen/smileys/default/li.png mode change 100755 => 100644 toxygen/smileys/default/lk.png mode change 100755 => 100644 toxygen/smileys/default/lr.png mode change 100755 => 100644 toxygen/smileys/default/ls.png mode change 100755 => 100644 toxygen/smileys/default/lt.png mode change 100755 => 100644 toxygen/smileys/default/lu.png mode change 100755 => 100644 toxygen/smileys/default/lv.png mode change 100755 => 100644 toxygen/smileys/default/ly.png mode change 100755 => 100644 toxygen/smileys/default/ma.png mode change 100755 => 100644 toxygen/smileys/default/mc.png mode change 100755 => 100644 toxygen/smileys/default/md.png mode change 100755 => 100644 toxygen/smileys/default/mg.png mode change 100755 => 100644 toxygen/smileys/default/mh.png mode change 100755 => 100644 toxygen/smileys/default/mk.png mode change 100755 => 100644 toxygen/smileys/default/ml.png mode change 100755 => 100644 toxygen/smileys/default/mm.png mode change 100755 => 100644 toxygen/smileys/default/mn.png mode change 100755 => 100644 toxygen/smileys/default/mo.png mode change 100755 => 100644 toxygen/smileys/default/mp.png mode change 100755 => 100644 toxygen/smileys/default/mq.png mode change 100755 => 100644 toxygen/smileys/default/mr.png mode change 100755 => 100644 toxygen/smileys/default/ms.png mode change 100755 => 100644 toxygen/smileys/default/mt.png mode change 100755 => 100644 toxygen/smileys/default/mu.png mode change 100755 => 100644 toxygen/smileys/default/mv.png mode change 100755 => 100644 toxygen/smileys/default/mw.png mode change 100755 => 100644 toxygen/smileys/default/mx.png mode change 100755 => 100644 toxygen/smileys/default/my.png mode change 100755 => 100644 toxygen/smileys/default/mz.png mode change 100755 => 100644 toxygen/smileys/default/na.png mode change 100755 => 100644 toxygen/smileys/default/nc.png mode change 100755 => 100644 toxygen/smileys/default/ne.png mode change 100755 => 100644 toxygen/smileys/default/nf.png mode change 100755 => 100644 toxygen/smileys/default/ng.png mode change 100755 => 100644 toxygen/smileys/default/ni.png mode change 100755 => 100644 toxygen/smileys/default/nl.png mode change 100755 => 100644 toxygen/smileys/default/no.png mode change 100755 => 100644 toxygen/smileys/default/np.png mode change 100755 => 100644 toxygen/smileys/default/nr.png mode change 100755 => 100644 toxygen/smileys/default/nu.png mode change 100755 => 100644 toxygen/smileys/default/nz.png mode change 100755 => 100644 toxygen/smileys/default/om.png mode change 100755 => 100644 toxygen/smileys/default/pa.png mode change 100755 => 100644 toxygen/smileys/default/pe.png mode change 100755 => 100644 toxygen/smileys/default/pf.png mode change 100755 => 100644 toxygen/smileys/default/pg.png mode change 100755 => 100644 toxygen/smileys/default/ph.png mode change 100755 => 100644 toxygen/smileys/default/pk.png mode change 100755 => 100644 toxygen/smileys/default/pl.png mode change 100755 => 100644 toxygen/smileys/default/pm.png mode change 100755 => 100644 toxygen/smileys/default/pn.png mode change 100755 => 100644 toxygen/smileys/default/pr.png mode change 100755 => 100644 toxygen/smileys/default/ps.png mode change 100755 => 100644 toxygen/smileys/default/pt.png mode change 100755 => 100644 toxygen/smileys/default/pw.png mode change 100755 => 100644 toxygen/smileys/default/py.png mode change 100755 => 100644 toxygen/smileys/default/qa.png mode change 100755 => 100644 toxygen/smileys/default/re.png mode change 100755 => 100644 toxygen/smileys/default/ro.png mode change 100755 => 100644 toxygen/smileys/default/ru.png mode change 100755 => 100644 toxygen/smileys/default/rw.png mode change 100755 => 100644 toxygen/smileys/default/sa.png mode change 100755 => 100644 toxygen/smileys/default/sb.png mode change 100755 => 100644 toxygen/smileys/default/sc.png mode change 100755 => 100644 toxygen/smileys/default/scotland.png mode change 100755 => 100644 toxygen/smileys/default/sd.png mode change 100755 => 100644 toxygen/smileys/default/se.png mode change 100755 => 100644 toxygen/smileys/default/sg.png mode change 100755 => 100644 toxygen/smileys/default/sh.png mode change 100755 => 100644 toxygen/smileys/default/si.png mode change 100755 => 100644 toxygen/smileys/default/sj.png mode change 100755 => 100644 toxygen/smileys/default/sk.png mode change 100755 => 100644 toxygen/smileys/default/sl.png mode change 100755 => 100644 toxygen/smileys/default/sm.png mode change 100755 => 100644 toxygen/smileys/default/sn.png mode change 100755 => 100644 toxygen/smileys/default/so.png mode change 100755 => 100644 toxygen/smileys/default/sr.png mode change 100755 => 100644 toxygen/smileys/default/st.png mode change 100755 => 100644 toxygen/smileys/default/sv.png mode change 100755 => 100644 toxygen/smileys/default/sy.png mode change 100755 => 100644 toxygen/smileys/default/sz.png mode change 100755 => 100644 toxygen/smileys/default/tc.png mode change 100755 => 100644 toxygen/smileys/default/td.png mode change 100755 => 100644 toxygen/smileys/default/tf.png mode change 100755 => 100644 toxygen/smileys/default/tg.png mode change 100755 => 100644 toxygen/smileys/default/th.png mode change 100755 => 100644 toxygen/smileys/default/tj.png mode change 100755 => 100644 toxygen/smileys/default/tk.png mode change 100755 => 100644 toxygen/smileys/default/tl.png mode change 100755 => 100644 toxygen/smileys/default/tm.png mode change 100755 => 100644 toxygen/smileys/default/tn.png mode change 100755 => 100644 toxygen/smileys/default/to.png mode change 100755 => 100644 toxygen/smileys/default/tox.png mode change 100755 => 100644 toxygen/smileys/default/tr.png mode change 100755 => 100644 toxygen/smileys/default/tt.png mode change 100755 => 100644 toxygen/smileys/default/tv.png mode change 100755 => 100644 toxygen/smileys/default/tw.png mode change 100755 => 100644 toxygen/smileys/default/tz.png mode change 100755 => 100644 toxygen/smileys/default/ua.png mode change 100755 => 100644 toxygen/smileys/default/ug.png mode change 100755 => 100644 toxygen/smileys/default/um.png mode change 100755 => 100644 toxygen/smileys/default/us.png mode change 100755 => 100644 toxygen/smileys/default/uy.png mode change 100755 => 100644 toxygen/smileys/default/uz.png mode change 100755 => 100644 toxygen/smileys/default/va.png mode change 100755 => 100644 toxygen/smileys/default/vc.png mode change 100755 => 100644 toxygen/smileys/default/ve.png mode change 100755 => 100644 toxygen/smileys/default/vg.png mode change 100755 => 100644 toxygen/smileys/default/vi.png mode change 100755 => 100644 toxygen/smileys/default/vn.png mode change 100755 => 100644 toxygen/smileys/default/vu.png mode change 100755 => 100644 toxygen/smileys/default/wales.png mode change 100755 => 100644 toxygen/smileys/default/wf.png mode change 100755 => 100644 toxygen/smileys/default/ws.png mode change 100755 => 100644 toxygen/smileys/default/wtox.png mode change 100755 => 100644 toxygen/smileys/default/ye.png mode change 100755 => 100644 toxygen/smileys/default/yt.png mode change 100755 => 100644 toxygen/smileys/default/za.png mode change 100755 => 100644 toxygen/smileys/default/zm.png mode change 100755 => 100644 toxygen/smileys/default/zw.png diff --git a/MANIFEST.in b/MANIFEST.in index 6629fb6..89e57c6 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -16,4 +16,4 @@ include toxygen/styles/*.qss include toxygen/translations/*.qm include toxygen/libs/libtox.dll include toxygen/libs/libsodium.a -include toxygen/nodes.json +include toxygen/bootstrap/nodes.json diff --git a/README.md b/README.md index 914fdfe..8aa90bf 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,6 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pure Python3. -[![Release](https://img.shields.io/github/release/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/releases/latest) -[![Stars](https://img.shields.io/github/stars/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/stargazers) -[![Open issues](https://img.shields.io/github/issues/toxygen-project/toxygen.svg?style=flat)](https://github.com/toxygen-project/toxygen/issues) -[![License](https://img.shields.io/badge/license-GPLv3-blue.svg?style=flat)](https://raw.githubusercontent.com/toxygen-project/toxygen/master/LICENSE.md) -[![Build Status](https://travis-ci.org/toxygen-project/toxygen.svg?branch=master)](https://travis-ci.org/toxygen-project/toxygen) - ### [Install](/docs/install.md) - [Contribute](/docs/contributing.md) - [Plugins](/docs/plugins.md) - [Compile](/docs/compile.md) - [Contact](/docs/contact.md) - [Updater](https://github.com/toxygen-project/toxygen_updater) ### Supported OS: Linux and Windows @@ -44,21 +38,12 @@ Toxygen is powerful cross-platform [Tox](https://tox.chat/) client written in pu - File resuming - Read receipts -### Downloads -[Releases](https://github.com/toxygen-project/toxygen/releases) - -[Download last stable version](https://github.com/toxygen-project/toxygen/archive/master.zip) - -[Download develop version](https://github.com/toxygen-project/toxygen/archive/develop.zip) - ### Screenshots *Toxygen on Ubuntu and Windows* ![Ubuntu](/docs/ubuntu.png) ![Windows](/docs/windows.png) -### Docs -[Check /docs/ for more info](/docs/) +## Forked -Also visit [pythonhosted.org/Toxygen/](http://pythonhosted.org/Toxygen/) - -[Wiki](https://wiki.tox.chat/clients/toxygen) +This hard-forked from https://github.com/toxygen-project/toxygen +```next_gen``` branch. diff --git a/docs/compile.md b/docs/compile.md index 995dc35..b4f6810 100644 --- a/docs/compile.md +++ b/docs/compile.md @@ -2,10 +2,18 @@ You can compile Toxygen using [PyInstaller](http://www.pyinstaller.org/) -Install PyInstaller: -``pip3 install pyinstaller`` +Use Dockerfile and build script from `build` directory: -Compile Toxygen: -``pyinstaller --windowed --icon images/icon.ico main.py`` +1. Build image: +``` +docker build -t toxygen . +``` -Don't forget to copy /images/, /sounds/, /translations/, /styles/, /smileys/, /stickers/, /plugins/ (and /libs/libtox.dll, /libs/libsodium.a on Windows) to /dist/main/ +2. Run container: +``` +docker run -it toxygen bash +``` + +3. Execute `build.sh` script: + +```./build.sh``` diff --git a/docs/contact.md b/docs/contact.md index c66da1c..9f80595 100644 --- a/docs/contact.md +++ b/docs/contact.md @@ -2,4 +2,4 @@ 1) Using GitHub - open issue -2) Use Toxygen Tox Group - add bot kalina@toxme.io (or 12EDB939AA529641CE53830B518D6EB30241868EE0E5023C46A372363CAEC91C2C948AEFE4EB) +2) Use Toxygen Tox Group (NGC) - ID: 59D68B2709E81A679CF91416CB0E3692851C6CFCABEFF98B7131E3805A6D75FA diff --git a/setup.py b/setup.py index 746163e..fb80363 100644 --- a/setup.py +++ b/setup.py @@ -2,15 +2,17 @@ from setuptools import setup from setuptools.command.install import install from platform import system from subprocess import call -from toxygen.util import program_version +import main import sys +import os +from utils.util import curr_directory, join_path -version = program_version + '.0' +version = main.__version__ + '.0' if system() == 'Windows': - MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python'] + MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon'] else: MODULES = [] try: @@ -29,6 +31,19 @@ else: import cv2 except ImportError: MODULES.append('opencv-python') + try: + import pydenticon + except ImportError: + MODULES.append('pydenticon') + + +def get_packages(): + directory = join_path(curr_directory(__file__), 'toxygen') + for root, dirs, files in os.walk(directory): + packages = map(lambda d: 'toxygen.' + d, dirs) + packages = ['toxygen'] + list(packages) + + return packages class InstallScript(install): @@ -62,7 +77,7 @@ setup(name='Toxygen', author='Ingvar', maintainer='Ingvar', license='GPL3', - packages=['toxygen', 'toxygen.plugins', 'toxygen.styles'], + packages=get_packages(), install_requires=MODULES, include_package_data=True, classifiers=[ @@ -71,8 +86,8 @@ setup(name='Toxygen', 'Programming Language :: Python :: 3.6', ], entry_points={ - 'console_scripts': ['toxygen=toxygen.main:main'], + 'console_scripts': ['toxygen=toxygen.main:main'] }, cmdclass={ - 'install': InstallScript, + 'install': InstallScript }) diff --git a/tests/tests.py b/tests/tests.py index bbb877c..e3c9b6b 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,162 +1,18 @@ -from toxygen.profile import * -from toxygen.tox_dns import tox_dns -from toxygen.history import History -from toxygen.smileys import SmileyLoader -from toxygen.messages import * -import toxygen.toxes as encr -import toxygen.util as util -import time +from toxygen.middleware.tox_factory import * +# TODO: add new tests + class TestTox: def test_creation(self): - name = b'Toxygen User' - status_message = b'Toxing on Toxygen' + name = 'Toxygen User' + status_message = 'Toxing on Toxygen' tox = tox_factory() tox.self_set_name(name) tox.self_set_status_message(status_message) data = tox.get_savedata() del tox tox = tox_factory(data) - assert tox.self_get_name() == str(name, 'utf-8') - assert tox.self_get_status_message() == str(status_message, 'utf-8') - - -class TestProfileHelper: - - def test_creation(self): - file_name, path = 'test.tox', os.path.dirname(os.path.realpath(__file__)) + '/' - data = b'test' - with open(path + file_name, 'wb') as fl: - fl.write(data) - ph = ProfileHelper(path, file_name[:4]) - assert ProfileHelper.get_path() == path - assert ph.open_profile() == data - assert os.path.exists(path + 'avatars/') - - -class TestEncryption: - - def test_encr_decr(self): - tox = tox_factory() - data = tox.get_savedata() - lib = encr.ToxES() - for password in ('easypassword', 'njvnFjfn7vaGGV6', 'toxygen'): - lib.set_password(password) - copy_data = data[:] - new_data = lib.pass_encrypt(data) - assert lib.is_data_encrypted(new_data) - new_data = lib.pass_decrypt(new_data) - assert copy_data == new_data - - -class TestSmileys: - - def test_loading(self): - settings = {'smiley_pack': 'default', 'smileys': True} - sm = SmileyLoader(settings) - assert sm.get_smileys_path() is not None - l = sm.get_packs_list() - assert len(l) == 4 - - -def create_singletons(): - folder = util.curr_directory() + '/abc' - Settings._instance = Settings.get_default_settings() - if not os.path.exists(folder): - os.makedirs(folder) - ProfileHelper(folder, 'test') - - -def create_friend(name, status_message, number, tox_id): - friend = Friend(None, number, name, status_message, None, tox_id) - return friend - - -def create_random_friend(): - name, status_message, number = 'Friend', 'I am friend!', 0 - tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - friend = create_friend(name, status_message, number, tox_id) - return friend - - -class TestFriend: - - def test_friend_creation(self): - create_singletons() - name, status_message, number = 'Friend', 'I am friend!', 0 - tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - friend = create_friend(name, status_message, number, tox_id) - assert friend.name == name - assert friend.tox_id == tox_id - assert friend.status_message == status_message - assert friend.number == number - - def test_friend_corr(self): - create_singletons() - friend = create_random_friend() - t = time.time() - friend.append_message(InfoMessage('Info message', t)) - friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t + 0.001, 0)) - friend.append_message(TextMessage('Hello!', MESSAGE_OWNER['FRIEND'], t + 0.002, 0)) - assert friend.get_last_message_text() == 'Hello! It is test!' - assert len(friend.get_corr()) == 3 - assert len(friend.get_corr_for_saving()) == 2 - friend.append_message(TextMessage('Not sent', MESSAGE_OWNER['NOT_SENT'], t + 0.002, 0)) - arr = friend.get_unsent_messages_for_saving() - assert len(arr) == 1 - assert arr[0][0] == 'Not sent' - tm = TransferMessage(MESSAGE_OWNER['FRIEND'], - time.time(), - TOX_FILE_TRANSFER_STATE['RUNNING'], - 100, 'file_name', friend.number, 0) - friend.append_message(tm) - friend.clear_corr() - assert len(friend.get_corr()) == 1 - assert len(friend.get_corr_for_saving()) == 0 - friend.append_message(TextMessage('Hello! It is test!', MESSAGE_OWNER['ME'], t, 0)) - assert len(friend.get_corr()) == 2 - assert len(friend.get_corr_for_saving()) == 1 - - def test_history_search(self): - create_singletons() - friend = create_random_friend() - message = 'Hello! It is test!' - friend.append_message(TextMessage(message, MESSAGE_OWNER['ME'], time.time(), 0)) - last_message = friend.get_last_message_text() - assert last_message == message - result = friend.search_string('e[m|s]') - assert result is not None - result = friend.search_string('tox') - assert result is None - - -class TestHistory: - - def test_history(self): - create_singletons() - db_name = 'my_name' - name, status_message, number = 'Friend', 'I am friend!', 0 - tox_id = '76518406F6A9F2217E8DC487CC783C25CC16A15EB36FF32E335A235342C48A39218F515C39A6' - friend = create_friend(name, status_message, number, tox_id) - history = History(db_name) - history.add_friend_to_db(friend.tox_id) - assert history.friend_exists_in_db(friend.tox_id) - text_message = 'Test!' - t = time.time() - friend.append_message(TextMessage(text_message, MESSAGE_OWNER['ME'], t, 0)) - messages = friend.get_corr_for_saving() - history.save_messages_to_db(friend.tox_id, messages) - getter = history.messages_getter(friend.tox_id) - messages = getter.get_all() - assert len(messages) == 1 - assert messages[0][0] == text_message - assert messages[0][1] == MESSAGE_OWNER['ME'] - assert messages[0][-1] == 0 - history.delete_message(friend.tox_id, t) - getter = history.messages_getter(friend.tox_id) - messages = getter.get_all() - assert len(messages) == 0 - history.delete_friend_from_db(friend.tox_id) - assert not history.friend_exists_in_db(friend.tox_id) + assert tox.self_get_name() == name + assert tox.self_get_status_message() == status_message diff --git a/toxygen/main.py b/toxygen/main.py index d630bb6..eca3ac3 100644 --- a/toxygen/main.py +++ b/toxygen/main.py @@ -1,485 +1,49 @@ -import sys -from loginscreen import LoginScreen -import profile -from settings import * -from PyQt5 import QtCore, QtGui, QtWidgets -from bootstrap import generate_nodes, download_nodes_list -from mainscreen import MainWindow -from callbacks import init_callbacks, stop, start -from util import curr_directory, program_version, remove -import styles.style # reqired for styles loading -import platform -import toxes -from passwordscreen import PasswordScreen, UnlockAppScreen, SetProfilePasswordScreen -from plugin_support import PluginLoader -import updater +import app +from user_data.settings import * +import utils.util as util +import argparse -class Toxygen: - - def __init__(self, path_or_uri=None): - super(Toxygen, self).__init__() - self.tox = self.ms = self.init = self.app = self.tray = self.mainloop = self.avloop = None - if path_or_uri is None: - self.uri = self.path = None - elif path_or_uri.startswith('tox:'): - self.path = None - self.uri = path_or_uri[4:] - else: - self.path = path_or_uri - self.uri = None - - def enter_pass(self, data): - """ - Show password screen - """ - tmp = [data] - p = PasswordScreen(toxes.ToxES.get_instance(), tmp) - p.show() - self.app.lastWindowClosed.connect(self.app.quit) - self.app.exec_() - if tmp[0] == data: - raise SystemExit() - else: - return tmp[0] - - def main(self): - """ - Main function of app. loads login screen if needed and starts main screen - """ - app = QtWidgets.QApplication(sys.argv) - app.setWindowIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) - self.app = app - - if platform.system() == 'Linux': - QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads) - - with open(curr_directory() + '/styles/dark_style.qss') as fl: - style = fl.read() - app.setStyleSheet(style) - - encrypt_save = toxes.ToxES() - - if self.path is not None: - path = os.path.dirname(self.path) + '/' - name = os.path.basename(self.path)[:-4] - data = ProfileHelper(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) - else: - auto_profile = Settings.get_auto_profile() - if not auto_profile[0]: - # show login screen if default profile not found - current_locale = QtCore.QLocale() - curr_lang = current_locale.languageToString(current_locale.language()) - langs = Settings.supported_languages() - if curr_lang in langs: - lang_path = langs[curr_lang] - translator = QtCore.QTranslator() - translator.load(curr_directory() + '/translations/' + lang_path) - app.installTranslator(translator) - app.translator = translator - ls = LoginScreen() - ls.setWindowIconText("Toxygen") - profiles = ProfileHelper.find_profiles() - ls.update_select(map(lambda x: x[1], profiles)) - _login = self.Login(profiles) - ls.update_on_close(_login.login_screen_close) - ls.show() - app.exec_() - if not _login.t: - return - elif _login.t == 1: # create new profile - _login.name = _login.name.strip() - name = _login.name if _login.name else 'toxygen_user' - pr = map(lambda x: x[1], ProfileHelper.find_profiles()) - if name in list(pr): - msgBox = QtWidgets.QMessageBox() - msgBox.setWindowTitle( - QtWidgets.QApplication.translate("MainWindow", "Error")) - text = (QtWidgets.QApplication.translate("MainWindow", - 'Profile with this name already exists')) - msgBox.setText(text) - msgBox.exec_() - return - self.tox = profile.tox_factory() - self.tox.self_set_name(bytes(_login.name, 'utf-8') if _login.name else b'Toxygen User') - self.tox.self_set_status_message(b'Toxing on Toxygen') - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to set profile password?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - set_pass = SetProfilePasswordScreen(encrypt_save) - set_pass.show() - self.app.lastWindowClosed.connect(self.app.quit) - self.app.exec_() - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", - 'Do you want to save profile in default folder? If no, profile will be saved in program folder'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - path = Settings.get_default_path() - else: - path = curr_directory() + '/' - try: - ProfileHelper(path, name).save_profile(self.tox.get_savedata()) - except Exception as ex: - print(str(ex)) - log('Profile creation exception: ' + str(ex)) - msgBox = QtWidgets.QMessageBox() - msgBox.setText(QtWidgets.QApplication.translate("login", - 'Profile saving error! Does Toxygen have permission to write to this directory?')) - msgBox.exec_() - return - path = Settings.get_default_path() - settings = Settings(name) - if curr_lang in langs: - settings['language'] = curr_lang - settings.save() - else: # load existing profile - path, name = _login.get_data() - if _login.default: - Settings.set_auto_profile(path, name) - data = ProfileHelper(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) - else: - path, name = auto_profile - data = ProfileHelper(path, name).open_profile() - if encrypt_save.is_data_encrypted(data): - data = self.enter_pass(data) - settings = Settings(name) - self.tox = profile.tox_factory(data, settings) - - if Settings.is_active_profile(path, name): # profile is in use - reply = QtWidgets.QMessageBox.question(None, - 'Profile {}'.format(name), - QtWidgets.QApplication.translate("login", 'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply != QtWidgets.QMessageBox.Yes: - return - else: - settings.set_active_profile() - - # application color scheme - for theme in settings.built_in_themes().keys(): - if settings['theme'] == theme: - with open(curr_directory() + settings.built_in_themes()[theme]) as fl: - style = fl.read() - app.setStyleSheet(style) - - lang = Settings.supported_languages()[settings['language']] - translator = QtCore.QTranslator() - translator.load(curr_directory() + '/translations/' + lang) - app.installTranslator(translator) - app.translator = translator - - # tray icon - self.tray = QtWidgets.QSystemTrayIcon(QtGui.QIcon(curr_directory() + '/images/icon.png')) - self.tray.setObjectName('tray') - - self.ms = MainWindow(self.tox, self.reset, self.tray) - app.aboutToQuit.connect(self.ms.close_window) - - class Menu(QtWidgets.QMenu): - - def newStatus(self, status): - if not Settings.get_instance().locked: - profile.Profile.get_instance().set_status(status) - self.aboutToShowHandler() - self.hide() - - def aboutToShowHandler(self): - status = profile.Profile.get_instance().status - act = self.act - if status is None or Settings.get_instance().locked: - self.actions()[1].setVisible(False) - else: - self.actions()[1].setVisible(True) - act.actions()[0].setChecked(False) - act.actions()[1].setChecked(False) - act.actions()[2].setChecked(False) - act.actions()[status].setChecked(True) - self.actions()[2].setVisible(not Settings.get_instance().locked) - - def languageChange(self, *args, **kwargs): - self.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) - self.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Set status')) - self.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Exit')) - self.act.actions()[0].setText(QtWidgets.QApplication.translate('tray', 'Online')) - self.act.actions()[1].setText(QtWidgets.QApplication.translate('tray', 'Away')) - self.act.actions()[2].setText(QtWidgets.QApplication.translate('tray', 'Busy')) - - m = Menu() - show = m.addAction(QtWidgets.QApplication.translate('tray', 'Open Toxygen')) - sub = m.addMenu(QtWidgets.QApplication.translate('tray', 'Set status')) - onl = sub.addAction(QtWidgets.QApplication.translate('tray', 'Online')) - away = sub.addAction(QtWidgets.QApplication.translate('tray', 'Away')) - busy = sub.addAction(QtWidgets.QApplication.translate('tray', 'Busy')) - onl.setCheckable(True) - away.setCheckable(True) - busy.setCheckable(True) - m.act = sub - exit = m.addAction(QtWidgets.QApplication.translate('tray', 'Exit')) - - def show_window(): - s = Settings.get_instance() - - def show(): - if not self.ms.isActiveWindow(): - self.ms.setWindowState(self.ms.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive) - self.ms.activateWindow() - self.ms.show() - if not s.locked: - show() - else: - def correct_pass(): - show() - s.locked = False - s.unlockScreen = False - if not s.unlockScreen: - s.unlockScreen = True - self.p = UnlockAppScreen(toxes.ToxES.get_instance(), correct_pass) - self.p.show() - - def tray_activated(reason): - if reason == QtWidgets.QSystemTrayIcon.DoubleClick: - show_window() - - def close_app(): - if not Settings.get_instance().locked: - settings.closing = True - self.ms.close() - - show.triggered.connect(show_window) - exit.triggered.connect(close_app) - m.aboutToShow.connect(lambda: m.aboutToShowHandler()) - onl.triggered.connect(lambda: m.newStatus(0)) - away.triggered.connect(lambda: m.newStatus(1)) - busy.triggered.connect(lambda: m.newStatus(2)) - - self.tray.setContextMenu(m) - self.tray.show() - self.tray.activated.connect(tray_activated) - - self.ms.show() - - updating = False - if settings['update'] and updater.updater_available() and updater.connection_available(): # auto update - version = updater.check_for_updates() - if version is not None: - if settings['update'] == 2: - updater.download(version) - updating = True - else: - reply = QtWidgets.QMessageBox.question(None, - 'Toxygen', - QtWidgets.QApplication.translate("login", - 'Update for Toxygen was found. Download and install it?'), - QtWidgets.QMessageBox.Yes, - QtWidgets.QMessageBox.No) - if reply == QtWidgets.QMessageBox.Yes: - updater.download(version) - updating = True - - if updating: - data = self.tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - settings.close() - del self.tox - return - - plugin_helper = PluginLoader(self.tox, settings) # plugin support - plugin_helper.load() - - start() - # init thread - self.init = self.InitThread(self.tox, self.ms, self.tray) - self.init.start() - - # starting threads for tox iterate and toxav iterate - self.mainloop = self.ToxIterateThread(self.tox) - self.mainloop.start() - self.avloop = self.ToxAVIterateThread(self.tox.AV) - self.avloop.start() - - if self.uri is not None: - self.ms.add_contact(self.uri) - - app.lastWindowClosed.connect(app.quit) - app.exec_() - - self.init.stop = True - self.mainloop.stop = True - self.avloop.stop = True - plugin_helper.stop() - stop() - self.mainloop.wait() - self.init.wait() - self.avloop.wait() - self.tray.hide() - data = self.tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - settings.close() - del self.tox - - def reset(self): - """ - Create new tox instance (new network settings) - :return: tox instance - """ - self.mainloop.stop = True - self.init.stop = True - self.avloop.stop = True - self.mainloop.wait() - self.init.wait() - self.avloop.wait() - data = self.tox.get_savedata() - ProfileHelper.get_instance().save_profile(data) - del self.tox - # create new tox instance - self.tox = profile.tox_factory(data, Settings.get_instance()) - # init thread - self.init = self.InitThread(self.tox, self.ms, self.tray) - self.init.start() - - # starting threads for tox iterate and toxav iterate - self.mainloop = self.ToxIterateThread(self.tox) - self.mainloop.start() - - self.avloop = self.ToxAVIterateThread(self.tox.AV) - self.avloop.start() - - plugin_helper = PluginLoader.get_instance() - plugin_helper.set_tox(self.tox) - - return self.tox - - # ----------------------------------------------------------------------------------------------------------------- - # Inner classes - # ----------------------------------------------------------------------------------------------------------------- - - class InitThread(QtCore.QThread): - - def __init__(self, tox, ms, tray): - QtCore.QThread.__init__(self) - self.tox, self.ms, self.tray = tox, ms, tray - self.stop = False - - def run(self): - # initializing callbacks - init_callbacks(self.tox, self.ms, self.tray) - # download list of nodes if needed - download_nodes_list() - # bootstrap - try: - for data in generate_nodes(): - if self.stop: - return - self.tox.bootstrap(*data) - self.tox.add_tcp_relay(*data) - except: - pass - for _ in range(10): - if self.stop: - return - self.msleep(1000) - while not self.tox.self_get_connection_status(): - try: - for data in generate_nodes(): - if self.stop: - return - self.tox.bootstrap(*data) - self.tox.add_tcp_relay(*data) - except: - pass - finally: - self.msleep(5000) - - class ToxIterateThread(QtCore.QThread): - - def __init__(self, tox): - QtCore.QThread.__init__(self) - self.tox = tox - self.stop = False - - def run(self): - while not self.stop: - self.tox.iterate() - self.msleep(self.tox.iteration_interval()) - - class ToxAVIterateThread(QtCore.QThread): - - def __init__(self, toxav): - QtCore.QThread.__init__(self) - self.toxav = toxav - self.stop = False - - def run(self): - while not self.stop: - self.toxav.iterate() - self.msleep(self.toxav.iteration_interval()) - - class Login: - - def __init__(self, arr): - self.arr = arr - - def login_screen_close(self, t, number=-1, default=False, name=None): - """ Function which processes data from login screen - :param t: 0 - window was closed, 1 - new profile was created, 2 - profile loaded - :param number: num of chosen profile in list (-1 by default) - :param default: was or not chosen profile marked as default - :param name: name of new profile - """ - self.t = t - self.num = number - self.default = default - self.name = name - - def get_data(self): - return self.arr[self.num] +__maintainer__ = 'Ingvar' +__version__ = '0.5.0' def clean(): - """Removes all windows libs from libs folder""" - d = curr_directory() + '/libs/' - remove(d) + """Removes libs folder""" + directory = util.get_libs_directory() + util.remove(directory) def reset(): Settings.reset_auto_profile() +def print_toxygen_version(): + print('Toxygen v' + __version__) + + def main(): - if len(sys.argv) == 1: - toxygen = Toxygen() - else: # started with argument(s) - arg = sys.argv[1] - if arg == '--version': - print('Toxygen v' + program_version) - return - elif arg == '--help': - print('Usage:\ntoxygen path_to_profile\ntoxygen tox_id\ntoxygen --version\ntoxygen --reset') - return - elif arg == '--clean': - clean() - return - elif arg == '--reset': - reset() - return - else: - toxygen = Toxygen(arg) + parser = argparse.ArgumentParser() + parser.add_argument('--version', action='store_true', help='Prints Toxygen version') + parser.add_argument('--clean', action='store_true', help='Delete toxcore libs from libs folder') + parser.add_argument('--reset', action='store_true', help='Reset default profile') + parser.add_argument('--uri', help='Add specified Tox ID to friends') + parser.add_argument('profile', nargs='?', default=None, help='Path to Tox profile') + args = parser.parse_args() + + if args.version: + print_toxygen_version() + return + + if args.clean: + clean() + return + + if args.reset: + reset() + return + + toxygen = app.App(__version__, args.profile, args.uri) toxygen.main() diff --git a/toxygen/smileys/default/003020E3.png b/toxygen/smileys/default/003020E3.png index a196fa1dd7ae2ace89ef20fd440768260a14720e..e64ea3aedb9925cdd6b634951ef6a9fe834bff24 100644 GIT binary patch delta 1273 zcmbQqwUlduWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;JxpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZc65gv)7+}{PFwG)n^}~=N;Q|`N{D+uQHaNI&$mf_RCMAW*>U;?rZh>vk^1)ef#-4 zZT{h~DZBpv{~s}JcgV!8-+uf`nz1XWcjKcspUM{RFI{vXplj{sleZ3Bc@WjVasTB9 z{_QKH>-*PVy!ZUa&)=6Hyo{Z&9`3q-&SfC~ikAfWfr5qs33xtx_UyUW^Cvv6kG3%| zFfb;0ySwP@x?8xKfq{X&#M9T6{TVwSmnyeTL+Mfm2B!L|kcg6?#Bzm#qWrYXoK%I9 z%7RpdirfMQ28-UQ^`YLI<{0qYc`ahubn&`OyX528Rh!Oyyu9hTv&5u54voD!t5vt^ zym#|I|F_(v>?s@{>fQD(m)~@@ zb^5OlPjcBdMZddnolh@pYowyz^D3T+67QAnJn*0B+Mv}TzR5L2=lO)YicdG5yJL86 zyYTjf+jx)iY8`x9@>$uYP5gpkSg3vLF%vQ4JyKn}PqYlx45PN_4F~cXN?0fqD z67S6z9|U?d%s=B1(!$yj^ZzdQ4u)0r4lKW|jJ7H{U#M93+sPw>U&Ue=L+0nZCT`!ql(!(&xQ+SlH*zd*Q^h??BtwQI%gU0 zldk#@(MAWco(D|ZS2qTTtkRpI>E`w|^{=B_!0H+6Bz_*A%>6_=!PQfEQ@YZrSzozi zY#vvrSWO7lJFI^AS5y1LwLJ6RaQHA=atCcvu@z32Kk-`nCxecA!Y}(2iPNvlCM>Qj z&}n{OH{EUdw15jseg#Z2)V?YEfQe5teD(@842w16HJLXyR;34y8os&f? zo}B$ZiOK4j!a~9L1)WWgRN~%FY!Ms6}b^zf(t!uU+CuAIBg60Qsk3~in+ zjv*44V~<@GYYGskXT6ZTy&-8)lHhcqfQ_I3+n+N1%(P>^*$4kg?wXNIQwpaA9Z*@{ zp?NXwbL%l}NA=TJt2RHKnkaDDNM~|*qi;*HbyIk&x6S>pwsE|SH(MPwDoR~c?zy+m zxPD-w%JR>u4^0bIxBq_p^U>B1=du@nKl!SB0pGrr3vc~p49igMkF!gW=U`x9P%Uwd zC`m~yNwrEYN=+U0xfmEcUHx3vIVCg!0NiRz-v9sr literal 1305 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y^bcPuEtJIF0Ll7 z28M>NMvhKS&d%npE{-lv#>S4WrZBypdBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{KA7lTvQ}>eK9=4lT!mb#(P2>J_(QliF`C7**r6H%ea?Z0SbLPY;Z*txB zlKYXt=L_6US5o9;+E!?*R_qsa*>#8KSYLlzRMXeTiJ`%U|GpI&Pk4F%(w8%eU&^?> z_xOMP_U(w+OOA)SCG&VQwsO7GD>0e2`o%tpo!b>;WACeTpPA$`*J4JpSO)L^=$|=e j64InTu$sZZAYL$MSD+0814BiCPl#*Mii?TM&c`o46T9$a^t@wH zvkyhg*cUcsSIESzLA@ITy4L!)uk>wM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umBGpOwg3PB6Ey3|&$Eje7#PG$g8V=pVL$?&&z?Pd?)Cf$kL#mt3=9m6N#5=*GILlQ z*%=rZ*h@U?eO=j~vGZ}MGM)*Yvzvi|sjey{q9iD>T%n*SKP@vSRiUJ^AXT9vw}64c zqIYVj@3q?sJa>w_wC2e% zU$@~p*U6=~!it2{t|!mfa$=p9^rw@%Gy+7w9@@4d^PzE0c|_`&?vkF*?>x6X+{SlY zR8R3|$;rhwj$$7)lhW$6>U}TGNU1dC3BM-#^oxZ3P3E{w{XNVly3OL&`>=e^Wl~st zp}{UiMe7t3)ARcCOqvc_9E$hYf_ZrkKDK=C&6H^CptE__5xK1j2e&!C>Yu~@;NLXO zhpZ{T+lsV`PU$dwNpd;xcI7R_9c`lDZgx&65&e7q;LXm)M-MJb>XlqkUdLI#*5c9R z>8lU@i@nm~8@}WG1pd0P#aX#|QY<^9oijwX>85Tp1(8^@f|wpX1*Yb_v=i&--D# z*p>OeQ^Z>L2PPA4PB=BKL@3;D!folF4E1Z=AAG4l!Ex-Jp1|=Pg#pn0?Q7|A zUd692>pye3GvnJEd$#)AwdnTMc)N8X!z8{V(>6|)si-(;9Bm|TRrz&)z=S>-0jZ;` ze1RT5582hfO_BYTCa1W~XKDL~sY^1H-8V1qKM_CW?~VMIt9i>`Z9g8V_qTGwlGl@; zy)pAYT0i~kqPP98=lO2@zm4x`&NKE>W$BR4twp&E3=FCz zt`Q|Ei6yC4$wjG&C8-QX21Z7@2IjhkCLx9fR>r1QCKlQT237_Jc`WNeISrv9H$Npa btrE8e`AG5i6BX6D7#KWV{an^LB{Ts5tULT9 literal 1151 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y^gM~mM(^FCaxx~ z28M>NMoxwXmaZnoZmx#rhNjL&7BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{eI2^SohKD<;N3!7L zVS^K_5xx4w*I6#_c=E9E$N%^8Y&jNH=MpzC#e6d0DLE*=$4p>S`p@s}4UF|1k{`aC zGm8INwv$_fr};mNh`^-uFS(K5<~Qv*a)Qan_x#Bd+zKCFVdQ&MBb@0Dx6{PXGV_ diff --git a/toxygen/smileys/default/003220E3.png b/toxygen/smileys/default/003220E3.png index 645c904ee6a1e91a215987e2a1a61dbf5318f115..8c447462445262217ddfc7cf5ffa600e12ecac8f 100644 GIT binary patch delta 1273 zcmeC?TFNy+vYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{wM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umBG6#g}bjl|NHm-iHN z*GJnJ7#J9nyxmg@9i&{$6d|3w7YM`#M;8Iv!@%J)Qhv(^K)5jiuCr|xg8Go?r3N(Ji06G z^+ex=(R)=_Z)7tQ>%F=ysZWqCbfc$Bd)}n4a-r8(^!483eAb;(_xtYlfckZ+N(ZDr zJKC4r6_0y$dGdD26M1(M*UL`w-WpdVWM!VzwdKSzE!lr5u_7zFDiouyY$@QoDH}BN zwG#8=f8SJUSFH^$Z1wW6yZed5{{d@B*NmC-ohF-zOUCgYo?Gf_q5MoW+QEC<%R+|A zi^qOH?R;>zoS{NQ>IA=JXV->?17Gjg?``1r5EW9qrxwi1zJ8z~$We1(Tw>oW~ zHL*myud=V1*Kpg~ms4Khw)LTvA2f{_E_4gB{Wi{Ke=HRik*@A^Y3f&d>2y`Lrsi`C zc3zO|-=Q|;(H8%>bxgm@1FUm)9jcvG^I@~p=4;=UH8$?P*y5IW)5!PQnY7gRvlDKn znb)uNSeIoQ%29H#Bj!Pwwuc7SRk1TXix>QgsGp#^BGgCP?dOLlOh36Ks*8;jXRTVe zud_LQ8~eq^H4eEIGJE%F)M`~o$$k66XyKb8wq%pa+#^%@pL}z(V|XoS@mJoccjLi{ zGRcoc*zEtTvz^=5(&L=vqdIrF(vPsPR@-sH8 zd|o&y{69DA%M*+X9UTm8lpiKMJb2j0`qS*nmK&SWtJLI-=5aj9SpQP_llbMjHQT>j zZL+?4`?1dSk2W5czIqmi-S$5^{mbL<1<{W#9pztXU~MdO^S|m#@y7RE74kbiXfrS{ zGhDcnFJr-Us)Z`%Gda*r;kEbj0NQb5NijKnn)r)o=*E;aISpS2+aPNc*40|P) zYG)i(y`VOC-ObHECcT(^u)t*Ahm{*nmZY5*uhG=056kwGlFrfG%+ zroZkze)6Dl`TvKWl{P24KZt(l(_ZwcbJetjEBhAi3gly8X;F`KRZFqm!N9RqPJyB7ei-Ez@)z4*}Q$iB}xg<$q literal 1291 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0L+s&d$b;Caxx~ z28M>NMvl%-mQH2{j%Kc=u9mK5PB6WmdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{IPbuZA1`{QsV>H({E}#iR*U6YMmn)z|;`n)I@2gWa_H|NoLsbbQ`uoD!vNtkLzs zV9)}gN@ywsiN{l1#?8`Y03o7X6)Gnf5vaR!nXJMRO&cz5PrX^s^+`xsL)A`QS-;@`cSMk1V;T>qkcmC(BNG8;g#!#ZGCQvZ Ty$retDxN%D{an^LB{Ts5vCO|` diff --git a/toxygen/smileys/default/003320E3.png b/toxygen/smileys/default/003320E3.png index 1674b69448e363a91070ca3b416df585668c0342..ab5b4bcc10d38ae5fe0a3caaf3ccde42d7a729c4 100644 GIT binary patch delta 1264 zcmZqVn!`0gvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{wM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umBGLomHThLeEan$dfu@|Z@`R<;aKWJ)58r$a zo3iWw|Nry$UkjPIHE!C@x%)0Zc>OV`cVooFEx&&M3Fun;=*`F3JI_V*Z@B;RgMa(V z%7we{)xUW6tFW#3g+zoeHmSdeI0|SG2Nsu2XTo{mm=d)+eo_jri!sGgA8v_Fa zW0JSKi?+(nCmak64D2PIzOL-g*!j3rIktAH$TKi7)mMc?lmsP~D-;yvr)B1(DwI?f zq$*V87BDba^iB=+y;gtQfaA_<5!)snjwO#RCT@LQ9sBLa+=-tbTjl+}TGyG=B9Xwb zeE$9VCHZ?+U0qTx8>syI(JOQP#7VJxDk?tv?ml6bxBa$2MGqX4VM-lX<`Qsi@?z2zjsnc0_JxLetvj zSN!wUAADP;`H=PGx4r_cno~RsFOPCH|2=u@;Ep!YYd19&U!M9kzjw0`^Fhb-3o$Qx z++&2j3U8&ytz-IK9$=lb>rm~inkVU|o34FZ*4ViFVvAei%}Hw6X`45Fcixb*d3M-@ z)lt6nqD-+Hnz--ev9>G<$UQhYQ8YyUHUB1-tzF04LhWpJI_zUo^Koj_-EgZR@5sUY zZR{6$a}8_*<04-6J6!+JtnU86WMbrmQ1`oaLiB*MH{ag$i#o?nRZ|+tIByENvPZY9GVoDranIzJEqA>GZiTDso1CEYC95zf}Gto@swA`d8M$xv$zn(6RArhBk zk6jgN3Se-(xVTO{c97-{qn)aD>_TivUdqig+kGhppzNV}8ML`Oe1!wyINAZ=e zcUr1HRP3uz-81>rin>QxQfq#hU)rE$&wfDV=C)LM%o4jRt5%a_dYa3bwD)a=BH$)RpQoA-t1vNQBj?X Ofx*+&&t;ucLK6TK^hGcL literal 1281 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0S^-hNhNgF0Ll7 z28M>NMvg|#ZqCNWZcc_qmL`^_W-z^;dBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@qX|NkYOSkYBrd}NlkLC5Dp zgFSz~G$(#M$*{S=*S+;|!jxw>e$#9rZh$J#*BZ)7R<39zT6dO%X#G>k*(u%zp6{|>fITN*Bqxmy!l(7$GB|HF-h*8 zeY&Ur1y7hR;i&o1QnHefCGNnaA}*6;j|mHYY6>iGlwe?FW4NDx|CZSYn}eW2$nTu$sZZAYL$MSD+081H+sEpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZpyR+xBvhDA3g6_*7DOIzW$7wedzT4*PG8jikPu)@~+E|-+qambs%iYuGF~)-hKXF zweon##I2`pKVN&|7N&jYW*PG_FffRh1o?qH%K!oOV9N8^vuDq}o(uzL2Gmw7?^6SLLy3n63Z0|it^Jkb5a#bDhpB- zDsl@L7%Y0HhI((hqrh{gxJ!mnMe*U|ik_QGwNA^}>l(`UAJ?yx4CjsD*m^~`;Md>J z;lCc%?^o0Pc-gI;X_pQ9T6rsvg<7S*oYwmAc8TYu z9MhaNIji4bOx|e_Ao=xBSbfBfUozWx!@5crH5UHMUL=>7 zZLzMv@YM45cTWX3HTbIvUJTBhl63KOwpxYJdX4mmKN;o~7v&_SCvfZkvoTiu^PRJy zr-%2Kh4QrkF@c)Dw%kh?Rw}T(6!Otc;E?Mt)#YsAW!^aNs>-(&t-?C8LG@2$8h(2( zYZ0HYR`#jMlPUFk87_1SGX8en>Re#9@?N^S)1}Za>zk|9rMjBWe<(N>$giosCFw|c z+&Zq`<$=Oxu@CK>?){8B8XEmJTV4N z+%{UGO^X7TCa6hp-hKX;J-R9SaLGXr`I#{a@l59DPZYXvMNFAdAKHJu&ohxVD<$Aj zhsK&4{N9!9evNNd008Cr?{z7@jE?{IXAJs45TdP_8RjC2n8xc%EOl(=k=k zL!HUf{+6v+AlttE>dM*c9|>IgWaGi$$)1!wM}<``&fxpBGwptHU-drTXx1&ij|XH@ma){=0I^-s7&5*5%LI ze0z`R{A>OGjPFlQ*R%7udbP7Cqt5%1{E|ssEeqH5iZU=TlzX~3hDcnFJ$7BJ$w7eS zqFFlQQHE`TrB_3J{{PR{GoHg-5x=UTe%^kKA94aZr<n|Vi zd&7SvQ}LvXfBGqvT#4sJdnKhVHkQ8Q&(3reU+FwC_SRuzcVGGSGN#OXR=3TlIkil= zWpjR_TwnbuZ3VgLlchQg3=FCzt`Q|Ei6yC4$wjG&C8-QX21Z7@2IjhkCLx9fR>r1Q wCg$1(237_JGkosvLeij?YL%Oxl9^VCTgOYmbhe3#>Rb#Ap00i_>zopr0NG*)YybcN literal 1238 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y%sKJt|pGAF0Ll7 zhK7c&MvkUVrjD+LW@ctaZf=f_aJ`;+#U+V($*C}VGc!}Hpn9G0>a}t%N=+=uFAB-e z&w-_YfQ@=s<*Z=pL^s;J$-Lm@s|BN0Sdba5g4|h_J4>s&M8DzsG`P?@C8DPCLK0 z(B?M-j&St~myvd)+_Z}LQOhX<@GV*md7Hm31#3YplmJ~9ztRye@$_|&;&Q?xXm Pfl3-rS3j3^P6wM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umB!WQ93=E7(-tI0kb66bN85kJYOFVsD*`KlVajEibdh&pcfq|*6DkP#LD6w3j zpeR2rGbdG{q_QAYp(3}wfPulHcWP+hwA%_if1ZojGI=;PJ+?Ub=j-0OZ$D%^RbH3w zd23e3>B7-#qFnIn@8|Ge59h1tew@49^mNpeugk8sY?>5i`gYf6g1=uVk@N0@ z-QP~!Xo%VI`}3)m57uG~Mn+~Y9`{5zN7g%h_OldUd9R~{IA*gY z*{*hd6UeO|!M3FCv?arL{+UAm8ggVcmMboQ!`cwn!(lORm2A^y&#!jYf|J%Qs+YZE zeKA0$W?8#&UzLaDLQ&;?pNm3o?C*}+@RIAhFUMm13zBu$dI?^nelwX7Ucr2lzflx+OLjWHGlH2v4%m<{lS;|6AWM9 ztrKwn^B_dO?$um(t5{CS=~sBv`y#*HUf5J7b1!Uq-oE-SXOF+76Bs7(9l2)V$z}8B z;GJkAd8^9H`vWG}6*6R8$=~6kxRG1`>p|xeeV;bRD+W!wq*znTw8Pqc_457`_ox27 zkpFTwZ~3e3$7B6IS592=diJw7+wzW1|N1ucK=r4k-Qq!|rHf>u-cS0%{&Qp3szf1a za|Q;6W=|K#5Q)pN$Lhm{nj8dNFQy+@(~u;`BAg&5{A|yE)5yD~p$*TU$32+8%G7xl z*Ne3pwvA;mLbBIHojxo{m^3fdEAz$@`wgnB&PGz!>yA%9*y5A^`RFA5?x|bKEG&Pd zZ2nqaz0ZbE#bVw$@qJ9|o|M(w#Mij)dF-{KZc$dM#Mk^4i6M1wzpA>XDOaxp6)dVH zt`Q|Ei6yC4$wjG&C8-QX21Z7@2IjhkCLx9fR>r1QrY713237_JseDIwGB7Y8H00)| cWTsW()*upG%Q{g}or{6N)78&qol`;+0ADy$Qvd(} literal 1286 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y_QbSMka=4My@8V zhK7c&MwZSNMwYH-=57XNt_GH_7BIb@dBr7(dC93Tdowdrte|=g@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{b*Yo2AFlz1kJY4I$7FMs%H z(|w7iy|>RDym|b+P4bDd%c%#J@y(gwpyzPh+xV{J4axt_H~+lR;kjR*xT-NxEkI(y zH$sQ9H{L~a!-YCJq$i^@w{gQy8K;%wP O$>izk=d#Wzp$Py7?zme3 diff --git a/toxygen/smileys/default/003620E3.png b/toxygen/smileys/default/003620E3.png index 07f549a29a81544e15cbab51b8fd02c62dc10002..617d2ca6a00a4e5b1ed54badc4a0f63b405c5f73 100644 GIT binary patch delta 1305 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 E0GxBt$p8QV diff --git a/toxygen/smileys/default/003720E3.png b/toxygen/smileys/default/003720E3.png index 509362982198d3b86e03b1df9fea7359c873b59b..7fce639f524578cfcbe87f198363ecd6fb451556 100644 GIT binary patch delta 1230 zcmX@j`HORcWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H-xipAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZp#1v|D)#}n|Jum(c3ShW*;hFedf{IFG=%{M$Fh3HDllH7ay87o(Y?>Ys#)m$8J3j znYcA<;?^f`KbI~(6x6%1an+G)Pu>J{t=)Olqjr z#7lzwK>lYy0-n#FJ$vr;{0WciqiqZf42((M?k>_y30IadFfg!}c>21sKV#?PQf1j= zpJBqlz*Juq5>XPASgue|l%JNFld4csS&*twkz2sPV9`4@G8US1S99*u@(XoO>X#MEU$&GLcHvre;llm<^)JHzbcs#!-6ih7ZN`nWo3z!FPd^LG zoqlZ3_uFr;%}iXsEb*jXwond%VBAUHrXrGW#bb({<@;J=L=rPMkQp(BI$@YbvASGyAkg5e3nfgw@R} zx!N9loUy&tA);JBI?^|(KZ@nxt$Ky8(dU>8>QA~g8;#E(XffRJ9aKe%-*iP}Kd;gQu%Dyzid1p``UUN9FVMs{w1z`8G4m zcH{UclPhhM87e#1R$OsY-`y=vF%uUl8JuVmjCx>nRzRe8756+5<;A}j{ZCNdA!<_} z*7IlDN!CxI2G_!b+`E?^aM;hp{y%jATNSIzOy7>vr4vuETr61si*W{9k;xJl?h-@p76{535S@p%%hB)IWkAfZ!fv{(!;}I$*+P*AE!JjIk7-?_Vy<$qt7G> z7uDveG+b!BVN*7Vkzc=}Z<uue%;(8gClG;mi?mk=jW6Qo;u9W37cKuLz8jwEa z@aJf!{|sSsx-L9jt*64kz)r1QrUu#umIhV^1`{10u0qj~o1c=I cR*78)NW%k}NU@2E>Rb#Ap00i_>zopr0M$z(jsO4v literal 1227 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0Q{AmIjWlF0Ll7 zhK7c&MwVt4mX?;5=8mQ&Ce9|7PB6WmdBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{-<~dxAr-fhe4+{`HVPeJIWo`T4^PMc z$qn-zzZ!1%^Z$Fk-h^o?7n3GbO|a9PR$u?$YtqZA4R+J&|Nl!mq0?o2c$ISAMNJ;V z3ET!O$OGF;*P!6+g4^r!#t<@dasBKjby;Nql&j2k5rAKIHS|9fc2AmXaI zO^b)S?@q#xZ=cn}Om+VMdti3J&9~{{*X8ed!sYfKUdi^N>ApoSzd=~aj2&-nnDtC6 zVi#y@pZW0ew}0F2_4gk0D$hwwSg3Te;Loq*wt5yO$#fkFEl-0r)`fE$19S|3ZPWSf z?--ZFvh>LFlqakL6^!1eG%xfhIyOC9?l6ZfiGf9eVPjnW^~&~Z3ZUY})78&qol`;+ E0Ka{rI{*Lx diff --git a/toxygen/smileys/default/003820E3.png b/toxygen/smileys/default/003820E3.png index aea2c90eb7ea43e98dfdc72c7f9861d8ee315843..3ecb8fca96f837612b6332daa2dd1df729252f3a 100644 GIT binary patch delta 1300 zcmdnMwU29pWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;1rpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZc5SG^S57q`u6M3omZcW)?SF7cWl?yryI{dPF;L*+oh-bu0M~Oedx@+*DaebbZxtI z@zL9e8T)o!ezfWAgQU3!x14`?;M&u$DZ3_azwrP6|4BP8hD_Z0;PuB@doIOI+c|gN z<)Ge;zv_SgiI})0plj`;Hy>y3JQvZw;r`1H{_QI(7w*3I;@yv*zwf4~`GJCr0SS0Md-m+P*YhVlu8+1cFfcGCdAqw9n_sPoV_;xlFY)wsWq-!b z$EC`w6#aKP0|QfCRY*ihP<>*#LP1e}T4qkFLP=#oszOC>0Rw|Y@6=GoZMO|L?i6>) zIkK@_DwH{W>+4><>OXuzk9PFk{&!4&TCV`JvB$Fc_vEm))#JuF(rav<&9pvNY5vIU&b5a-4jr|O)vKF!`~;KT%+RZP zOAUKZYOLS!$ZON0Iqx1H-+j3I`^1gRl@sfwu21BAd{2$RsHpITa8HF}q(jHg`N!-N3m})cwzcr|UM6CE!uU z3!~=Fu4iL3>lJupdCM&2(kd&fYCWeE+1;6~JCs~q6DF`KZ#j@UOHo5ARNO|>&F#DE z|ATG?+7)XgekxCveqx;v(JS)XZTdtL>b&M3B9D0rBUU8mc=$eQ`GLtopJ z3{UPypV*6PmFG8nVPoH;T_%Wp5a__W@`!(z#=f=M5zJSsV{Kz8=_ zCo7}RBncPUnh80)aLJLgKH1PNZsYE~ELeU0H*u~PXB-`z!;ULTr8@m^Zcw!T;#=7A z!f2}8q3Mq9ynSi@^C#}t_`UP~i`Bg4FSkFBJomSB!iB${#ig^CKT7=jL*#^?Vd-bb zQwz^&v_Ie8QpXs4qP{EXi}4CI1_p)>PZ!4!iOaFau8K7U2(VsA-rkVJu2vT3$||z^ z-~Z+qWBrETnauxHc0QS;>Y!Pz$r$pdX~m?dy%(=kC~ooCefjOz%ET8b23OpZ<5+}^ z=AISzjuX+|B<7S;VVKjo@wPlqoQ&GWoXguPIAu<8c~vxSdwa$T$GwvlFDFYU}U6gV6JOu5@Kjz zWo&9?VySIlU}a!%qIk)36b-rgDVb@NxHat5lZj?vn5eAE#lYa{>gTe~DWM4fNYGE- literal 1328 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 Hl+XkKUp&tk diff --git a/toxygen/smileys/default/00A9.png b/toxygen/smileys/default/00A9.png index 5f524260c794f415e16d75f2e021ae430d7d1e36..57666e9f557766f039574b55384b9629b917de5f 100644 GIT binary patch delta 1101 zcmaFK`HN$MWIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUbEVRz6PT?PiGnyQe9lAy$Lg@U5|w9K4Tg_6pGRE3KA+yVv$i{7cBfzxgqaQt~L zV%x;SvE;GE#IM)C|GN3O{D9}R*A=(({s?Ju7#cLE{QCP@{BMWrx@gaF5B&!c4K#D#47KrimvVAqCd=DFF6~2{Fv6hS7BETlooK98J?cf z+kI#6$yaALTy$%=c}TVX-|f3*(<2r;IGAM_f0|GlU$HY?O+GPU_ffvuw-175_eFIm z?|hj#>C|iv{wKjHf(y7_wdM*=l`6jRZbQ*JWB;Dt>jnEuqAwS=b{AXP{?X{4$XcVd zdevO7n#eRwoF?Ttg+lH;FGwS2TLV@P@O=z_Y$$F}-S4Fa#{ zZ+76Cz~!{zYs#u-#R{A2xrzd2vPay)tT(#rHYLbT_@(=t<&Is7(jUf~W&wWAeyUsy zdnE)5Y(lMB#IH-$mMOKYTU0M=V|6i5rY5bW_+-_MmJ3~y_7yv|-rRS6yP>4@H_y)p z=~n~RzHet^d?%%NBzsHli6yb7*46jxTTaYcp4qxX$=5Yu0;}?t1IbZ}8d9O+XGD}2 z|6cSzL3xL%kF{If=_gD-wG+DZSdG&84jjBLYB;Jm|Ev|@+a66?3pfqpTH<}H&(Il*uj;7f41!Av5Mtnoqokf{bXe4ZN`s<9y_nb zZm*9xs`%uel?TH@=8dNQlNkBy4f?0)wEM-q)fc&-&bP@#Bvhe5Krc$(X33ii+gaRq zz42Q9VZ+g00hzb9wJ!hK-kh&n{6BX`@P6BOMf1*gx2(&bwfT0P_P#aw>zQt9Ow)_= zFuiiJsG@%Q1^NH)y4L8e=Uv3Wz;M;m#W6(V{MEjN^?6Q?0TB>un49h-g$jVz}!MzGWzV7`&O z_pj4L#tU118s~pf@&BZ@e)czUgW0cxmRDVjtKRz}Wcua+flHnyC${f7?N!Fsc}cR4 ztNv@shgb1(>h<-tY;Bwj6B=H9G!AWbwpwDYARD|Q{43vzPL|SIhwn>1kY_00FgNFU)LZ}63=9ma zC9V-ADTyViR>?)Fi6yBFMg~Skx(4RDh9)6~23E$VRwfqO1_l;Z1_pU7>w{5r{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO$y)JHMPR16lCa&g& zhOR~y&Xz{5#wN}dF6M@APG;sXy`Fi+C5d^-sW5vp!S;gnn&Z`L#&rIbo)6r(mNGN*qXu1tt{a;s#=Z z(={lK+bJM2PikI@tx}Pay&d0WK~@F^MoCW>$B>F!Z!T^0I^rPE@bGM*K#tFBtJbBl zBEKh_sI^*N**;;jg-V20N7MU-Gt|mjsU&o-k@!ZDRjDaGulkyYguV0dWAR!((O?C#`mnMZ9 z8MD$lM9+NCNehzRq;A5u;&m0j=e+0oYnILPO^tR@we6-s!H9T;nrlwoM-e7e24Nz2mCsHQ%h@$KeIB;qezZuWjfle);wR zXSsy9tKrW)hj??DS$2a8A5T|5mvv4FO#tPTvb_KR diff --git a/toxygen/smileys/default/00AE.png b/toxygen/smileys/default/00AE.png index ebc7dd9113fda178db5e174f7e62677a0fff75fb..98fb62aa4d2e887dc853405b1a1e1d86b8043df9 100644 GIT binary patch delta 1107 zcmaFI*}yqLvYv&3fnn<}^H&TE3@qu6zK#qG8~eHcB(gFvFeoH@1o<*BRH-pAG_)`< z{9<5WXn4WEP-?)y@G60U!D}OzLU@!6Xb!C6X z&c~(772WP?%)r1@UlkHj5|mi3P*9YgmYI{PP*Pcts!&m%Tfo3z(K|IXdfROSjz8(5 zu}wT2OP*Rx`+B|j*UhKp6Fz+?tYe8R1xQBvjd{rmMV!fU$3Ci(6X_un?-#@S8U z>dB{{h2>5^w&(lpx7TJSu3wgTQZI70O76Un2hnRLiN(uuZ~n2WI_z*qgHps1o!BzJ zO_dX`b|)^}%CNaL*rxt=+HUVNfeH%JTjeTGSgFr@r#v}a;zr&^r}^3zPZ#N`Kk``j z+raZ#Y>V_K&rKWwtX~gp>+tHUzF`(odB(WB=kxuJ+Xd0>kGaI3m+blxC#TH)BPA@< zdg?I~wZ(fLb!ngMD%#c0|DI3(p8GMrC$3pB=Y1sp=Q1fMi?`by>=2()&)PEM{|wfk zh7f_KwS3F8B$|#V-xFg>yz7{gV%lk&CCHN9d?nwK|G-~W;fIVV(ax5vmP<7lz9hLE z_!jV1ASR~x%gs)KU0wewKfG{pFZ}RQ@LcMZ!)h&IG9R0#uRgR-KkLvX(Ra#E=FSgw z-fCse%akLfc%*ns?ujL#r6u2DTIx?&m0jj4bnWUopup{x(RkX#IlyF<-j3j&o_F2< zntLCF6+|0;idGXpG5r9ym2@nZL5-v(1G~NaiblCeMX3ZHo!57l_nGQF*JQx--Z>)Ty!Yn6OAg?=uHHK z%Y-glM6aplJ7}Ggz3PtBrO1_USd-@4HufrTJd|(Ktn)6{c~iT-M!tW2&;zrA^Q;@v zH&-w6;Pss^|H(}MlT!ZYWiRU)U(dd>H1yWe^(M2QCZ(=cf9EaWzZ;JEj?P#8D9UAjo z?(QAgm>E_JZrtnJw$<2r^IF+l`?;bbM@`ZU+>1?`IiIcxIdy;KPxb@mFO+lq`{J(L zW?*1YEpd$~Nl7e8wMs5ZO)N=eFfuSQ(ls#GH8cq^G_W!@wX-rc);2J(GB8-M^0zsP ij@{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y@qBkE+)=S7Op0) z=7xr@Mi$N{ZkBGQE>0G%mM$*l7BIb@dBr7(dC93Tdowdrte|?$@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{aGnDjl)HeRg5$#J-#=Y9+Gj_X@PEtKq>cZ|09?d0Zg|b|cax6ztmMbNt9q}z z%Aff8T*D%bI=Lgi_v!2JJLq?Lvj1Gs5387WUpo8w14Fr#zzg|I`9-gHefr&(=-M_T zb>`E`du(;w+a7X$d%2x&qds@vW1W{f-gIyC*8-e;TnC2=e}*_(22E7}}du2gqn qO?jfJ`N!>7jqBI^XGq@1D#4({IA8TzQ$!%B1oCwCb6Mw<&;$T6E3{Ss diff --git a/toxygen/smileys/default/203C.png b/toxygen/smileys/default/203C.png index e1c30571cad887cdc93fa3a53a75fb5d8abdc302..36d8dcf715877e9eb47a210f5fa489b35ba8b22b 100644 GIT binary patch literal 1311 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 f{5Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUbE^|VFAA_fMgx~hx$dCf0Ax;7#cLE{QCP@{BKA6Ww)Z~56^b%s+Dbi zb?WGiRXf*i^G?5a{B4=^nGNfgC9c#9-7WGqF66=O9M#-?mSSNaeZQ}26J&JV&=J}8 zCg#aIU$gwS)@#gX#PsSv=9S-$P3mJ}yt$(Hg!4Abk45Kw><%3$>lXbLb#STK+pPlb zMTak|Of46DcVhVoB?r+f+_#mqOy?!;K77jZyp6%R-GbjsZeJ>F?k={B{iD%8k+nu^ z^{TmE$)`?gtl#-aYtzCx4}U(s`|xOW+8c+sl-}1c2dZ(=;&Rew$WX;DdFveU%Jm(?%1a&?PM`Kx4`y0d%jtOcFIFYf1)oBKk8&rbQ!&J^1mr;MXL@_S-LzrA0irW5*E~RKlI!ayq;uu zbWYo%ux_8?difLV33g1E|0^<^?Aq-l)7u-o_(x*-%MvRgF20o${U-UV`n7Z2+E^Jq zw|>`A9W%xC>w8%QtOTNq)g?cCXnLM;vgpN=v;UkJYacCO<6dETisw+^jpp_#vcINT zIF-!wTK-|f(M|!Gv-9kf>chV|@4r|s{q9PB|K>TzKPxPJJ^9%iGykL0zrGDtus(X_ zDErDbt4O}{`J8_ldjqCmP zrISanIM>YcTEN0XPs<;2TXMAUJYaXS`8Z*&tKw}Hzn`XGZ8q;+pKB)Z)u2IC=(=g( zMFy9N3VH&Xs}`_+P~zh;{`U2u@Q=Is`zm5NE_$Yx|FzrAo%qO{*Zo8I2F5v0v=5nX z{FVdQ&MBb@0CuCZ=Kufz literal 1126 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y~ZZa<}QYo&aNh| z=7xr@Miwpxj&6n~1{O{x1}?^~hA_RJdBr7(dC93Tdowdrte|?G@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{cr(i3Tj&IfT~T5)7EGUh{eSN5Z3jhb{{NG8X=N%@o*;Xwf!W}o#C-dDw}}iN z8!dQF@wrZK-_3BW;{QL}E|#A4ix&$sEs-eP*)V}|bwh=e$n|8?hyVZoPdIb<@MrmB zZ66yWxM#XeYfp3&aO6x#S|HFPWst?d!N4NHP;@RfZ^^xEaZt|pboFyt=akR{0RPx( Az5oCK diff --git a/toxygen/smileys/default/2139.png b/toxygen/smileys/default/2139.png index 89e6eb401ca1a130faae0ce96899b1783bebfcfe..4393f8a8b74bbb27d04961125c4ff9581644e6a5 100644 GIT binary patch delta 1300 zcmZ3)wTWwjWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+{NpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zV#?g3_uhT|dHTWI*o7y5{r>y^|NkGq|3=R{cIw{iIR|e;OfO%3;@SJJfB*eEcmH+R zlwILdci(*We(KIkFW!F*nYcA<;?|S5pRGQAGpKiC>WrO#|Ni^^=WjsQ+LcGH9lP;3 zxO<&{ef!F1Z$I^KJa+lP%fQZ6zAej6-+Fxi<@>ZLn;>qgzPxNN0|SG2Nsu2Xj2MuB z=d)+eo_jri!sGgA8v_FaW0JSKi##i@07yN1iKnkC`!jYvE>)I2_8BG&3{3S^ArU1( ziRB6fMfqu&IjIUIl?AEw3Kh8p3=9^%Q$u4f-B#eKc`jnhG_8f@cwtlJYajXQKkpv> z+0l2qt~Z@Y%t`w#*POb#{}cCp3}36Z_jQNVP4V#km7J5`JUV)KcK$8bZH2q}OhPa~bhb;$3$@7ifu+5#ZoHVAu&>_Z+3x$b z+)tyFm^K;8pK-ok`|zoqabZBhmNW@G$~C%rK)`0+>wPK$ zW=uznR?oe$SWM-H)slZFDw*=~X9~qRY%8AgM#9%!Aj>ric5&4 zws~{?ci)bj&n9aH!ne-S;@OqZB35ySi^X;Et)`iYA|>*_`M0rb@5(%^AwM%lA)ZmX z&xfh#prsy1Xvg7ob62=Lcl-9yXmjIv8`iVaX12;TUUo@y){4I58S~R2FaHVi2HD2T z{}q)bcJFqOd3;b)r|w5=wN)&qziGLqU`~p~*7ZNZzXQ>;8ZVeKG=4s;m|bM@0T99&j#uG5aLv%te$;X? zEOFh)z`)Su>EaktFL62cSUPiKf=JuLt!{{OFE=qKOx`gASxf0dnr zlT;lt-82oiFy*d2+`Cx%MW>y4qhrE~-W{Gj^-C8_K3QV+{NiPUePXAlMo8;0PtMtv zbUZmNQLy^`4Vy4NJAoyX5U_R)J(_4x|$YHU{Eb_jVMV;EJ?LWE=o--No6oHFf!6LFxNFS2{AOVGB&j`HPALNure^1 x==g9IiiX_$l+3hB+!`LpM2aymFi3)I2+mI{DNmfJFUiHg;OXk;vd$@?2>@)9R}KIG literal 1314 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y-to6t_H4_uC697 zZia@gMwTu{E{<-lrj~{#mX2nI#xT8}dBr7(dC93Tdowdrte|>b@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{!*AWs*^kcwMLK2e1e8-)(A9GU0vho|HJ z`Tsp%Z^AT{i%AozCfI3CtFQn6=t|CVl_LTf3TF$xe-&@jY=56@+|u~; z{{HCUs`%Emwcf3q)V6#f>c zE+OF{o*<>5f9B|f_lr&|oI8KA;N!#Swsmu-7rH;*Z(lDl;bZ-R;K^&QN?x14>-2#m zCr&7+9yr>tj!n||@9*EU872S!{oK8M&1z-Q&-P_U}J`@w3z6F6JXo t+Qki+gBD0SH?^GkBGPf$fv16inc>~iNBfTy@JoP7El*cJmvv4FO#oUx)SUnT diff --git a/toxygen/smileys/default/2194.png b/toxygen/smileys/default/2194.png index 87aa8732063088148f12785696f1409dacefa1b5..c8f4d491311763e843550a5cb1014ddf0145f1d0 100644 GIT binary patch delta 1224 zcmey%`Gs?WWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+;KpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} z*_5KyXWxAK{`&LxsM&`KR-F3(|9`}ceFe*oz4`R5VA=7oDZ8#eeRt#OyO4=nbLa1W z{qbv1@5Zdzy92t`X3p6D`oq_ZnLChe{e9!$QU(SF@sc1vke38f};Gi z%$!t(lFEWqg^Jt)1_q1XsiEGR?ig^~DejVSWMjEhXp@{heU(n}ACnBbzT=*8YqY%G zI@qKVf7So|%U;{PJX3k+=?~A2iV5$IEPa}kbM?-yx0?*#KQ7zNn|5M%rpGGptG-=% zaUlzCubFHXFU!9A$Exbk!#vGO7DsfP%k+xAtF1Oa?0B2$jF{fX-16J8N&QTW_g3_N zaK5(lW01f9y)=d0M>%WfJ_wqfw^g9M_;9ew)N;Xl5Bw*();DN%h;MQY$y%eh+xgVa z^EL+Ow+rU4+_vlym+12%sXrY34_QmJRXel435W?K{V&&lz_fx%Fvr&T>O{p1oA0@bDmfNH(s5^x$nET4 zdCj)UzQ*ssUti&e%qQ!&@jf*95u(JfMBJDs&;J_Vv7=kv=FSyz3cvP0$42X-piGI| zjN+3OH(CO^GVMPViQd@XJZnQq>u-~vC(f@r7&U)78{0c6wIkV6axW~2HPx<`Yf3$P zdrMQy#AQkXCz>Q<5@v)6bU9sR)joP=#l98xCpfZrPxsBVx7q2ik7b?Gl6up%PuhF~RkN3SVMTpzt+FRE9TZ}`gL^S_+(s)ZY))BvMI!2T z^xEEE_fneLv%8jOw>@2*YwEtGE_!yxN5;Mn3X)pt#zhPa45}rr5hW>!C8<`)MX8A; zsSHL2Mn<{@=DLO^A%+H4#->)LhS~-ORt5&{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{5*lt|pcSrmiL~ zu7-xLMvg9~7A`KX&K4%dE@n=qt}wlxdBr7(dC93Tdowdrte|>L@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{5#_bdknI|eO%0)~J42-&-E{-7;w~~CK3MV!S9bh>!&*2YG$N$L< z^Bun$Zus;6d%oU;X(|_!CR9zZ)0|dc|KDrU%c>1_)9U~K3ld{<5H~omK|`F|xqg8h z^Ck(N@2oEypI$sV^WXo!hZznjoYX4UWlN0XW1AsiB=+zj!|CQLirvO;XKs9tZ|ZMe zcqraa&ej|Ic8_?QDUik}M9djNV+8bok{UA;Gp^ za@pHmCq6v8Y`%zxVdZSD5dKT-hk6|MN;`5kJ2v%qFEl*U<;dc}{yq25g&o^Y@UPif zchzvg=ZDi6_cHKs2-JQ1C+vLplQU1gU0rfRr(*Jiinm>d6G~4!>iPFp`+x8R9tlUy ziHllTygJXHJi)E-fho;_%Ou%j!h)Zg0?QjE7#P_Y@|CXy>`0640ToD|u6{1-oD!M< DQE$0n diff --git a/toxygen/smileys/default/2195.png b/toxygen/smileys/default/2195.png index beb8b2c4cbf6dac9648beada1e0542c86f6a8765..7d495874b6e7e0a30a2652be86daf8fd29834f22 100644 GIT binary patch delta 1238 zcmZqXYT}w8S@Ch>&U>cv7h@-A}a#}gF>=LkS_y6l^O#>Lkk1L zF9rsNh8GMBr3MTPuM!v-tY$DUh!@P+6==i2z_2L5C&V>r#l^&B=i?WjiCuUydfu_9 z*@q%#>Uq4fBVXe zX`0CrTEN#lBSMR@gH!V(?wEpUY7oH6ZviNo!W?*0tFA4Gkd6}V}0RcRpJ$v@t z>-iHN*GJnJ7#J9nyxm>oS$PHaGB7Z(mw5WRvOi9#44n5I4`llsHa|B$t$C-kcSLc^fcVdszrnc{C!r;m?v){Xg8rgCtlF6-Cvj4p~~>^gI_6c6JnfnU%#0muxr!5tsmDM zaVWSTnW?wrQKGn$=Cr?OPKPG^=gVqes(okqN%uN2-)qnQUOI3f_Hs*0>PD2w9aAGjGQ#DbF=xApUgiQWZV;e*{3w5 zl}ewuxW{7JLH_-xy_QeAaAAp?rsvwIGWO=en0tBF`8&J4Pker@#Mr{uxpkK(pG{4| zT-BR=Q}_Ic4|14a#E@}8Dr3V?I|byS`Yu;-|~+^H>zu2>cW4! z7#J8jJzX3_BreCEJucSdAmDP*D4p@>n#&y$f-D#R|2MVPu{gea=Jl9VPJ^P}5~n!6tzT-3e#(ZNOSvmYH? z)W7@D!K4Ou@2}HCiaOL!7w!A(Q`9nD!%K^MtzlYzV69f|bK#cm{9H-DdVgC5r`@MY z*D^3LsFt`!l%ync8R;6B>l&Jb7#cWQ8Jk*}8fY6BSQ!{hbbPo9 jMMrLaN@iLmst#lg4`d?6CMv3PF)(<#`njxgN@xNAf8-@Y literal 1283 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y%ug}7LKl_7Oo~P zu7-xLMvl&o7H*ElW{&1&=H^b8rZBypdBr7(dC93Tdowdrte|?$@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{IPbuZ9MH|9{Wd6Yyp-WHu6en3ASZ9+D)$eCb6|z*mJ@9-sgJ{!L{`VoT9Eb@EWa zyCsJfs2w{Tz_rA6#|+5}R}XwK{QH~zi9(`a-h#7wt9YDj-Xz#Jz2HhW-C(gRN+8Y2 zeCMH1=5>;62ih2pZrst-XqfeF;r|;><(XZ&b7XCw8?5t4Ncs8wJO9BIhnBZJy)UoC z^P~BE!~Xr91qbtQ?QAeykge;baL(^@g22P)hXQ^)du-lzCDPj9i}7D}HUXn`vD@Z9 zGz~B`V)*Fz=tY*JgtDwn?f1@wX(!{_uFLOpGGWdXkXPx`m2C)mRQ%=2*DrV7+g2Uj zExFFNhI5%?jn2vstQ~1zav~nEZpqLS@SZTCdp=X4lt4Z+hXKQMWg9_*$?k2SV#(9h K&t;ucLK6U`E4bbO diff --git a/toxygen/smileys/default/2196.png b/toxygen/smileys/default/2196.png index a1769d44761d72851ccd5c79242f46b98b03d826..210315d2bb5f65bff00dd4b13e2492cced5a8aac 100644 GIT binary patch delta 1271 zcmbQmwU}#yWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H*v;pAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZp#1v{}11O6+Q3Rm8b6$7oLckedzL&ck%O%M9kQCxlq$-qD7Njav5?r(}^W>M`&-H%^ zhF^9on*Q)?x2{^*=2xeV-dMGB?Kbc9d&l3FNuSxUep%v5z0lnvZ{tE9+|E(W-DfEl z_R;tIsy0DJ*9{$!ZEs?pyz@27Z)?5Ad`3*~V{ZBF*rYxt#+xg8PdIP0tp8Ya-pB6H zfwFGVUr`5_n!VjB;9hk2vdYwQ!FMN?pHOlTy~2H4Nz3$J;_kzzEYI5*oLesVz2x?# z!shN`%e#Lx`X{p1Xsuo~*DLwdNsaY8AL(scIOk#I$9Eqd*&3HA=x;jq_f*TrH^K~( zJ1u?*_f$AXI&}OzAF05)g{wCqdh$xqdY*%iE%Uh>H#}4@+cfKlT(uxew)^V)MSchV zF7|EVpO7p2)Z|IXB8Cgyf{ed6XSY9i6y=<*?({3zIIrRMKkTs zgQu%D)Qg)YzU2BY`RU;KWf!-W=1H;a$Zio>uXsCS^14+k-|Xa=Xj*=`?OB(JXrrT8 z&kNT2yvto}PAhgj@HI5LBL1tn_rbLfyON%4_tBf6-#Gomi3aaJ=i0yM-KV`{@;$k9%l_w%PS@4X z-prlXrT_Z&dWW*EmEH0|z3);iZT_o%VgI(PYlS6q(RT(0h6$c7jv*44V^2R9YBCUD zd9byd@u*mj+hRw-`s(-Z|A)mbGH$p(|L*OX%>N?G)RUqcF83A(y-;0xP~!6zJ7c2< zb6!^)JaH3q;;`*GcIbqoS7w}A@XDtPr+oS&srzrvDlG+$Cf6M?7dP>(6yIpL%{uB^ zh?hl;tpB~APLapHJ};|(vEsqZ5ZA||*Vi1(-K(6t{U4*y45f7y^PR6IF)%QwmbgZg zq$HN4S|t~yCYGc!7#SED=^B{p8k&R{8dw>dTA7$>8yHv_7}!}~;X~1oo1c=IR*74K We_GC_iHho63=E#GelF{r5}E*xO;AGs literal 1306 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y@oDk2Bv0a=B_3# zu7-xLMwTvS#?BUomIjWNmM+FFE-<~GdBr7(dC93Tdowdrte|?$@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@qX|Nk9w6+1dJafMaUbJN3X zlD`+uVk%F?m^ieFKjMTcdXWeWN=S-{kT-w)F@&Z`{qt{Q7@A&xx!=i5E@x zc?@bk+)8dc=eu0;mf4)79Us5Q9#Y+Oh$l7fm&5cwsSaWMhcxtNYDbQ68rxB zIC){qJB!0#)$;}0PIMZuNN6V(GnVbIWtZIajN|au`z@H8+mGx(qR?V)$|EvV#U@O1TaS?83{1OV8e&EEh3 diff --git a/toxygen/smileys/default/2197.png b/toxygen/smileys/default/2197.png index 2b637fe5048390005ebb9327eb3c030e528c7ed9..b7f91c48f95f414f84253e6f244d2e75f29e3741 100644 GIT binary patch delta 1278 zcmeC=TE#U%vYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{wM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umB6u>rdZZ zeDo${;@0S?J1#$bef`PX*r_{WrfffO^-)mo#&ySUUVHo|a?+N7uC?KP8!p~|arM#b zi2e=!_3bM|yVtBcb}g)D4cuu=cNyn0FffRh1o?r&g#ih8K701;x!3b2Jg$$nF)%PN zCV9KN$jo7JWM^PtU@!6Xb!C6X&c~(7usdjtE&~HoV^v5*Nl;?BLP1e}T4qkFLP=#o zszOC>0Rw|Y@6^!fN%eCKc>Y}1EMi%_+>+1uSpCXV>3#F3*&3fV%GuPY#WQKmv@4qy z+_$g4*uQ60*reTGRs_}*ew-bxA)ao|A74BD*q-mV-(H)k7#|y)q`1ttYnz7FhPC@9 zUAw^~?VEJ@?v;&)7IK&wdQYF*eP{70v8x*|MmPODq`Lp_yK0YD8iE47RrTjBQf7Be z|Jm{6FmKBCI}6v##;nQO>@?@#R|zG;>0S}B&$+C9O8dN>$#ebH z^A6`ow@LdnoqqYuy{bneA$iTJI^@Vv$OF@!sW}cCr-4- za;&xZlODTH=y!RL@U(RW^Y!Gv-ZY7fe!j4=dG|7I&ePj`XO)D?&b2i^xVi80md2Qg ztCZ>mPBe+eJSa02=yJQtn%_0`V(rcP34&*|;=wTich|l)=yw?YCm+#!jsRY=7DYWCK;>B z&-5~0gp@l#iiww99}Fd_c2ZITJisX@QJ3GDsz_ao+bF<{HAQX+e~w+CI~w! z>vXAQb2clrnY}JK+o;qh_nQAqqf+BSL#fZ)p+dq&b1%-)XG!G>I`y>cTh->ffy)<8 zxpmo2M)b5pcCA(S>CpPrhZArAefamu2cFZ5zi?@ZKM1?GZ&mak2Jg&7!C8<`)MX8A;sSHL2Mn<{@=DLO^A%+H4#->TN{|Rt5%nEbBq}5}_eC eKP5A*61N8VNb&a*71g;I7(8A5T-G@yGywqW`$wMu literal 1289 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 diff --git a/toxygen/smileys/default/2199.png b/toxygen/smileys/default/2199.png index 377567308b79739a706f0bee9a5d50a9ca15b671..34cbf642cdfe7650711691d40b1729fd2df7c242 100644 GIT binary patch delta 1281 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 literal 1340 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y^h94hR)6==B_3# zu7-xLMi#DyM$X1Arp~5ju1*$CPB6WmdBr7(dC93Tdowdrte|?$@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@qX|NkxO(CadGepvteYx~1* zr_b}e*0)RD@%^22ngm1afrg%ojN;PV&HL6UIB#tiYhERw&b+H?+W-3h3o<(96-%`0 zA31wYAbsJ9gjE`+1hQ04U)b~3XczZCg9nH6?P^nJl>ZQm*tkhTzRl+HetQQt5pB*Q zBcJYVDGp-H5BJw6tozfi$llX>KS8rt)v!&boSBU;OpI-Y1V=|fvB8nsE1GU@kFRl< z*nEWPf=IlxYnuhTESs8}+lB|PZ1j5${Q3G@|L~{9*AHx4B*pNB{qg>K3+3Ox*Wc&4 zF1I%=qVx^h;kDD{4W1m_xZ&Ry-!^f1dt;Fw5e6HW7waxa7QMK#lU3}y`+EDdiofrS zokM5XUi;E;yP>{*{^3bad!Oe^K2lq!9QK}j2BYSM9z{ndy9%x&nF?$U4U7zXCO)6P T_tpGgpz_Sq)z4*}Q$iB}MFH6S diff --git a/toxygen/smileys/default/21AA.png b/toxygen/smileys/default/21AA.png index c13226bce0383003a9687411e8b5638117b09a1c..588acf5b0036a0f770254e4cde1832b1dd044577 100644 GIT binary patch delta 1300 zcmZ3>wUcXtWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H<_MpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZc6>;%NHKK`}*_u|NsA^=N)U@eDT-szfrRfU3>ce)V)_Z%T7kj*!ShfuZ)F9!lvv> zoqOQj=kHZ3kB3a$dgamU6Stma%-Q?>*YEDlr-OPoRxdyJ^Vgrh|NhP2cPXH2ZP~&- zXKp|JR{!JI{Jj_b+gJYh`MYE7Vc(WzZ$Eu4p1lp?AcH2y=L`%C;w3?Tpa5b(0-n#F zJ$vr;{0WciqiqZf42((M?k;jgTngF@3=Hfgp1!W^&)E66R4o7ch^8xlq$-qD7NjaveTv z#20UNa!jmOExXKBICHhZ;RD=m8I93P9Rf^N=~M{!w7mQLuUYrt+DBUxmu&aRoAACt z&_La#O@Lz>v!HMH^2JtF)zrQ!>=arMI|Ft%iJUg(knR$wupM|`Q%qDhYhKZ?KvWE^oQ*SCZss7dT$M@#> zzti^JE|^|teXdCAe6z~k`6l7pE4B8m$=}a(Z?ga0KNGrE3qQT^XZjNR6+64y?rfN6 z#lXNY!_&nvMB;Mnnd?#q>m5W`9$ehba?w)pM#LI1Rj)7K>(#tH4%Hi`@7rl{z}|K3 zuMk#^D@|OASL$0YPMEdU%yYd|nC}Q!>*kacl5T%h|-hz#v)T8Zl8rfs28`)78&qol`;+ E0B{^y)Bpeg literal 1325 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y-o%u=H?a#ZmuRS zu7-xLMwaHz&Mu~=h6awN2F4aa}t%N=+=uFAB-e z&w-_YfQ1EXhyJ_|R|1Ij!;w&^|dF5@qqU*C! z!N!dXP8`*>YE9%i!CKHaOT*lGX}cJ+Hn(t?_Qb1)cz^u=&Ob*uONISf&98siZLaF? zjQ9NhHF<%3gPwzgg8CWWWzS{z9sTg|xBjC=AMek%=P}@6uAg`$p(1LFYV#xo^)`#k z`}gmcO!#qdb$bP0l+XkK Dx4+R4 diff --git a/toxygen/smileys/default/231A.png b/toxygen/smileys/default/231A.png index 699ddddf6762c56e0f1fbe6ecb493a1712a6bb27..dbe2607376507f182e62a42926a414fab7ec9e06 100644 GIT binary patch delta 1300 zcmdnReSm9%WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081Hxef;vx zzyJULo0-{OxO)Bity_P7{rvy$&$DNb|Ni;$|KHD?to*ri=Pp>a{=tL0 ze|~=Y|M%Ad^-P&vT-8vPU%HQhfkCh&$S;_I!9(HW=b6(VJ^cCT;Y@|k9;&CV z2{SM-FeZ7syD)UH%6c#`FtC?+`ns||W9Q>iWj$>Xv50|zsk$m8q9iD>T%n*SKP@vS zRiUK5vLIEVBDa8n!J>C+sOPph20V9ui&!>Y%;yE7V)ZSC{{n=HuikPd0mcT!~D5tTj74 z@rK##Zj*Q!>8Z67V~-jfQZzObEPOs!YyG{7k7+kDqU#%Ox|m+ucI%zYE+LK%w>9oE zdw7mswo$I!+4x4x`pM4bRc1H0h;=>KwY+0$S=ucD|EHaer%s4(>TETAHev0ApjVw% z$3AXzik_vLx#Pf*mGk6wEqCu=GV@YOT7G545~EAmdKFsw9?Pr$Wt2x;oF`~KL4Dri zN;jv9d}angOY2|4^`al!wloO*`mQ0s6xHfB;cVneNztaG&iTQP2X`&rAb3^d%|`LS zgqs&$_0JG4sNcFWp)u)pTZvTCs)-Dzx&`^RyImIvK=y!P#^R}3Wb}{!}Z}uq3m1}3?3*!`jBekX1>}0*pq%(4}A4GmK zVV&*ZZYG%LB;k9ZBJtuv!AT2rp2%!w;|uZ>JSxb%{`|xkg?MIe8O=YYJER2#u2tFy z)wWe1+~vreX2gBhbLE6RjMH8CoQeW2Eh_lcXtRF;|A8tNz5nez7ZSEFvJ_%>SN@r& zo&U5%H6$j;w$~!vG><*?4&&7NS$+Fj#o8x4d+x~OVJQ;!Sw-^0h66Slk^DY!Z}e3) z@=a$FMGaB2j-i zIe~%6Q87_4(9n_1LP1ACPf=4<|J`3sU5Qs5Z#mZ zOl;b;X=2aP_6Rut{IPaTZcgr+wLg9?_{LR#<8c~vxSdwa$T$GwvlFDFYU}U6gV6JOu5@KjzWo&9?YM^akU}a!1 l(edFb6b-rgDVb@NxHUYGi4>ctsLsW};OXk;vd$@?2>=UjU~>Qf literal 1466 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 ahJqhCwareB%NZCL7(8A5T-G@yGywqKZh3bA diff --git a/toxygen/smileys/default/23E9.png b/toxygen/smileys/default/23E9.png index f4b575aa7b89eb00ec079d24f70a3c4227f3fabb..4fc83f7d1fa6a7a9ac3630f38c7ab4056716b39b 100644 GIT binary patch delta 1300 zcmZ3(wTo+lWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+jBpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZpyo_KU=n5Y20!pdfu`B|NsB~^LO6iJ97@+j+%XF&()_fa}V#j{5W>b;fNXgu0MT$ z^~t-m`G>=%>^gh@RmjAx7w*42d+$a3^qoPy8#kT09oo0)-~ay`Pu>ph+Z52X_Sf$} zlee6jRKNL@fBVXJpTCtY*!A|)*V6gB;0|1RPh=kh1A}-;kRK>?7?6PHvuDqqdp&=` zX_@hdnsSjEHac2} zcxE;oE(dZKTA16q22~0_WUTMfVG42FGeLJzP%`gL*`RZ^ z&pIpXr9XOXH5CnCVKM#k1NRCZ{RP31p>IW`g7e%e&WbKR(N(bQ`Qh2l-P^z1Xx!t` z|M64HM>%T-o1(%G!jd1`HZ%zQ-d|nMDDEKIk??xv%03>)$2;@66%#K{yzwE+|INg1 zj}3eQe;*bz<>k-fkoVZe+j99qa3{lrxHAS8^VZ1rbZ7TkS*y0JTl4>BMaUv$ix+1M zo=^I?fpekA<+>+NwKmkVZ#{fT^xfqr%J!?-vTOI77!+JLP>frg)2#M(RpiaziV@fL z_||Km5Y5zLYSfu=khLw7b)iN`?1rE-J+HcdHTNvIw&Bf;pNA)NKhaKTFi`!)lv2am zcg#AVaUIWR*&k~})qJM!;CZyONaYcOPp1s4X5b{T&eifK{`S@|Oj9=aD_#j^3vO>|H)g(WBXe^zQ zVj1!&tKO4Qwpido$K8uAj)emK0oju#Y-zqPr0X($?n@Q9O)oemF5bJW|3v(fzY+Og zt~Oa;y}fw)=NdDiUGGfNx9-*6w?uwF(@l+SdUYPAH%=B*{GWb7zooXT!Yr=(C<6n- zbWaz@5Q)pN$FA3l9dwXzyXgOZl7Q}s{y;Xt9r|K6uOa})agXbtlp<1=9=Oc++n zrm9}(^gdKLC(nQR`wYz;>%5y6p7U5cn=QGg#mla9^##q+05 zZ+`!~>blkJwe#BroZja7msree(QxqceBOO7bghmON7L=tryqH?2-U|Gre3)h_?7F; zG4(53R>#*Cs<3vnJigArz@S><8c~vxSdwa$T$GwvlFDFYU}U6gV6JOu5@KjzWo&9? zYOHNwU}a#iVC8Rf6b-rgDVb@NxHVk-;@r)^z#v)TI#FGbi-Ez@)z4*}Q$iB}#~f6; literal 1324 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0OVs#)fVtCaxwf zu7-xLMo!L_j!v#FW{w7C=4OuOhA_RJdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{IPbuZA1`{QsV>H({E}#iR*U6YMmn)z|;`n)I@2gWahoFMijZN`q@ zT8Ed<->-P_$NTg0Y!YtlQb9}-Nl}-YcwX|oDzJ4B|C`p3SW@uupf=CO;_vZBKDP?x z*0C?=Hk-fi@P%FfROaw|+0OR6o0xICpzGo1?eYb-5{VyvpO0^wX?@=C&E9Vkt~tm0 zig|vovp-fKc)DJN`Ck3ca~C##<2Zcw`TK`S@9pZDyJS;$e0^kl?LgX>+{kb9oAw+z z!Q|t6{^SX6g%3<`SQjvAUg%MDYwM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umBG9U)em2PM$bDo@9>@9fByde|37N>p}4t+_gsA%F=JoG!Xr1Hy$_qR>&l}yArrSI z%-D7D!K}N>7ubsj4ZK!LXV@*zf7Kf zzx$m3EYI8PAJ5FF7ub2g)yaPU{{{IUwWB=BUdGwnOiG%a8t!*&+S#?Y6nlTZxV^13 z?ZoO#k4@g!)I4Xai@p$Bz2s>4@q;@1UQN7eaCiX+pP~2k=E-m3QobfxWau8qc_{LJ z+wF%!)gl}p+}5_sZ#vgI{nv*lxon$s{a(CgSst_Hsf*0HQhBA5H5~=@&Gw#RjH?vl zPKX7b+}M`WKIOPw@o}5^Zg<*lS&4+NuvmWjfqMmy{({Mop>KtxF5l?+(53A*N%Ucf zyuF=FR1HtR?1}Cx68ioU=gk-&Ozdu+f1*RYg|+3z|GV5f7*;v3M9a=xbwa>o-tTP! zLf2$FdDo{UosV&8+S>Sve?I$zkM+wsKQyI8J6o{YE#+W%d6cL5?a5mwckFn!Vzal? zi*vu`_qwZEH8uP37waj@nJG?rv_ZadC-Q<5@w_cbU9sR)joP=#l98xCpfZLPj}6ehDcnFJ@!)Upn-_%!{m8em!>_9b7~3L{Jq}TGi>$R_s8R; ze?0#=x#vCO!Fm&Yjl{5!V|Su-E{6z6t~OQeT$OMyr}ISX%xr<(dn1bkovtYIe^FkV z;~?w*Ep|$fZ$|CD8RZwFwhKsKmsah(y(79meHLGwYb?*b%ENMpE~*>z{f^%}O`&Y> zo*$1H7#LJbTq81-1f)wviLJYD@<);T3K0RSe3DKY>6 literal 1269 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y-rRRhUS(gCaxwf zu7-xLMotzkrY;7?uEwU8u9hZ-ZZN%`dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@eT|Nj{+NDfW(YwL8}#d6d^ z;t7*T^4!p)5=pwEk`XgzN+cBBIFiut^#6T(0XAV}=Q*uH&6}r*#7z46Kc2^MM%50+ z{|!t`b8>iC*WKcIHHk@MQs_J@iKPDqJU>3SD;^KzcUI&`5N2NK?#cfD-@mjMH_Q4S zUOIeTqHAMk;}zv(wF#Z`_tm9mM3y%to}Rf|11>z@;m7y+_j!uH$EVEL^_FJ>FK1>> zlEGJlGfi@mdKI;Vst0G@xcb^rhX diff --git a/toxygen/smileys/default/23EB.png b/toxygen/smileys/default/23EB.png index 80b209b2aeb06dcd977e41520457e6bb7e297614..3240476709a90151e86afaedc5049490e3b67ca1 100644 GIT binary patch delta 1300 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 UpSSaYc2Ifd>FVdQ&MBb@0OVBIo&W#< diff --git a/toxygen/smileys/default/23EC.png b/toxygen/smileys/default/23EC.png index 36688b290987543be6c2a1895f335fd2a2b1b763..9996c1adae7ff602d2e55b49c92c547296c6bac1 100644 GIT binary patch delta 1281 zcmX@jwU%pwWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H-WZpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZpyo_KmY&#A3g6_)8PLvs(_oVEW(#EgBrEQzkdJu`Rh+W*V?Ju&iwoTf70et{_QKbp1QT}*frmlWdW_r zFWrBBrvBCwzt-j6O^c(uSDm`?D7+> z$Mw-R1_lPkByV>YnK>+u>?NMQuI$g)`M6ZMbs9>SGB7aJRfR;91SOU$6cpvB zW#*(RlvEa^DpceaFfds3P7U?mRDVZ-=gw;pOQvh$^2d#5#ID+O=Hun2pB8fJm6EI2 zqB*u+(KYz{=U?-_r|Yv+cY60r9TgYfzq7Tdu$a~uF46NmENm5LqF!z-S_+2pGc`PZ3=u} zKeIij_~BDKqssQix6`b@Y;Eoi%?;}?-YIuj<=Acx^C!V6f(y7_wdM+no=(53-{B>r&SFcrO2s~IP>2zpQZm?QxsC)W(-;Onp zO>zaom#pI8so#{)CML0t&GF)jT@O4Bjd$t)<~M7KKKi(8roGWlhkZ9qUZ`=c*%VPbR3G@a!R4^(2Vu<`TX-27pWQ^`y({)dx!?r@yE_~b#yAJ?1xf8FA9 zcLeXZeOL7Ee0R&b{8^iC?-7l^*6-i=W{S7Cyoz_Jo2AWu)i2zNI$b+vsI}NJFfdH= zba4!kxEy=ts?Z?^0hf!%=GLnS=yVBpJb3s2fAATe5LOHQ>S{T$AIn#)ow14`>i6tv z4$s4d+7f46o9#KbDff1t_U_jK%P%U|TJl&JOKcK+yD>ye_?hq8nd(!U-~T@N{Bna; zzX8wUoo`%SVh>DOQ?cHBj%&`%KECReLVRy3Hk}h+X_OUSw%h*6+VBPD^ENJv%c*5N zouL>iwbc6v0|SFRdP{kVo554k%5tsu7SC(p-G6Lft9hTm8prgfq|8Q mK`P(TohTY|^HVa@DsgKN39e{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y)MS)7Ut$ImaZl) zu7-xLMvi6{mKGKUMvi7qPA=xg&M>{6dBr7(dC93Tdowdrte|==@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@qX|Nk9wRcn(l;3(So@XE)3 zxAoipn2HA+W_ZEGCNXc*d$q&&>;G@e`uFv_e1u%2Er;>n|G(QA+w%APII!WviQF}8 zIyN=ZI{j?a3}0Y z|E$?|zy4nm|B?Ik2S1&7=gw3Gl?Mdmjo4fTBGT1B&pPegCs7(2)aHd6W)o#6e zsW-}`Ox4U}_MhAE{Zws_M8h$rWs}q2+nrX>K6QG*YK4OZ-@XO6vHsRK`g2^c!{@-1 zA2L&gTe~DWM4fw=v`p diff --git a/toxygen/smileys/default/23F0.png b/toxygen/smileys/default/23F0.png index c8ec471c1ff7498cd7a71cc01b1b9154a5f1496f..63485f650ce4861c29da9cc89043bed25a379180 100644 GIT binary patch delta 1559 zcmbQidw^$xWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817mrBPl)S^6)V2DS^xk4f5nOwyLRo` zzkmPw_3Kx_!2i6+|9O#{H*em!apO`LsDG!y^-hE9p(6VwIo8W^tjDETcZ;(wF;bf| zefpf~)0Zz_zGcgn6@@u7_*kZc(aNHnrAwEdI(6#Gl`AtRPoA0*|Drl~N=n?BGiRpu zbRQ0L|DWLgKivMmpUHo3{b@a2r%#`rJ!8fZKfC_{=KtMvZzKdSG*CS^YufB-)21gy z|F3t_{B5VQ(okhzb@{fm#GP4bQ`-N4@m&JrFj|qSH@Zk-4wq{A8Y8kOgNuhpM zhdtZ2wHj(SN(!BlVm%lhFwNhy-QH^Nwr$zwrWt(o?70FQha!V^i?iM|R%wyo-@9^U zwxwAV8%r85TfevKO=G3aqO4Es40nVE=JK#@Yi}v$WjhcaxH~ConK0`GUHMlI#-ClS zTJ%c>43j)8#g2etvHKvS|6!pNm@SYC5+w zFfcGCdDpwUFr{(Vq%bfru$OrHy0SlG=i^f4+4STA8v_GVV^v5*Nl;?BLP1e}T4qkF zLP=#oszOC>0Rw|Y@6^!fNplQ%{(RRgVo^RhsaA&VL;R{wGaoO%{l-A&u$uIWL;NSR zR;teY^85MvFU|g2XO&!(Gu`CB?%xL?wVOw~+s*Hnh~BLK_4wlpr5pKqcQh9s4LiHd z&8#)=>$Ek;WGo^~(r$NIx=Dl_RTO-?%k!G*s&j#|?6s;h{3q@Fc658dDIviJy_L)5 zt==~|*H(NoYTnE4f1}W;XyvwPb6U=Qm57c?37#tYFJ-RCimr-{<|{KF%2acgrJiuL zI`;Rq=e31q{6{6V6zhL}I^|x$q`x5e$eBMvvz92PRG9MQUz2?JMWR24QTJxsamFW} z()SMwA1uGi_#iMt!9GPr>kt#u)A@TFxIILL6z{PG^YS=8*8Hy7xIs=~THMjkPp_LyrjTtU<3$k5Lzvg$$bj^lydM!(=U&l+=r>n9xHTx~! zc|ov$huV}!8~o$mG5szNu+G_axOUc`2kB;MYv1}ZGk0HXaZ9{uW zitZjU&ZVYn!vuOG4_YVc>4coQBC~qc3*{PP9?4x?(ZaLeoSiKFB>KUkSsl!VTN0+P za8c5_&|4=cE6eL}R6O>8WK9b9-1>&g4rz{B-j_UMDxLH6pDn>F|}k8Ge7s4$Vop# zPHz6++Enta;it2KPsZf~T({<5^5gm>!Q1+9v4c^>tOEJQg?pEsKXKpp_sV;h%DLaw z2j$C`@BH%Ib<(^1S*vf=Y0K@--_LYc$kp6_$)Ptr1u6fhFR|BT?>e_?N8?Qf28O4e zE{-7*my;6?Fr3j@lk>)5R&(#vR!NUyt<@`5tt>7qUYWgOl?JnGg~`?}r6pg#d@U*6 z!pl=BDeXzBDwoRm8<1$}P+<&N0p}(2Y~6foTTAzt=hky?(5_#=yX!TH+c}l9E`G zYL#4+npl#`U}Ruqq-$WVYiJT;XkcY*YGrDoZD3$!V35jpbSH|2-29Zxv`X9>M1q-W UStly0b1^V@y85}Sb4q9e04yD{cK`qY literal 1688 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%Eidi#L~jm)Wy}+(bdS%(9zk=)xyll!obDZ($&z-9A*YKy{^WNt}f;-2Cj~7 zu7-xLMoyN7&aURp#^#PD&c-f|hA_RJdBr7(dC93TdowdrtRQ-w-SFzQaxO|uEXgkl z$G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6-7vak8*9w{$au_}j_Sz|hIW!p+3ez|GOj$XN-hH-(%q)3;Nw(FY|Cq{IRf3UYA+ zF~R8?l*a885Sb@6FU3}=NXg!AO~lRB3=B*sJY5_^DsC+aj`avml&ITgd41WnTWRW| zhVCp!XOui#Fo#=vP4@wlq@Xwv5zoL&zS}ipMPo&sSS4M=7rXS93QK4&pP09Crk+>s z%S$hBmE>99zIn6%^>07lEvJ7ycxwCpv(5YJeeW&r3r6aQ{W&l6|K#&ft*bhR4Kio? zm^ro;GhF|5X#lLI<9Ud(TTV1;(K*Jzuv!d}kn~JWRx6U7N;%?Da737(cp;g5! z_-D;Z*H~xgsX;5>e5~5L<$vditgTh2Pn}xje*e}2dFhrw#{@l3CBBr)4K7?Kx;=6Z z7BI~)ka2vwknP9cfA3|EXCA$nvE|a!l0|7JxRSF(TMmZrWb>~T+%Zd!bLKUX<$LO0 zh~D92kC?XhO!b2kJvRf7vrUYQJoZ_Ez5Zz46saDLU3Z<9nI6^jFV$4v%ITNb;G(p% z*hG%$yxYgr>2f}wBMTNPBp%6lQ>T#<6+Gut$n_I#4!`oAmm4gQ+s?kjMdtBmZ;sz- z2bUXrEW6ZkGznzZ@#o^5raD*VzxgL@`<=aoF>SM?q1|Z}A(@i5;hps|KScChEM(&9 zm+e^hS4!we!lLUJ(r$U%D=XdH>aft`NQCt|1x?KZQD)7uR<_G*+pIiN7U|sBxf+Up{yj=!od4-B!_~mrx&{sRdjj@pElYRyt}nX5 z-K-y<5MOC|>Z;UgHwh`L7{iA;L{e$~;b~VQ{@i2%^h_{qE S6)O*_D?DBOT-G@yGywqM=7Q(| diff --git a/toxygen/smileys/default/23F3.png b/toxygen/smileys/default/23F3.png index eadb18c4889a64cf75b56ef410c14c403802de8a..0958429bcdf2364bf395f865a8e56c935ffe511b 100644 GIT binary patch delta 1354 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 E0N(Vpg8%>k literal 1492 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCz2>ea=Fa8@CXU9I zriO;DMvj)QW{#$=hQHfYNX4xs!QTGDfg=CXtYkBmn_S4O zWoZ@V*5pi7-lf>9cSR!UO2XAEveM>l_aECcOgkdrQP3#uQ;}n^X~m>V0jZ9ULvB@m zxhHvkZ?5{>wYDeA&s*NFJY!j$J|&@2vbm#e=FI==?d;mP&Rt|@y?nWvqsgU3yw2`_ z=&|3G$5t@PDhhR08}wB6^y#_&*tk(?(#hQ|zXk7oUg5aAS72G>p@WPQPOo+Ok@JoD z+xJGFWg-t{m#=3`EiE1*~^IW_kUv)b*uoB_2IJHjZnj zO)cBWb7#j+rMKDF?>~Ot@8`ERO=)VC%(X31wZb!=MbDXEp7Yb>t>s%A{?J;*+JoH{ zX42~p=15B|x6Rm6a_0N|k8a9)7Dnt`lU!ETyl&NG<(873&#vbxXGxjl7&g`xHXbmE z-?sW{V*kA?@%>R#xoS?Iy1C@!|GV8=&!214toOWPxs7?LkzJq9#vR35cmH-U<;i$y zGR5W5#8t27Pe}>lj?#Treed#tZQCV$UzpGK(AB9~w|<@7lz1g+sa;R6y>31BGBf{I zeoO8lwZw@VE`HrF{H~*=!Y+QnuG?oety*Pg`t_KVO^EHS_1&c_W4|Z6Sf7j1KW=3u zRJy)(?W@U?m)fhVbCjp##NJxB+w&Lq%eQ*_ubmeB`LVOJ{7~38liUUS1CKU*@^4^d Y&|jGqFFV1$3slZ}y85}Sb4q9e0QCDSLjV8( diff --git a/toxygen/smileys/default/25AA.png b/toxygen/smileys/default/25AA.png index baed68614f565c39b420e1612bffe3590a5f4a92..54992ea60805a6de57bfecb91f408342adb14da6 100644 GIT binary patch delta 988 zcmcb|(Zw-AvYv&3fnn<}^H&TE3@qu6zK#qG8~eHcB(gFvFeoH@1o<*BRH-pAG_)`< z{9<5WXn4WEP-?)y@G60U!D}OzLU@!6Xb!C6X z&c~(7v9(i0o`Hd>zA7Z5Bq*_5p`a)~Ei)%op`@}PRiUCjw}64cqIYVj@3q?sJa>w_ z&uYxB-c`Gs}*5ub4Cy!m-|DHd%*xC2!!DZz+pI5Z^2uBqj;g5UA^}9S!*zE2@J7>8+ zYlK!^-I*znFqz}vx{KQsCx=YDc(Yhgss8j0U+p8QdWU$J^(7Xv>It_l$Owu2pd!I} zSNtz~bW`-f4+c;5q|!Gk%yMxbWOQfx&)st?Uc)1uk4b8i72icwFPDu~9}H61 zoquZ7YuIwN>-x1k$oTd}@@My^`M-5*w?5SSTQ=wEmHF(RyX-U9<$VsRiL|$4-Lrhg zuAd5-D~wMq{Hg1?pF#9s*OnWr6;3iRFeG`pIEHAPuRl6rBX5HN56k(EB~49{Gy1z` zN;FTh)DSLUWbJC1>L4Ai_bxvD^wIw&SJ-cD={Z(5mB(U+1>=V^OWRH~9bc%ik%h&q zjDgL`^V8g4880f&SY1}|yf;%j#Av~z)%h3Ri`75l<@~|Gz@S><8c~vxSdwa$T$Gwv zlFDFYU}U6gVD7GKXcA&*U}bD-Woo2tU|?lnz;^FLGm4Jf{FKbJN|+8z4du-q_7fG= QxfmEcUHx3vIVCg!0N3!V82|tP literal 1118 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{;DKriQN0#;zu= zmWGC|Miy?a&Mt0_hGs^Vrmkj&W-z^;dBr7(dC93Tdowdrte|?0@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{)fHA zD=dVMFL)v|k8fz}SjVk)Aa_sTJE7iv*S3OMp+i>7F*OqISL-u|z(GKux zG-TU)ZF1qc#I>=-6N=l4^K6oO7H&2B^}w4+Q?*4vAlYVSP}YZ~0_l_Y6)7xbxu~wM tc;pIWtbgVG>R;T|VkO!i+L!O)G+;2xu-EmLaoq~a@t&@JF6*2UngCnpZF2ws diff --git a/toxygen/smileys/default/25AB.png b/toxygen/smileys/default/25AB.png index 34a504fb5310e37849a074d1917bc8bfbe2a4363..a957fcab7f3beb609300b9c842419e7f825054a6 100644 GIT binary patch delta 980 zcmX@Z(Zn%9vYv&3fnn<}^H&TE3@qu6zK#qG8~eHcB(gFvFeoH@1o<*BRH-pAG_)`< z{9<5WXn4WEP-?)y@G60U!D}OzLU@!6Xb!C6X z&c~%w_ zwC2e%U$@~p*U6=~!it2{ zt|!mfa$=p9^rw@%Gy+7w9@@4d^PzE0c|_`&?vkF*?>x6X+{SlYR8R3|$;rhwj$$7) zlhW$6d@s#NsWjyYzb5+hi-i46=D1D$JYu~@;NLXOhpZ{T+lsV`PU$dw zNpd;xcI7R_9c`lDZgx&65&e7q;LXm)M-MJb>XlqkUdOrC;?d;ks}KE)z0%?vzT^A^ z{<^TmS-E*qEIXu~GeoxOrfy0zv#QpscS@1oo!Po$)zy&13#`gp4m=H0(2%++W+Un5 z_|vJTJv^|o-DcfFj-%U??@cb^UZnELmJ$cuRv zr5_!&Lei%we~zA1$H-;bwQjfgeo+PnhA2-L#}JM4^~rzEH^>_p^6X&}lX%S6l^rT! zDDo#cpw0XK(X>PHZTvj{B_$;P85*k(RCm+)b*9=PgGRrVqoxe^>bP0l+XkK Dd$gPP literal 1100 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y>8}iCe9YFPOc`d zmWGC|Miwq67A_`6#?BVzMo!Ky1~9#zdBr7(dC93Tdowdrte|=w@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{nTu$sZZAYL$MSD+081H<+JpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZp!B$zZV_9AGZi(-2eaoAHMn0yya5V>_h+l{X2Z~dDi0N5i|CE`|*46kz3KT_J>W` zwfpj;kcnGAefv3c_r)-&gi%h@Kax&Cq6?H@;PIAtu6Y`pw_{{Bn+|5jaHQvNnj`S+t& z=K6_~V)s;3e2z7rxc7eEc0H!*;>Al_#BQ717P~33?`rfWsqBQrixIi{&m;r`M7v$K zyw;0+A6VM^D&mFo!J3I@%J=`4JGIk=B_;XZ%=UG~kDl5Y{p4W2-CM8yW~)=t%578U zw4D4Zp{BcO^{KX+O?q4*u6sA=W<~wrxh)$e`B_Q!@t*2QxjWV#E^PJou#5eq;Xi@3 zrfbH``CgMv)FR_}4^J(1wX{9v|E*#A8|B9gpBD9A*A_llJDcHzhRFhc$2G4 zn_;hnU_te&GLChLt+i!JDzUErtt_k!17vEf4!8DI9Wh-f>R$7yDD=ku?pYgNa($Ql zbntvu$l9~M%?#zq9KDCOE%mu}#x4E4Ye!D9`C5VSt+TXvb|tik?YP6m;=1@&lY0GO zjTd&`i0{?+B7Qg@PJOO3pjTXmxgClCb@6LN!qLLyLRBQ3F;*^anSH|&Y zYg1nxi_CG#T;Izwp+sR@&)g>m4s*}DIqBzx`je~ww>Ew0Y(8}Ap!2qaEGqMvY*O53 z&YW|@XsXqXD=DdIDd$T($@5`m{{PNs!(mMZHn{V5Q#$Vg+-}tUG zSX_RIQ>mM!(SFsJ{4aNRE%BLlJcEIOp~ch1F+}2W>=}2lCI=Chi$>{;M_W#XpOJJm zd{-YEc6&}ebA|hQHP%1IXX4J7Fsz=Rl4{^_`q09M8E;&Q3OrMcO1IkB2u-|Mv+$I5 zg6E<&*QC<~qlI#h>m>T`ue6vqBlX7;yK~~9b*zk?G5L*Qt{&fy8~0rK;I(@3SHCRt zgmSx$>+b$xa-O18^k?I09R>yl)e_f;l9a@fRIB8o)Wnihe+DB1BO_e{b6rD|5JLki wV^b>=OKk%KD+7ZQ#Y>)}=*Z1a$xN%ntzoC0O!P!WbuI=5Pgg&ebxsLQ0Dzx1{{R30 literal 1277 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y+-DSj)o@2rmiL~ zZia@gMwTWL@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{mc&xeb7tcAZLp=Zg zFYbDLNI`so#OcH54hdv0Jdm(n>(qu9M{L`q*YA74f8>Ate*=YNi3!5b(pG%Clz3u8 zSjvO+)D(ws_QMY1e-36^DJv^C_s(Kz-nfV3@Gax-l4UW~r#^hYUoT>+^Z(x;MTw|6 z4*5qTRvUSIC^k4_SHXO?VgG(ksSiJY>$g31&o_9p_gl+D`B^e?;(z}CpPzVsX*-X1 ze@yz1Kf=3REe=0;*ZrMmbJ^VV9bZ2&$6ep}$KCkB=kN1*PV>j5-KhI$cZ0Qs!Og1U zU9@{pQ3~U-WzSQdunJT#8Y{5QGF0)n@JFQMvI9>812aQz!k!0r^X?gd3M5ZgKbLh* G2~7Y9NyGI3 diff --git a/toxygen/smileys/default/25C0.png b/toxygen/smileys/default/25C0.png index ea2a965ca27071ea622faab957785d738cf4a720..0ab4d16800d9bdd93cf349925b3b23592f3cd48a 100644 GIT binary patch delta 1261 zcmeyxIg@LGWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H-NWpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZc5yulZ%hv|NP^3^t@xuTP{6%`{n=t|539KWiLB%^wx`i|NlqK*cUx(|MFwEzy0_f zHf7hLYfnNZZVj8XZT8+vpT7MJ>fM+yb^G2+_y7I-7tpn~e9_)J&)@mCuMFr|HFNu! zPv3s{*2A3RmuH*Iz`!70666O83kD?M`Rv)V=U&gB@VGwO#=yY9nB?v5qIe{SSB!yy zfxX1j*OmPlJ0F)Svr_cm=?n}^ja4BLB|(Yh3I#>^X_+~x3MG{VsR|Xj1q=)py;DQI zH{DU-x$|1Yl4)8C%klb+y*HO?otClJozefeaMJX`sfjHC-glWUpMP)vBKl94*reE9 z;{Mxa+(^4Qb@Im4;?-py$#%cC<<6D-mL)aq&7`fKm!5gD?mMcL`SI#T!|wG}DW*Lf z8cn`uE<$|pYN+x24BU$!;} z>)k3+>U*+_e{zbQ%MQi)D%}jB0s1-JE1t|ayzTI*?Fq>ZJws@v&zDF`Lq0g|7 zW6${mCKDDbszzOE+VQ6`=Kcxw2Ssel{yV#Ec=3*>(1g#g@6qnJmtGX9s>B4isXk6?4L90PnNuTa`m4R<6KLQj44ldxF{BK z@Y^-Dx4&7Sa8z{B>5wXp_(e4<*p}bCoc=`rRNXuCUs;D^f4wc7UR)oiQc{08=I*n# zKQ`6Xa6MTNvFoS8(}46Thd)O{U@`w%6^hOEHl7_{6|WEm-NE zOHUi{UsNczLA`4Pvn>lh2=lzw{7(NSPvU{Eb_jVMV;j3`O9N-j!GEJ@urfBaGO^S)Ft9Q(I8nUhIYLKCs#R`&N@iLmZVfy2WTGc3s&g?g Nc)I$ztaD0e0sz~oH#z_S literal 1274 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y>6D4u8w9dmaZl) zu7-xLMi%CV7S3iCP9_%4PDTcnt}wlxdBr7(dC93Tdowdrte|==@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@eT|Nj|1IQ;+r|I~s&!L%7d ztNKIbo-oY`%jo!Ma3osG@B~+3^UoVsHvReko838J!t7qfQ!b4MgF@#t3VO=Sj#|Ji z&~(P9hsVy!i07Xh7tfqGIuc#~6HOoRI~knz>^>M4EVX?4gsVzBADGWEPCc*MHv9a2 z!oh?=geQ zs7R=!72L z4Gh7Ti;X@paqMZ3oWOKeLP#Z}j_XLK0-HkvBf|pwdtAOOuRvuQgQu&X%Q~loCIEHh Bx5fYf diff --git a/toxygen/smileys/default/25FB.png b/toxygen/smileys/default/25FB.png index 1a9b1e4e1e8056f0f1300cb0506afb6652409289..02b39c87550c76032d123c330c68069b5e25e4e7 100644 GIT binary patch delta 793 zcmZo>Il(qTvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{LyLazCc<|sM7(9CP===BYKYsl9 z{rmTyKYzY{{rc(Cr_Y~1|N8ao=g*&S-@d(Z>0BPAu} z#fuk@A3y&1@uOzFre&pI&osUbE_vqsb6Brm6UU|AWhDcntJ@GQ=kby+&Lw>CboAgz;cuFZ; zyDJr==~&1bcp*SU_v^p+hoju*&n&Ln!?1G8vRoAt-MLIB7EDY1ziExwIfrQh&-D$w zHc!a5e(Psme=)J=xx@XEpVz*fdw1{OLB)->vR{-PtB$KLPk%06t5YM}(>USq>D3ok z)Ym#6J;3l-?Bwp>?H_lTS5DF3`>8qE|NQ2kHQzpY-7w*2y+18$2S=0Zf{v%+3wCVj ztg4P*YiIX(gO1Zl?wvtdd;9v^<2X;UIrB7ig@?>P;Z02SS2>a!XB?I|^h0}< z)7pCmvzOicGxO$(>-!jA#cSN0mecwP6v`#85hW>!C8<`)MX8A;sSHL2Mn<{@=DLO^ zA%+H4#->)LCfWuDRt5&Cd`EYpXvob^$xN%nt${@(xR!OIqB<7?gQu&X%Q~loCIA;& Bd#wNf delta 466 zcmX@X*32?Nl7o$bfkFQB|HR4S%~y1fT9Y8WP<6~$1wuYIl*`4ETY&&_~tXJK7h(px`Owc{$|U@4Z)Ee~mx5{kHF}yX#y6Ri1yAbU)@~^u%GGp_|*{i9GGjCpY;- zt(`WlWM@ps-4vsVA1idurUiepxt>4Gv{6=NuAjRsOQDSR%db_TO?CU%TmE3<@eyt4 z^^IB^=G73*_>6Ue?fmmw%O)&%6U4|=R=@l1sT{NV;Gey2&gUo8*vO@BjL>M@y6sJO z>f1fMS!T0)Pd$-z*c!EVjr8*a2XznLI1r$}ETC3cgZJahl1sLR1<_N_i%u$>z0sdl zuRO`=^Ea)n>?_*2ZruD@wO0E+vzm)DpJBw?Zr9gGYWUloBhUFWODs-R7JWUF>zove z#P#}Pg3R2koOb^iZTzpbv8?&i7X2{i)#{C$Y`u>1u1(nA$ b@i5fQ`(?5zm}46Q0|SGntDnm{r-UW|>h{(E diff --git a/toxygen/smileys/default/25FC.png b/toxygen/smileys/default/25FC.png index 8ae60bfa4657067c3e2f12dbeebbf4627bf83e4a..c1ba9c6e6c78e2367bbc1de304fc7a509316af37 100644 GIT binary patch delta 442 zcmdnM(#|qLQi6qnfnn<}^H&TE3@qu6zK#qG8~eHcB(hF4)8k=aO7eDhVff$Pe`fzg zMTvU$5>H=O_Gj#TT&jFAuNt)&7#Mzex;TbtoG-m-n-?4?a^&Ov%I|j3W=>w6*C&{! zSZ79Z^3GoD$f2kbtXh~Pefxb~zzqqdgLA&Ezj$V5hkvGM?}sSa+7+)ocK?35c4q6v zGaViTCMxQ+sBhk`z;m;OJ>ze6%3NIKeSZG=KDRjE!kB4(PJ@=>Gh`Oy@9_qU9 z&d#F^Syt8}qQ=E9nNnO=+_FpwZT%VVF!9LM$iQ6Po^XYiaUoB)JKxy#IlaH{^3sB3 zk0L8g!>993?|)OT!~Q=kdH&<*_^sdMlRCcYJ0CAu^{i5inSp^pwZt`|BqgyV)hf9t zHL)a>!N|bKNY}tz*U%)y(7?*r)XLOU+rYrez~FG@IbIYEx%nxXX_dG&Y*H!P$iTqB O;K}al=d#Wzp$Py`cec#{ delta 503 zcmZo?*}yVEQkIQ@fkFQB|3n4`2F?PH$YKTtZeb8+WSBKaVxpm5J>xb{7srr_TRoG{ zc0FxtH)k1w2-;+wya!N$-h4P?UGEXOD0dN_Ac70<{LaEh#?}|@K{36!6@m*1Hskr zGiSA}T6N;{oTEvN*Q5kH8J^3Wvs|-P#MLo0^h#;(ce%HFpO@_poTmEw@4UBVrz>pS zTs$VJynDO%xWwVK&5GxX^`>((NG;IuPTMSb&T^AZvWb*yLr20|RgSWAA1iD!>SsOE zKKQc4N%Lrl)za5ibBY-Z9eZ z4@fizX)z@|y3Ue&``F|k<_Z=&R*-xBROS64b2YXLz>4Eq-5Q;(G=L1_n=8 KKbLh*2~7Y$>*DMH diff --git a/toxygen/smileys/default/25FD.png b/toxygen/smileys/default/25FD.png index 66144a8d1c8b6618d4c56fda2568893581ead3f0..0aab8473db1d2824cc69727a1c533f73ccdda963 100644 GIT binary patch delta 1005 zcmey)F`Hw8WIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUbEDKzFRBLf3deN{+ANl;?BLP1e}T4qkFLP=#oszOD5ZUF;>Meo$m*h{w+cxs-D z*fLFPVL4vdRQcLRzWUF*M}Kzo-LC6RXA*PLe#$iA!5o20T6k}pQ&YCn?@3=o~|Qu0DA z@_k@w@2eXx<|gd(cviprel7RYC?%#%hVo~euh%|&YG+*8-uU*I?6+HrJ40TpswWmH zFZ1lqS#@cm|25afRV&1|x%zTH-*7i^>P|f)gLnVuE%D!8YPGI_^W<`Oxu=|)8vF%~ zKL=+{dZu)Gbz;S)bsFhM{+ty1Fex`p#vyLY$I4WtAMdRhjJSATIJ+%cRIj5@@b|mU z1g0fT0*`Wyt{xDunfH31ihvo@5u?>}Z!8v5xnZ^B--$}5y!@F$aSq$crvy(7PGejk zuFUjpc~<)asjwUA>Q0wJzu2>{&DNLewPPY=j=LE+g9^o zvs7sI?}d&HyA5015^o2oWv6Z4{N1;{Bj>ZpT7mGbv$S}2CA5fD+~HzzU3{x)W}--m z{BQnkEZe&>4{ONJj8TYZJlwT;n&94*QnPw3KKHJ0$v?4{p-I{JjB)r4-sNZCaLiFN zk`B5#$?wrA_M&QK`G(hm7Juc9nl|p{K4K_0=g_0}`&*Y^o;sn>v-jna;%SxEA{Y7& zN7tWTCmmkMv2-t=ki(_M8%F+bIEHAPuU|URm(Rh0$1Og6LjiB$|LPr0 zJK8#aCOR6V<}6#ScK>wt0^+TN^O`eU|r*nZ{5pV(|A z7#J8-OI#yLQW7IdQmvAUQWHy38H@~!jC2jmbq!5I3=OP|O|48Uv<(cb3=HyE)(0bW il%!ha=BH$)RpQnlA1VHRqM|w%1B0ilpUXO@geCxoY_R?S literal 1143 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y)G68u9n7*maZnQ zmWGC|MwSMq&Xxui&ISg?MwU*FE-<~GdBr7(dC93Tdowdrte|==@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{a!(h>kcwMJCVO%nHsEo6TKF(q;{JcR zeNG=tn?6R=EtstqD7|OJfrJTP{!M2$e7ltO6uvKzv;*C{2ERJ2JzXM U_LmPW&;}I+p00i_>zopr03Ho`8UO$Q diff --git a/toxygen/smileys/default/25FE.png b/toxygen/smileys/default/25FE.png index 300b92dd3a559836094d65a76f287c07d4f9c69b..1de985b27c1812d3ce5fb658e5f7f13e41a8b3d6 100644 GIT binary patch delta 1044 zcmZ3@xr1YZWIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUb!TMeo$mz-@C3IR2)K z#yYVuoh*_`{#WH^zxz+zq{3rX+iI^a?sV93Ky_kW-G9gXkF=sZ%wERX+)PU1KfP{F zkM8W~t+7&{@5%SOr?>5y)uysLC3n)6%Am&Fjb2eTr8;LGt$r8Dt1Q!%Ah@~Yrsc_J zu3`FZt=E{&i1~fYt)Kl?Z{cDG2a_z@M-xi5;+`o_+Ae)UZlhDYw#Cy$y6TTO*8LXn zd=}dx{m63@M*!TGOKiTJk$qevT_~OQ+8`$J(HuQ<`^(8LjI+*Qx zCcJ$T6I1-<=2C@{=YP+iTP!Sl^5C*|>**8uXA49X{^;LzNA=tLC2Bf%AIj;?{~Bo; zvAX#3frHB)I@!42X7h~-bx%L<`k{VJviVwt@FgoaST-fFi0|+<=5Ss7s%d6YN=f_| z`4pDTO_@he$WOeZaG#O6h0~(VFIMG4WAk#pkOjQ1XICsEg!eT|$ zs7s3qelqXeH_`vVPadED?K0<*nww>MdnYUZ%$sepONNuPJw)EkC{5F^y=TkDO7D5~ zds@Y2Pk5cJ%-G`BdNx^I^23M5$}K5YrJu6uH5un-3taHHG*Li<@6p#4hZo4cTyEi1 zGSi3ukz%*7VwOquubw}yx6c1{i_hKRz2EkH(Yxc0PT$qfUd@@;rT_BydWSNut7rK& z*{zjj=lvJ@#k%rSSKI7qul_MGFiiAxaSYKozqEehN!~*aJg)N(YjjPRq&zi1L^qHl z_5!oa3I>)_7BhL+JyN;_r~i2J^kwzrGxe(@xLF!%H=d6aQz%r~u+;5@OI(l4nnMSA zt*@#pOw5q9DOg(msh53Um$;Bx|(-zm**M#K6FyTH+c}l9E`GYL#4+npl#`U}Ruq zq-$WVYiJT;XkcY*YGq=oZD3$!V8DO*++-9Dx%nxXX_dG&)b*9=GcYhnf@}!RpQtU% P#lYa{>gTe~DWM4fcBr$% literal 1195 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0OWL@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{nU@5ef7!DF*XdR zH!1iAYb<^6+b-sMs{qrSiv~O|cJY0-QT7zt+?+WpAXN0#uC^&b&F2c%DR68#$E9&v zsP=yJ+Uj}f>^+Y=bWU&Su-?eJYeDX{S<5aKJac3CopW2vH84WfC2&dUvE)6Ia|^GZ z+}vzlcs(}lK=R{Ftl8Hj*SzjB;3>_klK(Ta^p)Oy{*8|<*5^NZ@7KZpAiqCTJb|I} WUe)voI@^zeiVsg$KbLh*2~7Z5%aO_e diff --git a/toxygen/smileys/default/2600.png b/toxygen/smileys/default/2600.png index ad91b055b94dfd35ff49be78efeb5c2515e4ceef..fcbfe564743edf74c220173ebadafde4adbb11fd 100644 GIT binary patch delta 1268 zcmZqTUcfa$vYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{H$twR(H~)XN>i_Ac z|C5#fPvHOmp!NUDzW*f1f4F4xG{C~{w{}99f|NoudiN9fBV5k=> z3GxeO(4Szf!us;n@3p0u))bfM_8A(Lu^CwW_{z_~z`&T~?e1c+fPLvT1_lQ95>H=O z_Gj#TT&g^qo;+YD9TUE%t=)!sVqoUsK_l~V6f<&8X9|TjsZt) zx@fEu3sa$G)|u$@FO#R==RVh8n)AD~sQ#FX(~g2h5BvT97vz7;zU5&id2i2+l%&<6 z;d;ly&gO25wfby#tyK2Rg!9W3SL%iC7I_;Nvfy@(TJAmzzOaw3-&eIsvbh#?M7F(& zN%^eSZGPDCHDj9a^os3g%ckF0?9gDIrTk~Y(;F2lm#gO+Pgq;%6tBMH%Cfy;j|%cC zx|V#Z*E=NpPh=*`ilz$1*)B_uNp0c{I#>Iw@n?lpq5oD@QSlWP(@#Heui(*N5F8o$ zR!Ay1&#mID=;9Mi1#Qp!%iG1?zud@NImz|iHq^719=Ig??(&o7 z`L4>@pD&siY{;A;us(3xM76o0n>Y14Z&-8FxBHsUmzJ5XpVl6;3OAIp;j=YKm-a^PLjlNU4QNFC(Az2x#!FO3~b+_XH`dEMa)EnpYP z`+6e!%_7dqI#VHs7cMzx{fr(wWPPo1vS`(lqyL;3V=Yg7k(%`WhxDBK%zgu9#>s5f zfn0?zGhSo_7df z?A}>F4LST1CFbNBuKptA8oox_RlXpm_wHkMr8=Xtr3sBLla57-E$0;S);rm~Y1JvE z=Rz&9=DF9m`r6KVbkk01s~|2*OM`Ny8|HYf4CdHC=HsEmnM zEpd$~Nl7e8wMs5ZO)N=eFfuSQ(ls#GH8cq^G_W!@wK6r)HZZU{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy^iKi=EmkOCKhI9 zj)sP=MivGJ&WuvGBk8_Hg|P3H8U}{baJw^a4}Yb>P;ah%=GOPZ1h2i11Yh$H2h2$vwp+3XHMibq zl7D`I_4KV!mOV|$O_x7>5rlCZpW!cM$r zX8SFWt~$(695jn>xuvX diff --git a/toxygen/smileys/default/2601.png b/toxygen/smileys/default/2601.png index 14ee8fd38b463bf5a8ad5a42f1c171bdbc19dc26..d1f979d328302fad3bd4147581356798c43dd060 100644 GIT binary patch delta 1375 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 O1B0ilpUXO@geCy(RxLjO literal 1462 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 R%nB;aJYD@<);T3K0RZ>=*oFWA diff --git a/toxygen/smileys/default/2614.png b/toxygen/smileys/default/2614.png index 154540cc4e1aff7e3606ecca4ae56f86a6843b0f..2dad11e752c9c53108cff11de219b5e0fb429f5d 100644 GIT binary patch delta 1540 zcmdnNvyNwiWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817mi8Pl)Si03^Y+%UQz!$r}#s2ODuZInm z_ZlqkwK&{sb-Y(^eXqgxUW3j3rO_|fB)(o0{yInd!Sa~bs}o+WPI$dE8pK^1{d!H} z>*X;oo9yn()+?#VBB9(}4_7_=>9zlT{;r2<&X>#e?s@s2WvD*j<$p%M;Iv86X_eID z^~TTkgnYOb_i0D)2Zs9N3^hkvEblQi9%raNHre6LwVZD+Dt}$i`yLd0nW6e%PWrVC z;cupTKC3Oh$xwI5E8ybtfDg|b{(R{8|F-Sl%HaBsZRW2IXMDR``)h{xv&A8=Z`A(U zp7goK`1$(i54Y=o-ER1Gx9QJ@n2$9^&vzt$xn2KfRrFg~C6)jG{~PPQVParla3~4# z1H~Q#66o*=ICG+NTI=b5|86CJdOzjw^7jRKD-uOMf2yiFe*Clvd--jDLBW9gC2aNP z7v2fie61{aZ!wF3fq^l}+ueonKa=h~1_lQ95>H=O_Gj#TT&mnU4W&yN7?>KXLLy3n z63Z0|it^Jkb5a#bDhpB-Dsl@L7%Y0HhB|J$ZNPEowTNvK566?o7TdPIu8#foW9~$u zHnw$MTkm)Y+omG-Xk4ms|4pX1r&ly$XV zZKqUsa(pEce8 z=6a^MYLe4z(#s+x_F-G5szNU^ct^aG#Ie&&Zz0+3uGQ9LO?R7#Ok5 zXY$#!y_=3JMr^z1yGCuw;$1VtgnRNl_N<(`F*!*m^2F55T~br7xE>O7j*Yk7>9~)X z%|S(5qJcGqS#OqTsb9#V`tucwn;$U#Im}hY(8IQH-%Ux2(iE{Jn^fi=nacmnE58-Kwt)3N1EW*hn)~&kVKcTK zJSh3IJJbGp^slUgv9I1%PWP^kReAYu+0IqH(H|$&)o_*g-`Vw3K{Rc7yYl1cNp*~u zCUs4l61VUP0|Ub$PZ!4!iOb0e3`}lrY;1K3tZI%kIy)T?I0~F#n#os^FmrMx{2uTVE3;9^|<=e-vpZN>8c!E;X5`u#H z%eXjsx%)eMy812#BqvFxIS@bU8V^z}Y};!0A&#dD|5ojiM5UqeSrPgD1DV#3v{ zm(2}KjP%S6O^vs2*pifxoc{d5lSj`qpFe!|^hrWGyVdN86Ztt#{ggP^kinQOogKm$ zoo`*ef`NgrTe->l9otR@1_sp<*NBpo#FA92FVdQ&MBb@0QDENm;e9( literal 1592 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{_i2uEwrTCXU9I zCWeNtMwYH7W=@V~mM+fDMh31XpdGoFIT^S``o{FEmUY~tR^8CIx`#*R3-TEC@tAAx@)bkC?G#-?N zF1ju9iTTA!`;T9u61Hf~3){dRXL6@zI+M??+_}BY$;kDa7{uIP+Ir%xOUzG!2=nfoY~iO;Yt(FWrdDyN@wtQ)Bpm73 zJyUT`&(wpd`o|nfG*_(Jaa>TK##3``s@T078&w|E+&9R*$+NoExigf1UV5OPGhf}7 zWbb^|JF|N?TrHg8xo(D7P>^;ndy3?}q}6=8xvnQYpTeuJHu=F^hoInzQ$h@Qn4K9X zOcL$b#5HvtYd52@_oV5HCJh3I4B1LwpSYrAukZD*Wy-t>9dD#NR9Mch<*g9z;$F1v z(j1TFhq*6(dU-J8&VmVN%BprWlvFEU*vPYKfj&>4>dr1n*H)*>#2bs}-u%+C;mYK% znqMACx}G+h$9%Hxe9aBntr4wj8eQ)9KMqg$(EnpS<9tnT&Byi91yhZ@Dqs3D#nTu$sZZAYL$MSD+0817lTyPl)UP|Njr1xrr6+J9Qm`cAdDg z>%^54SMEbl{n3kewjDaR?a;Xc=WamI=KZHP??1iw_nz3e_r&tedm(7;j>GGA9$B(s7aT2Gw*!t2oVj`7 z*28D7-hcV}{rB(R|Ni~^{rk_0`q%HT+9J$v)v{{8!{jg3LxF2?E-YSQe|yo?X--#>Nn z#`_PS{`~#>|NsAYA3mMFaDDrM(`T;Sdh-0$`wt&KfByXC%a`{bK0JQ*>dfU^*X}+( zalZcA)!PrAy?pcT{l_b}ADlXW^~&vs8+RQ&dExryTlXJ4dGYx9tNV{%T)KJx#D!~X zcN{r!;l}1YM~|Poa{T=D;{P9;~=HK&IcHeF9$Zt^jyY~dw?5%=Ax9`+n@X~zvSX+GF2Z{Ta zS$;DxFfb;0ySp%Su*!NcFfg!}c>21sKV#?PQsq7R_`(DR2BwCpkcg6?#Bzm#qWrYX zoK%I9%7RpdirfMQ28-UQq28PBDDd2QEn>+ut%c?IN1n9lt8|M0Y%_bH|Chv)A0?kedN7EKNI=&CB`y>-F{Z{rSuD!{y^0*%a;nHyl{-V8H=~{{o4Efre}!FKqb0A;8h!Gbv%h zl%xrLeVx7C{vG~4UVYOh`c6!q5bojYect1Y$N4is0cXO3(h>qro;-V6UqeUhoTg@I zDD$n=>jJ~#CPqibEnB&iw{TToUTCRr>C}04cRsSUxovf8JCnC} dQ!>*kacd9>u4SF5sLsW};OXk;vd$@?2>@lI^b!C7 literal 1607 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy^cnP=B9?GCYH{Q zPKJiAMox~dmX@YQt}e!=E|%sd1~9#zdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*Ba9V(8409viFM!hoh~#fJ-2Yo5CV7H`j?y{H{C2 zI-9iDZhQT1*Jfku-Nxy^+)ga1y3M@*=Yu)#|2((8@47*U`>Bp#>bgy=D!)I!I=tO9 zrBCR0N3Y9_0%5I*Dr%k6kL>++wzu;t)1em?IrcZ(7cRIRE&S#1`@%zt-U&SiO)S48 zadt&H7%fgvT&wY-HWTcc>>#P9PTGcLP&Og-*Fbq-JQ=E(t5U7r+tR@5Z!*sHi^ zZMIYL7T(vh$`!MB)&JN#&%9)N^A25}{eM$z-&wcZxGp)Pja4ry;m~{mU#&?eSPeWK zOl~#2JN19ktxT|&)HqIpS_-xw)sT4hG{BJePY1Mp5ku$O8WMO zy`N3=;&yhn_MGxuZ#gwICGVr|q|8|x=X+P3Zk^}-;pe)>&i`V^p2&Fbv{{z+EaCU| z{H$3rvhy<*ES5ZJ(z7tc>blY<7A28c$DcMFap7oU*nN`c@)8v>!}+Il)F=I6ce_{n zW@UHC+j+iA)^#tb2-kgaM?+F%7niU8!jytT5!+_@{jL;!!f?CabIsOm%RXHzsi`|- z&|`P--|5K*ub0b+Ub^Yw%aONT*lv6GDUCx4jdNBxUNt$Rza+7Md+WpvvS$@q>pvYh zxAtXfqdW6r#aj}ecg=Zycp-!6bjKxL1=W5!eNU3ay255m(oSieckTAp^4kx* sZE~@{SiNes^2hy=0;go3$20LTSO+Q}lwMu(1XQ$py85}Sb4q9e0C_cDuK)l5 diff --git a/toxygen/smileys/default/261D.png b/toxygen/smileys/default/261D.png index caf5e7fb53bc6bbb2e3c429c5df1ac68f6a1b4ba..8458b0ef278d27a10d8eb0e00a5ec9505a52a071 100644 GIT binary patch delta 1399 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 A{{R30 diff --git a/toxygen/smileys/default/263A.png b/toxygen/smileys/default/263A.png index 8a3409d83e44fb5764fce2d7a731c64e2e771b54..5db95a5e98fab6c37ae7e1d233773416a2dbd309 100644 GIT binary patch delta 1812 zcmey&yN7RrWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CvI8Pl)SGe}?}(4F5YA{*nBBqfznkHIFGz5vKf|m55Sr=FFfWXues8_OzB>JRp$sP%F|5qA=xQiA3FIW7(l;i($1`t}x@qe+xOn-*ekqrMgI{&{n^MC!@ zJ^$bA`G0T5pY`_3!x&D_OTMc|8dX%4|_jcS#Wx8^6dljKEAm9_wS$E z2Nry|GX4MiwI41`yK`vKr^J`1~znXr2b;+k!_ufCd z@%zv3zyJO*FfbgRpZEW9)uDNLfBydc{qy_7I~U)6{P^%r{iV}ObN=57|9?B;cS zM;2s!-0S)OoYeo*3?KG*9bJ(AdAsZXotppmDE~j8{r{xd{|lD?FKPe3Aoc%(#s8D0 z|95EqU&{J_4a5IUO#csQ{6BAA|Nn~q|10YMud4mOZ1De_>HmZ3|7SA%U(4|SfZYG{ zCW}%S9<34me@XTK1>GmhWf#RV{GZG4e+$F^V=DhI8^1kOG%tm*%9>$bGV6x}1^+i| z|37W;|AxW;`%eE~cm03A?Em{!|6fh}f6?dvCe8mxjQ*b!{(qI>{}qP+M+{#6ikZg1 zz)){q666O;d<+Z>3|f8He_XRaxX(UFtyl4oFGU`+CMcL|-$vcs5xfq}im)7O>#89N`BinXv)i3bA% zQ)g93L`hI$xk5ovep+TuszOO+L8?MUZUF;>MSbto(CA5b6nOqT7qMizCfxT}@=W}y zPct7cS3Q6D^;fl<3y&qRWUc&K@bmBI@SlSFmw7+5_Ro*YHQY9PC3mQDaPjMH9~Evr zds{Z!<;Hupjf>A|HLvDbf5_tY8u841R(e{$wPtrEwmGH6C_OqU9lEdj@zvQ67xgl1 zZq3?r`>xsZ3H4JQ8cdUn-#jQ4-_y5THQ)5c?xVbwcNeVkZM)`_c<0NVq@}$78LPE~ znWxUR{w5YC_HKswuHX||*L;M}cc~ZZn>d}(=Chi|Eq(R!g7#__?VfN|!Htu%LxLpU znTq$E(!Miup8YME5R4WzL-mcs}RfghEHHW&a*LJ+-mkdh6k7+INB z``|;)N!CxI1_~<`=6fy>n|Zh8$f7J}(H9yqoi*v5+vmN|-6VB@*Yl|RgGnbQPLR>e zbevP?^e+Di^9K3G)88jBO5I$`(dY6o!@~aUxy5s$oLnc*s_ZBZU2M&; zuL-|a_XsW8lW}#8GP6|NhQ5|H?KW|5*RxDvWgMO9_h?;pQ@{bXlhW9>HTXl-e0 zt7>a)cXwfu41B~RbkWdo{A2~Z6+$O0shZK2vd1XaaxuuCsn5cVJVe;hJ(`UyA zL_~zd)K3bJQah~>9v2uH8XFuP9ULASd0*lDf(tjU+_`k?+P!<%F5bLzW$B3pSMFZE zece9s(UOzF%f5Xp(KcSO!f&=+^0PHJi=UmFZC^WU6&Lg3TTQLqV)~K0a&CSS{(JaZG%Q-e|yQz{EjrrIztFlT0lM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`Gu2HtFfvpy zx70Hucp-l$oBHmzd*{pIn-onpfiKVyje< zTcDScnPTN?XldwbWbA5a>T2X_XlQ8RW@Kz(VQk=J>f&T-Wa0`l1Djq$CvzibV-pJ} zH#c)bLsw%<7jt7rS4#_H3l}$23o~PwUeCPZlEl2^RG7V)nJHFKz2lDVA%BLAf3rPwMJDcRdat(m~Zz`(5F>EaktaVuo<>3r!@f#deS zJFfTcHLJAnoNTe)Z6$LSC)3IWEL9H{9%2;@?cg?dXgaX)Kcf>z$L$I#@~;5scy#B``@4WQ)hfXa9_>D-}}t{ zJ}79dU-)s)i;o9xs&pUcSK=05SNQmU#b1Z{AtLua*UtNuCU|boCli@!$w%%7re8kw zs>HZ9^Vg@PtJ9+s4*pZRQGWD~Z|2Nhm8b3dMYEnkg=ewQ>G{IO+|iZWO4Mot?cjid8{}(aZjH%&+SOxL<@mQ9Hm|* zFvRLoDg!8wk;#J=bOYNuqh*`J7>GJxOR=#CRCn>Yvi#u?)`}t*&Fs~%9 zrnJeuXT-$CjdFdsZ+B>JS^uq`&(7#1CSBlgj;@B$r>YIJsTiF0pi{?XHs{?D~7H z{P&c}z3VvqsQBBA@IS?$iaP{8J}LilWL~1fIyLj-FWO#czC2w}ojOPM#vi#)Mz`+% zIv-LL-Yn!Ep8(DZ`s zR~LQH)bEwEaWyM=qge8^bkEt~6~A@&-L7~j^Xj4Yj(nTu$sZZAYL$MSD+081H+#HpAgp@jd!ls-?~zB<5K0d^JSOM z7F{@zcjide$%Cm!_a+|N6}N9&)b7n;J2nPyTOY9HM&q3ujd$v=*WcP0ybU6>K41$( z0%FRm?JxiS`ulRnt4o#Fet-D=?)bZZKmOjCc=yhvyYG&@J70GB|DS&stFAoX^8Eez z4-T>t1`>d}i`m%qRIeyQW~-o!%(GmrlN_y1(| znO$-FzQ6wd?7*{aQM=!qd;S0a|5xW;Zw}k>VC((+Tkb<#fA2a+4Fdy%cu9~SC^Q+6 zfakMk&z^fdf5PMXXd43q17ni6yNho_hC(VsJp%)KiKnkC`!jYvE>+G$eIlI<3{2Hk zArU1(iRB6fMfqu&IjIUIl?AB^6}bfr3>LjpLmemGG2qzqxy#0piRDwV&9qYQ(7wt# zsmx=qzgA2>yCX}5dC@|~nRWmFxY|Do-#RJh(%z~~IZ6E5>*n;VopC#9x5=};_HzB_ zczz~H)lYg9w8it%Gf&oSSH&*9TKzadFuqhjE8<}QOZ$war_Xtst>08-Dzil_VVdR9 z-MQO#xAZYFUR?3D!};1ui!<&j@_7o?M>uW$3baC_J&y>+d{>-wXg7!Xk;y9D0jxir zrnxRXBDaY*NUrKxU+F*TLg}>E1~G|`=IHU;Uru&moPDT%Ro3(?Gun(UtzK6#X^qG7 z-#)Qhzj>n8}F(b~oxR@eQ6 z?WdT;WWNIeKi1Dk<(tDSx;pKM$^^|VW=dBkBuziwF{dF@!QI(w%M#YYr}ey*KOO8C zpBo$el{a!&yqmjgv7v?Pr1#rA7oVOwp}_NNqDu1gM`aoXy${!>Uenzo;S|^B$iy+X zLpOO6PesK+yC|c%R+U@#2RO{PU@$rLO8LJ+%$7No5@#4(KCk(rVUtp*%;LUz`S}y~ zb$_qC|6(_9`OEFkW6%99op|A|Z?Wm@`t6StYk!NJ@LRa+r^3_~=RDe5OJ?7 z<-xkr4h9B>Wu7jMArhBkkGYB+GGJgm(7!vI%dwfAciqvu@9V|mF0l+wbG&ox`rnpQJP}cBWY8>g6sW;g_FpDX&oN34_=3#XfAm2SCqHfe#-%U*#nlP<_ruBswJ)wB`Jv|saDBF zsfi`23`Pb<_dYbE=*Z1a$xN%nt)slz!+xTo QIu`?jr>mdKI;Vst03YX(8~^|S literal 1389 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 V@%IxI)wviLJYD@<);T3K0RSe$lXw6C literal 1421 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y%vrZ22Q4KF0LjZ zovua}7A^*su12O7hOQP)W@fH1y`Fi+C5d^-sW5vpGgGXfdY$p=wQ?>>O)SYT3dzsU zfu(?ejQo=P;*9(P1?ONh1`yp;U%Vogx=fWzL(CABECEH%ZgC_h&L9KKeWSnM}7 zHZeDFbTo8!g}A`Nz{Snc!pzdl(89pd($!cAsyBt4Fw?hFu+ax44y42a6AE&112Mtr z8kENE6cCvwH7~_hsYuD*uG1;>5Ca3_UQZXtkcwMNf_=T20!3^;%amT>QSI5}?;6Au&b|Fbo=My>AYj%v$HZ?7Qxp9j?lIqMZkmuRaINw1 z?8@TzJD=~}8SbT<`cYosoZ~rZzrBq2YMwe?lf3;PySlm6bsF_f@N8YE`G~FXX2F(^6T#*EB2-nIcb45dXn#354*pRa<14R{_{!g-iE7< zLIsN^R53mERO@;vB*e&i?S?=5m)*^$=5potv%Bgg9lHLwwQBt#sq8JA0@gmgc~0_| z+T#z$YZdsC)^vWHu`$0_bjr%3*RICR3s36kcYDaSW5K8Uvv2t+Pv7%4Tk|k{njU+a^0^efeJ`ZS3j3^P6nTu$sZZAYL$MSD+081H*v;pAgp@jd!ls-?~zB<5K0d^JSOM z7F{@zcjide$%Cm!_a+|N6}N9&)b7n;J2nPyTOY9HM&q3ujd$v=*WcP0ybU6>K41$( z0%FRKr{CWld-rJ3<6FIVuQ%Pg)qC&B(kJhZzq?dx0c6U*W;+V0Ofq{X&#M9T6{TVwS zmnz5BP8E3u2BzAokcg6?#Bzm#qWrYXoK%I9%7RpdirfMQ28-UQp^lU8DDdogEm~j6 zG_8f@{6~pm@6f)=x+sIkU#s?h;+`p(bW%9eDlXca;ZFqC) zI?)tM2SysxQw&Q=%wA+~zS(eUHPboRZPchx{?0f(8P_w>1w zbK+9ICf&%;J&^NIwr%cjE+n%rkJ<9nMdoCw zyxPe<-388eo??uv7R22U3p{Dqp4&Y2xK;6Si~qelc(<2YtScz=o8CVEso|yu|FqDp z-kCnhhNraGRZLo=vHbUskMBM_s!e(05Vz!5amt||`N9m6J1u^6_Ek7WI&^$JAF03^ zz^ZiP)r%0<`t}DOqqetR*zhhRC&g5{`k9i{8s3ooPi+{!^UvbY&)8-L|$2s2nK@l$uQ^pofW8A+zB6KfQ@MJ(8}dPJ4?xU+o>o_kZ|jJ8WD)3QdHrYRGa zxIX?QUG&$Xj_Egp1>@!aip(Zg<~sHrJ9sj%X1lLc?7Ef~?^XQjlAA@gGcS5Pq3Gn~ z?GZ;6pH#`HG+bD@v2u<|J+oY#0l(+6$&%-UgnBFZ~6!tT?bO!b1^V@ My85}Sb4q9e0M*4+k^lez literal 1341 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y+)2^j+Pb%2CgO` zovucfhHh?-uExeDj*hMt#+K$Vy`Fi+C5d^-sW5vpGgGX5#_bdknI|0QH4_bDQlPS!x@s*zUd(W(ky6YWi^LsI&Y5hec*5oOn$ae)|6|KPUY%@3gzk zR}Uno-P`m1nfaNX*Y5%j#?M-k?Cc)rzMTKbZo3V9&!1P>UweMcNx$ZCPlxognRh*} zY~~G_Dm81V*$TG>X6?7N589qkaNj#&dFly6`ze!i^jUg@&6VW+H%mnFDQ+?9IgrC} z<rQ{`WPR`h7vWdY-?Qx$?|^#_J+&&3k4-?yDmSq0?pIa&t;ucLK6U4vfJYT diff --git a/toxygen/smileys/default/264B.png b/toxygen/smileys/default/264B.png index b02cca61315f9d0c2d3aa48ab1999b329d32097d..07535932e2d419d3f83cf65dafebae0a269a22ab 100644 GIT binary patch delta 1493 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 CKr}x9 diff --git a/toxygen/smileys/default/264C.png b/toxygen/smileys/default/264C.png index 6354aa459752f41af678dfa4b9e53efa62d31595..7a4428602dbfa61ef27d7b8cf897baf750409ac3 100644 GIT binary patch delta 1411 zcmeC~ej*Y?F)(334(Rk-ZAU-?}@xKXR9uJy8hwspMU@U{<}Ht*7Zp@zTEwMBr-zWOg;MN$DiZHr_R=$zc%&y`-|`PCLa3p?e~#_6Gw87 zf4ueKe8;6-ar<5#d+~JN(*xOupYDC~^7xBCKmY7YIk+uq_nx!^ug|=Ceg4hgUw`*y z9eRG``R1@454JtHxg6xZ^#NP{|Nk#^R<@LZfkC_^$PX0U3?NVsA{ZDLJfA&#_T20F z6CT$`+ZY%a7?Zr+UE)}-CGKWmU|=ut^mS!_#?Hs3%6X_yq?3Vxsk$m8q9iD>T%n*S zKP@vSRiUJ^AXT9vw}64cqIYVjrQ)$ZUS$6W4 zO6KP!Ot+J!Zu#-bV#d+%yPKuLEJO|}roG%$Jn3xrnm8WzwX8FA=RCS=c01O|y|E$V zO4Sj?*C8^`lqd0EpMHv*AKJXtN}pFIM@sKV@s`{ROF~OazS*#x zx5~TBRpi>$<)FYFR?v9dq&dK3mEH}_Ft_in{~g^7v~Ps1`6G6c_ml1ht>7(HIS=Z0 zvYcy_%5I!9wO#D&ip1p;qK>ROEjfd&$YhC&ca(_Ue#PtjPlPwjZ+!WG0;AO3xsH9u zj-H%Yv)xxJ*3IeAgja_>j_DmOIiX>#mw$5d_6v`Mifr|S9A1Rnu*sXm$gh9H-FsQE z`ucCvg)S_YVvy3bY}3%-dt^HQHA7u+k3g*2<@)A|6G=)gy=ngQC+_$Dz4HEx-Mr;5 zw?Ef;{!dD!#6ELfTCtYgrS1NWZ$wtj<`3d4bF;G9ulj|3rB_#nepk?D1_p-xo-U3d z5|?9-RR%i-%CvvfSM=V=;i+^r^m>ub%Z^tuMO$QkZ(Z`}?|$C>JB@iSKkrYMdtk3y zvP<|zo_oC0Nd3wFeuHn7C{nhwaf=aJz)sJ@^GqcY7m{Gvp({A7~(|NNw z0|SFRdP{kVo554k%5tsu7SC(p-G6Lft9hTm8p@ofq|8Q0o%O~%_tgj e^HVa@DsgKlZ}zaCsHo1xz~JfX=d#Wzp$PyPpRynT literal 1422 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 UXVXMQbuI=5Pgg&ebxsLQ09^KNH~;_u literal 1362 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{4wFrbZ^tj;oPL4*#&d$a#y`Fi+C5d^-sW5vpGgGXfdM)wlwQ?>>O)SYT3dzsU zfu(?ejQo=P;*9(P1?ONh1`yp;U%Vogx=fWzL(CABECEH%ZgC_h&L9KKeW*z7kp zF*k5@G<0@_xWK}|#m&*e%+k!z!obqf)mRCtH-(%q)3;Nw(FY|Cq{IRf3UYA+F~R8? zl*a885Sb@6FU3}=NXg#r*y?A07#J9vJY5_^DsC;A?C8zpD02M&EekyCM9s77eVo$F zo6jYPOUdzFtQ0NWYrB8@m6bnuuXJ(kUFG;iVAacQOi4PgilQ7->{=dqUi|hinmtM9 z)x#SK9^1LYZ)et(sg*d)wh~ARtUg{{ux(1XsxfXr?^xC|yQgD`>>pP2no#llrb;6(b&um>Da?&ZHwW{&x mtptzUGmd?mQ@Z>a*%+b~KfRr5_UtpLl=F1;b6Mw<&;$T_JM2OL diff --git a/toxygen/smileys/default/264E.png b/toxygen/smileys/default/264E.png index e000b39c9e48a8a4a7a81ac0748ba9c73bc20557..80fa2eb527198722551d6ca417cc9564776fcad6 100644 GIT binary patch delta 1264 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 diff --git a/toxygen/smileys/default/264F.png b/toxygen/smileys/default/264F.png index 82eb8eb5b23f1b787126268054ac36607dc390c7..ad41780286f85ecdc4901a2bca8fd0db9bf20a3f 100644 GIT binary patch delta 1243 zcmeC;YUi3DS@Ch>&U>cv7h@-A}a#}gF>=LkS_y6l^O#>Lkk1L zF9rsNh8GMBr3MTPuM!v-tY$DUh!@P+6==i2z_2mEC&cwe~ej*Y?F)(334(Rk-ZXa_5IoR?~lKoFS~qu%H98e{(ZXk>G{?d|Ns9#TXf-U>BTok-kiug zbFTIxvSs%-+<$TO#lEzI+oE<~UvT5=(=RVhyxbhNPS4sIx0808 zJlkt8*ME-Zcaqe!M?qUWFFo^Q-FH>&(yP_UiGuN^`dJc!0W94!mYzQ6X|{e-m8s4a zv4m-sN4Mv0+kHfam1$Gp>xu1aiXRoo&#Y-_d@HE^CF0=9klcDzLHDA&?VeokmF`UN zpQPg;dWAb$Y^u=v4Y?aW%}9SI@%(bf?Skm`$6VsiOLqQ@lvC#ZArcn4d&)5rwZ(g0 zby=V6D%#cGUEVJKUOAb)XqxIfW#MC+eHl`oJi4GR@v&=DgTUwcn;p0&a5-(zZC=UB zbMUcg{!E96aslZ`-=y6#NH!cb+Xlu*60j7x8w z>aRCW9Cuy+TUC@ST5R#-3&(S#SBb(-+I>F`o{mlYua|Y`lI%O}CzI!gI&V$QlVZ7H z;?yxck+>`@{+3zh@y}WXA_-}63FP+Q|^@}~+CtB`kc=GfN^B3R3mW+8`{0|ek&owPR zc~J7_?oIQ5_3pb}FuksN-qSnr?3{P!n}lz#)RMclJ-+dcNU*v65}{W;MHT#=vawP3}^fBb3>pKQ5P+*m)) z{?w0pIf0(fO-yS0yApD&X6sK=+wAqAX6wD2lTX4Vo!(^V=Ol(IEM0PV`Ic1yUK6vg z2)GLFbdxQVnVfC1VC%B?i|yu?3U?Oo6uLV}?9!ay%DIcbpLty_z!x_&@kTB4ky@qY zCv8kuGcYiymbgZgq$HN4S|t~yCYGc!7#SED=^B{px*3{;7#dg^n_8JzXd4(<85rcT ntOw;pgpS<&l+3hBxDE^r@{!{2Cn~CQF)(<#`njxgN@xNAt=&4k literal 1290 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 G5}E)eSo5s_ diff --git a/toxygen/smileys/default/2651.png b/toxygen/smileys/default/2651.png index ce713d815a48c454cf6883b0eb643a1d0a0c351f..7e40bc32212eb3e8c6decfb110ada75e1a40f31c 100644 GIT binary patch delta 1370 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 C_d&-1 literal 1274 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y^fBiM$TqVCaxwR zovubsMg~UC7ABTP7M8AV&PHx9y`Fi+C5d^-sW5vpGgGXfdX4ewwQ?>>O)SYT3dzsU zfu(?ejQo=P;*9(P1?ONh1`yp;U%Vogx=fWzL(CABECEH%ZgC_h&L9KKeWSnM}7 zHZeDFbTo8!g}A`Nz{Snc!pzdl(89pd($!cAsyBt4Fw?hFu+ax44y42a6AE&112Mtr z8kENE6cCvwH7~_hsYuD*j-B~G9|HrUmZytjNX4zBJr^t58-)(A9GU0vho|HJ`Tsp%Z^AT{i%AozCfI3C+h70RbJEMI4R*`w>;D@bFi^hWe910}=hoR*0(Qn1 zxbpAR2&Db`{av4#V{`W=qZ2J2Mtf{NiRIYz{C}lUX6GY+dcS?Wq>FBg}Q=1u|W;`49Ohh&LE4XJK!yYT`P)_^$s!wdNmL3jgHZ)XcC;u=;UW zAzlCI0p;_D3o4(Pw%PAjh?}%cIDLoBW34&*soHIa*k3ZUAKJF!?iAKX@m$OkS!c|e xH+iDC!voec4I&xGCMY->Yp7gy;AvoBW@ufur-<*Evn8lB@^tlcS?83{1OQ0qvN-?% diff --git a/toxygen/smileys/default/2653.png b/toxygen/smileys/default/2653.png index 85c701aa06edb1d245790f41b59dd5ec0cba78b0..a4da18196e8728a576e1f3e9eb77bcab98f52710 100644 GIT binary patch delta 1333 zcmaFL^?+-FWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H*>^pAgp@jd!ls-?~zB<5K0d^JSOM z7F{@zcjide$%Cm!_a+|N6}N9&)b7n;J2nPyTOY9HM&q3ujd$v=*WcP0ybU6>K41$( z0%FRyhuJCU8=lxv**t5&%bYW-MP_m`&RGW^JSOc9D4KS;OkfWU%fs0 z=4{c0Ta#~pd;0C$qp!E8-ae6c=5)pRKR^Eb{_^{5)rBKjC;$Kde=zmvvAmOizyJC5 z`PcEnQ+pE+ovUxZ`1R43&-Xu_@3^!pZr|e_j~;D*^kB=wr@NnQi`sp-_~@^XzYZ20 z*`IUxaOttlVLLwF`*^DL%*p1{5EnN~e(hmkU=S|}@&kn+0}}9j_UzenujfyATpw*? zU|?WO@^*JIS-J7$Uj_yS_7YEDSN3P@d|dS^Rw=G7_ZS$M>Z(E_N`ey06$*;-(=u~X z6-p`#QWYw43m6zIdZ&g4Zn~|&^XIx|6^jlhXWwIq&(~+xeftsX_-IGp?S03L8Mzlu zEn{6a|9<_2?LS0yRc4py&&x5~*q$2hcWm0(wYT(oE8q7YUu=9h&iC-7*iB}hw`^7( z*dC!;ue;C6EbU|H_cd*TjIJ9xBX_--lk!<=t_sh;Hx;Ra^Q002<43*D%hj@RgM6#@Cs!EJ@Q9UlRjWd6 zf6O15-MRK~$DyN^v2u0Oj-Oz%n;CjlZ>eGLQH}LG9(mSpS~Ta~_{_LEFzb2F zqNEU;4F;dIB$$QLr8hi2uwqVQrh>b(*On!$g+G~h?wdIOfJfPsDu&DT{}q)ZO3K?T zq}bKnKi)OVEZHd}q_@&;PRf~}JA9H`9)Fl-Q`#LWDsLi_5Oa_ zb9(1~J)vFuGuP#P4zpP!KcDHQX8p80I}gh%l21SUnRY?{T5#8#hUurbGcYjB_H=O! zk+>Xt%vI=600Ya#gLh3mINnL!QDyO%@cVw-Pj0TR?Ju$(#B0p?#ns;6@hkiYljkI( zNY4{bCACu`^)y_!EtwK2trfD?@M~Ly^{gvd#&g$2uvcA?H#j^oLOmo@w*Euxj0+|8 z$Igf+-Ho`Ebv9>PlI@L){|?CbC+)pp@^(%9jn#F@wFW$g_edW)obso`uKp-fhk9K6 z$%+rWPV2QT=QPx=XJBAZEpd$~Nl7e8wMs5ZO)N=eFfuSQ(ls#GH8cq^G_W!@wK6f& sHZZUxfmEcUHx3vIVCg!06AxSKmY&$ literal 1381 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y-w!FrY5ci2CgO` zovubsP9{!n7ADRXrsifQre@ACy`Fi+C5d^-sW5vpGgGX5#_bdknI|dl|Eb;Kau&b((YhjC=V#dv7u2(jxXIxdwk`|L_5jTipX?hwG7wopVy`~ zt>LdrfA;6czT(F+ckiCF{UjT?Go_5r<%Be5=xh1b>a;mFB!=t#; zgC>a$x6%!F@fNJ#8Sy>zxaN|;N((W=Z8{G2XPUq6*~$M{n@O&JzR5hNFa_tO(`ub9 zquQ?8GhK`J*gr{Q@vorE6hew4$Yo8?N+44m%mkirZ3F)wbeJTbKA=i=a~QG;r*{ldqjVJYusCT zC+GMFMblkuPiCjc=kRc*T`4Zz@Xt2B{a~WQ+pYI(8+5Y2r_PpkRg7eL&3#hnO&oX8 z_Mh)o`=kdM-eP;eVKLFlzxnIR%_cc^);%erYnB%nWro-+j6A|8w4y^OLO*qN$GhVX zZ_c<_(e*CnWyqhy(F;EMysjvcy?&ej@J;8!Hi0Gf^O<-UE_As?ZQ&A*0u_9ou6{1- HoD!MNn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUb!TG*+?gMopmsVXF*Bq*_5p`a)~Ei)%op`@}PRiUCjw}64cqIYU&?6tWD95v~p zu}&;ZCyTDk*go&_alQS{MfGd0Z;Ssbv$)0JfREDOKmVHd9Sz+w;mxJJ6`PJ8F+06( zPLJ;F=&jCDpD%9DeSKy__~i{L)7SV;GV|B^pd04dy}qwCvgWGt>O>a@L7k*&dhF^q z_nvrlbi;+HhMR{p_vO}aKdkypLtsL0RKHEiTw(q16Hi+6>0Tjp~wOuT#X#)&MmZ)aRhBBWRRJ5k9Xmp_X`K4Y8dl;8=$T}&6^ zdN^*(TQj$*JDYEAtdvvv#s4xkQ(kEB+1Vc2*;93+Md7HR{jZ|%8~eLwZ76B~&GYlc z`Bev_=FVngEbA3`WSJsuk{K#{YF@WMedO`HD93dkfr1h#2RZZ#W~T{oEqv8ve)L2@ z{0n}wrs#v&M^DJlyrXcRiMd6fMR8Nh3n`0PrHhzi#S719=zKVM#%SIRy%Q!al}yVT zWtyf;SmOHl6MIpu^8ALUJU;*1c`gK$vsp?>tGj=^Tb5a3DI~v;P+fMc~baqZ|)a&npcRwd0672&h?^?ugA{k zR-i%G@o?75tV|M*95yKVRQUMLpSa)aw`cyB(1WpGzFC~!Y0odT%RX~m-sg~-Nc%e0 zq5z+}emeiW{qreeqt)wRGCc>&EvROh0efd+g}7 zb<78J%fsL7);C;GAN6EYjGkfyL(dNRP0wvz94cqNPFg!*!?z=kC(ONQtzj@XCU@fX zmT4zKebjda6d0f5$`sMmNjNp7!Q77RJYR&*e|7NRvYP+8tNcr8+l$P1#aL!C#&0qgJ92Nz5xx44I+k&+esvYS z8<%T^EOIIp+y8;nT(d&!{-Mv?7#J8-OI#yLQW8s2t&)pU6H8JVj0}v7bPddP4NXD} z4Xlh!txU|d4GgRd3}*P;--V(fH$NpatrE9}mxAeR3=@^qxEL5bUHx3vIVCg!0PKSR A!~g&Q literal 1382 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y~b{4MwSLnCaxx~ zPKJiAMvmqNE>1?4u8x+jE~ajlmN31ZdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{$T;8na%s>Ye`N#TYa1`0r;7wmTjP zS-ASioAI(S%Ws~4-m>qpt?NHlwz&0-`3<}h2QtlOmmW+%@cPjC=UeAne-?}|o2KY_ zDNIMK_~7kCLmtC5VKVcUU(S5i9Y0%Wg;v-02Id{y`?ya(h;CqHsQn@w!Wx_aDmWQD MUHx3vIVCg!04#p#5dZ)H diff --git a/toxygen/smileys/default/2663.png b/toxygen/smileys/default/2663.png index 4dd8e0b23b0be76a987a69835a0ff64d04ad2d69..43b0f134e26101249a98eb86aeffb82627bde64c 100644 GIT binary patch delta 1184 zcmeytb%t|-WIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUbEE4tm)n1O+*zA7Z5Bq*_5p`a)~Ei)%op`@}PRiUCjw}64cqIYU&?6tWD95v~p zu}&;ZCyVx&KU@3!%f{318@?Z}%KaVMucp@0C@J*v`FHs*y6cwpKGC~8TRht5P1@C| z-5W!TUvE2^aPQgMve{=O-Y+u@ie1I0X|-p?fo+lMx%;fd!ajy>U)3hY=enUIa#>Ey zlXt#h`E9LcOlQROKGx@!-;PP@V`99x;%tZWwc1Ci^Cs@OoA5?V`^(lQ?_8@a#l8p2 zo=*t5=Tf0CPpO+Bbb|gdvA~rVkKd48Jm>Qn;kWhTm}wTViahe@`ZCPwElAC$3d@)_X|2-^i?>B;Ia!u)};wJ!{L1|F&E$ z3@a5_qWzayNi-cTz9+_%c-b+>Wvg)Y6(x>qj4SPv{0_X;6@JK^a@)a**Kn!_!9Q*Yj=FLkBDY!H8DnBaN@qROtfiHK&hkuMWa>v zulSQ$Hn-)upO&8(qY%&ZSm{mTkE54cnB5o4uW$%IXYpvGfVXAxR)*HoRV>R~`kJO_ zcyd2_#a>jeEZ;C)+2F6dk;CF$+D9(lurQr8zihhWa^-~&Cd|rIxv5=f6=HGpS#Ig| zdfl{*oICfM2|2uQ$T>SV<-lR~UxAaJ2Ao{|x0T7NNWe4m!-a*(&WjE@eA;lDFV|A_ zWx|Su44W(-@Hh#^Rr|_M+^_X};k_5DS<6>#KOTARbLE5!UpZu=j-{^Iz02eYn~ z-SR=arAD&%{;PiBUc0_)%FZue85tNDRXklBLp09U*G@R-#S|#gwtr`_chr>*SHX$a zw>5ODo9`VsR?hK?S7}l2`bRAL9QrT5NaL7P5I8eR_e)>;yWGb&ST6oFd;Z^|d;a6O zAO35c?z(X7+Kn4+tnJ%=W^TS^nbaO=`;tfD)YkGj`5!ZD#T&xkRhty8RlT3Z$bCRi zVXISnkLt+@md5qZcur25dX)c@RsORzPF)`ke7W@P(KBUS2|ui!VkTOX)c|EpmcOK#}pm2gwqKyJi?!)yF^MEYegTe~DWM4fKaUg! literal 1400 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 ngTe~DWM4fVhXG* literal 1582 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 literal 1336 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z2=6dmL^6nF0Ll7 z#)gKjMi!=KMy`$)j!v%5t|o4#7BIb@dBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{2_(F_cX8J;eVAr-fhQf7Ra-zao|<;XmTKRg}( zCpXM@{Ay@mG{TpuGWY8%hEPnmT+9gd@z0vn}<`( zg|G7`IV7;`dLSP*^YTU?9tFwINB@7Ab>Pqk_T5avnQ9!5&)YL?{J{RgQ$=RcNrpt{ zga4((47N@CeZFCxSW95cgVm?cTzG%x%!X%w@>|ri_X)pm<#R7%+rHKQ;5oZ*ALN^- z+6W*1FXp#n&#N`^Jmv5Hx|%a6E>Jpiq~NdIg>Ux%CD*9eI|gbjJacHnow>aS3vcOf zG5En-!YFdT>T|!NQ`MZQQt*;pocf{f2Wo9wa4% zrlut%{NrDi?_O+}lJ}c$TKTO{%@4!*_?b7!xwUP%uEBZo(3H7}>-fc+yEKds&%ItR zIcYlsukByqbf=^gkGf7%eF4U}hA)hhZ~qrjsAD|Bqw-+6!yKL@W)=yC2fc@H=PW!P Q1}e)uUHx3vIVCg!0D=3~6aWAK diff --git a/toxygen/smileys/default/2693.png b/toxygen/smileys/default/2693.png index 0a2095000eae696ceb71d6b9140db7f522a9eab6..f87253d8f447ccbd224b2fb06f3848faead5c48a 100644 GIT binary patch delta 1702 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 diff --git a/toxygen/smileys/default/26A0.png b/toxygen/smileys/default/26A0.png index da04fd67f315def52aa7bbfda6f5f6f14d5d803c..ec5d59f7a8ef319e0e98961f62a263cf1f13cd69 100644 GIT binary patch delta 1387 zcmX@bJ&k*UWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0810#2UPl)UPf6q=#V0f?&gzm3pxVM_& z&PouvwVdJRVutGr8LrF&p-XcZF3n-MFq7fjbcXtK(;3c80ijcq7*0)MI5`o4?yqI| z{(QxolM(-a-?_hz;qT|;|9_nM|Ks%E&&NJotN;J~L}jkshpV;sH_QM3cJ%+JHQD90 z-yhHU|6%dJw>>dwIZyU_{{OJ#|MQA}PqVy(BK|)w|Nn8xZD9TUE%t=)!sVqoUsK_l~V6f<& z8XCB1wgJzd@0wLC$|omT@-hCoKC|xIkGorioj7yj5Bo4gUh`T#)zg0ee~@tdef35bGqK*I z-;(;QnN}rD?l8Nn`dTV<`ij2ZeS&AyPu6_D`aNKss*-~A=VLY{lHBdp=by;3ZDTLL zvC+9`<+iDFT25|#p{BcO^{KX+O@3SQ8utOkrhN_kSL1ki+T=93OWYn>H&}?73F!rLcyzBQ>n~MzOxfhTM#=?&o9+>bGk? zWKH?qR-{#QN{8XgqrQgQtFxCssFw;`v6)-(rQWak&B2|Gj~-l>(A#odc^zlan~x_? z$0q*Q%Q~>k`cC)>b^Gp(*`Fn(SR#5AO}J7|o88R0Hr3Wlansk#MY9y#OO4m6?byBW z7OiX(jR=OKf&QvDsFPz!lL!4d3>q&bY;cI z6SD4l{tkZjGC(BVVOo^i{T*G3Q+`UQG%OL`xYU0VGrxYtGL_GsC%gZ*HkCYMSo%}r zl$y!y8xfxhw$vW7&Pd$(!0W_>ubWSCSuiyhIoAJ6x_!RNJ6`mTYySDoF}GeTcz#zu zdo_1XSNqq;!3(M%T{_FYa)b3=8Oi&TzHlGc=$ho_b2^%VfnlSki(`ny<>Z6|d^2Xu zoH^mAPSB-GK{_5xYm7E+GFro7H+#;WrbUxvtYaFSBV9v7U3;02JrB8l;mV~;R|1~5 za1~^}e(~z%%lcOt#g0Gj{9(wj~;qBI8AC4FVdQ I&MBb@08U{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy)I6cZpMa|CXU9I zriO;DMotEfmX-!)#!gOVh8Awl<}kgUdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{aN#LYNyS=am~ zu_a4Yb&a-65Spc3THlijTyf>G}ecEdyr}X2RW@Rr# z!g&kDc(`uW- z|2E!z)YlLm`*`1Z2g~*ktt>W@{FS~>zAG4tznnZh&*|oWj_UW853b$#oS)5pGrK>F zkJUnKO2MN)yffm}Hs6^%ZNJ~E!mYL?zjq`aSa+uRW>bKI^)`io?V4%fViywUuwDId zOK~Rqq}bZ+52oEeymi}yk4LQL?TZonart=^>xNU-0lZg#{fM`H&i|w0x0pzM`eAA7 zr?0o3yfY_jj$OnB6E}UK$#yo5&k{1L{uK*O&pf$g>2dd4X@7(g7$jRCs8=7=JqapZ NJzf1=);T3K0RYY$9RUCU diff --git a/toxygen/smileys/default/26A1.png b/toxygen/smileys/default/26A1.png index aa730a7140f099995dd173072b65a914bcc23b2b..a753ef441ead7dd3e82650aa355cfc89a7f89d4a 100644 GIT binary patch literal 1368 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhPMGeA+G=b z|No!OfCV*3{V#!uPql|CSQzxbl?%jP8UMclEW17jp=?{(|CNRy*=&aY<*fhLXM)(x z4F8wMfEb+&{}=d!7}M-PWGn0cHYE@v7iNgY|F_%z zKb`#l;i~^T82+D2`M=xa{}S#0|NnP2FTBaXz+hMs;_2(k{*0ZEOO-F?RihRI15-^^NJL3cV!1*=QGQxxPO3slWkIS!MQ#BDgGKMu zP{(bz4LI(+7O`#O;aKw6V%pZ%)v@1x$Ob&x(Rcgbt;%gp0>%eC{OtGt_qhLORgiGL z&6=odu95OlJ{sDYftQzAmoKWdk?VJ4sSYmmHq^Uh>AhWt=l&{P&#hNCY}l}NlX&1B z!3mPx9Tgw^W}2_w6BVj+gTjS#XIlM8iIZ5-QcEfj_+KGvwEvp6YX=Uu$FvU_? zMKAKkO?OeLB_e8{blGC#XWmh`&&bTvW27u{$h0j%a1+aH)eG7>XLmk`GLVg7com*z z$-La5%yE-<(B6Wl%scl^^gmF;=J&t7Ct=;Yre_`ba~_@Kzq@1k>4_5xJ$efsNu=7# zay{lb5pw#??DI*&LDptM4wqJLP%WFp#9wbv=J{;$r0~DqoR@a8oe-__2)r2d<4FFs z#BF~w9D5w3HYEtKg&j)nl$tYf|BY{(?_COKEx&mCv(EC5RvwrBdKQG%&iUrLm9^!ewHUX}?zo1BiR9rCCV3=B3d=JwXkHY_xVce-@!+PQlNFM4`OotZwd zw5+hS`1^+`lie>ZeeiUxM#quxpl^W>owvDN4eynnxqjWklYNi!baTZ-_Vwv0bH)4L zXXs>ScrbzcQ+6%?A_fKq)e_f;l9a@fRIB8o)Wnih1|tI_BV7Y?T|<))Ljx;gQ!7(b zZ36=<1B1hr=Xg;x;*P=7)z4*}Q$iB}RlaSj literal 1387 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy-p?u#s7LlIsX58zs|*l3MxmJ zsc5X>$nu`9WBfy&(Ngx- z0_HDn4r^JBw(Pg{IrO|t=*`5uq&V$@0{z_c>X!HAelI!}b@b7U7rr7hxX$iy+kUsD z;}vhZoOS9xk?ci}=07`k;6k30f!wj)>uw*MOM`!$%xj3NWKXMcQZe!PkiFEftG1E* z;uq1Pg^EqdLHoNF=~T-{>iXt2Jg`_5(WMmOx2@g8t3WDrT|OflL!j0T*3^LYmZ0L# M)78&qol`;+01m3)b^rhX diff --git a/toxygen/smileys/default/26AA.png b/toxygen/smileys/default/26AA.png index 5a7d5c3798e1cf2e62bbcf750119ffed1f03c148..d6dc51e369b8265b0b9e13311d67c9ba11be2e74 100644 GIT binary patch delta 1184 zcmZqTKEpXdvYv&3fnn<}^H&TE3@qu6zK#qG8~eHcB(gFvFeoH@1o<*BRH-pAG_)`< z{9<5WXn4WEP-?)y@G60U!D}OzLU@!6Xb!C6X z&c~%<`PWA@g@J*or79$%Bq*_5p`a)~Ei)%op`@}PRiUCjw}64cqIYVjVGsB(xsY-hsanjo&s zv@QIq-@&7%?(<_=7Vdsg&nGwcg#!D0xrZ@q@@9%#9(Bm?x+D18d?lxM`HuAy{AzIB)b5`<+11-0&Jm~pzb<_M`z58wzOs}h+_w-IYJLlc`CgIyFwdAh# z`!~K32{xBsB6C%;q@vFIg?LO@*IlWb$0so`Fv@$@yEukuoGvoAbB?Jr=8_vG~q%y!Yo^rpDsvUwdv> zp6Bl0smSh7JXvV>l14sLt)0~kYAhO!@-o}+Oz!&{tNv>hbMDRuDjph)&!lQrt^QeR z!(+V6#yBTHrG873jOu62DVp;3fk7(ePXY|WBi~(`cf4LE_IQ|&m-amoN5L<V*X!FWxTnJMW%dzc=^(kw~YR9}cbU z@hgp2@7g)Tcauzjg7XRHQmrlf&&_Y|Zb?q#P&vTBz@S><8c~vxSdwa$T$GwvlFDFY zU}U6gV6JOu5@KjzWo&9?Vy10iU}a!nXMKeaMMG|WN@iLm77g4`9sX%Kn{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y%v^kCYC1V2CgQq zj)sP=Mi$PFMlPG8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?00o?HnudgaC32WH39kC(#gox(!#>T%+T4z!pOo{392`RoG{b3Q?StoB@U#-0uu^y zaRV{I=^B*A?GzB1Cp9m{R;ftI-tKLbz6t{a<0elR$B>F!Q-b$qF*!=y`K|cxfq&w{ z8=4X(nn5qQj~sep>60j0BN)tnHaO$Jp(#xnq7zJ-)HJeGHM%OQX3c!ccf{o)f7{-@ zb7$U_-4{CR>@p9v$%od3tuAd=(3ge>!k}dx(s-f4QHPfT72<^m3^X~ia%L@ZM4!CgfXK%jQ z@v%Z@+2xn)%laD@=53E=+*7glUWtK(NQr9U-njpa*H18hmz%}3)oX9u{?^OsDnb`` ze6~!Twd}>$DmA~<1bKZA@jejY*mF+R%U_8*WlFF{0j@Z7ayWFbm zT?uLVY$n0ubFVYplKsiI-?hTGgrryU?s?Q6W5NC;$a(Ewu}_B|evoopb}>M`zmHAm z!KSI7H`!}V?b@p*FFc|9Xi}DffPmlf;AMNG)*eYQ5}fPT-W#yxh<9dy-gJKR#B6&; qNrhAF%r(rWcIgZD`u`J6V5sqD@%>m)d!2!Sfx*+&&t;ucLK6T+I`%XG diff --git a/toxygen/smileys/default/26AB.png b/toxygen/smileys/default/26AB.png index 4cf20983cfb321a23d67dda1bde341059b4b994a..d6037147cfc900f6c69f296745a243802e5a622f 100644 GIT binary patch delta 1191 zcmbQveVKEDWIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUb!D#g|19s>hYT~$a#Nl;?BLP1e}T4qkFLP=#oszOD5ZUF;>Meo#5?@f0MIPMg8 z$vCpHd@8h2&Yr$Xr}&RahF#xr&$u^Qr`#}Ah@<9rS4+9Eh#XW(liXB1 zaku%+J(6Nk{07lGj?{0zn_J##$;Fbg*i4E4=CdQGZH+728{dj)f7#mPoqH==vF}OS za!;-IN_QsuPjqe2S|Ps8H6&}#;j;Fr#q%l?=J+eWd%11tV>a>UMZ10m$|-aI2nh?d zo_cJ`N@mWMw-y8G~Gb=n(;xRl=CQ#l{sRA(?MD*U1@@v&`FeS^T~{ppP& z7qm>8a($PD8L;#}-=oWNsIB?Ni7cV&H?9I}WY_*XVafQtd^U%?!fmTlnI~4dFfI^R zX8M+Xo$uh$t!?N0S}ufsv1jL3wd!g<-%+3!$v<0Q$&8Q9(^nt*7ki~;srH@p6YTc0 z-L7BTnJMsKouJd6ww%SjyH+jR^jxXl<=P!z=~>#BLRi=$4Hk*-@YR;$TKKBT{OE~* z_!s~5soOwwFCK5=+WM5kr&Fe> zXG1q@;aBF$T4ni$ryM^2+j%Yol)Dz2nECZSYQMYm@=Fg5izRMWp8KX+$ZBOAf0kQX z|4lb-BWGo8u1dp&#v8M&Pd2pk?`c;#?Rawb-&Q88qJ}ETyOWY8P; zRH?MEY^DB%8;>sC5a-P7%F1}lEWx*pT}`CrmQqW=3>Pi^&$0Kbw=2qM-TRsQy#8Oh zg_65$!yh))vPfpDkew^c89#N%N~PrXDYvuEROb7#^Rb!4{NgJ=PAQokTsBEVEYd;I zP=cxL%8R@88`{GP&v=&XZ#dnd$jrtg`EZj@eyxo0b)}il6qhcNVA=oG@pSs>S<_;! zrXFDF^=9zMQMzGak+n`GEIMvdKxFR5C%4Kf9I{U*+kMITu;poKN>D3H&7O42wOMNf zOfJvrNZH}v8nUHk+tdnfA;&twkNx6vEqOw6?n46F1-1f R)wviLJYD@<);T3K0RZj(0Ji`D literal 1431 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z2#Ar-gg1p9h3B?` zUFx@6Qf!4d|2(UZ{fGHn1^!OVa|&qx=W-!&cZE=hM#meGR*e}C*g0PPx@xQ+d1>LM z#`f>Y>GLYfpGi*iTDl@c>*AqHFTb!&li9$&rZ&c&+5CWNjr{Z)&)H|wK27jZE7eKY zoBrBh@<|OJ86LK$+rw6;PCK2tIYMXNf%i<|%cpv!9@wZf(PINYdwjdoLRpE*y>UUI zQ$<`C{;bhEcy4c8zs~7JX`3s#|JNQ6`&coD^HZb41C7~dFU7A3&~ToI~Kdv{s=FL zRlnlC?r+`vg;p|sb2^SDT|JqSbiv-|@)mVLzIJ!3%l{642#R(tE82Zmo4t6gfHvp6 zkNs@hb46$SE%*6w%5~2{Z=U|+n}rT07$_WxVw!sQ$j`ppJGc(IOK%VT@`v{=7h~r9 z^XjG_q@{Yn7suQBWH zdcGiT{pL+i3<@PJJT{!#C+Yp0;Rfq9OYRR$5jN)z9NzSKHv=<6r{5{HX@4vxf{IE{ LS3j3^P6BjnTu$sZZAYL$MSD+081CvI8Pl#)1Xz2g{|Jm5sn3$Lt7#P;> zJ~Cm(+}whq`1pj)2T$!kd$q2mW8c{;drn-eZD}vBZOq8bu20X%s;Fx$uWcwOF3l|{ z43CVAh>BjiZu8nbhu7{tGGpo5)=9HkC(X((sZ7kuPszv;77-B^5z8(pe*XOVgh^9m z0H*em|00urjK2xVn3yX{?YwBq1nbIYG z`hMft+b#Q#KY9QC^N-(u{{H*_|No;GZ+`v#_v!oZNAJIH*m+>)f~C(se*gOO&%b~F zZ#{hW_us##AAU@ky&xncq_m>y$B!R#7Su2P{{8#Y_uq@l$_t81)^Fc;`;I#=32L@{3BAtlNCy{_~qJKiq%wdBg65zP`RLE-qd^ zKI?bvfAISAtrzbv-g$E1{LORsUtW9u;rONNmX?+$F5NhL@AT!}AYbZ9Q-zA~r58GCCwOYUAF+ zN3K2CdFslF?FU*X&04l~-^yJ_cAdC*#&TR!W$bSP6h^sdY6(QKTzUh zU|_0x`B&+T!>d=0EMLD(nfmI@zrVlVyx1PabmO+)-@k!e`QLw(@-ni^MQl6q?)Uq% zhca~em^noa)b&jawT#5LdY;_d#K6G7nB?v5!uX#__Z|ZS1AB?5uPggAc0MjuW~J!A z(-|0;YN|paN`ey06$*;-(=v1FQx!@o3sMy-atjz3EPAJgI!?M{z_I6ZmyIJE%cWu) z<=4|z$$b7}QjvD;^+z@7)u$#2E?USqv+n;NSNlhqw>;h%?XBE+^hjP<>`sYj)9rb) zd7kCo%iDgEr_!9)vvj4^k{2HqG2Km@yXEIAi-@D)cUMWJNr)U&Oq;o>aN=(B$i4NF ze786ZqIVqGemA$c)0T^+VzJp_{>bmW)2pgJC9-Xbo^#0tSaN`)ihV$ z^Qfg!HRH|?_-%kC(w~1n6#@8Iw<6J$v`)-Lq4> zg>?>`ef;p{)3=Y`zJ7lGG`q+HaSje1Eb}#`QYOqaD!uhZ(m4I>yvsJ_ z_i7~F*dlkba*ON6Z7R9>NmM)hV2X$dLG*)wL%tdC}3)zMkICO7Bp8(EpTb95sl7&osCu>Zfi zW+MXwgKCLuL`h0wNvc(HQEFmIDua=Mk&&)}xvrr}h@pX%v8k1brM7{Am4U&D;w8^f iH00)|WTsW(*057gCYpg^qOvL%1B0ilpUXO@geCxO9Av}* literal 1825 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 diff --git a/toxygen/smileys/default/26C4.png b/toxygen/smileys/default/26C4.png index 93bef58ac5fa2cd8dcf24bc37c7cbf91aef50fb5..f7e509b656ae4a61ad0555735e80a26abf4881b5 100644 GIT binary patch delta 1520 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 Xhgm=h`=O>RP|@${>gTe~DWM4fcaLjf diff --git a/toxygen/smileys/default/26C5.png b/toxygen/smileys/default/26C5.png index eb04e5d4205c408deb920627ccb4c092b6b0f705..86770431cab4803cca7aa1ebea5b5e3110714f21 100644 GIT binary patch delta 1556 zcmZ3;vyo?lWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817m7{Pl)UPGb;c8|KE4=!Jgyyb{)O5 zG3kOGlM<^Hec{$I@Szlfp!|4L4f&;hCcg$)1CsO&p_XaDgV z5dHsGa_%^MW5@oBTMt~_df@7&y%)FaJ-2D^#Z7xJuHSWb{jRfXwx3$F{nY9$$5wAS zwqn!a6`Kw(TfZL}{V!tpzb*0qvw8p5TmFBP`+uX&|Kkk*&oKNy&G7#u!~eq!|LadN zd_C%acE_IoYq&3-TDI@l<%_46zdV8Zx5cj zbL7n3PhWn1`1IrIofo$szkT)o`}@y7K7!EC*YCgIds_eg>fM)*UVeK1_RI4(U!K1H z^6>e`Gglt%JaqZu&F9zey}oh(&Gq|luHAcm{Nnws`_6*`^Z)BnuT!`0pW&Z7VPRcYe*Ot#oANen!j-o_-(Hyz&NlJgwQ#BZd(JU1 zFfb;0ySp&{XR6n|$H2hAUgGKN%KnU;#a|}3Y(?w&QSeObev(7}Hf0;b}KKHr)(wyI=MaNteVs=dM zu;2fGLH@_=TOMYT_x9XKNm?Bmu6Hc#Z0@#LtIu}VN@dSXIA6a^aiw19ZjrZfAqls0 z)N=P(n1y|G{l2PAlFxNRXJp%(n3T_I-R6hgUNfc%Pp{a1wru)^Km`SZlVA^wC~;L43{In7go&)XQt`zyc8 z+_vN)lkoE*$v+AR2on|p=PyL^ZLbpATA-c@IhFKle~J=|K+yRFyg<(gS@Yjc{?&tBe=an{Ly zMx@I9tTn}=iyx{uD)Ta*U9B_qLd*<>u!4%-Cu~3YBo;VnJ!DzO!291LT-a1OL4o^P zm+8L?{(&ZIHp<_vWm@La*ES`?ll#%DdiGC$-0T=nOV0SmFS+32UD2Z#Ei6nY$#3&q zetK#^foHGDq?oBYWEuLMPpy?J<<{j>Jioq~MZiih`m>7UhYwAqTT(1bKPA;KY&a*Q z5PklW)Qy5ayayZFbDA{{u(T;Xoi)9$jW;7P!fZ->-#k0z`i*a%*DYRec4z5*xpK?? z=dSfm-_6fneX~O}{!+hx;~SAxv-yL1U!_>p)Of!TKexMU;gZ|t9t;c&_dH!3LnJOI zConMb@fjRFaN@*?0|!nXJ#gUc*#ifTa*959`1G;3z=P+HpEhwBa7al=NlHp_IBhjB z{3tz}p;6ObSi*pVlY7R@=!hB7T^!tvtS+srYu21STfa6p=jajD88c@}MnwF0`$kq~ z?wr_|yLVJ0BP1dsm)h9u-BVlh_YX7E(#S~38x<9)G0jcQ&Q8wGhf<#2ka!|0a#-0( zQ9;AVtJBf)mbn^>mVlJ>@!SK_esVjOqe-!?&5%v=ayCduB8PFmY?TiNLXR$+}&ulh=GAYwZt`|BqgyV)hf9tHL)a>!N|bK zNY}tz*U%)y(7?*r)XKz6+rYrez`)M>3LlDw-29Zxv`X9>{L^waF)%Plf@}!RPb(>( Ss3*n6z~JfX=d#Wzp$Py-`v|@O literal 1569 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy_Uw#Mkc1lCKhI9 zj)sP=Mow=1I*k4bu)}|e=7iAxG6<>F0uY$yiD_v17KURdS z=tzp*YL@97clOL&$@417O0&Ay>)t&+^Yh)$_c3YvdltIN6du36c9O-u8#B1xy-+>b zdpGH4knhpZCc1 zDI43%2aB1%-VOJkzYHqxIKvDjo(bqM`=D!+e-kkT+ zV^^f<_nn*`9nE`9_xs(xWt=cIWp||8B@eMSp{e^ge{A!=)R_N&i-Kv`9F>1%Tg3vN zDOc40<2qff6h8A{yKaQJrtwtFq ztagRG3AIgH7|~l7t-Z|asaBn%%IQa?S2*7v+EZYj`PQPlqFw%}m-;@ja|^Vuu-!Lf zWhz~ILTTsbj}L=0*DxCR3X9h$3w>+(HSO!+UxvC1DnvO~g}Z6IP&Vi3+4+`>$8kT` zDocmc-Fz(!F_jbM&)T4HZ0m&hj{zGMHcaEcz<5&0*=AX1eAu<%pH3%VHuSSMH=h2j zoR}5bb|6aJeBQdtuCu#NEo;2vR(EEjffOIRLZ_0?Y?(<*fBnxEow7VWXNK9Hxc=m& z(VKkvVxQl*!dnnr@HdZfK~^`zoY^PVioY<}r!jL{WqoGl*I8osT(|28CS-G$^(HFE zYbZ^>%W(DHM4id;=hLoBWKQ{H{jq+d!!BR1zaj|?8<##7?q&V14Jx!fUHx3vIVCg! E0O5#D9RL6T diff --git a/toxygen/smileys/default/26CE.png b/toxygen/smileys/default/26CE.png index 0ad227f3bc42c0aba823af363b9a84cdbbb015f0..35c5af51821534d79dbd4c284629967cd583cc1d 100644 GIT binary patch delta 1334 zcmeyu^^j|VWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081HK41$( z0&dFx|NqaIUB1zI>uk}5+cWNdzV+$v_dg#mf4tgu{c6XJU+;dN$UF0N+tWWk{yg6B z_-xgMCtIHW`TG0m=BJN0KKc9e@AI9{j%1zu@#e?5`io~9F229`{^RuzS0`LMn0oYN z`I)_mhkn(6{&k|_^vTN8yW;je-2Cw4tq;emPVG%OaI*IFhua_SZ+x&VYWMcoJ-64~ z{r3Fp@2|hVKK*iQ#qI4ed*J@ad3kv@0|SG2Nsu2X5E+nw=d)+eo_jri!sGgA8v_Fa zW0JSKi!@Whl_d-e4D2PIzOL-g>e>0YRM~z!)NP_|GxRSyXnZnzT5kb88dP(oLa`Z zZ2tZF3)_E)>ZvvCXt2O2IE;|{en)!7JQ~muU z?aYs7HyU>5SEZQtaA-97p1Jhofy!?2n|mb8qIeCWEspMfTejnf6f4uF!0d_bImr)8 z-p|}~_`tUg*{XLBrgUGM)1kcc*H{DSLFsGn4F5WT{ETS?3E*@m|pisIArB#!SF zy#8)m>2Y4Yb1y%A*lgn__Tl83Rb`sJEA`zf>yL{1PvU)Q^4{J~Ca&g7kL-!=H6Q29 zH2C+L!NEBwO?75b!C~!^Yh+A0mjmU0=2<;{R zPV8is%b&@iuW(!Il;#OdSH=b6%1qa$X9X7&p1qhZ?({e!X zTaxza76_G0}8!8=^bwB7!lc*6FRPoi3wQJP0dLV@|Y%h44V z8#p&-9PY8%n?C(xe~xQ!8Sh`WD6c*B_k^9GL+1|Pfj^-nETnqNtv&noc-s- zcvgnRB&&Q1XQy+;6!VV^X+bfHyLguF{;+{n_29)9FC~9=Z#w_iEk1XL_kP>=MS1JH zTi)f*T77$msQ>HZ%N_XYyOz%858^AWvb5Q!`i1)zL)V=B_s?!-U|^W->Eak7aXI!_ zsL&w?5!Q>{)|`6!JaRE?yzkcij^`EbI&{uGocX_s3g*Acz`&qd;u=wsl30>zm0XmXSdz+MWME{ZYhbQxXcA&*U}bD-WooExU|?ln nU~cwG4n;$5eu`pdS|x5BZrMSLCMv3PF)(<#`njxgN@xNA=2?4{ literal 1396 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0L;4=B5@VCaxwR zovucXmIkhtmZk>g&gQPp#%3lky`Fi+C5d^-sW5vpGgGXfdX4ewwQ?>>O)SYT3dzsU zfu(?ejQo=P;*9(P1?ONh1`yp;U%Vogx=fWzL(CABECEH%ZgC_h&L9KKeWSnM}7 zHZeDFbTo8!g}A`Nz{Snc!pzdl(89pd($!cAsyBt4Fw?hFu+ax44y42a6AE&112Mtr z8kENE6cCvwH7~_hsYuD*&a-n39|Hs95>FS$kcwMNf_=T20!3_ZtCZs}T(s_PT-hFd6T^Clj3`y#uld=ueb zhrIY^WXuhkd!?ulCOWEj1UFYrM{K*n1si$7tVGTb{7bEwduig$x<b%6h*%Z4b>36p8aP_*{KSPI+tgkM88#XtL<4lsWC62`^P)ed-@Ch>&U>cv7h@-A}a#}gF>=LkS_y6l^O#>Lkk1L zF9rsNh8GMBr3MTPuM!v-tY$DUh!@P+6==i2z-SlX6XN>+(4qe)PyWAi=l_8N|8L#; zzi;37B}>08T=-@F{EzeIy|%Z|WT}J+ZNQXkl^3)bx(2>2+h{>&C_x_4F?4 z>7CKmKBKLDOhe36D=2K0k=ZIEvq4gFwW!z%Vd2Gsf>_ZTZ|}cT zr~W^31mvc}hyP8Q^rg7?|HX^{w{HEvZr%T*M?cmV6o8z(YSsVc%l|K3_R!h+|DHYn zXV3mWXU;D0Tfx)08$PW}D3=kmOcIn53Cdu}WsU4EC zO>Gx0{rLIg^M#8|ZIZGbQ#)nbF8}&+u373ipXpx)1_s6?Z+Dk~$9X<`7#JAXOFVsD z*`KlVajEjfylT{9U|?#f3W+EQN-S3>D9TUE%&AXRD5)$+Rj9}X6v_#a3oj>NE5w{d`+~-)W_{i-XCt=E4`dwBw!`PvSS9aCgVyIBS*ZS$m(T z@Z=p4$(-mLYW3*kE{y=upHACWWImMIWWB`bs}l43zb`$1nM5yt$TfLF&90{t=P7XA z5HU2iU#ga=H8YO)@Z3^YE9GaZ(FeS?Nft6xUOe_&wDZBua)x>r5vdpa+Crk4jE%p} z2P&{^;p!F0-yFm%+H^Gdy|>drw*?z|vh?3<6xZ2My5*PdGtM2gTQnasAN|%>qLp;Y zhvDf_X};f^v)P|Zg`LYT7O>m7kS*eTIZ4 z_s*iwgN~lTDle#x9<{iJFz=ki6St&{H`IJ)87SEGy44hBva%pYi}HC)s&=6Ykz zw7k>DNlWvkPE4h9p8N^+1Y4%%|DD-1ZpL1adHg^`&Hm3i+qr!$JB(nPli=(1jCpeSe{ar9%cV?~D4&^`x$&ohQTE52 z-%amlT2|djSb68*=2Ki*DQ4LPF8}gw^VfLaSH07j-@iHL*k=XL>*{B3=GcqIU)mnu znAf?gTRteUJk4s;fA353e#^SzLdCCCG1N0KT=H~r43W5;oWQ`OmnRn&XIIC_W{{8~ zWMW`qWM*b)Dv*?vl8}%lXl87_X~UK+8;s0_(-PAXL^p2RxOMa9&D%F`-nvmNF;$?n zproj%j4d%;-Cf*}tuWlYoZ~}5dHLrl2@^`bfBN?ABV$s2ettZ^CR?Wce+CNmdKI;Vst0I33pX8-^I literal 1542 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy@rO)hAw7?CXU9I zW`>5YMwSK!&MuY~#wLbNj?NawhA_RJdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{|es7bmXX+SkY9!?ZZJskvK?XowY%CnshfbSz?}Cu>8G(DHchXyQ9{yfEb!w9S z{A|_&3x@sjOEXMVD{M|qzkK00Hfdq*Y?n;^WpQPL| zHD{kQpLy56?QVy2*sCfSa?A|+*1aygbmHLEt19J|c6J{Qz1C$B7jJz1+Ale#+Vt%6 z-|O;ISbmmB%~D}yJ!_EExKi`M6P66KkMYTlyBIrP^S2l7-YqR0;yb%^cZ|c%p4k^K z`U!1b{BYj47=O{j;^GAtFL+ujXYBeX_tPd|eR4&mGz+f+uZYyF`LNjtG@R?ZP{X-i(9AV9(r~>KVJCEap{t&l6GrSg}%#dFMCbP0l+XkK2>v*+ diff --git a/toxygen/smileys/default/26EA.png b/toxygen/smileys/default/26EA.png index 17727e0b3e3ba284b49f17b770b88e1ebe4f5e87..d606692d1d5ddf616984735419ac3b43435628fe 100644 GIT binary patch delta 1428 zcmbQrvyXd%WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EWHKPl)UP|Nke4Gotzj&LSyDzt3+umcRuiSe)Y1*u&y5yEF!^w%k zGv*W?I(7c`;}?t6%UbGN19u-eb@BFtqv!8Vo-v`dBR+7}rX%O>)HD=Lu$j62(22Pk zjc0G%tq)zj^U%rjH}ALjEn{e!v2Fj+t}dffS8w@CG6hI7GxSYl=$@P#IDOOJBgZe@ zh*V@v)#T~zR$H=e%jP|Y7p>d8anGR!JGuY={~y1q`hkIgfxjfk4;11I^7RUj-@SkT zp6lwLC(XZ3-CslF;Cq9iD> zT%n*SKP@vSRiUJ^AXT9vw}64cqIYVj_oh1vJa=A;STap(VLAR$VzYPXG|RdugU5w7 zexH)UB`37(`pS6u{CoQs(KfE!C+pSjcB@DGyh*${b@Im4;?>){lkfe0{PD$^3E`I~ zq)cDqJITynYlCi>XZQNP-pHD(wyP6coCR}|rs-L$-`sQJ)zJ+XqZ)1=(!93q<~t!P zE|v{}uMaw3TWN8Iee!nc6LK4!`(@`8U7RLdIN`OO%cM&2hirD9VvMU4;!cPKo-}OF zsc)XrK5u98oOtED!#T2T+CFnGKYf~B!6UZeM5*@&TR!FrGxWsBeC_P1aEx%^`1@YRfpMvm;*D1)LR{MtK3=-*Vis}s z$h>Sp&TDN!^#yVbzjynzuujV5eJE1kCCG58TdG;@CF;;d3RDT^B>F&)n{Zfi^qyL$0v zv9Qf+3*U>r8SaLmnx`au11m(&1}|Q7anl6vGh9^@GCM5#B!2xm^@QywU&Q7#CNZOB z-aQ7%1}m6N>o0KoRVaiRFv}Yp7Q2xap{JCdAa&y61R>qbi97x@#@s)lo?y*<`M=`f z$dYomLK8l}z6ZN=FTE&IRf!3zl{h(L=?>XV5BNM{KCOw)DCF2F$Ihux;*}$(ezKvR z_1VQqFRz?j{a2N>i5KU*d*38{bEUT2rT+NFHzKQM%Lnzn>M5xB=lw$bJ!99RNjFNl z85kHYc)B=-NL&s*m2J-C$Z+gq`muK!+o$GvcwM}^>2Z*^=BA6A)B@LL)&B2Ke0Ie0 z*WT)X&l$xy6xkH_GfxTHSr1QCYIU;237_JCyJLmN70a*pOTqYiCe=?J(*~RiAri*3=E#GelF{r G5}E+x!i_@! literal 1557 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 O3=E#GelF{r5}E)p12;4P diff --git a/toxygen/smileys/default/26F3.png b/toxygen/smileys/default/26F3.png index 50e4a270cae32c26852ce63864c494bc62b12825..c51400b0c58d59709ae1f935db77f4ecc07f3b33 100644 GIT binary patch delta 1451 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 diff --git a/toxygen/smileys/default/26F5.png b/toxygen/smileys/default/26F5.png index 4a5c0293e4f3248568f4f6c97bc842dedca290e6..5fd1cfb935c3fbdcf32fdd6bd37da1a8c8278365 100644 GIT binary patch delta 1380 zcmdnT-OoKivYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{$Ly-Ne!5R%SG{%pQM1Sw7D;Dkn?U3ahTH9`x4ykm_eqZV z!En3d(Ke^dq7H`JeJD2hW|?}_Eb@wZ>`}AGqh^uk&10rzGxVh}^rtZtd4o`iFGGnh zh_S0P@Z>bJx0cCYEmQy7IeR{~bF28x6i$rvA4p`fro-zgzpiUBQ3b+)L&$Pb?BYS*Gkfcxq-Y!-5iq zg{2ION*H#wF&ye+INHxp=Fd>>&+z~Me`V!mehdr@x+Ot=pa5lHSjtxH`cZ=E{DSy@ z7MzccGgiinetgb1u}9_CPt`SBtp3ffXWPnB^Xz$D=FZkj3=9m6N#5=*Ay1e)mNGCf zu$OrHy0SlG=i^dk3XM6-$iTowda^&dVB`*n}E#Xq)*cI%L2Q@MQpz5R>mKeNmx?dHj^ zkIOOKI6E|4?^xK`+^w-zpY8It-%e<&HRbg#U3qJ1#`i@`_tUg9KcC&Wp(}q^xT%d` z0896b)D!1i-`z|3oEDK`dm!hb(EGCOcU7Nh2pn+BW#Pa1?8s@`nR^Z&DC-ve6?Jf_ z+1srG>^mQ3PCB)lqx^&BCXN8spNF<}c=a0QltIJ?mT!D<0o!^ zDS1PKe`@H~;4B|Y$(N?#F|KAyjBV=oSnsLuKgRdO_1ca&ABpdFj0a8}4V)ivMEVkw z)0zM0m_!1!M4EDamxgJu^grLD%W|;I`9^|itL-%G{u;y$viuSCjX zst3cTN1Y9S>m#>17Zk3&n6B>lLhIN3W_NX~NzLaw3iM|3&lZR>eB7=V-uc&hC8u}z zj`I`vYt4FBtu4N+a3EH>rJ{FRuhGj@v*y~0D{gw0*OljF-L=vy_}FEU*6@o~=~GOj z#L7AYo%xNHb|!Z(Tz}q2Z^HTp!3fXf`DS0tln(bc?o!bTm{jk}nz-=xJt%bi8% zFt$d|E$xoXaf*wV=2R#V*!IlN=)ptQ#~CM!UOYMbPl-|1bb`n6z!xEo&+A_qoSvuJ zvFHiM62*oFgAE+pRZT7GmFhRXIbOGTz1f|W_vO+p`=2{HT~|MQGk0E>{_Eq*9m={^ zcJl}IzDlvO*{}MAyQ8$L%I|sFW(Ed^m7Xq+ArhBkd#_40IY_Y9N4|NN$GUsN#pXEJ!B*Vb0JtO0`&%)&Y!R#e-^|+4~Y-g{keeeH)*FoYJbAA3oO9?5% zLlr`OHDZe{OB#!dpO$dibzE4xk*#p9PbTBSFK%;;X9#RMo3U79_HD_;`T7})k1{Jt z^hrFvx6;LVXVI}A9MjJ`hwi$!FJ`G$!JpVLIjgRk=avcCU)z3N37oxF>NivMcNuLD zu`8gWPPN1}q9i4;B-JXpC^fMp)t|x0z{p6~z+BhRB*f6b%GlJ(#8TV9z{FVdQ&MBb@0HMoJ?f?J) literal 1470 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy-qH!ZWboS7A{7v zhK7c&Mowm~W-d-fhK7cY<`&KdMlijedBr7(dC93Tdowdrte|?$@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{XnyCj$fHJ5LwKkcwM#f_*(g0!9A4JOBCTocYUn z7PqN%`UhwjgfGnQRq}`u>#|;{!49tSI7SEN|S4sX+Q2YAGqMaS_r#9Ru ztFYR<`1`7RVGJvxPCen0JCM?MBeC*u&WnOfQ%f`D*#;)e>C#LQ2Qgu03 z5M{IdX4&S)tUu1rId>|2!>(DMzXyh|u#<>+w25Qo&KDNR3ig+Kef(yX?PXgwcY~%( z6K{^v%club6IUJL`?F})naoSk%g^8Np7Kscthi-{z1a#E)!o7U6XU8+G5tTax_ySI zjPfo&_O|^(^D`{?0zMh2f97+ObYHyiaau4lucNL4Q-jx(8Olx7kq?$PtKL7n;?B)h z?|rx9gC!oz@O$X92nK$wmbp=fS?83{1ORA7 B9UTAw diff --git a/toxygen/smileys/default/26FA.png b/toxygen/smileys/default/26FA.png index 516ad10745a699ff27f0383c50f631932414477a..053a098c905bcd416ba2e3e39f74b3f4c49de561 100644 GIT binary patch delta 1463 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 diff --git a/toxygen/smileys/default/26FD.png b/toxygen/smileys/default/26FD.png index dbd528b07bcd270ce8f3492098fed3992e3248c5..fcf14e65eaeefd4fc36bc18546d6119806841a16 100644 GIT binary patch delta 1283 zcmeC@Ue7f_vYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{d{ErO{A5T*Jptpa1{=KU0cjp&ZLGE#|vMj8_eqK3Xy| zFfg3fWqM-9_&-+rSD?aSZKhi$jF$`;|2uK~cjhP(WN8v*5tEeem14P~%Q#Vn`K2kt z_m1p;C-?qd(Dirg(q0+n`^F6azI}P>tI#FIeA$5U|JN^fe3V-ynNR32KCN%f{dVr? z>-jyu9^Y$_WZtL67%#w5FU5TH!Spu_3=G00L4Kg1Ib46cUgq$ThNHiwzs>p4>rgBa zcZPw1ficP3-G!lpRn~)nfq}im)7O>#89N`BDz{EU=~4y;rmCuth?1bha)pAT{ItxR zRE3htf>ecy+yVv$i{7cBj@xF}8}QtDEn?d=G3BI8JHx%Nd-baS^gU?_aJqee0=wHZ z(@PmSFP?vw|Ds#xD}7Sy?ribdK5r7QPVL?pT0A>Uc?vfYJ zTv_*>O}q50J2_D_zGQt?h0+2JF+=a^bGz5vOZk`S)Z1imw(Tvf6q9X{gX;@ zjJsOTZ!?Ao6T27NAM7xn!rC(9zb*HYhOmW#^*O~xrp<~Kd!FSg30#vs;uh9?qgcNw z;qHZ3{d0s1>bG}(VA_=HY{_dmRfFNnqt1rgtFxCscogNFF7EU~@7Mh1b|KlL2ba5B zOkdOP%MoBz>T3tnfdnV%>>&``)V)k%SA36nY7 zt0^lQ`PS4ZN#A*FzM40n=Pi#Ph(41f{HZ2KrQt&7jrv*ECmY(udz>ddUGn7U zKPSeymJVw_bZ-jKsrh7IA(30Zjp3MtV&(=l4~dAD+f!ubOx%Ct+va;ORyXZ_@wReW zalNccN&TgmyT?{mMA+N0ZVL9fTQec6ME&%`kGc!v?;hz|ctE5ym4Sg_rl*Tzh{R>v z(=WxG0z@1hR$sDFSJ~`Q@51bQQDN_Y=c9$2>+7;}oj5ixFL>1a^;Ajh^(*rllkzguBHUPOtGjP9OGN#!us&1h{y?ds`V8~*CV82@ z<;4%o1>B}*i?}8-7Z&YSuMzscaB5or^Vf5i8qD(%{KP-)aL&FJ4Z8ax4(2g2{A5h% z6TdF2Qqs)8z@S><8c~vxSdwa$T$GwvlFDFYU}U6gV6JOu5@KjzWo&9?YNBmmU}a#C l%6D`piiX_$l+3hB+!{oJYgs2Ms&g?gc)I$ztaD0e0szM@976yA literal 1423 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 literal 1572 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{^uVhUSKbCXU9I zriO;DMwaGgu1+q7PHv7C2Ih_?jxfERdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{%IJ@O=_W4_zEx6RZrG=UwT`jlBAQqdGVg8y|Ei^$4{Q&w|&dT z_4(~yi%+I63N(KDbHam?TT`S`lqN3SrSqMO?|4_pTCSA|AB0t9zKfhKFIaUjXS=o3 z&N-r6wOIFa6|>~ZHFAx)L6}^M<k?$H<^9{tI6J}c zRqVp`ok9XtOy%ngAN*E%yrFD?scSIvyvKS^xK+3PHZ^g(R`Jd4oz`kY-`i|cgvxgX zd|UjmFgxVQybV&9x8y2#ebS0czVNSS;j%C4AH5qG8Tt%9-C7dLWe+O1Jzf1=);T3K F0RUD4RIdO4 diff --git a/toxygen/smileys/default/2705.png b/toxygen/smileys/default/2705.png index 0f82df9858ac574705c9d38505f63314debf3a62..9c849f7ece2843cebee816f6e289f0e8c82a003b 100644 GIT binary patch delta 1229 zcmaFG`IB>kWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H=3PpAc7u>kJH685k}zFkE0@IK#kj zf`Q>E1H(ZEhCK`nI~W+YGB9jnU|7e%u$qBkB?CzDb%uJ7hSe~kl`sjoDc2ZQe>uo- zhT+D~hyVZoU;X(Y$k6i)Yd#&k@#7)GQHC`i4>Ftu+i{YC;i4|X9)_FWA3`nL&#>X$ zA!IA}+p9D)FffRh1o?sd%fP_EP!A$N*z?)5XV1N!KjCqGw2gs*ficP3-9?_2S70v# z0|R@Br>`sfGj={M6{{3imwOBhOtn=Z5hX#1^*FAg${+(rwDXJbrvs3Vo|y{qf^TG@I{FO z7NI91&mYKC*sM5J_od3Z8in)uPlOBRG+zF%s4TI2w}Z^%gPJ<~ey`GvUDwj%y^3F5 zcJrCrj2{a;E!xG_*sdH4=<>Cy|y`KfE>;Dtyx-^s7Y4IF~#0fJF zMO%J$D644YSysQ`i$Y7!LpKF?YxeXf`lssNZU1t$ZTFYkpY^8y+vRcTuWxbb?fj#M ze|^-vpj-IzS#!vWa~kg7@;!gCPE_rxVN6K6#K6GN=IP=XB5^tP7`srDfdK1;{P#>< ztODT%+6P%S-2I=!CcAXc%=iYg)AvGixC_3O`mx6ai~ox+;HcSO-~Qpa^(9T7-yMs7 z$ha?K<=o6{5bvQfr(@I1_6bI&|Fb4}C9^CQ$yU0w#w$6c?aGqWZ-$O>T6wPjOpor( zY;(UXvpW556=V75RXKN`O8-t!*tx0h-8TjX2GtVRh?11Vl2ohYqSVBaR0bmhBO_e{ zb6rD|5JLkiV^b>=Gi?I{3o8QyJL@ZaC^~ZUQ!>*kapy*)&m6or{6N)78&q Iol`;+09nxZI{*Lx literal 1258 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y)KRx76#^q7Oo~B zovubsW`<_2#*WTL2F?~HX2!-ay`Fi+C5d^-sW5vpGgGXfdd>0bwQ?>>O)SYT3dzsU zfu(?ejQo=P;*9(P1?ONh1`yp;U%Vogx=fWzL(CABECEH%ZgC_h&L9KKeWSnM}7 zHZeDFbTo8!g}A`Nz{Snc!pzdl(89pd($!cAsyBt4Fw?hFu+ax44y42a6AE&112Mtr z8kENE6cCvwH7~_hsYuD*&eiL11Oo%3l&6bhNX4zBlpW_B1QVE&{1xihj{NsLaDKrr z2?@z{O_q~VBL2yAER~SdYxY$UH!w2%!y0rXA&sr=Y8U%KlT8-FhvgNT1=zB7E&MY< zvE_DO;?_5gB9>|?uGv=>m@UW-t39#coZ9h%&(D?HE?9rdU~`ze$?c_rv%z}V!@OtP zd3e?Ys54~>#Ltyo6#00S4fA315Pbt9nHDxB_JECclOD~s<1xRoUEzm#fs2mU2B%((474KAx_AF6*2UngD2bo67(I diff --git a/toxygen/smileys/default/2708.png b/toxygen/smileys/default/2708.png index 509fa7b206042a8a902dd4f34550ed0d85bafd0d..c053b6afb4a87b7dd4a1d2db8eedd382fa10c9b6 100644 GIT binary patch delta 1304 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 literal 1332 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy-r4M2F}JVCXU9I zriO;DMwZ5|u1=Pw22Rdy7On}9nS9@i$x-Ck{;Q4`jk=F` zxU?Ra{jO^Dyanzbb*mcR9n#qKf?u*jbysGbiYi;?4&%*p^EY+tt;u1xD|j0sUtLY-#F+1nnq6?yv2D$!Ytv@S%7->B*%PNZ#6G9gn)@5pk&pVm)#q94_z6+c8 zZR>Ke>09k~IlSo4t%nys?q9zteE*)_Z^orne?3_3SR@#n#C&+_C%EW=$}vw@KbLh* G2~7Z(gw>+} diff --git a/toxygen/smileys/default/270A.png b/toxygen/smileys/default/270A.png index 43d7ca87b7f52bfd03dab210c79ed2f64c68e043..51eefbb9ca5c7a0f8a0b7773cc8607f7cda45cb4 100644 GIT binary patch delta 1695 zcmbQkdz^QIWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LK1LpAgr(OB( z@85g-uFWj{b9>j{Tf6?=+WG6+*2n9+f8XBy=g#imw|4$Ix8~=m6%Q8I-JVzR_0)=Q zr&nH?R`~7I%I6!q&-bKUnUZ&HTEVrc`PXNcJz3jzV@Ao<=|wkZmfW0OdUH+H@5$|v**di z{_iK2{XDzo#pVeYCuCfmUU+kE<=evxzh2z%<@mBcH@5$|vE%pUO~1~r{(5Zb@ye{S#oa$@<-c~!R-)_y#=p#JB@^?$B!`*U;WuUk8R-`e%@$fDcxtDkJ>eYLCUO2F4_BcNfP0OuF|N z>KPc=OFVsD*`KlVaj7yZMgN`7z`)c{6%tVrlvu7%P?VpRnUkteQdy9yP?1}}z+llk zHPms^9RrR%uSF}HcsQOsuQ+y1H`Mak&zHxHH^=px-;4;IrX#@1d*bQ$^YKsF=jZKx z5$pbCh273yXQO9?i08}M{a&{EgjwG9+X4swy{SJoSL{~K)vWB0;1^}{yT3*-vClr1 z6m5K*r+Jly$|7g;l~w)gyi1Nd#&bTK98&lD?)HFFT!IgJe=d;U^`>chRn@0NwrcZv z8?JM%TzmUgmY|in()KMUmuYGLS!tyiAo?*WKj`u?({Jx?Or6rrbLRK$?lmj7EqTN= zbwZWYCl3Dyto0?cX3U)LGaq-x>>EG7Eup|YG7d*4-xzAP{*}6dFvR&m) zt~d7`-)?xy^*!^`!ShLHZtXGW<+;3*r} zJ0HDxq2jS6c|nEm7Euv#wH#>+o9d#*8rGiAFSd57ooP(7nthAq@a1>CzETI~7&u>j zA$swq!-o7S7QN0hO(D~kOx{udt<9!hLB8QDN6bHd$pvfPwLOuLUT*l~VR`K7mtHG& zEOFDyT&I=K-mutn>2}-vDT_E$>r90lPBq?`HuuSa!|c~|PFAgWa`eBGU#=2uk=Q>Iq&81}c;cVFWG zJUrUmJledRe?r6Z>D&9)&#!k#2q^H-@KC7Nu~E`7(y~&!c2h|&PDDdt+JqTX-prpg zYudbtGgbLSCY&fcee&d4?i2bNTQn!JaCwMMpE7OQ)bNn7(A!r$m|0V@jB|6|yne+k zC6zlXMN&fI>8$9e+?<$Ow|H}7te!|pN{EVB?{3IilbpM&x@u|zQ_rc|zrt#Mvuu{; zyfP6Dt)IZKc-F5klE&$0=NX4PPZrcUuwv=j#n%4*_Z>t}urXvN+XyHHgl=bGU{Eb_ zjVMV;EJ?LWE=o--No6oHFf!6LFxNFS2{AOVGB&j`vD7v&ure?>QM}|iiiX_$l+3hB cY#O*AI(F*GL{C&y=VD;+boFyt=akR{0JU#sK>z>% literal 1692 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 vCfWuDRt5&Cd`EXeG-yM0 literal 1521 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@qZk&L+-A2Brok z7KVndMwVsG8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6-6^Gc_}HH8C+ZHE=aDG<0+`b#!wxF?Y0ZF>y6EHdcb_O(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI(RdA@;*fq_ZU)5S5Q;#N5o_Xke?DL=LUM)v{*r-jxJD<0iC6QigY;(Pszic3VV&wQ<()V}#U zta==7axPl=W%bR==|<7-!xv;sjO#PJ#@>3EBh&ETea% zOcf?gRLj^$%{Flr3m}@S)et=5ot>wk14@q0G;jCjZ>I z)Wc}2z@qS*72lsNx|;V_t-$y6)V{SR)-k^P{UIa&`bN)izS~cXm7b&wLZP_S09Z((}vH_?&u=R>iI@lm2>^YhUQK_}yvWjUMg^WRcyT zeJ0#no4+n6FD5ADEl20gl3mAEUCq<^D|P$VZ@xM%1BN}94}Vu|HTVxIggssTT-G@y GGywn_1wF|C diff --git a/toxygen/smileys/default/270C.png b/toxygen/smileys/default/270C.png index 7fe482fbb1810e2fc57093f7440cf6dbc06b3287..437fc211f97f07f97ee2d1a7e345383caf640b80 100644 GIT binary patch delta 1566 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 diff --git a/toxygen/smileys/default/270F.png b/toxygen/smileys/default/270F.png index a86cf25c17863360eec8bd0dbb3cb5462e8e6cea..2aa3ee04112ff96b92076203677e79305a9c4fea 100644 GIT binary patch delta 1486 zcmZ3>^MiYWWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EX(%Pl)UPj|Km)S^Yk0{(mRK|Ns9P z7#PmXIu1dVtrO17IzDmc;>Px_6>HZmnUZ~fjn4m_4E1Mb9sj?Rp|W*CL)Wz0w(gGp zDYLrrE>70|G=l+x{_kY?y`A;tM%R1moabxOJ_g=w>lZ#&tSMT z-}Ae?)b9GqlhVag0tn^o;{ zX4dfs`)+-E{C?e{t@n4`cz^lnmX$jet=oR|%*E%2?`~MI<|NsBrlK<~y1_lP#k{~}&{4g*u zux+iE+xEl$Z|Dofh6f729R3D9mcDuGiRR1DUvu8PVq#=sVU;`Z_}Axq)?9o7Lc*eQ zQH#HP{V{==os&mGCV#)e%*a^`3=E7(-tI0e{TVj{8R{7r*h@TpUD=>1{P-zMb{0Gt4w^En0D4+b^G}WX>FuPFM5bGacQ=~X=%3CU|#)z^9UW@;GkmFn7k zqN${+r+a&YXuk3zhEFQR_uN%_(r+^g1bQs6JLwU!gtg^LeIJwJf)EXs-!?O59Td=s ztD4Wz@|9tuE1I zvsGZ%=YLy2zd7PiwBhATy_6^QhxJ;*Wd2Q_zWd0(xmWnS%XjEc_pcK>yXx%kg^kUj zms?v>Z>P;H36q_>H@_)ecDd$m&bJ!tv_d8G!$sm;bFXV;Z_{})X?2%eij>kh<+gqM z+=`lO*m;io9ncbD_S|?ZvGu=;@0YHrj(q(aj9%DW|8Yvgj`6a?xwa`ep8k*C@YYxU zbFgDPEji;KzvQ`1yLpdZw6U0WvOIq2#Z(m$o|SezDQmp^*oDtD9J=~#*HW?e3C|xZ zFt+#|xn<$WXH%0fS2xmc?w%j<8Viisl~vEJTrAwRc4ow1XFnYtDJJ zf3N5K%XmDtYl?rI7as!y!*Wj-#}J9j$q5O`6w@F=gGBjOkX~Id%wQE ze9V!n=j{~?G-kvzs3}PZNr}k`N~*F53yO92b@q1qH*_0xcB!fzRafrr@LsZHQJ05T zVuwewYyG7~DJg-jm!3YEDLKK#$VgOVAydM{HE(28Q$^X>LQP9~d3j5Hb#rC!%GlN} zXlQF(IB{d=%9%S`mrmWv%gnuc)vUE`bN4n+T)BAiW^X+PjcMxEb4zudTA7$?8(11x85rbP0l+XkKP<)u! literal 1581 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy-se92IfYFCXU9I zriO;DMiz#qCN6FUrj}+dZf+K)aJ`;+#U+V($*C}VGc!}Hpn8q*>a}t%N=+=uFAB-e z&w-_YfQe#g<-Z@Y{l5FY<@Y!*uejrnj;M+@q_;R8dJ&TH@Zpc2 z%Nb2%-PbPgo;~0EMsM@lbz&?xZmigO@P+hjxmS)qd~+EqH*dW8;E`|Xhco&LXD?cm zZHcYDk@(%_TM5r~p+JF{HEx|ZkA7c~Z_glmSV62;IbP$!-Dfv{L-Eq2nLGk3>D&XW8Et{noEvW#>fHTUTnX z*=wt0y-@qzm-Qa^&nbyB^d0_wM>5r*{HEurl_D!NCv`IYG%=H0?l{4OS=9E2`Kc-+ zXNx8G&N?1lE|_{;u`|hJqo9zgr;+xy&>z-QY^KPGg)aMiwp@C-uZ^6p`fk2eF4@ZI zC!R-!<)&Ev%`y{xucP0nctYOWd4+NY=bmJi$nVUTrv~`Qbh;`FH%?W}y88Zp%9Pz} zTstL~b?^pU`L-}bvP9D7<#~;#JG~sW*Uj6!+dAqyUoGF~z4ojU49EX`ThLd{`4v=v Nd%F6$taD0e0szM%PHg}H diff --git a/toxygen/smileys/default/2712.png b/toxygen/smileys/default/2712.png index cc6c6ab2e1efca89e69b962dc12c4870acb64bdd..97c107202c8929f179ddd6fa7baff592c066262e 100644 GIT binary patch literal 1517 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IM$-VF5ZC|z z|1&T!JbdxmXYrYZ*FGJ8^Z)1H{}9HhD>oqwhM5;34FAm!&%XQbzxm6*1~I_AX*sOcR-Z2?7ngG^7VxqcP`wx6CzTz^~$Op2N)O_AY6u; zeUq1NU_b@E^H-t@Ab9MYTrE@QJI^@4kgs|*Yb z@87>abLHmLQ=H|(%yDe**=_bvR)!S1{pB*^3$nbX98N)*hwD>wRuKRA%3oPw>W${A$V9lg6 zTlM#~7U>Hz-kVYWYvJd8g)8{|@1-eJe`t|aSDBubebVzqOuynKrP-_2d{BSA#3A$u z_su2CT8mGJd8EGSF6sGwd-59RT)8ge5T{R1C#F|0=`RRAa^{cFtR)*ye#nxTx5oI; zOT)TNtXoo~<=7{v=hpD~v%D{5Z*X7O_-Dez6&&0gcmChy-q8@EDEKJX;OYSZleph@ zssh)Tk6cuMotm&(RaWq9bpuT!^9?r0OecC&Rt zN&4UO$09o$A1t_>saJADc^&6ci#L;}?>_KP_6pzZcLnik^Z%SVwr11m~(8r?M}yi z%xo=cGctdOhnhZE6ltigby52Iie%HO)Wg~nJbj((=QmzbE@{B>z>N4*tb(VA=ol1*Kc8R`uB9?g-fRpz#74-sI<@NP=Fwx;DnC(^$jZ|LR?}F-8MGRxN$Tzh)YXJR!B;W&&b_P zOiM{!p35+j@kAmEqpNDi$x}T&0*r2=p%Xf0?P^mCX$bVbeLGlP$;eAwV*$I^MRNm_ zuD;IRR%%Hh1`Y_#|D}yv4|H^R|s!Hs{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCz0S@CmIkKACXU9I zriO;DMwZ5wF3xUlE|$(tMkbD~ZZN%`dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{?y0y zvqd*8W=@yX_cb%Swk0ZE;;F|h-5=j0-)#PNeE+q33Q8x6`gX7Omb$-Fnr+Y2xpG3{ z->(N1d}Loy_5Uni(?h|7H~e@i&kEeFXT10A_NvpqX1h{UPgOh!aN1||$_y5CDD$>Wum!08#>%Zn%RS^N=9(_}ha&zKi)GD%PI9jmjJ z#G)`Kp=8cX`KZ8D^{eNm^tSJu85cc6w}#{-%P%L>z?n8Xgl)1H-=F=YzwL9@mF`b()}FXAoweuu!ZRuVc9x`V{r%K^O7+6x zB}cy$S#Z8K_4X5s*>>Ocq{xOknUxPWMg7s1n6#??)s_c5_MdO%SzW&+|Kr_n$GZPa zwQ}Xb#ntZqGZw{_ZO&2H+1b>s_;b!ZgHqnVlhXEz)|NS*IeqTPnKL|>wg!EBbg&>y zM0=L#;V$0VWT)Ss>K~rk+iqIwt0pB=HtAr3!seyzEdmZ@i@5f32Tp8nRe8Roujf|5 qzoOXaJG&|}?>%?+4rczxVZfkjSydpBzw!{MaQAfeb6Mw<&;$Tiv|C#M diff --git a/toxygen/smileys/default/2714.png b/toxygen/smileys/default/2714.png index b6753966f5f3dba453469e261e7f5d18b2195f7b..df3540fb9f19edd76cae3ab246b443d176ce1855 100644 GIT binary patch literal 1400 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhF1YTA+D3o zGxVNeXg|qNf0Uu@07Ld}hJ>vQVH+8|Rx?;HWzb!~ATx)7aV7)9G=@p%L8$i(RDJtN zsIK~>P`zabpeAJRhMJSG6=WKO4BH4Y7D9Th2APjWS}z6J2q$$HfNY18GIKyKfsl+d zK~92@M7sa~|Npnb1@|*BFgTP1`GG=?0Rh%tw5nbESy=V&ggHN?HGfaJZ?^fH8f};Gi%$!t(lFEWqg^Jt)1_q1XsiB_R?ig^~ zDejVSWMlbMctMcLo|3xD3z_LxN zZfTk>3vsJDE4unbTS?pV{_=Lw_mUf#D<_&>(-u0m)t|xT$)gwkAxESyF*&`c?`Kk6 z5Sqbqd)CybK*b1~uiFH;%w$@7SEMEO@9kj9c3*9upF-{RL zw|H}Yo$Q?MY~Fcuxtzl9{eNRK)N;4T=1mj4ZA=R$ zXu8TMv&GF<-s!N9iH#wxx41QYi?c|*|CL!mlRL5)BM$HHa_DY6CK@Q$s5!BDg3y#L z8Zmzz;_jbdPq1dX{9o~Kl~lRoON3@>HMtH87&oCSh6`i1(YEak7ae3;|qe4vq46GNtPb_}(?%g{+o=gA#%gwhq)MI`A z8ACm1>>-B7-5F6+7+-hCKIRmgqds%jQcJ!)>c!`uRme52uXz}_#8KmARpbJ#s;zF! zE2Z6<1umKNi1j+;l>2Pf{`Tuxf=1AUB@9fDkU5>1DCk6%v)e_f;l9a@fRIB8o)Wnih1|tI_BV7Y? zT|<))Ljx;gQ!7(LZ36=<0|RrjS8^yCa`RI%(<*UmaLW!_#K6EH39=zLKdq!Zu_%?n jF(p4KRlzeiF+DXXH8G{K@MJ2eIArj2^>bP0l+XkKHtH^j literal 1262 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{;BUMn)E<=B_5L z=7xr@Miyo!7LEoM2Bya5#+K%0jxfERdBr7(dC93Tdowdrte|?$@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{%)Vvg1r6MJJyB)K0o-i;l%6YmthE&{2no}~bQR%>u0}l1f*O)%mE9~QV zwBgS`?@4Le3VcnQt~c=haGk|?G~gGvfIqYJhsXREXN5LeFr1pleI)-NTg`vpEsReu z82siua@W9d+WI4s3pkH_4EQFaP}gPfKzznN{v%ughQ$hYt!WP89=Zbc?2p1yIH%=L zFxb(P;=nDiTXAI}Tavs+wUU6nyII4q_YU!Q429AH=8i|x9eB4iXR$SMm7Glwf1%f~`DVip1A*C&KNXf2F>PcJ$Y)N< z&sli-liWl;hrCXPN5uiYlL9{Oc9_$oT@^8J(K45SoF9xIwG^f?Jd$>J(=At^!S?64 i&#Cr_IqDn?Ogs$pQUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhN1wU5ZC|z z|Nl=(0ioE~|Hvpf_&)@>yZ;BHzgE@|^g&1GgO1JkD0|R@Br>`sfGj={MRhB*W872%2Om$Tu5hX#1 z!zeJ=uQi@wg@Zm?4Co=wd+-|&gKck;F?GW~wz z=FOeEEo-akD$SbR=Z{wDG`9LCZ(KX`ac9G-2$e<7<}0iE*Ljy5cZ%nHHaTS9@4MRr z&WS21NPj+PQ*u*1?%m9j?h-fhViMPj21Re*dR56M&t=w@lgqTUKdrRV3=sW#$ZTci zV~K6nYbJeGVm|-(o$Ixd=u3r--X6BGpEUd@u-43)F>}7xfZN=crBk1gK^GbSoK z=xn~#Y5T2-<#t!t{-<*qZoALsigVbuUSqlP@>5I>`tvw;*zGRkSeMvqWi1%AZc)AL z94{Ub_W1`37Do2(5c4p6JbC)+L;qs0@I|jHn6D`RN9WkOv%i-)HtaTRaa){oQ*HLy zpqsyyT(;fu4H15_B5&DU7JqY-dslARB_~bMd2=eToo(q7&mBh>ye?J~KM{VQMMLwu z+ghgOD<8^tdVT4$nIQOn+H95g(+(JWSTIa?S*9>`Qi;&@dlR1XKVkk*!lL)T{Y=80 z2N`{Zk4?nv|E$Zll;sp{zv7{OHt^}o3nJ+bQ}2D-S12|+;MsLICJ#%2+ePY@A3ii0 zsz&mA#r@G2nc^>FvPIf-^Gyw(;+&I*^*GF=3JZO=OfdFhY1g~ee`!*)@+b4#byuQ) zu6y8DRXyja(L8p~opzb$@_w)Uv1wl|SBdtVT|X2|%hXR#{2LwgkKs>8*DASN(>oX# z7_vNF977~7CnqTI8yGcusJThZpV>OKx3RHz>Rd^!0|zzFpFGN{Vv*prqf(@zp+h{8 zLw^4vrTq+(RyH|#c{w$4?Ay?>i9>+%@Q+1FT91T;9%(&V6u{`InJLM}CYh<}IYTJY zQb1GPd)a=5Y)4l2h1LHLGB7ZxmbgZgq$HN4S|t~yCYGc!7#SED=^B{p8k&R{8dw>d zTA3PX8yHv_7)*40xC%u>ZhlH;S|x4`4`d?67#J8NK{f>Er{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCz0R&i2Bt1@sSeNZllIZxqCfDU+4r_Lr zRn->~T5=iobtecF#HV|7T}s>vArxAA25X{C#jRdk)(jAu+bdB9@NQ1gQ&5f+}fi zzkPe_z9#O+x390&6}IxH7S65l^ iQUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhI;`%A+GN|1}s2b~60m0TbNJ@P9Lm4bg&3ZesYq6}K!zH zn+*T||CdT!dW(U9!K5U}FPI_AMEaNfbpC}agxNk_yC-X9!83O;UqzXW+_57Z0eklZ zvim)J6wKmDmJE?t7_X-yM1&dXb%D8&HxRm!i zYYMD%wYdD*eYSJ=wl6mt_Z0Mh{M7P6#+t#VsO*Wl#K*P`4FaF;?``1r5EXK~HhCpq z+k%f%ZU;HsC=rm3dwWE#Sditk!z%k4zXNaQE^A^vv8}N{s%DiS!;7QRY{!kW*&n}I z6qv5=bZY8Xd+C0!5*GHklF#NC%Nglu9Ni+nCx-Rg`vqk;idui0{Cto;^+eQteO{hB zsy-XCr{pg2+~pDWt&U@wYW`)dS)Fko)Sks;o+%bv{7}vD5HIsl?ITeEF%^l?2Oi#> z%>6_=Va~hO-2y&}k}L8LdZ`9pZf31eeCrU_QOG6L_LFIH8H--04C~3jNn%G{vKQ4W z%QyVwi227axghXv*AtHzncfG>?^aF;_Fj<@WIWX(-E>#8QBP>ruIIW-cC=iH7w1%X z>agaSuhE0V#>y!vwjrOg>K87Mv7Ep$VX^0jS^9-(Ea5ncur4rhL}sTlYlccgxRby64rQXXhbx^JGEB z|7la=6{mF_N_P#J%)r1f&C|s(MB;LCLPA1H1B05z*)xZ@5;r(%l<^7s=?OH?ZkN_M zke!uw?l^x6M+8R`YfFo+tBWI#p>|=19H*G4fo`fUuc>HCch8zRy=%AbX;KRD@w|EN z-oc9p@A*1$09+^m9aVjUOCF1CHV8e7`1WXWumDeGUqOI*&r z%X8O-xS>N=;0uEIgSCD*qTfUHx3vIVCg!0FP}}NB{r; literal 1347 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 diff --git a/toxygen/smileys/default/2734.png b/toxygen/smileys/default/2734.png index f95730ed788b5099446420747548e8565616379c..93a995f5d5e1fa78fd1c0da90dda4e1102641fa5 100644 GIT binary patch delta 1253 zcmZ3*HIZwAWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;k)pAgso3nTx}5Boni=>IIA|I^+6 zPqF_$!Qy|9;r~v}|E-Gu8zuhN@%^u6{$IiHzl;H-cwuBcNJ9k!RHzIl0XOCU|Nnpg z{NK|BGHzEp$iy8@|9}7ZzdR9S^wzrn8_Pi!%nJqCv9av`ukZiY=7KC+U-*BfH^|C0 zIUrkCX22~L>r9AaU|i<=OP)0UHAYQ+-uPL`hI$xk5ovep+TuszOO+L8?MUZUF;>Meo#5 z?@f0Uct!9Vf0$nmHRBsw0^7Y?n-oV zNXt=rbkI6=U-i?os}mQ0W!UUwdGBrB?&38|9URP(ls`Sl)!lO^UG2TWhPz4ad&&Z? zX~h;V^2qyPvhmV6AK9NN^?E!ZZhJR~id~y=xU7BZGpmaScKp)~oS%2Q+dr+8e?`G{d}tB!+miEqoT4G!aWtP zkq#YS?{9YCn!x2WVe82dX6FwT>!Ljsax6M}*NAO&*KJC8IpLS`bIu+6Q@twc8E;+- z^mFyIA-KUl)S4w-yw}cJaMHR(|1B#@7A>~;(ZliF=C$JEmasV=o2Rcn^e^^G zlW+Kr^Aq^%%s8*!vNGW)n9gx<-NkK+lT9YxyqPVmblS#O`bg@!Lp;plJr|hzJDmbd zR*9TpQC|3a(fuurm%N21dFMlnw+`TsX zW9r`@A|K`@y?oTXWZ^lT_G{Z!{xa>W=$f#NL;f)X1H&{=7sn8Z%duyU3pFwDxL!{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y`~11#zrPC=B_5L zriO;DMvi8VrjBl=E@m!nrbf;tE-<~GdBr7(dC93Tdowdrte|?$@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{2QDz2UVq1{!#lsV_Y?%aF#u1d4q z56pPUvqv!CCbOLW;)*nx6YGl}m=pwbly;g=ND}Of_|wic;jH^nq0L`=RoUV*3OA;j zMlx2hUu`=&Y5P|d*7<%XawRS9hwR_OxaZD_shf|e=D+StE^|LS|LYfC=KSv$ciOY> zx%YRwN4s*3l3N~=h8&}Fa+R2M{G}iHx(_)@oZQqJGtb?;%Pg{OXLC=KrFYDyZTIp` zemHc`oSTujJiDQMsaedLC%Lu{vun2B^W6L+MRJGTwTP*YS?mmUudj^O_cnN)dHLY> zCm+}j7E9l6vYGd9;;RT1IY*{>Cz^iPGx0Det~k}cw9_sDRC;;3`njxgN@xNAYG%(W diff --git a/toxygen/smileys/default/2744.png b/toxygen/smileys/default/2744.png index f88a35d19e2528a2a2dbfdf08a2577b3aaf1a96c..bf4d09ea3ced7be376adcbb268cf83f46b22d109 100644 GIT binary patch literal 2099 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 UXKZq16{zy^boFyt=akR{08Q}DSO5S3 diff --git a/toxygen/smileys/default/2747.png b/toxygen/smileys/default/2747.png index 6179ee0dab4d34344b816f510ff51b3d4f1fff5e..1eaf5d24bbb3b58855e2f91643d79819fda13d97 100644 GIT binary patch delta 1428 zcmcc0y^VWnTu$sZZAYL$MSD+0810zR(PlzkSbq0p33=EeU7%ng{oMB)% z!N72of#Dzn!yX2P9SjUx85lM(Fsx%>Sk1t&k^!XnIzv53!)ln&N|?k|28LS(_&DSrR=^Z)<McgS(a`y9_Er|kRvs#>G8lcVF<9z(v^wnql< zC+@lH@a+MowV#LH)oqg|8|)B^UE*36dQ-svnrGvx6XKgZeYw9+SUbV%dbi1%$M+}a z+|7B}R?qF@^y%ru^r|L}gy17*{!E&+WL-*?DUbg()|Xpe*xzIh+w{GI`NZUNHGLi| z|8FxmxGZF>iqkshEN5kns-xY1u91D`C_LkvWAuG^a{7c^D!F^Y-@bg zKg0ULzh#;aS&x3>eHn6O%1nk+-J*Qk#k19)ONHG?pWd{j-uiug**QNR753R~^ZE0lDFrEFTA|4Y(Y{h z-_rx@W_@rp4s$UuG^>Ag{=iD*&5BcXU+TpCbeMPlgnEKK^YZ`BZN_EgjF!i`gJ;xy z4&7cN$tmf)s#N*y?lUg~0?K0UMX}{yc_ehICPt-UNpPa~eosD|JqhQhomuV|_eWpV z{hg)v=WUL; zl|5m}Z_i>=>EMqO>T0-3{6p^6OyIit^5uyi)0V`a-q1BIC4Jdy1_p*(o-U3d5|?9- z-3($f6li@|cAr(m)PbXfh27kQ#~_iPIZH$-!{Ppa-<)+TK3Rt@uWR@(baM696efkE zMm-yI^t|UMuH=1C8TepNhvhkLrStXXj;FE%C#C9~p0s0P5qFcqijGsd_wIJHzsjvp z3H{h|e$lQ|vH@E++{@5fI;*!TB6zoe=tLJ2A-C!U(Qo~>FW>sE)cWnTd+m0TGZgvG zCQM})IR5cOPyC0kXM0xgAG}rmIZ@qWW3choZya}?=e)ZkUT1AS|La}DIX_#!*ZthQ zB)nqb|U|^60*$_NYOOA_y!PC{x JWt~$(69CK7U_bx> literal 1493 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{;}!1_oy4=B_3n zovubs=0>g-PEJmi7N&-7uC8#so_WP3iFwJXFncpIQ>>tR&G72AaxO|uEXgkl$G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7>^C(w zF*k5@G<0@_xWK}|#m&*e%+k!z!obqf)mRCtH-(%q)3;Nw(FY|Cq{IRf3UYA+F~R8? zl*a885Sb@6FU3}=NXgz#&r;EJrp5s{&zjYCd$PdQ}zK7#1xt|wIdbYTu`n##ZJ6nbKVRz#s zm*jIM`Y$wdc%XYYPOjndRE8q`rumlN=hQ@%R?L4jtC>Mgx$J@3roVPV%Yz+GyUse^ z7@waY)b_jNz)J7fnn(BdFd4Ldipt!3XNSxByV>E}9<28Jz#@Ghk)>c`feMXjE*v${$sJ{zGN?75m5L ze;C&+|DEC0dh+@7yurD|LR?{`h57mSd->*?y}vd$@?2>^#EE*$^> diff --git a/toxygen/smileys/default/274C.png b/toxygen/smileys/default/274C.png index 64036e1cd3cb3e934b39e77be9a00e6833e6b56a..2bf25262600a26a9121f670557465c3374063463 100644 GIT binary patch literal 1396 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhGPLfA+G-; ztp10Z{|_|!@1ytMP5r-v;(tr2f5yV!b$C9ivb|DZek#TEK$PLG5CceMgcXzwH3#W} zkby=Z?GO`u^q}UrsY6Wz8wfEIV(fPv9*E%|RoNg`yi#C>SoBni34$JoGQbhYB8cHQ zocI6#|02D8k_-$CW+g#>pip5z0OLJ^yN%zU5!`b|Q1FbB;F;foXOw;mo`Ep-oDl@c zfz+tGoSeLLy3n63Z0| zit^Jkb5a#bDhpB-Dsl@L7%Y0HhI(&$tiW^UwMa3G4ku^dV~NePS4E!txSaLp56imw zo!X5Y3#aX3UABM!{|oDXi0G=!F43QtW4Lj4YPjFAX=m5o((A2!zy0=FjhbP5u zGV{D;v+}_92vyyER%U4*L%*+S6J&JV&>6Yw&773aN~^^WyJa(`aWDUoKl`oTMz_X> zoGVpN6ko?w?CST4OH9~(l&|*PgDKtH=5#3Ue0Nx7X1QSA3H6go4x(4MZ!2k8KHHGH z;ZsEVJBjD(1-C!k#&=v)@7&8TANJa~iG4V^W>uM1Z>El0|fAd*wT9Z4#VnGh1(ntG0tEsGFjr{9VIaR{)E@uKN;rq9r#jzf}!`_ zIRW>_51wf3yRFvi7w&XO^=kX%p2)7-ip^y*_a-gR+t}rN;%nsu1}FBU&la9+Hh&J< zL>b9iRc_VSSimpCAhl_c1cyuu_l+4gJU0XLy0l%Uulyn)w{3@D)8d<#-Ji@qRrkXD zSLWflzuwkOFRqVKDXG0Y=kBw$Kep`K%ayV`VpoOY(}46Thd;+U{bzXX-L+^l*Q;v` z3=G>mT^vIsF88)YN;^47xb06g=uue`8YQ5+MLF`{|MgkNQp4vyue@K(@b7@cti5L^ z9&FY5{j^8WDfxM7G)LouXO(s{xh`{V*5*(+_sli$jQHR4E`kPeRjHBdZtPJ#CZXc_ z>3aUTTj{!uXKv)IUUe(4;?x3-Q+huNnq;$CIXD;ZTK7FC(1U}~)Nj@@Prkp$S8?6i zn0xzjr1kt6{j=Ci&h9!rHK=NDNu-zNi(ha5U9jwb-fPQK_k(H6eTj3a|C`P*Ffgc= zxJHzuB$lLFB^RY8mZUNm85kMq8kp-EnuHh{SQ(pInVM)D7+4t?r1BlziJ~DlKP5A* z61N7C;95|LC<(G5I6tkVJh3R1!7(L2DOJHUH!(dmC^a#qvhZXos3>IcboFyt=akR{ E0RAIKr2qf` literal 1169 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 diff --git a/toxygen/smileys/default/2754.png b/toxygen/smileys/default/2754.png index ce83bb601ba7849eac7d4bb09998c4bb3c11ecb2..c1afcb2c73e3efa88c5b0395d8a2689426bf7265 100644 GIT binary patch delta 1111 zcmcb_*}^$NvYv&3fnn<}^H&TE3@qu6zK#qG8~eHcB(gFvFeoH@1o<*BRH-pAG_)`< z{9<5WXn4WEP-?)y@G60U!D}OzLU@!6Xb!C6X z&c~(7cqVktZUzRXx~h|* zz^ZiP+L|j}Tn8Vm+};|n;h{#3iz#>Y6D6)SvLW?H<~01Ct#*jJD_8cV$&nBx#wp^) zGT+p**`Gg(T72G*b)okA`ZBq>FFg3nl<(|JvCDDFIQm7tbf?#w`<}iLyAIXvs=1PW zIU#p$I~(6SDZL}vTXHWfS)En(?G8)*`B}>|nRhDrx++XyHQsT+V6B2k?<(%Fu9+8W zFV;^GJR>ztJMCY`6Q-YB64lxVXD>dP&@a)@ThM{dxM*QvI!V54y5Dl`g%2i4WvcAdDzpxnBHOORj6#meT3MBb3kx@x`cGoy*N<@bp60Bc|82U^g^g^Cy&|$N8y+ddyeMCSl6{qbG6Y21_p+^o-U3d8s|&vFB)pQ7D^odcz);iwkaCi+DDhI z@#?%9y`hS6iunTNW=FHxL20L!?O`!9GSKKgdDc|pxn)~%f3e^9U$&Rm?>qm#^51{; zlOL02Jabt6K22uF_0Nyb?ri1qF0$GDcFVOpTOTFv^WrEJp1x7=vZ0u@qMCAV)uG_x zA9GVGk~H@Lj$j!#D5r>w<*Kdgo%oWqlLk zn;!r2@=f5h;bW*@pTQruBvj>#_F22lTS~Uh-=q66U%5(+YxBJwu7U0!Mb(#Tf8oCR z%I}*`dB_6>1_sp<*NBpo#FA92{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0RhF=9XsW7Op0) z7KVndMiv$(Movx!X3kEo7S0w1E-<~GdBr7(dC93Tdowdrte|?$@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{xVFm_9CQlc~kcwMLfByfsXXd!GyS%?KV!@In zEa}DzPkJ&GN(;Y1=;e_}Of2~P%(v#A9s7|shd+WHtXHmH zHQf8*`}_DBwmnUk-dG%FxSQ$WAk>k&@J+7;?}3L*X$o;{M`9fQFm{~wFkQghA?q-a z<;X^dKOG{>H4Qr?PQ@`CiT<#RS%=M}If?Vsyaw%t50VSokL>fiz@W47fRE3<1gA1a zsXW$4=L4i%`Qn4zj;T7FVLnp%qUea!1?D61jTJ(6%nVFC44ik4l}fPv7Xpzopr0NOQ_)c^nh diff --git a/toxygen/smileys/default/2755.png b/toxygen/smileys/default/2755.png index 74b34e077959d613bbfda3a70f862edce01ae2e2..80cc60eaec42cd7ae362f539858792fa975c4108 100644 GIT binary patch delta 1044 zcmaFIv6*9nWIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUb!@~@9*3IhXEbyY}2Nl;?BLP1e}T4qkFLP=#oszOD5ZUF;>Meo$mz-@C3IR2)K z#yYVu6G`#5I@pR&G3bL@q3L zr$n^r_M2rlXMF!xVPj?dWOJs+D(|blU3qaK3vTDAtd|SU)G?2A+ifE2!;Q^Sg-i4{yURi zgONd`gQmN7=FW1bPYRgxDaMQEgD2H_(!Gatqhj{c1e6%@S7rttlnv@n2 ze?>lpWoz5ho|*PGJ015iw>hdusa-;+Utv$>eS80bFWn-9am_4CpkmNlFO(> zz5B>j{wF_sYnaxhC;YNck?4N4PQm^0qbnTyZfi-$Mmaf7ezj6%r`APTtxJ8UZ@+q= zTVGblS!t&y|?}nj6^G_Nj4oHUCiN_q1lxz35aT z<)i*_f~$LnOz$i^<@$-=iuYa&=YAKIe?IQq->V)k_Fay->K1OZM1DWhP0ek2bsnZS zP8L-B@qQt0&Czva?L@)t3=9nYo-U3d8t3Ou+{;_fWXR(d?^j~Lx_E=6Ps0(r9;cZF zixw!}y}8Vz$cXEXXHD<_d3M@`1`K&MS@!A8ul*$23@f)VwI%Y;PG;BWa?>?j$!x~U zaO~FZ%gjs@#2CIde>uI7@$s#$AKMSV(tdP^i}eJH74KO`fhQ{dmR=uMb3MJkereT4 z)y>U^uSYKsuRmKA_DA>LCI+up1tzyG7#J8-OI#yLQW8s2t&)pU6H8JVj0}v7bPddP z4NXD}4Xlh!txU|c4GgRd4D770@S$kP%}>cptHiCrKP_hy0|SF3$cEtjw370~qKO6) PTnr4Ju6{1-oD!M<-qN)T literal 1134 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y-wz)7KWB)ZmuS- z7KVndMixd!Mi%C7<}NOdu4WddCNRC8dBr7(dC93Tdowdrte|>b@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{afd_Yjp zA%WyW3%~IT{C7W8Um)3)#Bjni;Ez-%@AG74ofC8F|NlEU<9tH|ljkc-J@E%d4^(IE z5_ggO@cn&!jM+YgtE?}i=1qRZRKgq=^r|(zq@3~S#t%I@`U*!6g)5|`C@>i;+|9sg z&iJufA+M!V(1hU$lS&NB5hjNi9%1FH4JRG`h<2zyFlJCVz%V~^fpe9*@LEs_;OXk; Jvd$@?2>?iGY`y>h diff --git a/toxygen/smileys/default/2757.png b/toxygen/smileys/default/2757.png index 09323192b895bf849c6222f744a9db7f46fafd69..5ce95cbebeb2a378ca96fb21a233b2bcb1e52d0c 100644 GIT binary patch literal 1291 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhM56AA+G=b z|NkFh^*_wwe~|HiKZE~XTK`?t{@W}3x0L#4Ec`>4_p=83TP2nka!ijU86Su;AQZ#Z z!?nP4!8N{+V}c-v-Up%#C??>r?y)4}|Ns9lv=(GCFfgc<1o?qH%76_1JU@GG+uV7r zxp^TrwyNAs?;F3ge`zWFdC-i3fq^l}+uensgH_gpfq{X&#M9T6{TVwSmnzF1`wSBX z2BwCpkcg6?#Bzm#qWrYXoK%I9%7RpdirfMQ28-UQq0!r(C~*8q7v0Rl%&}`uNl$(8 zxtepImS3oQvaDGC^1ME_iA_wLfB*h%uRFRvL-FR4>W?Yiop*J17a48K$-7#Xd8T^* z-MqPypR%MTy_vMt^U^a<)?G)nGCy7|H0WNxE8X;rfku<_nMz+;x>r3 zIJ!G`+wLQBtW0|XUq5VLQ~Y?PolWJ;#y4W>Un-k|^==g@@jc(gKQZN=>yCr-Rk|5M z1@u$8S3HSu-sX5JdH&AidH%|I&bhK(+97i;XMV7?X%oM2a?PqTuHMWXw~C{p$|t)D zcJ<4@=hMB{ew^=#YgW&4^`7+E3?EJ$oj5j}FwS9{X-n{f;4TIS{WOW3{xz~a-PwIs)`Cvq z*X+&ZroQ0dv$8$2v!~)lOF-A*x(`L7H@-XiM(jFNJ7dr1%~>gH&v`R5&u-)VD3dCk zd1;lER<&GHs_gbm*CN-hE(Znfv?ol5O_UdEgv4$LI@9y2`&V<%g0Ky1X8b%mnfrfTqnw;3zjF3GrsZ0GVu9@R?N4TIzj0Lc$*(;g3=5ez zR^CxzmW$iaH_4~nEAA~f*9&I$1~-d7HK|5LjkD!)ZnqA)b%`xHedddYP0B^Vro|@@ z3jSQ4X@5of@0JICU(4P)3o*rp&yP)f)%iLW zU5bi!Yp}38|GB1<(JAEIPA`T@OX8+2^<$WA+A!r))oyRj058+`Zki3(etoRT{5zLF z?)Fi6yBFMg~Skx(4RDh9)6~23E$VR;C8p1_o9J z1`{10u0qj}o1c=IR*74~1DQxM1_lO6kPX54X(i=}MX3yqDfvmM3ZA)%>8U}fi7AzZ SCsRQs6oaR$pUXO@geCwo@)N%R literal 1169 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y_Tj17Us?-My@8V z7KVndMwU(n7H&osj>d+LjwZ&A&M>{6dBr7(dC93Tdowdrte|=g@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{izK~ERQkcwMLfByfsXO?k^GJe2%x<84- z@L|9&_KyF`5AAoHNpXDm_cwd;UbZAom6_L%NPc*3-t(2`Y{PRGsSo1EsydgwW1YzM z=TNHhM7BS_n!TAP#{KxqJ)Kd%vxL!4-$waV!~gwtk4hPp6`AW4V;W4wZg5I|kQQV+ zzoD{m?gs7$FCS?0i9bHiz5o9o=VQJOf8;u(6O(Q*C>Q0`D77Zt6IXcFwyTX@Md9-s u`8x4Ge?QAh`m;S+?_k5I*2c@gBEj&%r6`+YVHgvrknnW%b6Mw<&;$S>^M6bL diff --git a/toxygen/smileys/default/2764.png b/toxygen/smileys/default/2764.png index db9de9e4f20534703757c3d900ff295345a22d9c..20b145d54521afaaa9936735ad31b2811484f420 100644 GIT binary patch literal 1616 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 diff --git a/toxygen/smileys/default/2795.png b/toxygen/smileys/default/2795.png index 33bb432ccc7ff83a76915ff0965c83011fd15c6c..fedda3dfe19b9a5b4988c676cfb225f668fb5db8 100644 GIT binary patch delta 1005 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 R)wviLJYD@<);T3K0RY7EkMIBh literal 1031 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy#`Kh7A7V}&aNh| z7KVndMouQKrf#mTZqAk_E=EqSmN31ZdBr7(dC93Tdowdrte|?G@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{*4_6YK2V5m}KU}$Jz zVEDzrz|io5fuYoZf#FpG1B2BJ1_tqhIlBUF7#J8N0(?ST|Ns9#={!TvX@;y_3^AJ- z)aNs>&0_ff|9@S6|8fQf2F8*gzhDN3XE)M7oFs2|7xsFw#E#bt3=Hfgp1!W^&)E66 zRM~;EPGy8$A0@USMcY;mTmEexg{d6c`V+jX}|x!#{D0!uXW!mb?JXKdG-B0t)8VH zpFP~1zpE?faoO&AUcnpb=21}@n?qNx4fK6@yH@X7GN(?CRCn%K8Gn|Lqe_CZyF9N} zm&7iz7W?I8Fn`IXyYKh47OfLtyg7sa*}~IyAB)cW+)GT@UD&!e%p>>iwXkl3or0N{ z)Ml?*b7J|cB@UrixNk36)|-6e-3G67-8?qmcZ)|}&hoA$`Ht$x*1}a+_z{>-yAJ&hP@R^ z4}QJ5rMRQb^wk>|kDXoro<9gls$XF7gQt!AY}Jvi4qcM-_r!>Pd%vjcMp5f;o}UlW z4a;)xne#H;=~Xn5Nu6>gfUYwE`-e+Z^ zq3U}`mo0X_@lJ<*EN(B7gqoER=#U{CSmTE{PFPnUDGa43@G&IEx9BUI**UnGU4;LZQpx0 z-)J$~Zzkk$s`17&>ywS`;ys!ts{%efs$aN3#xh~=5BAx;N~XuB*}Q2|VC^_vl(|qr zLr`Ydi&-`n7E1M7-?HzWx}NLZlI_PM^&VGFm{R|G^0PPF=6C78KEB*x`=OQH{6Wd( zn`Cb8SA8n}@MPD#;47~mFfcG^db&78;Nkl9Hh;NcYp1_sp<*NBpo#FA92cptHh=QqTzu|q}W77buI=5Pgg&ebxsLQ0B1X$ AfdBvi literal 1060 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy@pPX&SuV*maZnQ z7KVndMi!R;6akatU-m{$X@dG~(DQ=1X%?3AL kKKS=uQJ(DxLjwadL$ryUmUG4D?VxFVdQ&MBb@0O{{g{r~^~ diff --git a/toxygen/smileys/default/27A1.png b/toxygen/smileys/default/27A1.png index 36fad95c2133715e56dc4da0c338ecc449223b85..f0dd3577e374ab4d4302a21da4e735f95964db5d 100644 GIT binary patch delta 1213 zcmX@b`I2*jWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H=3PpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} z*_3x*f3|JC95wsU|NsC0{{6T7#N8OMx##Y`PM&utY|5_FcVC7~+;3A-@PR!V!e{EAAi>$32}7dGX2EI(e6!XI(cPKi%J zf7hnHN`im3b2fB1G5?w{afJsrN6dd2R;Pv#0jC;SpS1}bV&_Zag*aX_CP`jZ`L=?U za~s>r`$=*QzuSE{^%ZVQ*I!!Mp=r!`p<9mccJS8bVqWbvn}ro$p89?LTx4hClZ4AJ z^-7*7uj5>5@n`b%)kpr#z0%?vzQcbyf1TOltk*Us1qJ52E@(>g5DzY8Ak5w!`d}uQCHj>w> z{JcM4LZ6I))Ja2G51)tfOg1U(tTV$q>UD!(D%w0dA!RwswASU{vNy-8yzlGo2;OV^ zuIT0YZk5>kCh40iwfCjnw`1LA5Vq^Ss-Gf2GmQuhZ@FUj5TBI&k^f zqtwp^b=Q5{I+o2pr6ct;(?ouS&nyj31_lPz64!{5l*E!$tK_28#FA77BLgENT?2Dn zLz56g11n=wD^n9~0|P4qgH*nwJK-8MKss{sQ!>*kaqAEXu4SF5sLsW};OXk;vd$@? F2>?!p8Cd`T literal 1226 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0QVamX;=luC697 zE{2A#MixevCQc^C29BmqM#km_PB6WmdBr7(dC93Tdowdrte|>b@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@qX|NjMvu{nqvJUA@u%p+mq zx})Hf@!$Wy+ZpA>o+Kq}e7}(P;i6D_!Vx8Yo@V=k=Q{r7N8b6UxwUZ=tFo0A+O@6x zz23Oy?^pJd-1Ywq4j3pVtWY>KA#we=hBfRjn(n)>-}pHDyu`BidwITD^BAyw&HK~u zfBBY@ecS&3{|t33D|!`nHT_F|{LlGKDGWu7 zshpfsgyv74DDLop^-P0E#<2+sj-P`S{Fn_Hn0OfOS?|2INOq49sC4mk^>bP0l+XkK DU%Q}m diff --git a/toxygen/smileys/default/27B0.png b/toxygen/smileys/default/27B0.png index 8460f2e0207dc0976d920c6cfb3f9ac308e6c466..3ce1cc0efbc86dd38b3b87fca6ff249a0ae8104f 100644 GIT binary patch literal 1434 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 diff --git a/toxygen/smileys/default/2934.png b/toxygen/smileys/default/2934.png index 7b52ecdf8975001462bd6eeacc575565cbc96cb6..3517e59421c1cab1a6694da7c19340b2a48c0bf5 100644 GIT binary patch delta 1244 zcmaFM*}*kIvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{wM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umB{B;cKWs~yYlemrTfn-7VLu9we;8}TLuP(dhwDVKal4c zkbvj2XV0E{J%7UE`e+*i0|R4{x4VntksMwz1_lQ95>H=O_Gj#TT&gU4>@!Rl7?>KW zLLy3n63Z0|it^Jkb5a#bDhpB-Dsl@L7%Y0HhQ?mHt-w?BT*Q{i!|BlR!lugCKJwLn z-aY!WqwjWIeQ!FGn3MKft~qse|0nMIczuoP-q#&sH@jEg-_zz<_VLxj?)+P>+X{Ez zZR2>;Ev;+1CF**P?Iz8d+4?7=)-?8pCU0FkE7`>%G)L&-!r3W%-7lNYN?u&vR5|fX z`TpNxr?$GVq$JzTXkYjH(CON=PkhX`d#&GWbuP-imhE@wiJMRTWz>a^_Vjhe^jvOCTjkY!@R@hwM zt0G{=bi`=&+#8FLCj_T4E)Z8{`nEi){ee{2&2)9A zOQB!v+0)h8x|+`??6?rwze8=yqaOdbbzHy81Fds*9ja}s`LJ0kH2Rx!GedbYNAIa^ zOMSM@ID77%x#Fg?m$x{@TuobLka3Vh_rs1*jxM*YO*0ckO5}g@Z)4fs_OdCk-p**J z!#<|AtCK}Pw0Ag&KiZJ3u61E&+nbDs3myCJFg@tn#_~td!Z*ci$tKmgN3QZedD>gU zpy&SJOZ^FsWAD}p9Iq(Q>Xxs&KF=@Q>6ohNvdQNnpS=xmwC2lSy*n@FsN$5Tr4tw? z@g2EVIZ39X;@}+Jnf!D2{EFA`aOdOTjbyH$o}tLEvC&UXZqwp3K7MQ7c=+)x9zFnuiZ;kwXrn^GZ_3S*RzB*Hs@_*Wr z_;WE`Gh$0-tYBbZX!CS&43W4Td-l3elY@ZEMYD9qqb&;ClK57yd-;F=nbaFv8s|^H zt(1B&|8c!v(K_ae%S!88W{R>*GI}=CpzF{UQ5}~(D&ZR?L)W-EDH|?s>vi>dbt7uY zlE%5NUbAkb^VxVsTXcSI4VTQ-Seo~;`?vE_s|!)~XMa{@Up)FC*yPPY`P$;-{r{OC z`zfUP&eD0nz`&qd;u=wsl30>zm0XmXSdz+MWME{ZYhdoKYiJT;XkcY*YGrDmZD3$! pU@+0~;VKjzx%nxXX_YV?m>M3)M2byRROez~@O1TaS?83{1OQw!G+h7y literal 1261 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y)I^srjCYYPOc^{ zZia@gMvkTiCT_+C7S1ka7N(XKW-z^;dBr7(dC93Tdowdrte|=w@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{IPbuZA1`{QsV>H({E}#iR*U6YMmn)z|-jboI?LHLhjlcdL5O3b0M9|NrmNl^iYY z0|JR!+-dv&{%U^s^2E&F8&@U&{QjIj@L->}>;=~S zoyH99R(tdWo;_r^e82wh!RJhGgqY^M;pzMqmGJHB@%54gE0cfrt0XF@pSmFMv}4hO z@ALQboaT>B`|)3VIky0(<`p+-*T=o;>m{ej?m1KN`DXN-dn`+jJl~KKxvxN6^XEg> qGYujc$0jH^ehyafV>V=9;$hI*z5e*Oob$Y(0?5gAdsS@Ch>&U>cv7h@-A}a#}gF>=LkS_y6l^O#>Lkk1L zF9rsNh8GMBr3MTPuM!v-tY$DUh!@P+6==i2z_2mEC&V>r#l^&B=i?WjiCuUydfu_9 z*@q%#>Nj7Go_8!~`RP;l-&}a~Hfr{voMk7! z{rrt=+1pRw0=m|g%-{3#*Pr4Ad;Hs1cCJ1A_uoI?mSwZIpPsYhG{nXa^}Lx67#JAD zOM?7BLBN0nJfA&#_T20F6CT$`+ZY%a7?Zr+U8I>3t}J0-U|=ut^mS!_#?Hs3%6KMp z&Ta+ecy+yVv$i{7cBftzkC@cel$V#_qGh2?l* z(&y_l>%RTCTi>d3{`|-6_{TFFS}%C*W?FvVzJ96vpU~Bw?{6)3`*!qde%v9I-4!1{ zJlmb8RK5RhUM$PEXKvn{y4y0h>2A`jS-m~Q^xA=Ai#FbhJ7cJ{K+CK%<9h6-dy8L5 zUEO%myy>UX%=dNQZKq^+a-2A}$Cy9%`JvObDW3${wn>-Y$ZmfURG+JD$vOG8p-)sw z_*CAXDX}6ex~dYRuWTvcyDb|g`C3W#@t^N1w+o`%A9GEgP*e3JaGnD94-rFS`>AS~ zI-B-LwW+`EDyr(4|AtZjru#9*C$7@-!_}Jp=Q1fQzR+NoqM~(*iRrq1dZUPfXp2C8 z^Gde12Ond;t2SAkv$~oz@n3q4te^{SBcz zTFZDB?@29ctYPihV99vaZN`Ph!|P;Mxa7|W)jq?v+2+&3hXIc>f3VD9Gm;LvImz$Q zDe0o$4t5OBl?#5^r?4cKi+3d3ShOB3-~W1=U%1mT)vN83`y!vc4REmL%hwGyU-wAB zt6Em2VF~lb)Hy2ba&ZRf-p`!X*Z&jex>P@r*=eyz7=Ndx%_&uW{+kQ))zr87EN%a= z;iH>^`{w2SC+<)Adn5nlZr<`&+mFZU{jHq19xoDQoc+_B<)ePgVrkDTieJXrJ zr=%BG*Iz$ie_Adzhr8fdr5~f$N>0nUXQyg7WxqAt7V*l3Q+wmR0`0{;v03Mvw=^ww z^jc*hxnxP(W=F4AJ2>nuKRv#9AoLEC(T?MbJKeQ<78zu6`#=6VZOg^qPrh0gu-`v( z;q5<$7k`y5Zao)xpMilvwZt`|BqgyV)hf9tHL)a>!N|bJ$Vk_~T-VSf#L&RX*wo6z wLfgQ=%D^CxWj!cAB6Q^Dr(~v8G8h@)&>$Zv{(hpOIu`?jr>mdKI;Vst06pV5A^-pY literal 1295 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{-o4E|$*bhOQa}t%N=+=uFAB-e z&w-_YfQoKU%lFvo+tSnVjY^kt(_~&Y69M5~Z`gD)E zd!JCCrLeEN^?|LrB3ZIK`w(N=Pc^yh;4<{(W&xW#ZwN(mR&K9pLe3Oyl7cGP)eB;Kyvpz{JC# Xx$aN>_Qy)$pd!lC)z4*}Q$iB}BB{rp diff --git a/toxygen/smileys/default/2B05.png b/toxygen/smileys/default/2B05.png index 8bacddafcefd223fb534194c9edb1553c5e8b9e0..368e2fa6f3cbb140b20088d04acac7c8780b56be 100644 GIT binary patch delta 1213 zcmX@Y`I2*jWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+sEpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} z*_4iLS3Z9G88!P*%6f_uv2j|08DXOPPP@(!)1lQ+8c=_&Q|b*0^aqPu_kO z)Vp!ffhz%BYwMOD`0(XBvVFgp&J{5*Fo>4~`GGvk00H%2%JbQ?XV1N!KjCqGw2gs* zficP3-9;%jHD(P10|R@Br>`sfGj={MRo__p(akK(9J}U}^wbxht2y^+`GvYC%ZlYE&&d;Z;aYX! z!u|X8FT!K$UAa%H-R)M7_IZ6Bo<2%XBU+aTz zm}mF;zShW^tG25XT^s~;lBVfdtKZyn;?>a&7or+&9@4y*yZNr_GYx?Wy;1!(DRYJW zzfU}=&6Be|V&QqwIYk$z2^XqdubrXtS?>_$BlEO*VsIpX?^Tx z{*l}rYY!K;b{AXSt>$w>AN(gSS4Y6hszb;c-rqr@-QN5gv)x|)WnyQv!$*&tY z7mB+7dGK`AhWFjGHk7pf=BRw0el=k2Ip1c6*=`&kWpbsBGDBtO+KMY~>btweDQ4m# zC4&=9f>965&I*Y1uHv32qP+O`qW=lXJ49{5dj3p1$@)pvV4DV`E$^M4I@Wr>7-rEI zQa^g$Xf3d)JK<%(93SzO!-v^YDrk|aZpZ2T6JJ|v7^W#3{FOIySiIZ#$i*8LmR;|+ zc`m*@aYBK|*M};PHIKfW;Am}@e=>OcjH7B#e%MuE0+<4@|(U;~gKe1lx_rm)xmh+as+WtH8 z+~2|p7ruHHm(5=O=<=^`niJ+Gy?WF<#d&TZ-}!vbzs!^My6Tp$2v%TVU?}%=aSV~T z9D5>MsL4UV_2Tjw!V4rWIxgFq>Du%A{_LQuB0bOF$32)Yzb3npzh(Ba4XtZe_s%`- z9CJ&)KIv+sp_=8gYb*3R{L)u+K!lYR|O_AewrVhRcz`KxXYf)^w4F~ zW2a3I{<*tlhEh~r*6NIJto66$m&&&k$ulr8sFt`!l%ync8R;6B z>l&Jb7#dg^n_8KgXd4(<85pGU9o-4npaIg6o1c=IR*74ONN_FdL`8Kj1_n=8KbLh* G2~7Z^vKX2G literal 1220 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{2vk25yF~F0Ljn zE{2A#MoxxC#+GJAW=3w#uBI-=CNRC8dBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{@=s<*Z=pL^s;J$-L(4u|ANHW9K;P4BxAVNoV$JL37{nB1cImdY zotXLiBF_(ILxtnP;;zPe48i>(Z2Ki0cI3adYzr=*Cv3*BO0G?^xyt;Xdze7Nnll2q zt6BM)AMEpOIg{Md&d9UIx^(ixbDzz5T=zZW`QO3e;`aaB)c=|uYzpE5(M1Wc00K@jWNlMj6t9(J_il?ie%Q~loCIE;-kX8Ty diff --git a/toxygen/smileys/default/2B06.png b/toxygen/smileys/default/2B06.png index b394430236f9b6330663a9c340f9ae12a3638706..56bb9548f874b28af2da9f1d7f344d698032ccfb 100644 GIT binary patch delta 1216 zcmcb{`G#|XWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+sEpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zVoKJ^^JgEvOI>j;cHznRg(uHGeEa|Z{}cCKN6$O9^3;PRC+?O!g+Qn1_lQ4k{~~jXBi-%9!z;Yd-m+P*YhVlu8+1cFfcGCdAqwP zY|gfAU|?WiFY)wsWq-!b$EC`%>B$2&1_q{vs*s41pu}>8f};Gi%$!t(lFEWqg^Jt) z1_q1XsiEGR<{0qY`7L7EG%@9*OuOXc*j1a(e7u}{zTH5jvQdj?)0%0!QYY8d{hzq+ zWBsbI$@jLp_rB>~eSc4jXW7R`4_Cjx$1Ud1Tf)0N z*CMZg^W^eqxu=|)8vF%~KL=+{I;L`Zb>fcAc^b=)??~aVIB93Zr=Y*4hq=J`@By@;2QH0qt%{o0=b29c!TPn*f4zG z?!zgsa9jG+$`hLPn;9;23o_mg-s)VytNmuPu;NRtU-O$c3o{=*xZF7A#R~Tr;i$qR z>iXfWe|N9ooV~7KzM}k}9Jj02c4i7Z$m3ZUcw^g?lVO`L-YgbWx_!e}`bg;7Wn9gA z5(`=Lwlg_iT(RxJSp%+J{eRh`o1%}t?h3Rs+Uc;5$?b;6whd=5tUge$#J9fD>&vP) zkCN^fbtIqRi{|?0^jgq@H^pqpCe^t|rt&}e+FQeL&i%oc`V$<--suS(-%-fb{l4z{ zG(UHzV~SVXC--eXb9rGyShwk^t&th{hMQc zeOBg8d_S_XTRv!^)nb{M|0jLnUcRHN?a-_9%?u0-m7Xq+ArhBk zPuyf`WDse2*!jkLX@JLZy~xl1-d~H<>w5ocKI6a2f193FI|!Jw@(FE?%GZhVIBK?d zZoS*BV{tMaFIGH#bmW}4-MXDA?B-z;G*ou)(7q>TYa=2rR%;_FFUGt_L|)8!kEpzu za!p;Yq4w-UoQr;L-`25AblN(_sESX?F$@e0swJ)wB`Jv|saDBFsfi`23`Pb{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{_hthL$c)hOQ{6dBr7(dC93Tdowdrte|=g@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{mJoA_D^>o2QFoNX4xrpQyr#jY0=lj?8oT!_)D9 za>IPbuZA1`{QsV>H({E}#iR*U6YMmX)z|;`Vlrg5dX|!waAM=M^n}D;=No+Gmi529 zZ?C}4%bY4XN%x6F&l48ONoP|eH>_+-RLfabo!P}~%H4Q3Yn{fS2kZ2@PK)yR{Qv(i z(c##vMKL@Fj}>Xi^%+b}zrfJ7cr|O{0`7kX3XgN#dD+s`+(ejX%$y-n)bQvaoK^Vfq|K!H0@=hk=ms| PP)XzI>gTe~DWM4f!j_{9 diff --git a/toxygen/smileys/default/2B07.png b/toxygen/smileys/default/2B07.png index bc9532a7e3a02dfdb42658bf6556614f63566703..ed86d820c3c353a86f66767e89df5d7f4603b921 100644 GIT binary patch delta 1217 zcmX@h`Id8nWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H=3PpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZp#1v|IrM3_xbz#FW-ZDH`cB^{QJ+}zkmPLtvVdgwRXXt3yb$(@^4>x`09g`x1I!o zZ3^vNb@Ile$nMn;^R<_3s@N}XI+ z_rHGPzK^TICf_S{>3!3^`u?64&$5q?9-cLSt8{zE-8^TOZ_nJkId!j@M)&7%-G8MU z*=xFCL&lnIqQyNNE4o~jtV$-Y`MzS8TdB?$KZAIc&$r+2Yc0|fWV|_p|JlOVb{~u8 z&)jqPK-pui>a`PMR(+i`*F2{oNB3WWbn@4 z|I+f?R(zXg$Uuj~AZoeXL z>3`s@Ztx-Y6W{uZ>a~ha=`egrayjrXG%%VBU5?BPaS)%GsQH zaI2$u&jqIK(Txi-LZUyUNwD5M{;yg0;Mzx96Q}h1=uKGPka)G_M&+^kW3DoB!c$-P zvRa1zGBB^vpCRh7SW)pFqlIsZ*^*7FbB|2rfAY=Ej^VVR#b0@&rj5I~j~M37IrOOg z{?_G}Cr&8z=zY25v+m~-jRmr^w@-^TKbIuzRh_HSu!MPI>N^#7IlB+%rk!c`i~FlD zvP7A^rEN_klcHhkcB9IQ6X${|Tk3CY(zb0)|7jt3;^Mu_?oaej)xFvN$RS#D2bWs z)O+xETTNTi?#irv>F}aX)0c@46BloL^d@H8gcW}s%V+J(Q7-SRniC;A=cUqh>BZgF zw)6D6{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y)MSC28K??#;ztV zE{2A#MwUiy7On}1`9u{?Y!o`ca%7&vAD)i? zlN;tcel^_i=l}P7y$RD)E+$Q=nqa3nt-k)h*QA$K8|PIfqN= z*;X8FSSQx9@z|kXm(3-osIp4rYgTe~ HDWM4flnkTo diff --git a/toxygen/smileys/default/2B1B.png b/toxygen/smileys/default/2B1B.png index 6a833f522475ed5ad2c10adf3d788c328728ecf9..9f51c6bbf1091d89f2cefe6059ca4c701b461aff 100644 GIT binary patch delta 1070 zcmaFFd7EQ`WIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUbE@l5EP-3$y&4OJl#B|(Yh3I#>^X_+~x3MG{VsR|YKxdjXi7QItL9k<;!;JEWz z#I}itW65KSX>@U6c5Zi)G~oliJUxRq4M?K#(Z*w(1Bz47fs?I#hAJ40Tos(*UY z*4oMa_RN)u{?A$)PpuH&*4o-_e|XzLFGGJ5gLAvpUOn8__Lxh2am~&bk#dUMKSYEC z_fB!sRNK2Jt4aHFS5e#1{<3EAw-YxqRz7@oeIn<>d+H2EMrJP__e8ix);oOod4ID5 z*90!73EL7@H7Zu@c~x{vNCc&r&L7D<0y;|J!SdJpl$bxOj?b+BaZd2|3ELeU0FLAC*Gnw1n3fbfoHgea05ZJW% z%p3O(o#2-Oa%T2|a~9vcoc=`rRNcGnU#_<8{&M@X&hn2|9+&=l7MIRm{wVS950MY^ zYF<5S4vCqo(f(|^%3r3hY+WlFcb~e(z`(G{)5S4FqyBvBMMp1YM-kVH!FT62EST8f zEf~|SU1IDfvx`kMNknz!x?g;&9v@J7JNw$x{m0jeJS$SadrmlWgy#L8N8B2v~{@ht9aY*Vb z$BQ+yXTF<%{}1DWuTHaMytFGA7#LJbTq8qOf0kw46FtdfqiD#@PsvQH1Z$AQszW|f{QX2lbuI=5Pgg&ebxsLQ0BgqD AIRF3v literal 1250 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y^f{^Zbr@~ZmuS- zmWGC|Mot!HPA(=!t|l&)PNrrS<}kgUdBr7(dC93Tdowdrte|>b@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{W`5}Bq>-Ovy4-&u=N{dsNr bkHdiB^@_JNn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUbEVRz6PT?PiGx~hzUKF#)3(Ny?Tv56tiNn+_Rh7+QtW%O zY`LdazLL!Z|B0>*S}Vl2xrSuf94>30T0FNh>E8d|iY2#a^=Ze;37C zA_}4{39nCvuseUKSa;h=A;+qtcTHJRe-z6>D}}G_=QwxlO;y^{kRz?JTyeP&YeU=_ z4vTrKWShFPd+n?Rox(5vm$9+B7${Sd)>3@3>PE|jE=l`}omy}1yT08}()yd{=Y#aC z0c+p4voXGtQaqBqCHKUVSX1lj`z`e+W-ZTb-J#^`nlORYc*_BUwE`l&tGMTh7%%?4 z=zoIp4pW=5Gk+$YWc?(%VH20C(1wPK%<9ivSgj@$3YfG#?P$Jq@O#tF4w**HiHj9g zqb@b=_|{ya!x;gEB7a>{|j-18Qzo?bXP{5Lmi$rFVIj!ln+dXp4uiWd6cT$taf z?J_;~rHI_N9fD1Z=Qb;U5>K`NCSAMrq2J$cJ5E>H=?m@Jmw7Jl_u3zu>T0-B7Dw#* zp>Q=o{q)1X(N6mrKD_K&GS}kyW(Ed^4o?@y5RLP-^%EV1m=t+ftOeHZJM{nmVfz*R zA67^oV_q1umep^`rJ0gVomVR#d^b(h)4TkRx8Pz2-}@*gEv7veIi@rSy)J#tb@2Cv z1w5_cQ7?|WT@aNzD7-vLqD?^Qh6Vot1EtWi_rKE&UVY?pYisg%+QxXr)L{Dwp4LtG z4r{Le|G88nX8Qe{pNyP_=1blzOUz?nU{Eb_jVMV;EJ?LWE=o--No6oHFf!6LFxNFS z2{AOVGB&j`G1oRQure^1;d6f%iiX_$l+3hB+!|gArn5~{ROez~@O1TaS?83{1OOs* Bz@`8I literal 1170 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y-v=~Zf>rwmaZnQ zmWGC|Mvjg~PEKxaZZ4*lrjDit#xT8}dBr7(dC93Tdowdrte|==@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{v)%YhWJQrEo)ov8lu_iwq8Pcrof>c%oB|i3eN2f(>q;s&%b$&vF!1W zTSHfAKPuaL?dwg4Gi-BGnD=Z?dt08N#dzfQHrr$Kmw5`Ud94-oL-pm>Ku!t!r7p*2 zY_{jHRCwTUci;PzwNu$*dYYy!*_|K0b_SzR#Lth<;!~zFI5Ym6t=J_tp~Cq?y1}KU x8H_T5hyNe6Ij>Z?-715%@q675i#iqwhQ;Db3eG%!+zl!vJYD@<);T3K0RZ9Jh7$k) diff --git a/toxygen/smileys/default/2B50.png b/toxygen/smileys/default/2B50.png index 358da2bce9ae40dc2c92a0eb19933eef897f11ae..d08be3443a940c197acfbef91f368ca8ecb46d26 100644 GIT binary patch delta 1407 zcmdnWy^4E+WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EXMoPl)UP|NmbuVR*WT;mLf4M{^k- z&Sv<3fZ_j9hJX7R{_JJ=y^G<;b`bi$li|m1hR+)rKCEY`|FE9n?HUkzwVdJAa)y^n zK4tU+@3_|NsBr>;J#cecj3M_f5_J?|c9MIR3x>`~H7#8$NAi z_;uI#@5{n}@8|scyzT$j1OGnn{P%wTzgMNd@0fnr!tnbB|L+?@A2u_5+^hfNJi~|G znqSVu{y4$#?I^>?tqfnzBz(Ud|8Y0N^Q8>muO$AsVElR=!>hFnKh7I6EWFLmz`&qd z666O8Vg?3=vWI-NoaGN5JuWXVt0=2?b7*clP=EOGDo4BJPpTeQKbaLiVIl(q17ni6 zy9+}HtE>kD0|R@Br>`sfGj={MRj%lES7QbSrn;(-h?1bha)pAT{ItxRRE3htf>ecy z+yVv$i{7cBj+1U1@a%pqTHE9kwB(PB`nJ5eFE^gocTkU9|2TL1M;{e$5yvKh*t+`v ze?sd%Twg1`_jQNdRrT=wmE4nGJvw_h{eFq)&0}xNq?ID(-`#P^Y4+Nv+}IvfM~l*$qTj6_ZLe$-&wrSK3Fs9RQdkjVyE^xvA87LNwn+zK6cth z`Da1%R%!nmuUoEM%gx=&(JVdD`<uRMC$ibL&M`bk%G$TbcP-CcAu%*Sl_>p5Ob$ zBQNK89%fZj{QBjDvaO@YhRme2IxkD)flP_4?X;~=GMWTZKki@xOnXR^lxjimf-}M9JafyX?BmI z6pnuJk6S17yFAD`XXl~XT{RyzORP3~Kb?*5otwB+@21q5IcDCe@7o{T{A8wkZPNu| zx4^U8k5!)gQF2G+O~!HNkFzqg1;p8Oq%CaRiyCWKd5(9SFsautTD&hkc(zaHEJ2SX z;hDSITMg&kR9X9oBSvUuQ;63k&zNfGdG-_d4^*+}{cq<9Shahh#j)L{{p>mzDGMr=42L<-M2hK!7OALba}>-qcGR39HO!>CP-$o|k0*EN{KNMBlN= zFV|#OZjX3ANk#XDPHxfK+O5-WTEultDE5Atw}0`M2TmM}$KEO1&b56mytl&em_x_= zs?Y2m1|{_^oQekuCuX!%3w9sB!FH72KP&OC^hvil=l+5-scS?@N@7W>RdP{kVo554 zk%5tsu7SC(p-G6Lft9hTm8r3|fq|8Q!Ge{)%~3Su=BH$)RpQog@r!f!L`8Kj1_n=8 KKbLh*2~7ZRv6@c+ literal 1461 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy%sJe=9ZR@CYH{Q z&W47rMot!P#ulb#h9-`NmQIe&<}kgUdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*4mvmpdvGro>4v`k6lJFy~Y)4vklN_}caSO}e z@QO}eV?6ofo!2`nONE5ZEk6{#+gUvCzU|=_fee=^yW)j%m~QLLyx!mWW`R@>TCRSg)9x5q5%-gIa+ zFZW|drqA3;OOA8+dr$Y(Uld?<|3nE3ugy-b-vzg1r>p2x#C1nXWP5yk6CbGfr|a4M zzx#%~vLWf4G3Jm$}J6@tu&tKG%c3XDYp9me_CZ zxYRS_o@~>tUtDW`s+Rs^@%ea3IKt#4Yr_AA;?10oKWJBo3I6Z+5#jh~eyG{XS2I4^ ze&6l2dQ;8KU*%KUk`*r*Msc{!TNpImtZb`;yv*b3ze_JxKe!|JX2<$1@u%06@U8wA zcR)+!LCB4L%fvI6mxgwqYpjc~=Wo+U+q~zLrJu5xK$vao-c3dhzgTws?Ok!@(mDPm z#xKoZbTuli2ogL$V{<>x$ElWw)z!NdcU8MrHN2TRyIASWy~%Zs;XDypD{ao*e&nPp sV0R@U;ndf^wUxq?yLtbf-65X9@Wxz`@71&Ir$Gg&r>mdKI;Vst0MF$Y4gdfE diff --git a/toxygen/smileys/default/2B55.png b/toxygen/smileys/default/2B55.png index ff62f1b4eb8e977c9529d1c7eb7e2de6dcb85eee..bb71bcc545be569cb07d25b911eced7e53df516a 100644 GIT binary patch literal 1466 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhCcy5A+G-; ztp10Z{|_?$@2CIYQ}e%*%70tA|7PO93ltMeUMRb(o+*;EQEAY0htf6!d4Ds5!kN(X5tX^%Rm5vzG!j5(K{6uWb{&= z3F4%uQj8FXJrHF;Lm($X9E00O|NsC0v}EEt1_lQAk{~}&STZ01k+w@eE;I#y_!#_8 z^y94{KCz}70&N$9F8$!+`T2v7=ktZ2i%r)B+QdHI2;vR?7xF1sr0w#rFXx&=1Z`3n z7#J9nyxm=btI}oJ7#JAXOFVsD*`KlVaj9}1>J#Z?U|_1P3W+EQN-S3>D9TUE%t=)! zsVqoUsK_l~V6f<&8tQxPwgJbT;x0KyHkM0;b57j)x>v9I51)noy2sn%AB&%M>u8iz zxqSY;{fp>7v&<&#_Q{XSG2A#iG+ggk*x9wW)tW13Z@-;uboid{;h@-6e418!Rvg$K zsh+#fN-XSS==N1@f{d;kIwF_l#5{TD8AabH4j(A%7X1}Rsr{-%a>KAnhVOESbjpuLG%juZ6z(!d5OCZpRzn}WB9II zS?=YwWskYUpBL@=5h$n3{UanS)OzZ%DU$=^ijRh-xLRKRe0=ud?(Ju8H16@}e?67+ zv0WKM%9BSI)FnQ)ZE6tsyg$8BL_xGA;q}Q7cIOWj=WaVGwy8V2*UnndDg5Gp85^sMfig8|EyX9RZnRwJ zlC-bbsrBZ*>)Q<_t-pDGK1jbBu=f3IHpX{Sibt}yW;f+f@C|BB2eyLLOt^!5fX{*hTO zSz;x`#kX>%-z0xkKX#X6YEQ$;?16) zak$8QDIu{u$^1sEZtuVU$1C<8p8UA#mh^-989I8WJ5xmX9R*f?VLrX&0)uB-@p%V5 z?@0ou#CaA+XifKaJ**Mp@<(PBs z7qaP?mCO<3zH_5gUB;~Nt|t4RGoM3x-A%53erChbz4ZP1YqQT*J^g*Um0{72yL*{- zuhnQ$@%(*Q+FItKgTzJu=cm7xUEj|;QC@!MT_vRtk4{})!ag8WRNi0dVN-j!G zEJ@urfBaGBwgRFt9Q(V7vFB8AU^GeoAIqC2kGn%^vm)3=EPW t8-nxGO3D+9QW+dm@{>{(JaZG%Q-e|yQz{Ejrh*Do22WQ%mvv4FO#ly_XEy)< literal 1307 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y`~067OqBaF0Ll7 z=7xr@MivH+E|$iouEs_tE+&o!&M>{6dBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{rlhl zQSb1PRUxnG)Ix@1{}(IDgzRgMXEp zNE(OmVfh7&_5V7UKH4axwS3BFE;JDM>|AIN(!;olZAlo&RNav24}4gZw~B{r~4vYQls?coO}fl2@MnK#Nku8+24%kbFj ztombNV=M0mhL7nQ(oagt#MbbPnEYW^DAA#vXtw{dWrZ`#uFz^ zaCl%}|&> diff --git a/toxygen/smileys/default/3030.png b/toxygen/smileys/default/3030.png index aeb952e9a42174612fc854bf1735c4a5d8394a1c..9a8d53a349666521b40196a5be60d5536acdb194 100644 GIT binary patch delta 990 zcmcb_(Zex8vYv&3fnn<}^H&TE3@qu6zK#qG8~eHcB(gFvFeoH@1o<*BRH-pAG_)`< z{9<5WXn4WEP-?)y@G60U!D}OzLU@!6Xb!C6X z&c~(772WP?%)r3ZSQQdc5|mi3P*9YgmYI{PP*Pcts!&m%Tfo3z(K|KNaocSLo;$@| za*W(z4;Y4-L}qMsW9evZvElzg9;{>dJepN{$2j%?EkB-9FgW$_-*Qe%J5L+a>)ws))r!*~*45?)jlaxrux3CcJxS{p5z@&XAX?>YpC8 z9TgG3HFKr%yffU)Qzz;_6BE<3J9einva57aW1XF8Apd4lp70qGPrVnXSG8y)1beuA znIyF|;>(v+oKeq2U-ERzzh%?DrJTh4>CoBRf}9U;sxv4VnZ0=26X61yPb>}3!obXHe8N;3XMyh)na^89@S6cRlvmx$F z!HxJ;bLVXK{AjgXVA8ui|8MRLU!-VxgQq-Q=I4YWC(X9{=TG%E)~lN)UY31#`sw8P zp)p&{_<3b=9_iian!fh5im!@RbzW0_dgtXWPBG84&dhkgYP-cDSW`fxRg2rLXT7hgVhpdU%m*QB8{ma#+-B)gZ)?5B@m&c{QzQv}qw?8uc{ZaFQ z?axbR*;nqcn#gxH-}4vi<3nBZQ!_3-V_;xN@^o{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y>7-%7Urg=2CgQq z7KVndMwZSl29~al#x9N)&gM>rjxfERdBr7(dC93TdowdrtRQ+#-0G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z>^C(wF*k5@G<0@#H39kC!obDN(ZbBq%+SKX($dvf392`RoG{b3Q?StoB@U#-0uu^y zaRV{I=^B*A?GzB1Cp9m{R;ftI-cD9eCyjxDp~=(5F{I*F(x3nT?U_{@Ivbq~vKT~p z5;*zz>bRGPGZntQwKaQx6H8;#9fwt{9giDk2x`SQ`_8*~?3kPUi3r9c3s?jhO)FHV z$a6l`Zu@M<#FlKB^w7nV}aw&qB;yaU%b4$JVCng)182yEG3n0Vq#)lhZ+h^ fG=uXP92yuIG1~J diff --git a/toxygen/smileys/default/303D.png b/toxygen/smileys/default/303D.png index 6701f76499bca2dd1b199dec8df95957ca54cdd6..09639eaa7b7fb031a76b6d417a79c2ab85f64cdc 100644 GIT binary patch delta 1461 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 literal 1516 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy>5m^mS&~~ZZ1YH z=7xr@MoyOICXSAVj?Na&PDTbsaJ`;+#U+V($*C}VGc!}Hpn6^L>a}t%N=+=uFAB-e z&w-_YfQ&CJ!%*xb;_$wdjOH-(%q)3;Nw(FY|Cq{IRf3UYA+ zF~R8?l*a885Sb@6FU3}=NXg#r^P@^*1_mY>PZ!6Kid!wgz8S)S0{_fzn#|t98M!#K zeCjS)^C3*xPY*wm7SQ)cb`($$$2?=gDt7rj<53jnCMpu9GsG&bm&S*?GA- zvoqgbxzDF-tJzFK&*sm{mGHjxX0}(s#Cr}0Y*wXSGFq|ogs%M7m(N$++r|6!gTl7C zwwv1~P4HA_F*~zbIDpN|XQB?%(!D3*cJO3o^IcK?D)hthfSF)Ge^b!*g?Cci8S9kz z%5OGI2~#-v$!gn7hY$yY`lnJtKXoH!RkeNd|GeVt-J|nQF8?6P`f4Hbi#=9q8x|gU z#Uj$OBjLN+gsa>)LihDuTj==WxnJVoHtIOPD#jSj8~ZkrX;z8!x5vgz)s z0~bpxPO1b=WBvWt{fVE9@Qe(mPyGz;{Ex05arRh$?_ljmp^rY&Yh-UH+Euh1I+=Kc ziOFekl)@sLDc=tMHE0jRoI^FLia;m8&7xKQAF9RIeGEQ6KJ1rJ-+tBS@lCg&V1Td+0z?pIHlx2=!O?)`fl xZF-=tU&+EgcqZqzHIMCI$2_evGznvsVAwZ7_%DlvdJ(An^>p=fS?83{1OSD_GqL~x diff --git a/toxygen/smileys/default/D83CDCCF.png b/toxygen/smileys/default/D83CDCCF.png index 754d3c23f07247c2e2fa13813ced7e8e369c4376..3ea5b82a335e5afa6b432f478a371c958fcd87e3 100644 GIT binary patch delta 1465 zcmbQv^O$>rWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EW`fPl)UP|Nl4dKel=Qv5k9X}sE*$+XR_aA%l`rZHk|JUt2`0LlNA3uN2pEawxtgL?d(xu;j{8+th@3-$iW=@&v zZfhGG5_0IkL0wI)ckkY>*u3k@*KfI5+0S0Q?Ck2cHZ?Ohw<^iWaWc1<(9^SI!?ur~ zzn(pJ;mwO@r`OMBW8>uG;m^&=y8q}&VPVmNHJjdk{Cscc!arB{{C|1l_oW?ocg$b1 zbj6$%>t4VAbbnp%n!f7)^^Z@l?X7ySwtL3nl`r0XJhiI->z*0cYtlaNo^fi`gemiu zK7IZE!LwJn*%=cHLiSI}>&XcU4GK7N;>?6u3(sA?ZfRr3!pg?OE6C2ton26T<@Wua zX>%Vue^XuGBrYX;_|$oOCs#EM?T*Pa?>u`I7#g`|%Z|%;ADUWNA3k%wwSU^9*Y)pi zJ$X5I>B>`=t}k1+>B;N&H*Vj%ar~bp9t;c&>?NMQuI$g)`M6XWb_cD|Wnf^cs|txI2}&$iC@9KL%gjkt zD5)$+Rj9}5c)%pXVZ$O+57+OCDFaU5j7!Y3AeQ2aPw!^_$WojQ7B)y~q}DF*qU%Xa&wo!Fh}vC8|ZqHDXj z=nb>iOU{NLKc=R_~hIcg?0>2vksz+A3Li zVwZN@Gs*gs?b0XmHaf>!@3^vTvDhPyysEAxpVSU9e-w#iSy)!8*I|kXuM~V*lO_ zrq>Ru?HP&$QY!aNX!HxH0OXP+y zVbcrJCNpAtA0|BbcBRbaSd?su&b^A zHeKk#a;YtI-Z!aamu&;8w)|0VX&+#la8 zPFLFT3+=MYJeT%+<&R1G{&J-(4v76TA*)3F^25LJPW24W6uRbCRPqThFfd&5ba4!k zxSX7jkdX4Ig-OkgO(5afqc$eJv<>wP{QB|&AGkTVK7MLoZE&Rl-tw!F_0XZi z^~y?%7bPD$eE9GIrj3P#A2|y-EZcJ#La%IbxEnBwqoY}HQWCMe@*_mR?H3^Jn z-(oZ#BqS#=FgVOom%3_kMT3EXLAAs+q9i4;B-JXpC^fMpmBGls$Vk_~T-VSf#L&RX y*wo6zLfgQ=%D^CxWj&~*MQBjT%}>cptHiBCK2rSsL`8Kj1_n=8KbLh*2~7ZlpRzpw literal 1559 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy~akCZqBA|&MrnS zE{2A#Mi$OyPHtvKrj{-)mQF^l1~9#zdBr7(dC93Tdowdrte|?G@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{UcB%7pSn50OIB$`?D_FwXKUS`5V1e@ zlS0B{&jlyWGh|+NVYYwgl~4Mms}p#Vx7Mo8(p5YXlxdBE~T)p<0C*_38!43EOA6!;nVK)2Xy;|n)@-|0)9AP=~sc1u%(u^VY&Uf{-U?mb6w@PiysoDH$zfJ^ z-8vz~?Z=MXI3%=b3;UMCk-IEx+Y&*O6N|9Yw z)&{nwC!f0%vR}J?{YQm~4V(Y*(vq^${Cmx(?uK&ouv{#DZB`aKQ?Twg-}3YGoadhH zd+~>9kL_LqiL$(;1A3EI6}T!DzmWfMaMOCW+I@}^zG@zYn%nhW$EnO*5TfxgwruC! z0-4-Lk2e`joW<)DHKjt2iz)rFH+s_Rq?iV^K7NfAPl3W6Mf- sI@AStHa_2z-u+x=`L1QE|5+p$E^Ns;AG(Y|2~<>jy85}Sb4q9e0MCs|fB*mh diff --git a/toxygen/smileys/default/D83CDD70.png b/toxygen/smileys/default/D83CDD70.png index dd8262411e83a5e1695d772facd78801e37dfa10..75ea41b88e10a960bb8c7f79ac1bbfa4a5f59677 100644 GIT binary patch delta 1238 zcmaFM*~B$LvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{yoRM8{U-t5kdIkmt@sc1vkpCHwfakMk&z^fd zf5PMXXd43q17ni6yNm2==E8{#3=Hfgp1!W^&)E66R4o7ch^8xlq$-qD7NjavE*nN~r>5r>$DU1FCG+`D$>B3=>aRcc zH7lFCu{FT^72ERr_Vr8U_pG|Qr2K85^6y8l%=Hr|#qO!7_#A6Kaqs=S?Rrer#fz7= zh}|~1Ep}66-___%QrQWK7b9}@pGgP?h<3Ydd94@uKCra+Rm2PFgEbS+QiksoAkIsT=#C!&5HWL zcUv}0^0Sic~TxjWV#E^PJou)F(7!+!#6P1lT>^SvgUs71!{9-dq3YH52+wfcbf zw~2)el^2iwp4$0fr!PZ_h}462+0L#_4F`Unk5pg{U{yMD`$dRz`-6{jzN;=ww7n3K zW-49%q^X{F8*j+|r#1}V`DY2)UC6l~;OFW$lXbyf3BiKuO=TSG5?igTO;uuD|65s1 zmC)d`oBPno-S*Keg`#N2861)rQS6AdpJk_1d{lq(A<0OIE zMyq>#WvUefY88^=9ZxzHYU-!mI5thw{ej5@O^>B2b9=S(PrPOR$&lmz;7k1phNZi> zPZ;LSIrM1v{ZjAc%8HLCygKZ8Jo?$ofD2_g_oCGD=Nwg=^6i!fgJ=7(X&WcYRQxz# ztvZw6EAFqpuE+c;hKzaW!|u)MjLl^wYM(harW)DqVwU!2`IeFOtK(1XE&eL+ z`=WO|^ZPf){Q9imd0qYN&D?oi`mb-Vcldr}Ww(5gqV-~#ng1t!VPDeN)gn-Jfro*C zq0ZCAF+}2W?6I3tP5~lq5BFD!DEXFY1#$VB-1*-dnQfg=&A;zC^Q@BkaGdR%?XjsQ4Qbmv}EJtJ%1O@k?T9d@7JimzJJ18{lt=~<_CI@bP6bSR8n75N-Lcj>{nKiYk^+6)2_itjj%-VkSCU{Eb_jVMV;EJ?LW zE=o--No6oHFf!6LFxNFS2{ANqv@$leGBMLOFt9Q(u(Q6xhoU1lKP5A*5>*GX2LH62 UO%oN>xfmEcUHx3vIVCg!0Df5%Qvd(} literal 1261 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 literal 1272 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 diff --git a/toxygen/smileys/default/D83CDD8E.png b/toxygen/smileys/default/D83CDD8E.png index 3e3a43e06a68b5cf1486211a0d2c52ef12fa2ad9..f615013d991661748906dbabf67163af5f63a54c 100644 GIT binary patch delta 1300 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 diff --git a/toxygen/smileys/default/D83CDD91.png b/toxygen/smileys/default/D83CDD91.png index 2f37aaca03cb70a8cf7ea8d8c15b4fea3cb2ba1d..09b02e07aef9b3625a6022d73516c441f633b0b9 100644 GIT binary patch delta 1253 zcmeyvIgx9EWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H<+JpAgso5mx`hEdB=>|MxTa@1^zM zMeV=6!hcJtf5yT;ba_8(u)kGec_GL2Sd#I9C<91wgjN0jFpI~Mj1Zv*q6|<8WK#l- z|L@!K|Ld3kL8kxz{|6bmdfxxz`~L@9{NK0?WWn24|Dzls*8G3{;(w6k4_)5>^C$m5 zy!*GE(*MJI{;!<-S%dwnq0s+LEB}A_{D1SRuSOzJr__Vpvah(so`Hcuyd=mE6c7wZ z!1LL&XV1N!KjCqGw2gs*ficP3-9=$@wrv9g0|R@Br>`sfGj={MRh~^x9xlq$-qD7NjavgQH+g+-3 zVzsBomB^)q)1F02>^hn?>Cx)P3pTCav|1ufBITH((z`vu&(3nMkKYooeq#ohE zdFYT-_6akO%s0ZNXFlh4-U^6rf5auayhf^ozyASi$rP6(ehUMoW(&p@9z3x}Y0kUH z3-2y;{r%xagJVPZ3{l$*sj{R z;hlonp88owALuwU&|8h z*YlmxIxL2Qn4h9gC(cVJ-aC788Gh{lQa? zIsf=27X;qzdg2iy)B9j|?ww03RZ~n>+D)lgQ&hsv`gX@2*YtZy;?pO*`mDsLBHMAb za57KDj{{a-M)F#fTkG{TJmf7dbg=WJ9FcF?{9Ofq_A_#5JNMC9x#cD!C{%u_Pds!N|bKNY}tz*U%)y(7?*r)XLOE+rYrez#x_H j=uQ+Jx%nxXX_dG&hy>TNPE=IqVqoxe^>bP0l+XkKDNHFP literal 1276 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y(W%sPUfbDPOc`d zW`>5YMwYG?&Ths=CYH{|#!eQ-E-<~GdBr7(dC93Tdowdrte|=w@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{hEX{C-&4)FbG6*aE8 zmhhkdP&{wzzyJ5QeER=if4*YH=T-mXdtD9`ywM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umBGe3=9nQ;w3?TAkQ-(0nca8 zo;~+^{)ET%(KZGK2F4_BcNck9UV*&~3=Hfgp1!W^&)E66RC$j+zA%A-fvK@7B%&lJ zv0R~`C_gPTCsm=OvLIEVBDa8n!J>C+sN=NT1{`;byW||%SUwfbIdSXjUcKr+bpew; zTFfh}m)aX)XGZZ}uS?6phInjb%?weMxzRfEF|IQR@tPng?%XYa{pXA3S=HQYR;dhc!C zZtpjN3J0XO3f7%4Gmm@q@?^W@iM&Fmc=a7umhBaLRFGFu-?ikE-XYn4B9SaBnkp2d zU6vk`+GM?C&hInKpKGKF{kQ6hny;{!e))lW1&{uM;Ki3x8P{Hj0uoaK&vhSDB|o7V*Wn*CLo9I6ZtC)95$GtgUS`s&RTfnA&a zZT+~y#l7HyWa2s1$BDvDn#=y4I31espD*jsCDC`6pD5eUX3Kt^BPFpTdxpUJz-^9` z*R6`Y`B^|GyX-Psk?X832L7 zXw6l56*8q!y|KA|oBJYj8`;=-XBk(l;x3c6G4ztAp z*(64O{Rnr@Y0j$o-=>RPST4gLwWZ!|!UBt?E5A-SU*fCtj#1p@vutw3iAQc7GG}Mm zDb-K^=Dhb}IrqDe{Qli@j(=8K_uor+;}HtYCfg+EMnEY}S%|=kq!LGRCj# z%FqcBYGYtvX!CS&43W4Td-$eUlY$8Ah08X}r}Z|OFBWlGob%_uzV^H+-=d^{JpVa+ zR{eX%gWEJS5}#)!zkj8WB)C0UtoYHMZs#JAZCL5idkrJupdCD$i-PThx z({^53@|D50((v0>9Ue*JBfg>KyML)J5|mqKIBy+i+&WFM_sgF0JbV=#-TqlWW2aJ_ z?)Fi6yBFMg~Skx&|KRx`rkph6Yx~rdFn=+6D$z1_p;K l&+($@$jwj5Osj{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y+#H`F3wJF&aNh| z28M>NMi#E-25x4C&ZdqA7EWdcjxfERdBr7(dC93Tdowdrte|?G@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{IPbuZA1`{QsV>H({E}#iR*U6YMmn)z|;`n)I@2gWa_H|Nnx-*v@Gw@oZk)Bk5pr z!IbAkjk#%{fxJuekM2ij4w@67f#I%8EZ`Jk_N)5HJw?Rj3*2=G4Io#F69fnibN z49^*}=RW-M_d1WOwm*~apTi7~xj$}b{`n^+Ct;t#6n-WJskTUmji0`s&u^>$|IfLW z^E%is4W!C<0r$_$BIUXzdJ%sv1Ao%T2QuRMY6qvIn5yCbao zAI`|UJ=2=U@*Nw`?elk8XZM`D&$YzykL>ez2Xo`*K4(3qV3+K0{nmexBTCBLIw>bv z7fkV3$oT)Cow~Aq!vXFYjG7mE6djwMEq9p1mc+m!!SIFSdMWooBTzxd;OXk;vd$@? F2>{B$xOo5o diff --git a/toxygen/smileys/default/D83CDD93.png b/toxygen/smileys/default/D83CDD93.png index 47a754e8c5718769510971c6ff46bb77865ff610..2294126fbc1e7c42a11109c8c9c484df4f7d39de 100644 GIT binary patch delta 1198 zcmdnad5d#`WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H*&>pAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} z&6NND|Ns8;cl)Ks5i|DXEZ?K`N`ey06$*;-(=u~X6-p`#QWYw43m6zIdZ&g)Z<}qv^XIzO zE|$g1Bl$W%#-D$gKK=g0xlf8Xx9{aumq>|JK6|3A?*AWG`%mFpC*4`<-utM#`~IDJ zr=1qND}TJ&ZSrjI{k-kxcz(Z{CB|)-8??nWurdGZ)Gf!vA~J+#zgyNP$mqJEBXZZ9 zm?v_f+g9|o-V-__uJ`j@`F-c2J|V`NE54s`-e&o+NPcEbOXJ&V+F!Odb%*ALbr|oI zJFIeSH;4I?;1t0HT(4Sl1w~J%-*~s-(~R_Y63^@JcHS`;=38+gg_66`6((|riQz)G zAmi=et)+igIK9JnoS(p7 zXXbnL+RjXY2kRuA4sFT}R*MaFPe1S4u_oC(S0H@JDh{4a2`yqfVtAVt1(qJ1oRm@$ z|4lxJWqa3gQRV%qMU6F_XHG4-dA<8Vn|;9t`P<4D8zmY}&RJET#^!x%!pt*2l?`~G zEff(T(JryXR-T#sYtt2%|ou?zBpc(6d(&RG_`TVkmG|Y+Ex$i^bh@s7_Ga$9F8$ZHmphc5 zTG`D%X{ps>nR)kxela`#>59?1CGeMlfuY3H#W6(Ua_rG?p+g1&EEg5ibDB>|35YeR z-v3{gvH#7}&~Vca`lrpe+Aw$J246gME-N{|v|z)o0u|YQ_2r9SAG}cO^=&+wpLcGG8l zer*N@2GtVRh?11Vl2ohYqSVBaR0bmhBO_e{b6rD|5JLkiV^b?r18oBXD+7axjt^I% jXvob^$xN#RYmmgM{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y+%%sCWgk&#;zu= z28M>NMo#7yhRzl)hK@!~ZqA0TaJ`;+#U+V($*C}VGc!}Hpn8q)>a}t%N=+=uFAB-e z&w-_YfQ1EXhyJ_|R{{@M#DToK8FgRa%J%X@y8 zg34XW%pJUszqiabezWIC2V<;UtgZo1T{Ek*dam=wTYJUjCG>1+4mSA8&Ezd-JmVr! zfA!;Q9>ry=3jV#a(dRMaFlH-R``6!5#$eN*cD6GOOPK?_YW&>d7#hV5n1dEbIybSL h^>4J`J;K0Yz%Wns)D^!?SwBHViKnZd%Q~loCIB(8lal}d diff --git a/toxygen/smileys/default/D83CDD94.png b/toxygen/smileys/default/D83CDD94.png index 0b710e1dcec5b8aa943b078b896101ac31261bdd..5681f7a287dc43b55725061d07b75daa515e8efd 100644 GIT binary patch delta 1219 zcmaFH`JQuvWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;k)pAgrJ>95bHy*!)p^kmYbqY3v8 z#@*Z-ePw6(g)O0{Hv}DB9dKZ|@9xFk+vj_3nd`RUV*2Zg>96b0r@fr-xdkFL*KGqt z0@;-R|NsB}^Y7i6uP3t~9E`hpIO*2Q{U2T(_^>zn%CWqgzdrui8Gd1B)S7pnsfJU#Y$WAqVZi*I@^-p|0mAYKyW7tB!4fC4<9J$v@t z>-iHN*GJnJ7#J9nyxm=7=CC-jGcYi)mw5WRvOi;HM#Khg{m%D=O>V%yOha?A9l^!l7#d+S_p#{2%`i-ixy`5szy zPOEpb$@xPcbi*c##LG%gt(_Ws)IdqW*i5kS`COy?xQeeyH!^e&z6T|wcg>C@*u72MR&KdIx!QWLLa!&BON6~^l|mjABED6hC^ zXH=%3AM@DONb%1$&W4^jvcFiA;{wD4lKvKpc`yb!a%{4iu_}Q>ZhdLA5XWo2B<0nf zZz8)DBBYkoovvhfCvT)$|A+DBwE#ayKT9r#y%K^O_J&xqri=I5?G~8yZc)9gjn&0K znVPDWV!5vq3Z1n3ejGd(DamJK-nP?N_&CmC8x6+)#1aki8}Mgi2^i$+tT# zCye|qa~00)HaL8M+byHyRoHsmk*osV4+`Xd&mgk@N%KVdMP5Of`^(Pp5--UH3 z7Z#Xwz28>oJzZPz@uXLWJCExef4g9TZ2R^XS9HS)IabQCb1J-aS<^Q;<-lS2GmBL} zubdqIo168eFuT;&hIXFD<^sD4iA~P+_hrI6bb?z`#)M z>Eak7aXI!_xKL97gY(53+Du(j70TYke&26$=y&7snX_jyq^>hRTdDWpeAgeYXWO*I zwn^1HElk?dQ~ElBbMv*?Yx9_`7iQL9$W;=Y)faW?kY|Qfe97${T$}U0x973@FHHJU z^}6ii#;+f?el`3f+AG8IxIRyaCulE+LhIuA3N;1>2GtVRh?11Vl2ohYqSVBaR0bmh zBO_e{b6rD|5JLkiV^b?rBW(i%D+2?zdmow^7#Q>rI&$+nLybu%D=?&c(ps M>FVdQ&MBb@0Kujq-v9sr literal 1254 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z2@fT&aSRzZmuS- zW`>5YMwV`-Zfg7@md3_TW-z^;dBr7(dC93Tdowdrte|>b@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{;62Ll76n5ThF$YWCC&tipXNk6~8?^l_*j{SA=&(H7mT@x8h9`JWHT&;0!OYZ#s zFh}6W_wV+Hk8|Ib%sXFy_QUhn_E#MKe131vY{bTx@@?UdXrAMj-WmM)_h+`FVMqgG ztnKCb`yJSX*rXed{rmb^{P5kc`jRX>l@b@OZvON0GyCEA6G;Z&o?qWD8S!(5BuKE$ zN>oyz`{0e3LpePA?fxV>grEQP{Jp-9xI$;+jWcU}c(fWf@#y>QPjv|Qzrgx0d+mXZ zA0AzaZZi(fXL=g2_rQ)j<=^YMCNV0zSt%$h{;l;(i>qKXdvO20y{1Ezo`Cm+1wSbP0l+XkKi@3E{ diff --git a/toxygen/smileys/default/D83CDD95.png b/toxygen/smileys/default/D83CDD95.png index 25149a72f411db8f08d86027b4eb1a0b0c88341e..4b2176bf16506bbe13006a69e432a8bb547904ff 100644 GIT binary patch delta 1239 zcmeyy*~~RTvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{wM=H0Z|vtdEfii=4rF4iY5JMZ1J7$W4^umB=~ zX3GEn|ND1d`SA5;-G*}!Gxj}u|21LufeQ~`AGr4P!oxRVQ+BOCb$8vVyFY&Z4w<+$ zX3F-b@4j?zKE3bC!=BBjgL*f1Za9%TV`ur&186SLd;DU1Jp%)Scu9~S$o~vT!1LL& zXV1N!KjCqGw2gs*ficP3-9`2_bKyh=1_t&LPhVH|XY71jDi&Sds-GAbnChxRB1(c1 z%M}WW^3yVNQWZ)n3sMy-atjz3EPAJg22Q)J!1L$1h%M8!7MA0MJwIPp|9$h(TB!4S z{p*U`xqpN<2uNkRP5AQr`T8%1?L)I3^|pte-lg%{xtlXpRX6Ka$#cIg{l_00b|mC$ z}&5@ejYv9p8y{glehr&fm^b$iRVj61ddx^H=M zymwzij!XOVgRf&O?kG?0H`riX(=KDJFeI^4{h5} z@X<0ydX30y7v{%*uDaOXxmFPKFlmZDzwK$|6vp{ZN{gmn(m1+kdbsb0l(@ii8-85K z`>@E?$V@^1&L2x7#XsLT8+v+pe_1Gp1&RsO{4EyqU<`8PxFqIdYQWNeeU~0*3orA= zc~@1wt!Wk3sh17f|5T>oce@X#zCyJ1sg)-*s~H!FD>HqYp0)fzPSBq7dM!(Kzldl5 z?q?#xKL6pvp2c!Oei=tQ-tW01`tAK9wVYjtYg=nRZSGoq?b$-d245SN=#SeD2gf>|;Sm9uRp|S1P&4k&h2H92F`SaquW+dMMSi=@_`zaH$muI*!wWf9%Cd7RymZOwoV?_~ zVeV)5CjGo}a`nH~rZ1h%LOS&ldVwBq4$0fJywR}vEvj@n#HywJY(C5LjG_X!f9u{X zuk*g2yTg0G?fW9B_1!9W@0)~guhf>iwtYX-y~+A_|0s%vtDjo*Ie^EbWZc=#rIM*Cy=69*N;H=JgUU|?WSEpd$~ zNl7e8wMs5ZO)N=eFfuSQ(ls#GH8cq^bTY6qHnlP_)iyA&GBDu3d~Py|j@{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y~ZY%ZWgY_ZmuS- z28M>NMvg`XMlKenrf!x-E@q}K#xT8}dBr7(dC93Tdowdrte|>b@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{IPbuZA1`{QsV>H({E}#iR*U6YMmn)z|;`n)I@2gWa_H|NnwS*c8MAQW%`Cym9{T zxS;;|)c*!%J#CHr@%8`K-!9-g!*)@@&VyH=IpzaNR_D2U@@;F`pS|BxA@6gakHh^Y$=$voDzP yV@}T=1}6Okwj~b6Z7iI#<}(#abubDfFkE0ibxeC-eklV31B0ilpUXO@geCy<;k14L diff --git a/toxygen/smileys/default/D83CDD96.png b/toxygen/smileys/default/D83CDD96.png index 14d0d3f197bb1cae3440c6e7c4b84fc68d625c5c..1835eec2a5534f499bb6e69bf20063aa6d219e04 100644 GIT binary patch delta 1300 zcmdnawTo+lWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H<_MpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} z*_8kP|M%^>mcQ!ktrs7E|M?p=`_Pm3Ut{JTK7Qxr?ki8?<{sH|fIR9zv=OtPlvBR z-cf(;*)=Zfq_B1B*+gGKnzI0^Vzd!&%K^M;cZ(E_N`ey06$*;-(=u~X z6-p`#QWYw4>kAkdEPAJg22Q(e!13p~h;0)O$CJkv6Te>n{_E!BatF_AuPbin{zi7O_0%bLq}xWo0uo>e9iLPTCXvm5!3rvpI3f6HmQ$^@#c!& z6VBT#KNg+$u{(62tXuR~)WM}@Z?_7#7ahK=GPPXr-HGKVlpI8_aNkzaGM$&W`|v5t z^EL+Ob_;$lxqYdyxx3gh_K!yYMAjOu)vM-uC7(K}v3}T0j zQhHxc<$Qckox!N6@P%+sg>z)RL&wkakqWFHtco|TrDU->f2cTj+esnEqN8_>+D3QX zri8Z>e(64ExnrNAw5K8GwZ?MAdx-9vles;zqp@IZte>WK0D<@ zJ5y|PoHCB~$nS{}{q}y5nojIPJ7>EeH%&sKpD%N4*zLygQ7Tv3=;f+eb8W>H>o+~i zyQQ$sBUn%(^w6+a;%4P(H`PK`E93aH+|v42Vw-QY?Ad1~e&ya`E8ZxqLxeJQ|aZcD=9nJs@u5S2UUX&*>EH_?tWvR{uNoGvM>5 z#qUHL1akHWpAFl6RYZwH^X5yhQ*reMZ#*TNf@FF{ucoZBJ9J^z=AS*YxP7x^8x2iP zi=M4Y-e#i4m7Q5$ee3YYk|kRWE!>}9TQ_!t-%R7+eVN>UO_QmvAUQWHy38H@~!jC2jmbq!5I3=OP|O|4AKwG9lc u3=C%Y+~0+wAvZrIGp!Q0hL?irYzzzxk|nMa)fKrI7(8A5T-G@yGywn-tXGNv literal 1335 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 diff --git a/toxygen/smileys/default/D83CDD98.png b/toxygen/smileys/default/D83CDD98.png index 7288cbbaa506c63d92fe895fa272a5c97b4d78ce..4cd5f0cc50910947ada55677c2ac2225c3c1880b 100644 GIT binary patch delta 1323 zcmdnab&YF+WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H|MxTa@1^zM zMeV=6!hcJtf5yT;ba_8(u)kGec_GL2Sd#I9C<91wgjN0jFpI~Mj1Zv*q6|<8WK(8z z{{Qgq|NA%pd#nFv2mgO?`~ROmAfvy3`ycQ2KR5jUg_Hm1P5AGl|Ns0+kbOt@{=atd ze^uIl2j%}izW?{q`|qXu-%{$omCXNw=>Ny}|94UU@2vLESonXc|Nr`M`~P1)|37o| z|D>jWHgf+bH2%MM`iCy>|MHao&mRAO_56Q(`M+?7|F^IG|NHk}m_52D{{Js?xO^X_+~x3MG{VsR|Xj1q=)py;DOSx6L--x$|1YwrOI@ zNtt$rdtdkJRsZRG(h}fw`~HU}$7!aQdU9So|1SSUx6W7kq}JWp;MMfX6?Bcb=(ZoLyK7U4MIw$@#4Q%~n%TjR?1#9rY0<~cHf~}cR<2o9rrE2h zzIadlv#!-Anu>Nk?=No`e?N00bLAw1F`twYh4q6-s z&D4X9C7OT34yFVKtRpDhq&_?TV4zP$DC?iGyQ=#ta_KZ951f0dgy6rv`haNzgbiC z;m17_?TvOi?qhX}IA!1~^Iyo~my5@jWf_jVDGrJEio}mZKM6j6V9JS$6NGd#9b^7F z%)5VreM9<6c7yuA@;1Mg2tLyZe0+zTgIv|Ru7L^0Lhkzvv00IQpg zx_&JWGQNC~{JDD5{9nEMZWTqObxXS46FPsvQHglQ1N)ZvyLv}mHDIu`?jr>mdKI;Vst E0DAdVX#fBK literal 1335 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{0A>W)|ia2CgQq zW`>5YMi!2ij!tHl25yEXMrJ04t}wlxdBr7(dC93TdowdrtRQ;L-SFzQaxO|uEXgkl z$G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+vo z>^C+sH*j<`bar($0r}g)z{Snc!pzdl(89pd($!cAsyBt4Fw?hFu+ax44y42a6AE&1 z12Mtr8kENE6cCvwH7~_hsYuD*?#epLy$lSD>7Fi*Ar-flOt$rK3Y0l|ek;43s|^XTTFvV#W~|OBc-xv|H~iw`lqF8x{;!yBikUD%A1KS=w+wBH#zZt+fX}vKC0LPtC}Q z<>nTu$sZZAYL$MSD+081H+sEpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} z*_8kP|9|-UbH@IgQL_&%)@`&u@h{q*f;*pywbKYp9I<08a@_n*I4Ejw7f z{7^vG+KHP_e){$U**;Sv+f57%4B{n0ejv{>KtMg1@_hE}*>kVwPk3A(ZDU|yU`+CM zcafRH;>gaxz`$PO>Fdh=jGd25m0@?#8eIkkriQAJh?1bha)pAT{ItxRRE3htf>ecy z+yVv$i{7cBj?-=%aNI5Kl5=EZxl}mk#;vb=^{W5W1x)&AF|SN&Z-kM8-G_-j`|JNL zlK)treaqvW(VohUM~~!%#qN}dHr<}LJLk;r9e4AZB|qg!O?nknI(gA-anT!Qua}$+ zKYmPW->bN*28S1Nm>Hg)Ft_{8-jlD+Zn(IOVRNhZp4)fJre6qDP>|XxSa`xpRR43& zlf%3z+ixsfFB_9}d9BzZj=YMlC7<*TNk7uu#1X*y(`j3MhgYv@O1X#S*E7tYYotC- zOUss8XHclO{9ykViNFKPHm$m)X}T=Lt?I1k;uBpZZO{A5+eP0?Ze*^UDEC;D^YKn! z2A3y~9;j~1K{4#&B1IGd`mknRngt)gRe7thoN#jO=h}oi9t+qvitk-x$_8*~5l>ZXY=xxT;r6gWS$d8-{iugo3On2zfiw-2M(ArP*%U=$kP8nX!i<87!SLdJj%lwn!PTzqq^(Pp5->p+{fBfKzN8OEB zGpkrm*6CL~luw3s-d?E0C37!KJ%8a*#V7x)JQx-_CuYx6VU~;A&^Jw|-7oH~zUl&h zQwEca(vXCcUtC@<=JT|^q*vJbqTXnx-y_9tX~is)>R%myVsG*Pb-SOtBY3~PJ@cK^mVBCERjgLun~War%%`o(<{g@6X!&ZK!w^7S8;?sUVb-St07WD{oGXZ%(bQ3TM4) z(e~-r+k&R7;B&8(&G7B~RWj|m>#QIp{qRcL6iHR{``6nIf4-e3J!?MW8r@TyHf-se ztthuoaNa({wDr4wvV43L8+q(9e@L{v{g=6y{xC2wsFt`!l%ync z8R;6B>l&Jb7#dg^n_8KeYa19?85qp)xxdR0MMrLaN@iLmZXGWL)7d5}s&g?gc)I$z JtaD0e0sy!O8KwXL literal 1236 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y(VtPW<~~1maZnQ z28M>NMwZ5=X3j1a7LJCFW-ex~mN31ZdBr7(dC93Tdowdrte|==@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{{vSv*}FLn>}1`9u{?Y!o`ca%7&vAD)i? zlN;tcel^_i=l}P7y$RD)E+$Q=nqa3nt-k)h*QA$K8|x#^xYy(C}fAg)v)+ zSqIz0lZtFS*NT`VUOCKo`v1Ot!Q~Ab7eALtyD;TJ@`>_`Nfsr~Roko*Bor)e+)$V$ z_~h*W|A9vY*#0g0G5hV|VoLw6%XV})icU|+3wejmxN1n#JuOHO7 z{r@)gzorLU&w?o)flf?Ik33I#!YWX~Xsp0G%TUGR!XJ^2%MLsZ49pCtrfe^KA$M~g PsGRY1^>bP0l+XkKWdo_E diff --git a/toxygen/smileys/default/D83CDD9A.png b/toxygen/smileys/default/D83CDD9A.png index 7c34f12e8a3ba93aedfb73f299b56caf5738b1b9..91e0db3a0fccdecb9fcd1ebc53e706a5120ccff1 100644 GIT binary patch delta 1359 zcmcb_^^0qQWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0810z#_Pl)UPg^~Z~hy9-$^naGm|LJc3 zr`Z3WVDZ1l@PDV~|5nBSjS~Os`2JTj|F2;9U&a7ZyfCsJq@jWVDpUrOKsM#$^Z!c| z|L<)1|Nr0rzkmL}eei!{Dah!H8~z`j`v2hM|CcxY|Mvd>+MNG0J^t_R`2Xo8$kvTz z|L>ppfB*RZjphH3&;7q3;(w3f{|T1=zkmEc#p(ZSzyGI}{C|7@fBlE2|L28(9C-K0 z|FbLp&-DI(XzKqjZ$R$d)$#w&umA6#{9j-4zuVw{tMdOZZ$X}zWcUC7|NryD|G&ER z|NW!?YjTmjwewiuX$A%c@sc1vP?$0x0nca8o;~+^{)ET%(KZGK2F4_Bcb9tKh75&N z1_lQ95>H=O_Gj#TT&iqAhMW=%3``AGArU1(iRB6fMfqu&IjIUIl?AB^6}bfr3>Ljp zLj$MXR^a*bT*Q{i!|BlR!lcjFzyG@VxSVa$n(H68-Tra(hEv88$;Qj?=kLG7|8LgT zCFO4~DpePLoqgTlq+XnD&Bs{tiF@zY=WW+x`u)hwn|JND%x$`xG;3z-pNv}5*czI= zb?>ZX7l%+Cp^NKwr|fmVY&t7>VLr=erJ3*RzS~YI?&LUeY>zR2?DHe1Yg0Z6vTc(N zzmeVg988Owr{EaoxK?)O2l4+grZXJ(Uyt3h&vTG`n-{;f_P9 zD)a00DyOKQV6qE6bH;zEr_^Mf^{o$2?OHTP?y-9H0q<`U3mJC4IP`jI=YxH|3@IW~ z7utJVoFg3`{CTc3foVyTz@yU%S&YsfD$Z4VDy(7eNDZ5OW3iaZ4Kt6w(>^oYu}|@; zV7$3+;heTPM%)a0B?JqqSCw(BOPpF;rlb<<`rpdJy52BArpD@UYhTq7(}kk$HJ^$? zZ|v`$wc#b#cgar&&u4|?&YaE0`0kj1)YM$>nK_2ib8W>HH}&4#;uLc=ZIwaBK@Q!5 zouM3EZd;qw4{N-z`zD{mnaA7D?N)jE3DZxZ9Y%|rPEKTe8ep6xF!cqu!EDZVt-EK| zH<(ovm%kRY;7zGFQ~BF9dB?A|Jv9pQ4SzZ2{NtBg@a7#?VUP6kO+Vh1-@WwG&wPN}pCxzKm^_Nyzsn{Tvi+G8f;H*!T}tN*;3+z; z$knO2=PEnDbD7BO%9eii?XM|F38DU-P-3p?;G3pQjTU@}Eo(Z(lJ@&QO<`W8z~KJ@qFk zT5}bTaCmLDoTV?~{WM~!*GkT=p!(I?y=hyohS{A7xazn4`r9w;hXc#jtP(Rgrgr&Q zwb^WaiOo&L`(ku|R&Bn!_4}OL3N02g{EtffWc9-p-|Wb=EVy2D==+bS<%$9|b*)VA z#0#x-SO0jwYX<`ZgKCLuL`h0wNvc(HQEFmIDua=Mk&&)}xvrr}h@pX%v8k1*p|+KQ rft7)Qx!Ef@6dk$wDVb@NSapCkxMc?|ny9GG#lYa{>gTe~DWM4f8+eXN literal 1362 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{0aPE(XRH=B_5L zriO;DMwU(%PELky=5FR@7RIJ-#xT8}dBr7(dC93Tdowdrte|?$@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{Up@7(6knQH zVZ>S2V+RjTT3oN0`AFrzUQzLcMN2&TZh2LSYSly^1X+ab7XMHac*?$H(fZ}x%24Utx`Q{6}<;< zis@{h{DdQjX}jG9izC7F=1iR=f8dtF6RCx{VpZl#f6dsE{9Ad!ief&~m&=O--)~;P zbM5y(ztb^{eQ5<{jmN)MukL6zpS0tfb0~YvyVpN2=-r535?Cl`J$+k^RC`I^BEQHW zzE5url%BM*RqUA%dE%xKZ)=dPdib5Yhwtq_|6IJ6E_%6Yo_xvXQUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IhSC6^5Z9y? z7ZaDAk6(NycHznBdB>t=ABvcD#i*yJ@j!!-Aw07a{7s zn-)VvJR253#E}j8|NlRJa~94hFlS(35HAVx19^f033xtx_UyUW^Cvv6kG3%|Ffb;0 zySvEDVR2+C+sQ0Eh20V9ui&!>IOgU-CXZ#>`)uuBaFXx_bH&Cf;)Z*E+X4WDs1w-t?s>Vx>w)d)8bk7@zKMx<#%1a)$P8!i{s7N-f2QnIhS*yQ-Xhl<)6%2 z)7a{oymjx4WEY1}9ifZscBkxhziPTFd0{@wXIG>Bf8SMWJQot2&|8@yvRQMp|Z z-Ts(s`h=QYPXp&EaQ_f7G`63rmg%!;k5rrW>#m}z9`A1r)88mRX85$I_q(?6!Q$Br zCp1hJ@Jn`fZE861^ZsTBt_fUD9or9vFgt&!IQM&!Lj=FT={0H_-E}=ScveHFN?l2e$qVu>WZk@v)LHm9TSjh&GnwSW@hiXdEEjxkL5)vuFEnFl_)vL zpZ_BVv?XvImp{XFr6=_l6>E+eNaZ4Ofxb{t+OyTWCC#WiOeH^}^`q7o6{3`?I2KJq)_x#1`xUlP(N75TJ1_p){PZ!4!iOaEP?us=! zh&Vjdby=h-YIOWO$NT>s(>7bJUiLmIK5s_x)5;L56?(x}oAjm?^y+?$``7kM)LiwN zX}RjPDeXe9+m;Kxo+eOYZj!QRN<{gWjdRL#&VMuxmCv~K{>2p$dnHM!hG5m7TNxM_ zR7+eVN>UO_QmvAUQWHy38H@~!jC2jmbq!5I3=OP|O|49gv<(cb3=G)reP~9}kei>9 znO2EgLwU1@Jp%)SB*=!~{Irtt#G+IN$CUh}R0Yr6#Prml)Wnp^!jq|>QiQ?N)z4*} HQ$iB}K4t<9 literal 1169 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{;}!mSz@~&aNgd zZia@gMi#CXhL)CQMh31}1`9u{?Y!o`ca%7&vAD)i? zlN;tcel^_i=l}P7y$RD)E+$Q=nqa3nt-k)h*QA$K8|x!loeJAn7fnYp9a^ zO#ZkgpZSCT_w5thzRWqS(KT(^4Wou91)a(}AMpDmxPd(r`U8@2}%oW=SH{rk^T vp0Em3Fd8ec&N5W-xbR1$QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#uEWPA+Dj@ z|NC$K@3HZ}-I~9~%YQ2@_{lcwJHwPO41J#&+CMNfzGJ9)!%+T;q38ue?z7PC|Iv{D zR*-fG>9G-H4l-%C<}ZF^y!@X-{ABqnP`KeCi(Y_26+-4dW59-pjGF)d|Ff^&{FQ-$p{pdw50ow#kN`*Y zh9}?GyFUE6+W+_EBsoUW50hBES3UZ6XNE@IVfK|jlTQRSyvbB%HlFpz^quenyW~6n zj%!a|`R_aPDft&~#UARddMfwj-(mkLS1eg_u1dTqJ0f=RUtG#wfmhQ68?S#oAFcEv z+4;wXG# z89N`BD$5@G3=;+hriQAJh?1bha)pAT{ItxRRE3htf>ecy+yVv$i{7cB(VK1?aQsad zjcqy;9MvZrSwHt>`tixg(qCH+|2nzK6I$@D=S5*JA< z@Fv&j>Hz_rc~$WoEximIC9nFu*(m0@p=`=8-DjLTYNvF5VA`}-d2ZJXFExg$jfWHK z!`?oKank+$#)Tuc^8c;MvLpq|8$BiBMxPVKowWV_pFAC!^xrS5eW~`H<)_*0SEpUQ zWoJ@QaJ@t^@#eNjwcJ&kH-C5ju%>uc*w)Awtk&IU%uhf6Ic0Uyqy-bZTAziADVvC! zb%@H$Q{L&YkEzXgxeLQ2MW(6C7IF8KOR8~AOt290lGHYBtKe}LkL^2PGI6t_YV@Va z7S+yq@+a66ESWCdsC=L`%CA3R+gLnJOICmdk=!OGIw;<`Y_&CM-vc>9aZTar=}Hox1x@$L4_)m7Ad=gqOHEb@Bw#6*wn z-IpHCnUOawcUJynZ4K_)Az4w;<-Kg$w5j1CVWIwOS3XUA@bu~0)$`dHd3*Ql;i+-B zaOKXWTi5Pg3VtQ&&YGO)d;mK4RP{HNt>gTe~DWM4ffUYhm literal 1397 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 B9JK%d diff --git a/toxygen/smileys/default/D83CDF02.png b/toxygen/smileys/default/D83CDF02.png index c4c18670957be85ba7d0085e8aded10b2da62019..c668d0d96889f205eee7b16b53ee80052d69bd40 100644 GIT binary patch literal 1601 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#;O3H5Z71Y z1`G@g`sOxR(JOI-SKKbAC}%Pos0UBD)o#LD-}DEPNY*#CqHzjUMim#+FB)BVpV{=Z+# z|GdTTq^$q@l>JZd{$DZszhUbC_$mLBCjB%B{O?=yLn-Wk^o0M_Oa50a_@6fWe^|?Z z=|5K;_&zbt)v+BQP z^8bMP|7jEcyO;fU%>5tP^xrP!|NsB-+c?`87#RFYg8YIR7_z^e*8XUi>UV?=yVmv+&)XkF8JU>hPvhj_7Z4N@ zW*49O-G+r#OjIIZ^-{0DkqitBj7i?^F15=x3Y=tMU|=ut^mS!_#?Hs3%6KMp&Ta+< zrn;(-h?1bha)pAT{ItxRRE3htf>ecy+yVv$i{7cB-kata@Z9+=V%c=@x=j1Qis`F# zivMgmQX|neeGgA4v&W{WtgXI3|9%etDY##4?T1r`XNS-3d7Jji>FS9qchYX}5YByE zwwqV!M*8FpD>i#Q*SfpfAZhkl*ID{~t*7>#SeCRyS3}+^EHq&Sk9M`C!$a z1#=(%Tzpp0H9G8#!@P>V*O8nL_rGUYapI=o{DdRYx0sxM++Xd$DIv<{Xr>-)Ea7zI zaDFgj;$g>}kgdYib6f>E(ZknvN`C#^SB`&Y^{<-yiDI}g<^+4Fg`hfnT3b6%k}LYg;B zQ>2q8MXF5QtM8O{@~%i$lgv$*9h?vInhtVzzmrqsJbCEc$14jQwK5moNV@QK=OpeY z+6im6DJ{&%32fmgknWkZ!oks0YAS=V*2+Z-GeZ`2C{{A-b;>k7nXrVl@K>YF{t5gC zYFPCCxA&wh%sX13V%F#O`Eb~~rzI*OGC|7C7W1>_@SUZ@ll-);Y+%fCJjJ`l}${V2OmSdZ$+Pp(at5EQ+7Wy_bI zGi!28XNZ;Zo}F`N&z~kI-5r{TZdth=Rb9HPievT{-czTvwRk7)O-P9e%}wG;nK5&A z^o-fFXEOF~TC*`JC1t~gZ5y|4E-%=!y|AdPurxWvU~|Fu0wyl2s;a8W-#?6)7@Dsu z?L5wJ?#aNwpjzS@QIe8al4_M)l$uzQ%3x$*WTb0gu4`x#VrXDxY-(j

1GbWnhrU zvK~}=BQ)gZr(~v8;?^J^DgK^;fk6^vLvVgtNqJ&XDuZK6ep0G}XKrG8YEWuoN@d~6 RR2xvy=jrO_vd$@?2>__GkkbGF literal 1527 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy>4c%X2zB-CXN(I^%Wj?c*b`b6D6m%}g2nOUlcUaiJ8U_WII0Bpi0;vnbJ^<=*m>>R zDqo+COK0R}OL;vKn)UUzR3hh+!UxCC%>F*(=eNC)3yp7@^E?onsggcFJ-qb){WK+W zPyL<#N^N`1T{1!r_@tc(EZdiRXM)qjlsdcGu7{7^xY|y%Cg-jTT(o{~QN{GMPT8h% zcc1X*cQ!Csr@yE^!D#f&TjbF!uf%-{EhqM^JrkR`gh6MM%_Qx)KC80(UIv_2-h7m) z<|kJL(}%Zj_a0L|*!wQwF5jwe^VPR~&~vtPnAVW~xc=`pzUjKghKt0wpTE+qc$jy0 z-O0|n6~U1SQ}4R68yrZIx8KmpeD>lK$$78Wg)gX2efT>;b^6SWZ~GZ9&u=X3Yf514 zQ*R96`mlYb+0R3ZOW!wqOVP9z4UOxIb=^FT{nVe9aEDe`zT-bHWZtme)&Jp6LN24? z^Zx74_K5BN+;(N2MSso7o^^T$j^$LzO?`UvOHi57si{$`4`g&dy?-<@^-*d1MvLzv z^Y1YiTPoaP5?mO*Kp_S|9h<pR@~J3F0y6oW&YEx3+J2(IU diff --git a/toxygen/smileys/default/D83CDF03.png b/toxygen/smileys/default/D83CDF03.png index cfbe0c04c69531c77c384e3f5158a4facd28bca6..67e776ae1e107683060113a8b4ee3aa2d007af00 100644 GIT binary patch delta 1242 zcmcb^)y6eJvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{Y?n2J1ow zyCS}+&6k1tGMU|_1P3W+EQ zN-S3>D9TUE%t=)!sVqoUsK_l~V6f<&8XCQAi2=u-bkWT%94wEDa+3c&oAYzdQ~$=l zA8y?{zGZo@kdvcm=a=u_<$sCpU)Gy4_2JoWU$wH$uTCAkv1;epZJz1(j@7>{lRmTI z{IbNAdZD{T-o}MIxSgYxyU#)_?4#@URc(Tdt{XZc+j3%3zN&SbA9i}pm?k{EV*6FI z=`$8PIGANAf0~dwzhYOq+IoWxcavJ}!V6aVR_h*_c;|zO=TqMn>ra|SoB^y~51Dm( z^`_qli?Dn?ll|D=yd$=Er(I;Uz$;5bSit@jxHUI^ooBK z#T6f?Nm@@(pZT~d?a+?zvJ8?tExvHN?OCX!aOBT%oe4}!ngnu+jZB*rD{P+SDhZg$ z9B~V?-srB|l<;=KFWu)XcjAl$;~ch?PPsfGxRc32-;L!B|5d(&N4L7opUbgu^^5uJ z{eJZ&BJA@8AI5BwGgI7>bc{c49oO%DD`cmwE09-w|3^n^)!E<692-KFTPk|D@y;v> zJA3Y)xZEAW+V(rEH4T5*1V$$(BV-l>R1`#!`Q*I`Ce&Orz$+`o)T>QJ0!_{A#n=KY{;170a^! z&TJVa<*gP{(#zdG9u9k1VkN}Iw{oK2B!5-EcCK3+E2Hn0c1Pwo&6_XHsZgS@?U|p^ zgNLl=FHU-T;beEMBV+9Yh1RPUy^B1&-6plSzj>r#!F62TOrzJ5^D3vgFJIpY`>B5~ zQQru^IV-yt zDWT(+rxhjh%wOARCM7;O_~D$*XZ#L3T89U4#v9Jp&Zr0p^$dTY#v8GX;kNeOhw?h| z+j!2`EWdTyQ7SPd`m*qaqN@v4KQ0N^sEe$-`1?Hjidq@}_`UoF3=9maC9V-ADTyVi zR>?)Fi6yBFMg~Skx(4RDhOQ{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y%xrn&Q5O5uC6Ao z&W47rMo!MI7EX>P&L&Q-22M_H&M>{6dBr7(dC93Tdowdrte|>b@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{!bL$=Be^2GVa ze?BB+$KPRQKm9%@TRr%VrA$uBtH@_ctU;TLx?AlX>1m36L*wd2r_aEe{yYg$B!qU)F!{p>&>*Rd3>NTsX2Ccdh(g4M-Nt< zwGS$M%y(Q|luuxCeE+*879}BC|6hOqCz8N0y>yGg=j^9(pz_Ys)z4*}Q$iB}d^Yig diff --git a/toxygen/smileys/default/D83CDF04.png b/toxygen/smileys/default/D83CDF04.png index fdc05fee60ed609fa595815f77adcb6b853a1b7e..8b5e8fe78dc23edc12646647f2d56a58265ed81a 100644 GIT binary patch delta 1736 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 mi}tZAQLGzQBj?Xfx*+&&t;ucLK6U11yHsC literal 1559 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y_QCn&Tg)*7Op0) z&W47rMiz#St`_FTCZ?8-ZpJ3g&M>{6dBr7(dC93Tdowdrte|?$@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{sjcz5)+lFUr+ zWDiDWE)FN2ixqEM`5!X>5VC8$_lv=LpPrt_o>)ZaS*mxszbIXhL$b?93$#<^)b$ zGj)yWC5c9<4fQ9Q)Wn0%1#3?FdZ~D^k7kml%9S_r3)b~cH2RP<&A4QLVafc1n%lQ` zSi9JHGd?b>dON@9OT#-(R{O`t_XWvizfN(QHUGxF#n%fQInUZ0==f;DDt$cX?W8kl zr#{D4Ei;XHc8#x@&71T4x*NM0XHDjl*FGb4=ho7{b6@JS|Jo6mJv+twNUfMCd-$E& zu8KKYGJ6kJAMI2wzj-6^PT|>oMc!YcUt}NRkvvN zIy`mQ*G{aP7q#eKKx_Qssas`oADf0Z{jvL~I&Wd9@2sg0r^@!Eto~PR5#*X4FP)_o zKcnND<<+mOp_8Ac6fcY{HA~yOAY6R9Zgrsly3lp=Z?J#n`S!O$bJne(uq)?p&eY92 z>Zs`T|6W2$UCFZawVB3zyOZ0Zmat^MZO^oPKGS37R33Z%=3f;XMYys{&BLBE|4$P6 qHbp`se~oO$(c9(nKj;5aKEQD9s#0S3Y=Om~qT18d&t;ucLK6V3{zzB= diff --git a/toxygen/smileys/default/D83CDF06.png b/toxygen/smileys/default/D83CDF06.png index 47856c72ac87d93d4c375a937061452adc0d3393..f456a1d9b8463e52689753ce2e87ccccee1d5584 100644 GIT binary patch delta 1307 zcmcc5b&P9*WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;9+61H`RZt$58*pQRwuw+gCIh{@-Wd zW@b`SVt8yTc!saOC_|D`UMJZboKC z#yyU#vI-1`&s}C_WJsx(;geQim}|rs!Nz#_%q1oU1`8uSDS3t#Wd>IkhC`<>GB7Y4 zJays!|No9P%o7;_2(k{*0ZEOT}8)slC+XyCNl1{{B$i`X{ta6EZzv9bQ^_3yuKJ}!6j z+Z1>FcHU$0(`-i=nm7FU_tU%X!}UF#ch{z$77C8IvuIg1XRnlGan80?d4J1xmxi8r zUf`7#_4R7k`p7#8x0A&3cdD3$oj$t#Rih~1*$tjocjZRCnRj+tPGj#zAssc{n*QpX z%qyIN0=@3(GTYvCP5;^P{$_S@fgGY@QWuHHx#( zto-aC0&d@zv(CC?E8T-q_UywCt)voF}LBWz<%dTH46pfzXcmCsL z-NdE0J1iP)Zk~B9+WBbTJBAJo-GltvlXx>38-JY-RA32URWf-NxKdKI>1eS0|i847>cbE3onD|`iI&A;CXzGpqlW!$H<@)~eQ{eogCvWYMm$KMl9UBq0O*iw? z&DxT0`#9&-&RNF#WR|IU^8vLr517I$SQlo5%&-6OBtrD=@qf)e3D-VYBwyX`p*KOl z@nW=-PKD2(&KGgVtqlto3CvCoWm-62-D1&FN9Rv`2aG1%oN#O!kJ!3>is$7|us^6` znfBkAO=I`&CYk34SNiNbemBgrx1~w(YWu|A<%eDd1eVF%3oF03;;7=4pQRHRl-dtn zE1bksQE|{Xx_*<4Rb}V?DGqj569it*aq|&R=3LolWs~WCh;?Q0gayqD!(a5Q;m)(m zDscIy_qM%m@_Vy8EAPu~j`>|Y;l$S^mb2dme^jmgEpkIGB(`RP)=kNmFaGIHw4bco zRV1$VFM@%AVVkFmV~E7%*kjj)nhZo-FJ?RH91#mh*2&Ou?B%WR5Q={D|G&G@o`w2y)VRd*DK;d$L)fM&!5LIQI5Pm44U* z_JvapD%yByI!pc#Sa~^eW&P>UqwCT|5Bg@;*4xDYTKCoawe%i)&HNw#ell*Gs8BI) zZ|GJA1_sp<*NBpo#FA92PsvQH#HE4Fz~H4|I@?4=buI=5Pgg&ebxsLQ0MkV;ivR!s literal 1375 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y_S~F&W7fOX09f# z&W47rMvew1MoyN_E{-ndhQ^NOt}wlxdBr7(dC93Tdowdrte|>L@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{Ke0%WB&!^Eu2*?*L0iAw!SjG6}I}!q>x*pXQuQnSmu{D%l>F~r-*}!e zvo3yf*mWgkrbp%ra#v}0-Z+}L?&k8eo$uy;4PKJ=cH@?UH!Lk$OKNVYioASKGFzA7 zN_9-(X0xpexz_VbbZg8A)lAvCdVz)m^P&d57b%ikp3mLrBdYBm#$1x*>3Xn-bI~nj zk=|e}$y!sA2A8QTEf-lS+=+YE125KOC~@ z>FokRk$Mr0kb9FSluSLjvwoGq=gT|x>@5HPV;`#ogY-cuw|t+{b)X{8)78&qol`;+ E0JKKyHvj+t diff --git a/toxygen/smileys/default/D83CDF07.png b/toxygen/smileys/default/D83CDF07.png index 235c6bb90c82d73d1ceb194b8443ccc965a75d90..0627f7d56298d054a2239a5b277cac0f87521610 100644 GIT binary patch delta 1512 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 diff --git a/toxygen/smileys/default/D83CDF08.png b/toxygen/smileys/default/D83CDF08.png index 428c8428fbeeb6f201cbab2dbc7131c4f1aacc58..0f9f2812933e0e27c3c63fced8768f55bf7e2d9d 100644 GIT binary patch delta 1518 zcmX@bJ%eY0WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LN!fpAgqecOG86{g9#I;)Poe7+Nkn z)NEQ;Qu?~l_5Vzj|7&^wZ)G@t^FBk@wUVlNzshX>uVMLpouPjBeTLfW49XW77|t`C zyK#?U!VQMrbJz3Y{x9VCc9)^^1_Q%64u%_v49{jTyxYm}?)cg3cNk{eVVH8^XP(pl zy$lB*F)*BEV0h5X@Zvhdh9?Ypw-`LHaob!yeeE{G{09v4?tCip{(ppF;T;Bsi`ES9 zPcY1TsH=CmQTp0$gS)pZ?>(xwy?5%$Eryj(w>35W-_Ef59s|QAcZL`D7~*d)Q-|*H^0DIvRPMVdcx?7q2ty zd~>C>;Qti{hIiW-N^UPyzPTsu?y(Ek81_8>KSk=mBL;?RQ49y4XGmRn;Cq*0-$#an z^&gL%zdB>_`u_(RY%ek}yxGbSee;6Nt&DY_8BTsZeD3m{2?alHGccT!Vz~FzU-QcK z*oO=kejYw^>3_H5#YYSbSA!XLJZ&?(GH=cohUJP)6e+N%p`2YXEwp%?n0|NtNlDE4{M|PT66axbTdwq$guPggAc0Mju-lLB%OkiMO zYN!f{C<#g|S12gTPs_|nRVb+}NL8rFEnr}<=$#rGxb2Pt$KQ0(*d|7nSUUtZw zxssoLYx$Zy!wQ-ZrO0dB;0vm6_#&c^&E}l^jH$ zaNktYvb;7ScfzL&#^q-|=N`$OvG#C5YiIE;p1NhnJDAL7hF;ZMS#QXjx+FZtaMBu& z<-aQ`$|FA7DU~Vc2PN-OO5Ags#lc0d?-z@5+ypTJr~lO=9E?ky6mPsrSk4gtH;;Ou>!5RdeTbXZP9d7H}%R z_y3KJ)y0W2IaND~C4X+O<*r9vBBG5BVrN#cwq?3GUR<#&Vd)IXyW)S@B@eDih&24{u3~$o|M(@(E!u6|wYadLNacR+WVh+siVr8fI_z;w>*(7Fj@EqnCpYIUJgWHQmz4*@ zLg9^-b5xk+>^AgG^I7f}_f}uz!p!E#h~C>Ag?xIio;=jB7V>?drYBTl@P%it%T-Q& zS(!N#_eXpy-hVM%`rW1c^SkH#E}VGbuWxa^>Fn)~ynlVvo-o&`^t1D+z_}Co&ewDP zWztjZ+9H&f#KxJdToE^KSX%6yA-SXSK*RL@c!vOw zh?d!oAv_#MR8^KPNlkhBMASu6Wa^YDQ$jh5?HgH{xpQJ;?%uJ@$(69#yQjA1?;mES=BB+32PP}3nCQf)C>d#4iSRf{ zBqcq5BrG&}QgF~gg*HW9TV-QyYjtyerb`l^xVgH!yuD5@P?*1P&!SDMb}ie+$8}2b zm9*6CSZ_h0upe=;yIsFt`!l%zx^mZVxG7o{eaq%s&87#Zmr znClvvgcurF8Jk*}nra&uSQ!``t~|#J*8$Ryo1c=IR*74~CY7>{6BX6D7#KWV{an^L HB{Ts5c6zYM literal 1482 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 literal 1514 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y-o&>1{RKHj;3%L6D6qL6mr5$-%i0sACx$d5(`Wy$i)rB z1gC3I8n;tGWS-Q#6kDYtC3`!`yx+nM3`|mlOta|kE`_65t-LJ04 z>-_&`d@M7z+idrr$&INOvy|12{g-htWb2z*yhc(|qOogc!;Hxj>knI<{=dJ~c=CF$ zj~k9l%=!Iovf{A^dpM-U1GG$ETAi+UEMsF-kY1&z_|Qna+rWDBiC?MrM3*hE|Cg?D z>kNa+Y8}5~!|pW^FW9H}`rERrC`$c%8zq0>sMew5dLb!AGunF}{?=y;P8HBR@N@f> zM+*A81p>-GuL@A$3_0`V{r`G7F-Pf};<0^~TP^H;ErP$i=67DB$2@^$lj{zr(yJ{8;Q^No)Z-(U4UQTTGz zqVlJf9UH%&Q{9sBjo}yLi+=O<=M}sEg{4H)%~QNIfzeln|LWANnY;>Bai< z3if|~9dA(MWBLDIdNV_*ZS7aa$S((k-{;%^`&eYC=~W_Gte21vfslcQ-bE u{rz2jyWGCx8y?y}J{TFeq0=d#Wzp$Pzdwl*37 diff --git a/toxygen/smileys/default/D83CDF0A.png b/toxygen/smileys/default/D83CDF0A.png index f844fdeb4aaf7d193d00c62946d149c401addab2..fb659245177b53c25cbfd1deba80771806527848 100644 GIT binary patch delta 1587 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 diff --git a/toxygen/smileys/default/D83CDF0C.png b/toxygen/smileys/default/D83CDF0C.png index 8f503806f118fa860151e0d22d7fffd116086f53..222ef58f1d9b64e258f3027f6629762b4c2ed712 100644 GIT binary patch delta 1691 zcmZ3-bC`F6WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CvaEPl)TKI}b13e#oa0!@{M_CFQ`v zt1o4d${}LG!mZ0AZ046eF(9TUy?)V!TMziPlY}&aRlV!$*@VpbmHk-wOt{2t4BXOX zw4>d^C*)Tyj&IuNmC%3w=6wN!Y*sM`b}3g0vveiTI$mXeJ?Bhg$Bf9bg&BqOa~fCc zxz(S$aZkj&j9)XFM=69;&QDN1%sH{$C#Jigb!~8bw^Ph?Ki`SA!IR~jTh3m;qu|}I zvBAuDQ#l?ORV=zM&AbQY&()o=^Sk*s2|YdE4W1wH;CM-9Jxg)Xp?NvM#b>W z%CY;7oWB}hb2hrC+X!NF~3Os+# zYo&3{XknT5Sn%iURX^uE^=ChK`1MCSx79Ar9_3byFW*?Ru}ytwtC6Y@Z*Pc_Pv^S)j(+hhnS)F z^tqFB;!?gQMP%q6$ayGof7{J>Ld7B+35%~ax34LF^wMs|o|<}0V`9tB;6BY)h* z7BmDc6v!#n2wf;(Blmfm0LyE>#OA9a-y*sNBBZ_k9;;+{x86w5PGOtrlHdcHu1pie zm6^7=U*$V^)YN_cT$Y8aU(9Fk_bX9hpD*_?#?3CL-YMg#pnb_suQ$J!sOiK$v~#-m zHPUod*z;wM4Zg}P6@A-!jWR=J=h})ZZtA4R$@Y)yJ%t|oqB`hf{uA2c|GHDoMi2^s}7uH&xIO<>A9D5xV;WBld@<0|(A zsS_6`2fwcy~1=*K2qCCX>5N-L(w za;eHF6;4>tY$(sl<*4=0(9}Zd|CTrHb&Kbl-C246UApD>5%OH1(0F2 zwCkyM_}aM5Wj}X)Jw44R=SByUnL&;dGs8ZuxKb%jjo%Cm45}rr5hW>!C8<`)MX8A; zsSHL2Mn<{@=DLO^A%+H4#->)LM%o4jRt5%a_dYbEXvob^$xN%nrGd@BpuE|`exjl} P7XyQ*tDnm{r-UW|qdC6? literal 1582 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z2;`-7M6~VPOc`d z&W47rMvfMaZq8=L7A6Lku8v0LE-<~GdBr7(dC93Tdowdrte|=w@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{_2Y|TvW|7y?*ETjJd?JDN&lIf8z#Rlqd4qi z+2*+ANi(D>PRjBAud!oUr1rAgA;hbH{^qVe;RutET1)vK8@&#N-Cp!)$^lNv$o*4U z9EI8^=eeJh*5(}=&Z;jd2Z29AJb+B&1nJH2R?VOx7Z+NY<%#MGYboJ%6GWi|*V^&KGm`NzuH5MjY z7|iaAyLm{HQCJ}(XIX+oWwls`PP^}hkhocWxraXYN#4|o+OgftYHh!}ooFzV+oEZw zPfdLLE+;(Ac+sWl&s@r1{o2cNHvMc^`JFFHD>yxtKAN&&YoZJD>K%b|Ol(hVSaBfM zY|S@;^NNihQg|w+OqH~E^6=!`5~G&*bdFe6PTXWgMv3Nq>J>&=tQN111YD5db7C}Z zn&a2`P4UCb1O3Ys7fiQMyDRxYMe@@8=@Q?p1bJM!e`KvmpRTJRUbWYlNg^fZP><9< zyG`frANSk8p;$qD_20+lM>cwPi|Rs zPFNzqpnU)5k9WDZnpZqqfB3}z`Myh1)_>ROza%3oSHCxK&wow>hOCcX6^@&lTtFqb Mr>mdKI;Vst0GjVdmH+?% diff --git a/toxygen/smileys/default/D83CDF0D.png b/toxygen/smileys/default/D83CDF0D.png index ab306db61110231fe2939205c2c62fe03bfed7b1..b76776d332751756909dbf32b5f8851389260422 100644 GIT binary patch delta 1826 zcmeyxdy;R0WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CxJ%Pl)UP|Nj}LoM7lZ!7$@G!_un^ zi?4zhlaH&+Jju{;jA7P;-m~rOGnT5&IL6R+GGT3<@vQar42=g3W^^$09bss@(7HdJ zq3;kw?Ouka)eM#U8M>!4ld1$(({Jm;mR$^H znDl^Q+1I&e0(RbZIP%Q*-213&-%?J0>EHZj>HPbh-iOw$x$MC(@jkxZ44>1=%#cq1m)pmUe!?wE&3qI8z$YSWZ#W4FV!-fN0$C6vmv~0WWvj3s`;io<| z=O!~O|IRS$4a4kp49({lraWPo|A}G64~7kY>$^_oZoX>1?S}RCn>JM^+Zk4UVwm@d zVe%t}#xo362N}9a#iw8xJx}ea_H%4HQlFCm6bK zGE8}@x?$1cqY8_UDlx3wB)@VNL+5pdy5kH*+Zbw(F|=P{m~&|1QKgWLRSa`BFwB|7 zP;-=_Xe&c~;bw-a1A2?wb2rB^v>jn6+QLw}i=likL-`(t!Y%**|1Z00Q^3H$U{n(1 z7tEm5cm2nYYg%km{{Ef8xqC0$)Tugu{^;~ixPIm5=bt~XU+?eN`7@Pm_ueUgXK;el zUVH3$aV`S`17ni6y9+}HtE>kD0|R@Br>`sfGj={MRmL;*p>uXKFfi3tg+!DDC6+4` z6y>L7=AW{1F%`S2})OEkEDfsE;_Bes`5rn1#qewKU01g%gj?K68&pI+xdA_KqXlb+_$o>0@HN zxT3ei`B`k?jC7TJ^9i;|?Q+&Tt}M&eJtBDLgNWx--omT)pW)oG_q59ohMU`z=C;l7Qemjtc{mepzL z?`J=V{A}WCzEJjP$WqN@Q`d)~Ti!{f_D*Y!d9-#R`^=ytg-0%2zwVJY;eEr_RRL8; z887U4B)g-LOXSGKfF%>e=X{Oe6_QzX<%_X_YLUqj7w;&6aQg|rrGK*Mq$m8cPqFBJ zw@#zJ@7TeW6aQ>*4cpbz(xhm5$>Y=7BPA=EBm;I_-J7@YsN$148I^_$oi}EwpKNI7 z-{bB*Em-~hH*u~PXB_<``&mp+t*^ND^0^c0J}eQbQDIMiqCf5L zo%}CX+jf7sT|Dh`1+UPq`6l6;YqjJq^~X2nh^(3|A5`D_D#dEgfA1IKYr48>9Br&+ z7#JA2JzX3_BrYc>Ffh5PrHO@^sg<#DJb3WL$jHpl)cEgcRplbiGQjjYU1 z)&`~~zjJ!)^4`hKi@RsH&xwcUkW$@0#zx132Nf44J``M(#Peuk;6=lYg&#RPJx^+` z%oO68^ipzW zP0ZG;ud}wkzP4J!*kDE8jUAdrpQnXJZ_mBIFOmMHvjnt3=9maC9V-ADTyViR>?)F zi6yBFMg~Skx(4RDh9)6~23E$VRwfqO1_o9J26-&&K~)+;LvDUbW?ChN241)h`AG5i T6BX6D7#KWV{an^LB{Ts5qDF7$ literal 1786 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{^t~MlPlo|AaWr)?0{h#-*~G%k*uc`!*~!Jl#Y_pRH-(%q)3;Nw(FY|Cq{IRf3UYA+ zF~R8?l*a885Sb@6FU3}=NXgz#-^ewTfq_}e)5S5Q;#SDy``OW@636T;`c|K>(=epJcOl00+DW>)=8d)sf{&He~CKHnd6>6+I&)+%;?!waj=?EPZfE~Gu< zYof^ghu_<4e|+0i{_fa1Z)cU|?|o%A&g0s0!nICeqR;%h|CKL(+bi?pH@mKZ!zyj( zh_&2T4z3iBJU4ykt{ata%RV0XAwT)Og;#K@ZuWVh+cTHxx1G~?+}YjpMdw4#@!FRP z=ghMwKUsCYaCOa$kUi&a|K5B&=KZ~#$*bAY>T73id#AbRt^m&`ujl9QXv|w09j7MIwVb=G^n7qm@XTdOm*Z)0kdgd6K!oyF3rN&lEa zSF=oY)O_~gWR{fFyk$+(4;sa($TVJZwA*xC;m)z;d3}2?s!BSl3UCzc{Ui3=+>XJ! z>*}fvH%%<22XYwoFLk~;VTsC`SvhgN`f^JR&w8!qcDAyZUmNKG@RqIlxDKjsq ztpBd_`RiSCRki)Q^iHR7-z;Ch)UC`X-+GyCad%XjX+7iax~>3^Jr^AWg(iJ}q9U#I z=E{@!?XkBk@7Vu+aQN^wv&QAmEjJr@m1R4%9Q$}DMSI1Hv)!}O-LI!+R^PV#uYQ2x X*Xdfn_8n)AfNCR8S3j3^P6 diff --git a/toxygen/smileys/default/D83CDF0E.png b/toxygen/smileys/default/D83CDF0E.png index 3ccaf4f1de6c12b3dd3181ea7b5b7c2d360e70e2..8a2185514f16ac655ed79603543ab544619adf2c 100644 GIT binary patch delta 1828 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 literal 1769 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy>4c%W^M+iCN4%U zu7-xLMvf-VCN9nf24=1nhK8obW-z^;dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{o|AaWr)?0{h#-*~G%k*uc`!*~!Jl#Y_pRH-(%q)3;Nw(FY|Cq{IRf3UYA+ zF~R8?l*a885Sb@6FU3}=NXg!=z;4kB1_owHPZ!6Kid!MUu^u9UBLAM9d$;EHt5qvz zM=e~Zw8SYW!|~SofJuQ@yafa&r6dJBD!ve$pC#ftX&>uF=e?YciXtwOfg0;}{#!Gv z{`L7?@6MfhXR+D%^31zDvKBJ$Y`_0LXMTTgyRomZ_5MlX%%WP|TXbf&OFz8iyGy2SwtcqQ?i+CXyWfc|{-yEOTP{7*y%IS~S=P{4e4{S=y?>V76ANVa zH!W&Z{dQ^g*>a!la=ri6Gv3rIh&(^SHfz<^wo6QMGy8J1-Y)D=%xe*tbjM=_cZF4< z@cO?~4-_SA(3ze+XHV?vTRLLXW8YbZYYN|Rdk}N$Au(^!xiT^?=t(`HJ7&i-8Q)(ovojD=~1zV zvd?3kc=8ejIeneyv_9CQL?z>rUmD|uYY&g*D=-> zZ=G@Dgxlo;<2n}Y54vxhB4!_87yO|j9I^30%A!@vocuZ7y!Hxon3S>6@16VH9X2&{ zjhw?5$VEOAw~5p{&Uk7>D*R#v!N%+Ri*simlx$T$WB-agmF4rbqe7Cz@`f$I2w(OAyHJYzJ-=FY% z^Ox>h2|}BTXLwb&w;XtqWiGOomyexUe~As_Roi(K?`IGld&(*U^FihB9d(C*pyh>2zvd$@? F2>|h)tnB~* diff --git a/toxygen/smileys/default/D83CDF10.png b/toxygen/smileys/default/D83CDF10.png index b5f35fb9755c80d6f8ce87e9847c12ec140af0ea..1b50e854d5fb48f59be653522d82c7faf8b27fe9 100644 GIT binary patch literal 1771 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#?1jfA+AX) zE+#HJAHVoa?81}L^NvN$J`^!yU)YpgArrR-^==I4TI=7w(zj)qchh3eh6PD0E+V7E zW#=(b{NgjXQ0&5!cu@4bV+2vu>_eC+V#Yo+1aW`J#I0y3sCOeK3g}vk2l=T%n*S zKP@vSRiUJ^AXT9vw}64cqIYVj znCY|@0bbriFTbCUf5~2dHS1Dn^_3HLJAWPao{=K1FK73AnQzML{^N_g9qe`o203+` zp1r0t+iCl+IctsySwxtK-R`nakl=&f z%H{IA-blCq`tam1PYVCM4Ub!%1g)Jqr{&yN2_@Z4E4`ZSBh^_~DaD;oGu55ryv<1~ zIetm7;bz`N1nZZ3{l$`mWiyK~7;>+}k7b ziXEHQDqhXE<3I3L*_V@h!du>lAwQ-_GQ2p-(foGm*2#su+G{p*E51DU>-w?C&c+7| zE@$eM98q4!xzys#dyxROfI>*+W{k^oY@oI+C;te@7)n=b{OMTy- zaPwGhlIcR3t7o^&N+@xDstudCXS2%h)tL#Zy@p`qo`HAfZ)>&%4&uo)q zU=p7$YP?9OR!!;8Lci9q&~+Qy)~%S^c-bM%QOo<1XH2E@Jo^d$2ddco{>TjO)#M21OzN2V1{mZ^wHI5+9c za<8~Q`XWpGWzKl4-0=E_%e$Eop9+@L9+KMfKwg7g(b=qOs%SxqHGBG#_sjl<90U5>f>ZC!=OeLL1&s(E*7CU9*wJ~Z)f^rU|bcUimU+^9J7nt_4g zyQhm|h{WaOgab@JSXo-7T3K3u9<*_D6AUyg>{RnCGz`>qb8}-;%an|?tX!6+#%k%R znyRM8CK_s5%ImvUOgEQTwlr2Wjm@@}*;zSxa+q+iaWQwTw-}ptc9?W@*fq1TuxRVB zYW6TTcXhLLakFh@W@h5yX6oi=vK}pfOva-U`$3^ANADu8^#*{geW?h>+Z{p0UbLSp7dtmb9 z+0)|>Ts|Er1QrY713237_J zseDIwqG-s?PsvQH#H~RjxE53qNrG$$&QB{TPb^Aha7@WhN>%X8O-xS>N=;0uEIgTN Q11h#WUHx3vIVCg!0Q-0zrT_o{ literal 1403 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy=DfcZU*KiCXU9I zCWeNtMwUj-Zmw4wN|lQJPcKNi^zU z(+U-iOg3}fh`@Al_P{lIe0fI~wub$0Y+4+a@p6^LikU6DEN(1(^~77$MKOytb0e3P z2utoe+mF9h_D4H&#@c*%IIsAf<@w5LCFf%6d$nGXon`i71$}e&U8@UB+9Q6}QvBNF z8Ll!IB0hYcTD z1aF(G+$y}sdGE2^2Ew+v8$$!JcTFESI<)uIG@$b9ZB1{pMO;3+BOv;S=`zzopr08SD1H2?qr diff --git a/toxygen/smileys/default/D83CDF11.png b/toxygen/smileys/default/D83CDF11.png index 078260d6dc99e81d1cecad0674e0162778184177..031d83f2404ec4f126d6e96372cda6152fb6e78b 100644 GIT binary patch delta 1713 zcmbQvdy{v9WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LKbXpAgsfp4sg^vomt4!(*}nLQ=eZ zV|)YS*PRI`kOLcJ%PfRZ_ zEN?Hb?X9YBm{3yHnOoeHmQ@oHnc?gn7L!<*SJGTq)>_{>wWVujWBc@)rb$U@<-UOl zc8-3&!O6*)l_gbO)r}L{duCTRPRuWDP06YW3QM^b1NZs^~1Q?J2MBY-pQS zR@0rDS&^7t78afD=@Vsb=N%H6nUqnPnb%NS-JM-nUsqArTUOJXlu_a49cg9nWA7a3 z7m^a2TpX8Fl$u?YQ`A^k-jSYNotRo45}D@W8DVAX0nq3_p zn-dnD6&8~l7@F$n671+2;un+<5tkPcmmi;69ub%8AC%r3hz>qX^YfymO*!wuR1bg^K2ZW^h2d8-W#yYu%+B*f>*n68>yIa_L+SvO# zI0ren1XC71=rI#<2PM>Cq zirT*Y^|PO^pKaeB6?OWw^2?XX%4g0*)^B?HY*Wxlq$-qD7Njav}O94eQ)x93(; zeU5ps_$s$$&#vV@@6C|kemhsd@t&{KqH|i(;WzhiId6~Dzf@By6Y?#zO!P2M^BD`5 zBhBTjYQ7&^ZGPDCHq#lgRUdQ9Z_iC$&%(4vQ2HVJ`r^k+=gr%5)}gFh_>+}m=ot!G*0|!M40rE$$zf zth|(xmS0%$B(3FojDgmAj`Rwgya^PD^OpAPxZm~y+VK&oby zAj73@Nw)3j^;h|h-E?hEU*0lB`*ppUoKFc0`&_|ibByJT^cE!D>faN?`tAJ!wVa)Y zYG>6vNuRnRYQ8Tsb2+E*8>ub5MxNrHY3H3c=-l*OA;@+8;JS0ZA{RR}jy0LG25w9; ztTAmXii4-|@=MomZ zPMMY`3zoP(`og?(|3v-+RV;e{+j)`?=D8NCNVzNjNG!j1>ZR9;9ZTG_3fE=b;oH!z zwshI%yp7#zOa5i4G)!?$GPOS0(9XZ6dD7PdPpd#4shR27InHTxR5H}GJZf&Ld)Mf&g0c6cMn!9NbN&0< z7i>7Oe8-Y4K3$EDyY?*Fq_uO`vTf`3Ei?>iYjoVUa;NI{t!EeSJ-mDK>gc{kN4wR_ z&3CM?KXk;CZ{g(S8U_X&+J1A*YLA_X+${U7{>RDm_ms?BCUvF!P}D7}pj}nJ6u$B>T#Is4P2Xx-&cMK+TH+c}l9E`GYL#4+ znpl#`U}Ruqq-$WVYiJT;XkcY*YGrDyZD3$!V6b52Z*vq4x%nxXX_X8{GPrbH{Nmg_ SQBj?Xfx*+&&t;ucLK6T-#LA5T literal 1687 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%Eidi#L~jm)Wy}+(bdS%(9zk=)xyll!obDZ($&z-9A*YKy{^Wtt}ZUFZm!Oj zPKJiAMwVvAPAb@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{PZ!6Kid##9cjsMk5V5U$cX@ZdI4o(VQUL36V57=M0@PYlpQiX@wFN72X6}UJAM5F{1->lmkS8XGATZX@j zWlO{+6XRLc?<(&Y%GuPk{Qvo;{@b(ZbLZY}e)zyo>B*j;O_S4q@}J&zOtM1i~y!DSP405I07>@+@6pAQ*VSZA%t#VuWzp(UGpMUDJ zIp4Sc_VMqvsOq9k)`o_R*Gm&4#g_O4tiF12MfHZA@?U=Mef~N-Tl~40*si(v71Dm@ z@2%b+m2b9t-gTBPnX6f%U#ocMU3c1T+t_y9;dJ)rHGHQHcIn5jS({dwm({>8Z?BW; zY8ah3@pV{0zR3d%Go8yS+`_Jb6RP|?`Oa(KVUAptc7I>p)+LPJ_PxJn+Eo-8rz6hh zwpg&O*>1|I3Cn~6Eu5x#-eAcyn{B}P@9pjDyQLKR9!G9}By{sao2O=_rMpD0qRX04 zv*=u@EF~%74iTC4sV#Cvhv+JjfE$;Lb5|UT$rln z%U@t%x#KNlsOlox8^!zBV9{eend^=x9w~fysl8Y|VHwve|E>CMnSZVb$=tp-!AYUh znZxM8g@$I9`8zM`7%AS3Q0P-;+2{YN$*9O;+h;fCl4Z-M3v1q}>@nalkZavz;ovaA zusdW?#;mnVT?B0xY+P{j^5ypFrUzcmoGF^Nd!ow5Wik^xPwzPO;?F~YzyIW#E(&yA zIUR9&i$#j`^R#MhsfNnud$~V*M^1a&rE*E9t$DuAJL9Pn&KjvGC0Y4+X#NU(6E`C| zM(g!A^@%0BZi|cS=^Cv%8{&CH$Ad%d#HuTLsi#Ul6>*tA7uVO9vnna5{;wNxJ^#)X zZoU%_esoUmw-k?7>WZ0~D!04v%ZAsx&TUq1@4vUd=Wo diff --git a/toxygen/smileys/default/D83CDF12.png b/toxygen/smileys/default/D83CDF12.png index d0a0f7296d8c34eecaab246ffde8122ee501a64a..18339393fc51c69cb6ae80589da4f7ba71122e9a 100644 GIT binary patch delta 1788 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 diff --git a/toxygen/smileys/default/D83CDF13.png b/toxygen/smileys/default/D83CDF13.png index 2c7289657da99f14f8a8453e54e697d3bd7c86a2..37c2f2463bcc442c7ce65c818934d49a050ece72 100644 GIT binary patch delta 1780 zcmdnWJBM$AWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081Cw5WPl#)K&+Pwy@3eN$icc=`4UG5l zjhWb<^5R^{n`=$KUM>0m_f}Cwo0DtE_j~pKz8oqnZ?ksrt^aYi>fe`xEnPEh9DRS> zDG!gz`2BpQoul8kn}z1q9$zjeemWm+X6gR%Y^aUB&!_V-!C`4X9<*E9c)mOC{r}I+ zyy9jvOSjht?Ek#qYH8!~`k?cl_nU&l(roN~%q-ojvm7t4wY)Y*G7x!?#poGZy z+y!$xzP?!a``yA{&w8Ef-NS;z(^E66iYhu*t(y1s{oemSkN^L=~D+qM6HuKfRX^52(Dzn}M5+ITv=%;g8=RbwQkqrJIAcb~ z+uOZAo=^S%Y4QJ0^Zvi>{dq6n%-Y??-lyKvCps`ZEig1KI-zj-)Ry-T`hPrW`}=0X z-xn>vA6I<65@+Wa;O-OY;vV4}m^7iU;^TwP-!Hp=KdJe8BlYXm#1AKZJba_Q{Np(#W6rvl8aJ(|kHUY$<*b}9PP+2Hp_Jxnd#%&pyPa@`*7a(;8b{^ee) z|Ns9-)}NHy#K6E{QWE4B%)r1Dx#`vKZ~G&el+K({QhNDPiRtv|sHpARUqAc#`q}pF zQBkK)Gbz7(seI=MN2KEw9Usv{L?0j4* zmVbRjQy3VS>Z(E_N`ey06$*;-(=u~X6-p`#QWYw43+fpdEPAJgI!?M{z_I6ZmyIJE z%cWwQ2RGRa8mag38dGTyFSIO+sOV3t6P7sYRU7uBPcmW5W;pqu;yYK8h z`RZ)Jg>4L*TfJ*;*WWFheq*sigL#(np9!UTHdilCo-TPJuh40}`i?8hqIHivyz@cC z^J#30`H$cf!3A88T5|-aN@bsTH=*dAG5?MVS2AiV755kfk+cq@R3;dqHw}E>B zm&=B)XF}ZD7JR(1y>-HdhaNc{T6XNzH z7F2I2GtgUS`s&RTfnA&aZT+~y#l7HyWa2s1$BDvDn#=y4I31espD*jsCDC_>pSahB z@m~9tBPFpTdxpUJz-^9`%dW0@Q^`?3&2%@XOyiEeSFbK@QIfjaRef;Q;((1prZuLH z@(&{uJT8U`=w3|8pBJML&v>|E;g&TKYOpzL@&Tx8n-i?VvRw#P&WS)v22dum{$!Jw>-M^&4?y16xK1E5LS8u+aINV(}(UWVQ z>M6&?E-G#O53d@tyFbalRJX?bSKh(7uihq4`&_{zv}>M8`c_NP`IolGH@*>x7Pns_ z^(Mu_=D+F}b{*TUb0G`n*D^3L{PA>g43W5;oX}9u;3mOPIAca+#LXL)7CRR(-LX+y zmiA0+TG*bN1jahXZ0Tt0YIb+^bVtPn4I4VTSI^$vzI^)j{`K=U#Fd+8)H@^u6nJDr zRJf#sY_U*ocJaxHsc}jQD)P#Ts%leFZVpKcEAz{Xt8+{YEc9GyqU;>G)6+FIFSOJ* zH@4P!F;Bg6^P2kLV(;weYWMW;a{u)*%FQ+L|64jItNG8g%KgE~x! z=l^SJ?H1FIT*Xq-UNm$6=}k|y!_LKQt?KR4y0A3sRciL{^!3sE6CbPb`fiB7vc+tA z?#*4fzxi@ySLC;re*MDB)7um3xQ~_L(Rz=L$CAH=85kJiRZCnWN>UO_QmvAUQWHy3 z8H@~!jC2jmbq!5I3=OP|O|48!wG9lc3=H@$pPS6Uz<|(@o1c=IR*73fU0<30L`8Kj O1_n=8KbLh*2~7a?DOQyL literal 1717 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%Eidi#L~jm)Wy}+(bdS%(9zk=)xyll!obDZ($&z-9A*YKy@m#Et|o@&=C00` z&W47rMvfMyW-iVKMkW@{AY+YSdOh=sOA_;vQ(^XIW~Nv{^_t<;Yvo*&npl!w6q28x z14{t`8Tlpo#Toep3eLf13L4>=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDj~F z>gr}{=wfJT>FNyjx3RgotGR)rtBa+vk-4#@5>#&rIbo)6r(mNGN*qXu1tt{a;s#=Z z(={lK+bJM2PikI@tx}Pay`3b#;bR5{rl+1Rjv*DdT7pk!i8zYbhF-mU`SRqMCo^Ug zPFizJouf;|u7jg%k+ry>r^smUj>uc=A)qH;*KYG-A?X_d6Yrh!k z&s-m4Wj-e&FLR&Cr5)+{_MbQ&JX_Cj_G{YXx2v!BTZ#+EDfWNb#&zndp@{qIX^$VI zNUfdMGfnQ^iBSLel%E&RM;F!@tTCP4`}28manI+@Pah1jwgz5#o&2FIRKc8Y^#=B` zO8YsNeze5YT1{`eFSVa>i{YQA-8taPFXlyI0p`mrDl{O1P$KG`#fO z`Yp$bIU@3BUF*36y7O2go0NHKORRG>)=top*NSMISexYZyj5#Qw9}>NJ(C!Z1o?3( zZhGLNy1+Eo*d?*U;#+1`g1B2pZ`&(<=L@rqla#I5H)}09)*~_5NnL7EQ%&OJy6FtM zCoG&~bPg_Szs>jaVIW82ED1N(+x4N1YY)%p(NmUmlsND{f07D!0Lm4~B3b zGD^r~@h#H$=*ITI;#-s2@(>~AP8CUw3vbI#HuD8BJvDoJA$HLoMrQ}DjG+AHk9T(S zG-TN|H?pjK_}eb^?c)2d7rHAH`uHd)=18kWxnGHFdb3vPV|0Pm?{uRE=gGpGQyv{+ zP*4;L6gW`CvgKmOuUro!zA3g-m&6)tOmdy4`{bDZnKXu`;^H4Kt}IxV%{)P)Z{y9^ zX|Y0AO}Hj(nJ&0>zeCwRo^RK@ey{Uu=KTCy>-uYJJK5P*hN>*}sV4$Fm-eK3$d_vr za5}!$K6H+2idS4X-@O?^OZU#-wr-O}@wXZ?O|kY{yT84u7WnnBU4u(ztl#s w7j~7rtSx>1arRT@?duD-U;Fhr|BvPYhU`xj+Fp!jH9!@Kr>mdKI;Vst0LC_u>;M1& diff --git a/toxygen/smileys/default/D83CDF14.png b/toxygen/smileys/default/D83CDF14.png index 66696d896f486804d8a8b705034a15b8bd32d559..68e1e8a3b20b1183166db0e32484a75710b20b10 100644 GIT binary patch delta 1812 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 diff --git a/toxygen/smileys/default/D83CDF15.png b/toxygen/smileys/default/D83CDF15.png index ff5c8e0fb8ed0ae12252e01d0de0508f5fd94de7..8a915537b65d11085d46d4bc987fecb859ab9b60 100644 GIT binary patch delta 1781 zcmbQiJC|>QWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081Cx1xPl)UPzjyxsz4hzWl5h80zTGPO zd^!Eg)x4jNCjI|&>))3{-|yAGKOOe>!;XJn4*s}X_3ntrpZDA9f842fd(`v)pPRp* z&-{L;@%xQ}w@1CcTuykg$MX3OxIAHF8cYr|NFh_PZwhUzM1|1%eMdZ-;ezNdGg++%NzCanb*8d;a}6`Tx&#kiUPN z`ulnF|Bs7*KWY4MD(KaIyC3(f{=T38|HrPspAY~3aNz&%OMkx}{PAkx@3(XQyz2gP zJ@f5h*Wb_TTmF5R|L^0f|KIoh`Lh4#>oxzrAN~LH(7$in{=T30<8Ima8(Hs zZpPnFEB=4o_~-qiA5Xe|JZb;;rswbTrZ1Oc-W;(1dL{1f)9U~4`u@J1Qvd5!-{1GM z{(YG9>qYmE2UUNb)%>`b{^6w0i#-0QvTKVTi^N+hFzn(Vyd)xc>MeE-ujo)u& zyg%;!dcXa%?S|j3CH#Jn|L0l5pT}iCZf1TyAMy6E`-@%X?~l5EJ`?!ua?H1@vEMJp ze7+F#;e^kdgAOltnZDd>_2z)x+e1#T_uIa#-);76yTSke|4lMhhcPfP7?uS21v4;3 zZhG}hx*lmoJr-&zy(`LohtGS3{16EArU1(iRB6fMfqu& zIjIUImGuRw3Kh8p3=9^%Q$xKs-7(;}^IF8RiHBp!;|jNHdZCfee!QIa{BgycCr8(9 zN^Q(oz_@(={rV;OdsbatQZ5^){QJ=>bN$3gv3n{iKKt%IVV1Z3w!p!<;>UiRy4Osj z`*XPNzS52CH7(eXv1XfS@t(v0(RP=T7iy93150~fS-hBApRmv4+4lRj+)H%?8Sl;D z|F!V7Ud2j(|9fd0${%x9&z%sn>g%M*7j_C3dw1uoy7bWhnrq{#4)N;lSz_xpmLFf| zv-8A(>iVilc2}>h+~K6EGC!}fMg0SlmFt-^{tG>4Ikkl!E=Z3RJl9cqQtZN{+$tG| zxGf*+DwQg}$uh{bvi+z}bX%mPqY&_Kw|D_#NF&EbGvlih6fDDUaLQomioRu z;bxk7qq4yDfLPzvqAtl{1^N-(+@jj1$HPOv2+osiWp=Gi`ByCf2{aK8HklZlZNPE9INTUVp7-TV_{ME`-8|0g8&T#a?; z+gQleEMIqho}aQ}vPbXACB;)MWxJB<*-}=fKJN`OS-2%!nNdYnC^}8u^23KlQ*R@A zt;(bJokxBmX9eL**{1EmTZ&Y@DVvI_el{-BMOF)3Z}F)U~3*TRh_*X&%nb?x58n^#A- zH9BrvzI}cDp(CD?m;1~$t8J5EZvOLS=H|5XGp+OgHMMq&=?5kx2!zeqS90^tQ*E(z zF)g%Wv_CqRbR7o?c&wT^BIE9 zGcZg#?#aa5$1#(EA%Q`)#5JNMC9x#cD!C{%u_Tqj$iT=**T7uY&?Lmrz{=Rv%G5~P sz`)ADfbHIgW(Ec%4Y~O#nQ4`{HIz4d*iTec=VD;+boFyt=akR{07P}us{jB1 literal 1688 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%Eidi#L~jm)Wy}+(bdS%(9zk=)xyll!obDZ($&z-9A*YKy>5;!P9|n%F0Rg& zPKJiAMvj&y&Xz7_F0L--&W28=E-<~GdBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{C66?T=gqkf1qbyx!JsS z?`o%M|G2Wm<=eRJ^PN2Ydp79Yi}u;I|L>|(UsF$LEzwe*%w+fW@NxDdzva)VoPQM` z_uKWF-n%&rCmxx1XFmTp-|=yc_q6j&tN2bZ%do45CUg1kezVfIKHgJq7x)P-ICix`$0auGTUgvVeX36H>zw7cWg)w3G z{`DJ=bZO09wuejQ{shIHng7&goR`Q}tW-NJl`N7wbB#~A`L%nDGJiMEW3PI@<1^2M z)->xn-{c^#j>JdK5wjgyL|;TndrcQ8owr7u;T%uoUM59}`8A$EHr^CC8cHwF_w7$@*LJ-n#BblF;23Pg@UuzFq%9A?Ia~=&BMearT3f8{Vv& zJ*lkdjH%9tEXWZw;~?bFS*y2H(jZpq&}ww;NGVUAwu UzQ@nTu$sZZAYL$MSD+081Cv95Pl#)K&+PwyZ~c0;Ss*M_wosMat$e}X#4l&VEvD~Rb6d`E}mi54!(uu zZ9nc*q^2g@Ir`Z+`nGh<{QZ2Uy^~*fRL1uk1;HWFc8-2uE+?4Vc$itbeLNjxZtec* ze2k5~&yNS~R<<6&VQK&W+46u;qSML9$)GH`)2n4FWdfqKl1`2Ww1|9`Ij|8@G$mu*M(_W!tF{{Q2m|KIlf z`*HIBpX>ks-ueII)ZfpWpWm6=(NbV(Z1_p-If4vg-_i6S2cYS}~PWkn! z@85?xzg~3zcu@83R@J1|I9E3xGi&z`Cw;!(NcsD;^3RLrA9qWBJ#F~+w)gLg*1u00 zFYZdsO7u3fbo+KK;rE05KhGNeJTCiwE91kZjD`JSfqp*b*6#0*x_&+r`0aAcx2v(= zFUNd3AG3FXUs{~|%e_`_>JQkxJ>>LqpX1&+P6f#>|NsAYRS*5dz`$Tv666=mz!bUZ z^{;#DBAArU{Cs;xN$KUUZ!eWj|NM8FDe6zu_J7-7Kl}Om+4k*G|Du>qpH_bP_ocG( znO|okH^2Dx_IT8fhhL5`FfcGCdAqypzkGXhC<6lndx@v7EBiBcJ}y;`t(_|J^$ZM5 z%~c^0B|(Yh3I#>^X_+~x3MG{VsR|Xj1q=)py;DP@C(SY7`Ey;fh(-D2q*@ua5Amx$ z&3wFEwM=4K;$+?s$=j2zR&C+=`|oFW-I2>%Ch8dNsoZ$-h+J6gPKjvK?Rm4;oVi_L zBiAhX`>sjZD(|bIUF+gP7TjJl*=)Wn`|A21YobFBi?g{FbVRo0#H4&w+iZT=F`D^| zn%>53N6n^B2vj&Ay;ZR8#2#+GuV0>|vQ_t6EIiJsxAkQdccF#3&Gsdq)DCI?(KO-= zVEvesFEmxE_>Jz3r&EmiYrbFXxE?Y)xUj9Oc;}u!f^vtsUxb8(?ru3Yl zl;=GBS^weLg-5!!WeWb69^2X^_Ppj}=#P>4+Sya#9O1z6GhP1ylUJ+LhOcKr+}jp> zoO0X6<3@ps*`8a%)$3eUBEqNqI{TdEPF$*z&V-z3LBD2yRVIh3kF5v4MsA&4Sh)7! zW^bn#=YCy3*4)|mV1er?&Soud{_nMNOLR@+)CwfiVEVP|D#$j^N>gu~r zU20G2OoSX>Ot~>@ZpwkfOrIB|SQ=Jt+rOm2?x}*8?DUkg6E!l@~?C& zHIljaU-b*Ser4DByAci77#J9Sc)B=-NL)@%IKU(^b4Fyu%^Q{$3v2Z9tgG4G)#c*s z>I~u?F5EnN_3Yil!5$GVB1_`VoW6bh`gwZ=1CJP|psq$oD+?799h*b?Mp{;GYGKnF z9rg6=6cr3LEmci*W9^(98yyXewXN&b&Gq*$*sx;9l90~Eg=_XK+O%rdvTf`3E!?;= za%Q9B(xqG1?p?fj_3q`{*V~6pZFI~%bi{M=auc7qX0^x8L~gz+DPi*L%v|&OgNIzZ zr|ZNn+Zof{=op=Tu4^iH_}aM5Wp{t^?s6zMc=0m-&X&s4*K%){OGrpH-p$L|RDAuN zZT0v1nX@DvYnSKdC1$4PCUXa!XJGhg?WGf*dbWmvfkCyzHKHUXu_V{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%Eidi#L~jm)Wy}+(bdS%(9zk=)xyll!obDZ($&z-9A*YKy+#&hrp88Y2CmMQ z&W47rMvmrg=1wl=&Q8V_28M<%CNRC8dBr7(dC93TdowdrtRQ+_-SFzQaxO|uEXgkl z$G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z>^C)abu%?|F|@RFbq4#}*xcOJ+`!S*#nRZw+}KhHsyBt4Fw?hFu+ax44y42a6AE&1 z12Mtr8kENE6cCvwH7~_hsYuD*ZbPJ-0|NuIgr|#RNX4xs!LivQ;SzP{X5Ku1^KOc7 ziLVETN-x)5j-`!RTSRktzbq4H&8(VOttld?FY@0(|JJ{zYufoPjv8?tO3$1O7ED~z z_3dLYbIrC=D%;LWHeu6rC-*A5}w0Qs9=KbeA&o{`|{bK#Ee&)OQ^Yb!K z6JP%`pSb&9?u*;)*UauTF8y7%bWfX+d3X3XS&M>J<7M~O8}6I&U+bS`dDj1wfWL3W zo?rZK6M1L;sou2jJ-yPxk8dWe7x!6fw{Gr_-|L^Nb2l?@U%Te&Pw@jggMX{J{(V+d zl#q05i&|pJv4fXY4bl{+pHEye#dz1WDR*WUI`zklnNjm_7CUtA|GuH3SB3xo4{ zsg;ff^H`VbD>kh9{iEShLR3=LA_srFlM;n37vD_&^vY>Jv)s-7`rm}7PTCU0a%kZn zHKpZD8ICvYoc8l-D;wI)I{IOq&2opeVX-}oDL>k7Y+f?`Q?q$P$%050QL}~0?J5Q; z#k(hZNqQKwpJXw0Iq9g8_Q-J61?e=c1zGZe4;nok)ut^tYqzNRME1lw#>*2)MFx;adz zapujbCtf>z%}M&S!hrGSYNt-C+^z^FNv87U&1ZY9cIEL0X{($$vRFbP^3R8$7uyu) zSk1iNz{h!JrBhX?(zcUvjwdy9)|r1e{#h)>{?gx*Q@j>WPinYwp|2;MP{rLmA zOR{d?JMc+(>aNYRHc0#Z3bZmWd$>7y{`|V*fAsEg889SyH?TiR?E=+c44$rjF6*2U FngGafy;T4J diff --git a/toxygen/smileys/default/D83CDF17.png b/toxygen/smileys/default/D83CDF17.png index 97e3de6fd0899ae6dace004882d0933c77e79933..18e5714e3bc73f44a4ddc8558494a10d048bc976 100644 GIT binary patch delta 1796 zcmZ3+yM}LqWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CwolPl)UPzjxYuX8-?t>({F#Z>}}H zI9D>UJ;lp6#y2oNKDns1d)B`%hrZvdcXACWs%ZQ7<=~IIRrS^mzJ=v&Kkii6IQq79 z&HVj*W_VP__ZtOvj(%S*Cz#uKm|41gJRM|i?f&U}jE%j|j|c6+VQK&W+#uM2y1dq^I?K__(%r`1Cpawi*R$SVFJ^vy zI)B03j_8E^fUr~-_wc{LvpI4LqecANq zb<^f`HBFNW%iELE%6$V9>>T}m+%NzCanb*8d;a}6`Tx)LpWkk7-m;;( zabkXHYf4s4P*}RHgP*01=a2hUf8Wpl|6|wxU#I_mKK%LB-t`;Tl+|>nW>zGo*O!Gw zXM6fYS=)L4e%A8u!~B0ASN;FK@6VV0A0Dq-v1(yiO>a_0g-<}7n|Gv@z0ap}VSk>~ z{(C>|*UPED-!1v~efP&_s~0VvnVwypoLQNeS{@Ra=HeM)W$X3xUjF~LUH{(A`1!p5 z@23@C-mIN5qa!>vCoDQEEG9QFG}X~1`0JIp`oB-B|G(?|`*zB&SACzJO`krsB_b}@ zKPbsJIN8}P%*x)|%-a3KNuTdGQvN=z{PUvu$K8_mx9cbLRfL3R28N`WTZ1C#+qHz> z5AuIKZTN7fuA?#C$u-p8DbU8=+uYjy{ZZG?X9B-nj(K%Dwy8YK!6nGX-pA6$&pI&osUb!qRU(L69WTNT~$a#Nl;?BLP32|ep+TuszOO+L8?MUZUF;> zMeo#5?@f0Uc+Imt{~&Z8T`iddNOgI0@n=@Lu31;YMFXQd!^d6Uw4(botdA*sC_f-2;&o1>Gk3DDoxe*83h6} z7TBezXkB7ry1w77Q6NA|#A%Q3GAW6sqs4cpvmA13h;Z8~T)l>sb6a0f{Sh0+?d3Ct z;vBY>F9{Y5PGedi?r-tt{3_Ww*%1rR`L!(3e!sumPnB&_^VtQvFU;)Uq2^KeOI<&_ z^RMknS+m%OcCmIpB6~x#t1lloSbz70)8c?_I+IN|uY6N0Y;*gK+7*u0?11pgS*$l_ zh{kXqb6S|R`7i`k8XUtP>(jf+qPrI>la8tt{U#UAf?weM&>nUDl(9W`2pE4!^&3`K8B+ zdW$8;OfFfTvXSLda{PR4)Aiof8BUq&-IzSA1ftW_EkAr{GE|+(?-lo#Uu22C%!VsA zDv_?YXC@TA-_*SHrQ~-XO9|CQOB5eoRAYC4Qm^wnF#lydYx$+yk9DShwDGv~)w9^_ zw*S%VZ;mf_*nVVbH-AuKX`0ohdhbj6%yYZ;=(X0fB``2B{PlEk43W5;oY27Jropgr z=8VXQn>Q>ib}nGLW23e#?U~rLust;ijCG9J($Ut{?C$F6j*1HyCU59oJ$rZi^6A_A z*U#_JP;Q=4?~o8s;E@qg;gS-v#X`B+#V04G#wjVN$SW(Vs!c_?IV3Ht%r7sluCY8Z zu&~oa*||P5veGp*wA42@w$?d$GLLd|PO*1(bhUeWc)5Rm{1zGI=9>R4os-r4XIka{ z>Y2GYP20dodIwv|trk?vGsvW*I?(v?oyH%kJtR=2oG0(ZP zrSkN(+}r8d8BKo|E!~^^eBE8|?{BVNZ21)y8Sa1oz=ad}5h72b8Qjl%DQ@J`xyZo4 zpjzS@QIe8al4_M)l$uzQ%3x$*WTb0gu4`x#VrXDxY-(jCkWnjR6`P^g_4Y~O# cnQ4`{HPrQ$=}%Nt=VD;+boFyt=akR{05KL?<^TWy literal 1702 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 diff --git a/toxygen/smileys/default/D83CDF19.png b/toxygen/smileys/default/D83CDF19.png index 4443ab3548f98ab15ad5368468f7ff518800c486..6092dfa0803c5303afa44b3bb37213c7e34e265d 100644 GIT binary patch delta 1462 zcmdnU{eXLdWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EXVrPl)UPW)XBWP2>MshyNWCAn{${ z|EDT|7=5z;+r>eQJ-+oIs#yfYU#bscZ?OT9y)yqdTY?zPBLDX~{9mF2Voy^7k&gFjoBn+{ z{Qtwc|F7r$e^U1E$B}5l}rv3d?^6y>!|10VLZ+QKG zULXALUBth)G5?=O|GyFO|Ns9`e*LWs3=C!^L4LsuT^IVa^dJA7!TDn^+k`2mf3!LJ zuN(jD?)|K*@%>8w6Fr^#6aIYbF){k~XX@15d;k8OG2zCqFNd}^hBGiQFeZ7syJV>* zP32=?U|=ut^mS!_#?Hs3$`|vhQHz0rsj(^~q9mw3v0R~`C_gPTCsm=OvLIEVBDa8n z!J>C+XyB&X1{{B$i`X{ta4dOjvF+>inRVZO#1=f-(RX{_G2@9n0^D!Bmd(Fke_{I% z(_EEqp8U8RqmAvM;d;ly&aSn}GBE;WnIR_uGyw%k)IUulohe5J_@p#l0ilUH1sad_K-Q#;Su7`*%MwrAPxIeo@4 zb51{fm|oeV^B_1f^lgw-u%27xSyBIql23Wg%jfgy-!n>P|D;lHJ5O2o*#BM33d-W_ z_7?^7HMlu8{;L-AU<_&G*fwwOs}}+`aj(q;rOae{d)ItBHg9JK+iR{>_C0k3Zcyw#qIX&KmyI<7v$$6ECu+JBK7_&*vOwq&ev3vYG zuHWT>);f0|?sKyHZJfQ~TK#wDW`=Gzj*n8CcxP={y=>EW-;Onvv(hGLUkYJin`zL* zZL>_Y#c73A;xY-zSKNOOx)o&4DC_w%@g(mjU4zwv7Ya>Wn0fPfMEtMJ`rIe8LF{HL zNAM)Y6=HSi30_YGmb9qoN`>1`_$~dD#b$2f%l{J?rCzO5aDV*ZibmZHCGFVybuCTa zSxY8AS##uVfS_Fa_N#NZM;ujrQYWL*aAD;}Q~ybf{Q5K6RZe@J-28VbYsovtjH6pu z1m7`rH1EnT&}Dxcu=a`wi%Mfygo#S+oCQB;*;**qPyIIe-iy^uyI;Jmoc_5YR;8r= za?IUt(H}R|)o`UOj@b26;c9~V>4$%#o%Yu=bj|82o4LTlih+S)g{O;Sh{WaOga#%z zwKOp{kpriWojcg6bm&m(v9rxehqChywksXT&s0_D&{0UqP1f#FN=SdN(4?dA>ctbC zV+wr&6ACsKPEARe)bw#_>YX`vQYD`}5ml&hRps~B&rfA@6%92l_4DK9^p%gduh(^D zV|!cQaA3iM2^The=s2+=Lv}%s+}#;BcI?>kqvgnw6x$7#D}Q_m4!V5F*oc=Wah|Y= zk(uFC(;$|Oj1#U)So8)io6Nw#pjzS@QIe8al4_M)l$uzQ%3x$*WTb0gu4`x#VrXDx xY-(j{s%>CkWngf)@*FRUhTQxV#muxy+&VU?lx>`-sLsW};OXk;vd$@?2>@jRy)FO% literal 1457 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y+&>Z2Bs#iMlME9 z=7xr@MoyMSCa%tA21bUK=4KX7<}kgUdBr7(dC93Tdowdrte|=g@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{j!toI#Y!;KX~8#xs~a2Wa)o~RK=u{M4pQ*OonPX4N^{r z+L;th4{y0{{8Tws;_aL#b9WY>oAZ6&&hLA?`j7qew-NZlv5Y0TDgUeMRfa}!@y?^i zJymW;{`l8qTkcYnx?W(x#u=%}d&RXky8HiUIDY$>s`wXY2j5?RmOpp-RPgs3@2np6 zI?jacf4b%u7wnQ>SH|n5_PU?p*$v%=(S44)GCI0;*Ks=R{gIXNIv*+8v}VyM?hRWvKM~xc zE;e&Q`9JxymPIQ$&y~)+Wuy5wdxItK(yyNV4^%m|Js+M)pZLRLA9vFQMm_I$hIiAK z@0{fSI>Rj`|MWZg#L8_=ulwHiZ%VXTIzxIRCf z-xW)@e|YxVIFet!O0gq%@3W}%jkofgHwP}aYY0o6(zW1D*p*XSH5KMZpRJD$DtUEQ zWKD0nV)6XovW69J?kI7U2%oxGlu-2De%tDrnS#ca?rFbfOK)~6>HpGcw#Wl>;HO$Q|+!f4laS5$=a|`zXt8jA*cXy9;_l$IRkANUg z&j?S?2rsWlFRv&t8A%M8no(ZU(AU*BCpaXnrlEhrltq1$7WPhD&@*9vcmKT3-Z|yf zJ;7n=z5z)|Y1KLPh0O(}Esd?y>YJz3HcqOln~G|^)9%incos*N(Ydbn7r)Jj1CKRP-*VpHlwAVLH*t~hyrp>#m zYI`YQ zUfmlIoKjNRRax6#UtK@3xS}ICETg2dJGY?8*FP~iy(%T6y0EMxB04)XEG;B7Ej%(a zr?4e4wK6`b)Xy*8*EcRByRNFPKewnkuehbEt}ipU!N)HyATTL8t-QFhv$(81DYY~( zFzNsQ|96Gn^)oOq=#&Kcfzp^(-}N8Y*rxoQ!O6CJ?^NwS_5Ig>{``FXO8*3%KU3Lu z?*++dPq^{x%N5IqPdOPF7#Neh-Cfq7%UNv5z`(#>;_2(k{*0ZEOT{Y1)#V-o15;yF zNJL3cV!1*=QGQxxPO3slWkIS!MQ#BDgGKMuP{&Dk3^?|D?y_-YWBF8UGp*D+w6C&G z%I5Lcsy!#uVod8N2`*YFee%oi=lZ_{_b=;BnfmZ-x3Ai_4X;iey|HTN*=?Tb_l`aO zC~;=P`DKYK^+I=xyp0Qaa63mWcb|n>*hkmxtJ(w^T{m<_w&ld6d{yf@S z#rCUa(<2r;IGANAf0~dQZ*j+Xa=*a_+oX0m>m65??G=0EkykNm$)|d?L$W_bX0ohk zs!E*gvhLWm*;JzS5T-oy?y>u!%YqTsi9lFGktbSJT(osaTS|l zY*W9-a!vQJQ#x6FV8f;*WU^xatA=wJ0acyz1V{8)~K zyI;h!_xqKIu+JBK7_&*vOmR!nG5)xBT))c$t#$4`+~;KXGtwkv_IKxIhShEyAEk1o zjb5&qHFs~mQ_9)PTO490E>ki%(IOc2z;LaANbf4)w62L4YcJMs5WFL`%-F5&^pkqF zpL`MmT@D?5ngW$g(#)!YOtu21ecs({F4}I(Ce3uyyDT|F$Wkh3k*aRT>iiR***|ga znA7<3{{%suUAvuRdYgk6|43Y2vMYv@b@~+#^^=jEw-+j@@a3=Gowu>e`NZeK2@Fo^ zN!jyMnC0RO`lsnE_ltX@FLFVhPi;d3&*OFVoI4M?G-q3;xL;qU5xA54!H0D(AG4HY z6&1MsTlVI8-QxFVcb49tS8myV-PP&4`Pr+v`$hR*-(K!u*0ppte-Ll!CYgKtRll&8 z9O%ljOI_j3z`)4k>Eak7aXC35At50pEipAYIXy8gg@KvRPfs@1);3mFFHcTj{&fHQ z2QHkrQGevhnLADMr#Ciw9=LSs*0F2n?j5{%^5&$;osIz~j$S=`_weP@w~t>xAMY{2 z(L%vM!$QSG$41FQ#Yih^hNH?cEi*kk#fzq#9%{=w3_6xATbiDdwsTinYWkBWyE=_J zo<0>95fhz0C8~1T)bNllV~-F?shgsq*RNb#dh?pOiI{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y>2efuIA>ZhAu`< zW`>5YMoy;A1{MZJmIlVg&Sp-ghA_RJdBr7(dC93Tdowdrte|=g@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{3KQ=vcri&n@!_cz+}rCK+E@d{cNHvTbH4Log1NB)f5x1b zFT=NPw|3evY4WO&tG3_o6t8Z%d6RvI#JbZ5E%*DKu8izFqp|6Z{F~d`_NLxGXQUf% ze5}q>Xm^jG9*4~F?jQU1FFCq3{cWqwY4J}NN^Nb7d6=IDNgiKnly5R?JxfC1G*@Gb z$^TOOE_~6O>2+VfPkiF(RL@R+C7y{s%O-fdIFWMu#<5?&ngZAJ2cO~5+<0{1!omu< z`gO;Ii)Y5j$vrnX$7`Ur@aiTfli9j&-o06r8P%bom2>=ExsLYX86VP)|j!e@7lQIhlss> z?Xe>_3LY40GIU*M)BoPiw)>u6UCI8yKlhx%LfBqI&_Oc~* zLdrzF-#dJNSGS8cbZ$9a5ViWH&{-9CN4?p6um653?Y(tP;q#db9!V}MV~(FXp6~HW z%0#yD{ zEjC20Wep6RnY{bPoe1@BV!NuVC!9=q`7uQ?Slr9qT_mJgs5kI!sf^s4YL!==7B)78 zK0%+-mmgc=@N9)4@6SFynRo9ORmHeHeZo@zBC)jOYml2@@8{wveBV50es6kokA=li zSKF@q{fypY4q9^;E?=!Y*H2j}bm8(K4H2#t4{$j^C?wb8xe^>3Tv zvZ&pmtHW6RTC+MWRx~c`+WR&)#NX@A^FOM0Yh5<{``sIUOyZ$o;w%G!KWT@`78w0F g*Rpl>^#5E23L$p00i_>zopr08HAOVgLXD diff --git a/toxygen/smileys/default/D83CDF1B.png b/toxygen/smileys/default/D83CDF1B.png index 3f93634cce3a6672fab699a8c7b8f6243f9d2de4..42516ba4d612fae5ecd116e7afe7dcebf4f2f4a2 100644 GIT binary patch delta 1468 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 Hl+XkKD+0fq literal 1496 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y@sX+j+TxVrY=TK z=7xr@Miz#q7H;OQj^^f$mZnA~jxfERdBr7(dC93Tdowdrte|>L@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{lyZe|)^NQA~Q~c`6wewG8hP83Vl>H0B zX4U%c_AULjRBz_}z$qSz)ipd_3z_P8&rUV6Ils?s<>vr~w{;N$M^0X8OrGE>dG)+; z!Ca1n|J;YJ=9LOr-BbUP5foC&y!EH03;(?%y}wH(oLd#js-hW^-|P|dJ&?WDF{F(7 zoz*;>eNI#7EuK-am%YJ2<+8ZtKK8%uZVOeX33}=|`5#?)^!xlp@iP4E7nG%}6^?vr z@M>aP{)_pm|N8@f(-JBrxE2Wg-*lGk!5QYa{2Qj<6AEIw&XzcPb@~gDvR0;=`_=ho zVGpf+e_XM+_Fm~bJvvX9aH(6J zO`M(?HEn8iReh8(Tl>S$CSu*^#aB2aPs!92*}yr4bDgp9Qtm}d`VKwb^?H^n)0?{= c{)i_qggn?Ecj?{2L!h$O)78&qol`;+0QxB<>Hq)$ diff --git a/toxygen/smileys/default/D83CDF1D.png b/toxygen/smileys/default/D83CDF1D.png index c9829492b5c321ab2251c5512a9006ecc0083106..3c2f76ab89d840251085fbf9c952092e85ea7f0c 100644 GIT binary patch delta 1793 zcmbQuyOM8$WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CwWfPl)TCB@7_6l;Ob|hNqiAB#66& z;r<4O`x_bVE(OWnUBPf~1&DD6%&mX09;EcnQiccX7@lrqc)Wq(;d+L9D?tdN`OXrC zd#f4lLi8*Fn*!Dem%X!u;loj>|1TZ?zxVwA$>;xj_xC5n-tJ=f|K9fh6P^G6!~VZ_ z`Tsxu|L?&6?@j-|xA=H~;mJ0J|4)qm-<0`Z|K8^RYlr{;Bmci~{QuhE{~P0X2O0j~ z_x%6G=l>n)|F<~*|FZr6+vfi@hX0?<|G(Dx|J&>TE!(HtME}2u`+r0I`w5WG?(Jas zdxhcuQ<+Cw7(VW0c(;q;|2@_JZ^Ga175{%g=h$HeZFz=0>z$r%W%z$fL$Rrt;nd!c|2Me*KUDeuO7Gi2hWV|c+vgd7 zeQNOQsp0=?uK&-A{{O%J|Gzc=|J(fkW%U1X$p80g|DS~X|E&A}ul4`W3;(~F^Z$bQ z|I4!f|8M*Mb;X|I-ZrpXvNM$naw?!~ffI|KI!lf0g`f1H=E@{{KG&|G&cU|DOE+^9=vb zG5o(F`g0${|AP$wZyNtUXZvIe!~YL<|F6jWe{Avpf!_az`u}gK{eNum|AGGh^Ai7` z>D^w#@O%Tq|BI6U-`M~E;PU^u`TvK8|Le~RKU%}^Y!k!(i*o;8+5LZJ{r{ZU!}SdR z|Nk$#;<^KrI7@>3f*G{>uK&2kHs$Z%8JxTKOr4_r=THCjpFcleztTTJ=g*(rd;b2N zapTvQD^gq2I~W)k7?Zr+T@L-t-*<(9fq}im)7O>#89N`BD#zAN6?p~*royU_h?1bh za)pBWqWrYXoK%I9%7RpdirfMQ28-UQq0y6;8gTqg7v0>#!SbkR&#^zHS8L9FdOjg$ zZ{PKIFRy8RZYXGAp7^ik&$9TBBDyN8P4wsG7;S7%4fi`X?QHHgU#-vA|$y_DfXO|omKYmbW--~Hi4Gt~f;4}1|+*?0+OY}auvSW)gDm$u=1hj7)L2UEJQ&FMJ2^I5I8QNB{lL;p#h4O$)I)st5| znUS1-RIl8!_^`!3x0v0xE{Te-uvmWj^!X1GaSrO6SKU%IS*F+Xp-cOo3iqMRd-C^W zqV|;b%bu8g=A+!adV_tX3T_^&DE`0By@O$u1Iz6@#-_^77j8T?6BN28 z)5*KuE$MuWOVie-SLXArAADQXxxr!6TBSKm^MhC#z9h9A+_kyPNN=6xt2dJbc5VK* z^WzE^_ks(ViRYFcKdjag^yZtgetF|x`xT7dE_nAsf~r8YOQ);Oy#p2+gN#3RBut&VM3pA4(yugR{DK1qN2<7CIU+}PkR zkK}^DyOK{bO#J#D%w8MxvPe}Wraow`w&(n%J7gPL)t*jb&sTI6{#0Wk~G5^*MW{@@4~5jo7rXYi8TZ z)V}fY_5I=DVQTU__f2nI-aEN@arf+;n0OBDtNX{;=y*_ZVd6ue59#N%g5uMW=>dHQl^ZPo(w7Sovx#!KD%FoO7BX(qboV4`xw9_XU zWVajJ-ddNx4NGXQ@GCrjul6^;Oi0kPwqM~6 zQLdrBu~BSCa@ZJzzW6>)IX!7F0|SFRdP{kVo554k%5tsu7SC(p-G6L yft9hTm8p@ofq|8Q0o%O~%_tgj^HVa@DsgKlZ}zaCsHo1xz~JfX=d#Wzp$PyyRfa_X literal 1691 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 HDWM4fwCCiR literal 1650 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 Dxc~`= literal 1671 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`Gtf;oFf&vz zGto0NF|ahT)KM@pFf`CNG}1RP*EKY-GBvj{FjRm7B|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9ucp-l$oBHmzd*{pIn-onpfiKVyje< zTcDScnPTN?XldwbWbA5a>T2X_XlQ8RW@Kz(VQk=J>f&T-Wa0`l1Djq;7bhbZXHydc zGb2YsLsuh9Hy1ZoR~K^=H!}+pH$yj=UeCPZlEl2^RG7V)nJHFKy~cR;S~(Y`CYIzE zh2-bwz*0a!Mt(_taYlZDf^)E`o}of`W?o8uc`+z@z+rFYl3J8mmYU*Ll%J~r4qvNG zEcTn2I-47q7?>DY8ko5n85&wTo0vK}8W@e{DX@`M&4*1=JZJAQ?77?a`L*mp?{8Y)E!C|r z7<|2QZinyDin`F}K1W%KHiay*$T55#k(|Y8cmBDU?gh0KOAFp@Wc0DW%`L|Ja$2Kz z*zC=rlDvt>-hVyW=`wS3JimJFtosQx&_;YWf_({bi0iTsl2dsPM zFV>&;;TyO9rt*{454a0<+-6~&`JMImxhboj+cAB7@#~!zZ&;7(2cELMs_U0+n7LN< z=#`dhhFwy@E2Fd)++H4@UQ)KSQKxjHOxv*tCPUrDT_u;6O?I5er0)G-f!Thp5A}>o zY)({)e(1J3lJmvlmrOz$%Pl7H>sOa$S(ujoMpRtqUvz76&-&TgUS7vYd9AuWT zNqMIF-Di9p$+>2%&5v3RJeu)hfB(&c?%OXF$r?-zZ+@veL$>0E;l2M1O%DI(m&EMP zWUT9-GjE>uUG2}4O^=?P_xYEPu1;U>i48Ly>XeW2SH{KH&Aha((bd+o?_PtF>GLn; zwyMd8mT5rrQ`=uL_KvjdMtDnm{ Hr-UW|FYJ4~ diff --git a/toxygen/smileys/default/D83CDF20.png b/toxygen/smileys/default/D83CDF20.png index 502a017409a6986757e762ff4923d201ba94c083..ea8ff38d1f71c39167ed09570c5f1a97f99b9504 100644 GIT binary patch delta 1564 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 R)wviLJYD@<);T3K0RS67<{SV3 literal 1713 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 Uzn`e6&c(ps>FVdQ&MBb@07XF(hX4Qo literal 1371 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 diff --git a/toxygen/smileys/default/D83CDF33.png b/toxygen/smileys/default/D83CDF33.png index 25ad311a3453f4d503d5c3f515d6062656145ac6..d9130ece8d232eecdbcf9ce5b33f3b40a07f59c6 100644 GIT binary patch delta 1700 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 literal 1606 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 Oz~JfX=d#Wzp$P!<-F86$ literal 1542 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 literal 1636 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 diff --git a/toxygen/smileys/default/D83CDF39.png b/toxygen/smileys/default/D83CDF39.png index aec58deb69f2ba81b71b1a512eb4e0604b4ce7ef..5c285fa9cc1601923b42f289e0d19c9deef974d0 100644 GIT binary patch delta 1428 zcmaFHy@z{(WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EYR`Pl)UP|Notu8CG(jpw(OqTcy|_ zf*XaHw+OSWqJX0WPeu&rj$ z&0%mUWw5Vha4Q9|othb(ni;ORTAec2+@;9Bo*SfVvpCE5isC16Vf&PYPa7!TG}k?^ zr?ioe@q(kt6*ueChRQDky>2>~?UZDD|ju zd)Zj+l9AdTdEOh!f>)GyPlzz@7i8MO$GDN3;h-?nb4#5MfgZ2z44-OCJXRNapu~GY zg5|Cf&ud%#kKuuz!U8^q_U^&H{rg`wK9Noa z2Bzk!kcg6?#Bzm#qWrYXoK%I9%7RpdirfMQ28-UQp^lU87;xXGZZ}uS?6XVGiXT6!weLmPRfEF|IP?rpPw4Glb1&s%+Kvp{ z138a`s-ts@JB_(mDi)g^;*b2+>}+fM)1aRDrgGkk!e;N>TiJ?y&)b%JXyq&2dFVgU zvq7sve3NHL)*8iV#iusuax=cyE5BR0ZOJ1h(dR{ye|Y*IvX*GAUKQ(39~1!l;m-I zT$(3b-?-tSg4v#1owm=FIIi`D?Eg8Z;kNrM4(SQGvJXvugeWmw=$2&rt(~p@_)*m2 zb9yZo=6+q@>#l0m)O?PkSWl9Fwt$!6TXy~O#=q7pWT(9=h*z8cM@K4h_V;CtjiHBI zDtb5dn!H>yYwF(krqr`}QCrd$sh`oAv|H+R%+ZV&Ds$>R4m*6*@>UfQSL+a!iL>45 zxR2Q_bm8?2^Q;s_yVfSDI?SGT&t$HM=*kbQ1(PE(7V$SQcaj#`uSJFhT*<(|u)@>DF+}2WasmURTACOeTbLS~MnZCWSpB1bu&~6m$1NNM zD_5=5&d$inEOrk4@Z!a*m(mhalAoPeeSFTI(bZYICO0AN!>60KZbpCLwm85d!6G|% zPHfEGmV_vYJGM4^_te(>ZAoNO`p4Mlcu;ZSq5!+a5fK$GDIq027CsJ6MrL|;dX8R9 zY6moQm1k>ctDol(5AX=`39I)Ba^PTa6zWKDVDjY2U{GUV(2x+zyn4&Xg@J)VwZt`| zBqgyV)hf9tHL)a>!N|bKNY}tz*U%)y(7?*r)XLOY+rYrez+l13-{vS9a`RI%(<*Um axcJ4nn}LC0qKXO^1B0ilpUXO@geCwIRyAV) literal 1510 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy#^M>mX78w1}2Wi zZia@gMi!3FMoz9yhGvfDCMIqM#xT8}dBr7(dC93TdowdrtRQ-w-SFzQaxO|uEXgkl z$G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z>^F9GGcs~>wKR4$adb5@G_){wc5*T^G&eJHHnlW!a#VuqO(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI&Wnz%iPfq_ZP)5S5Q;?|U4?`+{f3ER&neB|c( z$enf0zPv@NY3nXQ*Mpa$3IbK$p4$4~;X>=|Cf3sX^$Vi!ayBWj@M`SXuxN{8fcC{B zoJKLQH5^G$UMMCB| zs@2OFf9{^JRLgF!o|8b*gni*&tT$)ee!cLalgc5z`8KA_E_p)J(wey4H++4^`%3!t z47*ynixEP#S7S=QZ+O?w;C{ZiWIf$`;8{cot7yD_$w!lh zTcZ0J*yAPc?5gKEuP#;^<@2WLaoNJxDF=)`JT_jFz-(R^&@j_|kb diff --git a/toxygen/smileys/default/D83CDF3A.png b/toxygen/smileys/default/D83CDF3A.png index 1b83115dfa7cc65666fd18ba26ab80fea5712d75..6058a373664b3b0a4a5ebba9acf0264734d8ad15 100644 GIT binary patch literal 1971 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 I>zopr0A1^GkpKVy diff --git a/toxygen/smileys/default/D83CDF3C.png b/toxygen/smileys/default/D83CDF3C.png index cc737e74bd1908245cb619d235f7be7533de3d8f..18e70261f823106795b63662ccbaf7e1761719fe 100644 GIT binary patch delta 1644 zcmaFCGnIFOWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LMK~pAgqGw;rCp`QX%z`=_qmJAU=f z(aX0FU%Ywn!j1jsuI)W@W%sGeJ5OBNe(b`Qqvy9AKD+VInbS8P)Stfj02!USaUaUP ze*g6K`zNm5J#p>su`72DU%ZKC*6vf6cb~evz!srqa8zW)66|NsAQKYl%W{o&rTH=n=%ICbsrs}Ema zefWIh+TFi@|NZ#+>%q(SfBycxeD}%CC$DZkd3oXPDGnY zkIr5=ySl|^eun6+Cof-r_%T&9_-m}-|?mRwu_0GS4|2}{H{_o%aohL56`0)AV`_JEh{J!(-^_HXO zzkK`g_505|PhNih{`2y^CztO&{tNQJBQvSTXBZe5%u0g%KyhBrfDHJzeb+esW9u2s z!^gjUKP9+j>-TMfTeqD0_Wk4;?K3}5Z$0&cf7^F~f6M~*3=9m6N#5=*wij{(BN-SN z*h@TpUD=ok-uWnf@xs0xWF2}&$iC@9KL%gjktD5)$+Rj9}5c)%o%-S~8AmpjPlYzqwt9z7v#gV{k+<)^ufABy+pVLGE#=qm-~4~O;xD^xI`!e% zQ8nS+k)=CX|8d!F-n0|xmnVcwUlrR`rmuA&*KD#`ybSy5AB&rQN{&$qdHdGd706M2PB^VN4; zS+-Z~Q9)is*OE_ahh+bW%w$>7RG~QAW$7`gO`<{PYM(V$)=L%oZ&ei)Utux*@`LIM z9{mNuk)dxzq=NI@D$a^7KG9Ur_PpP`T`d3QM&`;%md7p&AKUz%;lqie6Xz!!kv_!a z^rrp`lb%AT2g~g_K4np7mJq)#Ra&OhvTjX%o{iQ;L75V_8O0|nZnOk+W!hKlVct#0?-=FYY{1anD-+?dnCk(`{#!i&!J+@Nt&xTN|x$9b*yt9@}da~-s zTZZh6puE!a(Qg)UR@RvbIlOSlIqO$%^xz@uYmJjdE1n$Px1hnUpdrC%uh_yz`2}n? zDL*DQa5^5=yZ89g#S4LKs+&!?oF+9ZfAYUvx5E5a=E1qI-qub3ToI#EQhRyM)o;-s zHEVy1oN(J%`q6RK#JN1}-}5>DGCRd}T}(c%wu^y*;h(3AV~E7%Ef98fvuMc6D)cJ$~ev z>#^Id+8c~DcHNw1t*)Y`x_rs3+q*Q23^bPBT$-LzpO%{bG{xn~GR;CGjnz+{ii?Pe zPM;z+<>^YTV#9*fQ+MCA3atna`4u{IWnr=LhZW(suU)w|^ZNCwTV@#_J{Ww+`ekKi zW_o*@$<}RKvp;?^`jGkh#jBU)C1o$AC0=EG{%H82xnbhQ&W)Ngj`A}&9XES+;7jZZ z1_lPz64!{5l*E$oRIB8o)Wnih1|tI_BV7Y?T|<))Ljx;gQ!7&wZ36=<1A|n)qdSpw hq*~?Xr(~v8;?^J%T+2F9QJsr{!PC{xWt~$(699x*Vp0GA literal 1640 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>4daW(IDqCKhI9 zj)sP=Mi!RlP8J5HCeD^_hUTuuaJ`;+#U+V($*C}VGc!}Hpn8q*>a}t%N=+=uFAB-e z&w-_YfQvy zY1@`*bS-Ew+!h%5im&AHwIe$|^&1?M5dP0CyqI(1QpG?sJ&%-!%r`y+ga}M%H(*_+ z8yuBk^|<`ltQ`lYPX1F={r=~smG*#w7)B7th<}i z6q5S%W_N~z?2a2VKc3L~z5mhVCvWC6`EH1CI5>I1vS*hBd098lioRSfz@J&5$tC2- z`}6Ll1SK)2EAJ0K%hTK)e5d-bJhpQGg%i!Elh{+rmWMdr-6WH|C(oQ`GmoY5nc9koh(gVt+ZVi3FP=B~bRi`& zti$rxt7Uy#*V=@vXa8Cx_xUh?;=0;~sy{|nc{Yc>FzsF$a`A(Y`lR$7H=YG33GJEp z)~a)zkC0rybhk491HChkgZ%mA)^Y2f~fW%cjz|Ax6*6h+m5m{I*&{ zZ-4VM-bG~*&Cj?OZZPTVIAXGXvGCp}g6S77p8XAAXtO zdjIJ3rC)a}(rEk4m%?{1Jzr;m)ybqy2UmN!He1Z?Qj+Cdv|+oxZnI|wH=E0S_3B3c zlRMAPJbE_w`Nf*&QLC>nEDEdaT($h@P5u8}OZt7} zgGKh*iptxRE4BKcejRc1M1PQQ@*A6(s^u$Y7W>FlT324#Z{pB)GE^z=zEbPrmi;Ue Z4B{FSHmqUUTL&upJzf1=);T3K0RRLXZD9Za diff --git a/toxygen/smileys/default/D83CDF3D.png b/toxygen/smileys/default/D83CDF3D.png index 648a2830c1ab1ed0bfb3882d1fd28c91834810c6..853a69fa44bf53a77ec2359a3985f4696ccea385 100644 GIT binary patch literal 1937 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@Iro;fB5Z9Z1 z+#i<8d|fT~bG^dfP0Ihbss7)o_J6O&{{x!;59$0rs{8-AUisRn;??1W%R}-O2WHOq zOPl4HFx4e`fbzqyBPotqnjgE7L=Do=RkERPhpCkHaq4>={ZV2PUQkg5A9B&qi ze^@GWp_TQ`Lh<6&;c#@if$_~k@xtXH5Ol1X0V0yOI1rAGRWo$WcgdXZcdVKrZI{GlL+XMneelsL_ES1~6b@V{$51cPiHa z^RN&qZbIhi@^;0__9e>JIf{nSQhZ8m3=9n4_NIToFya3;&#$Li|3BaHetZ1m*+%~_ zlzuwZ@&Ci=ciZBMSBKx~Wd5`+@c-@JFK2rGzd!wMdwkE1(%N-NZ)U0d*ct!-=A`de zru=((^xdwM?4^D;S{Qz=b^3p-@aLtzKX(`YxWDP+{abH(4T3HX1f|HILS|2Jo+Pj#7hx-EBM;H@Tx{|lwxt_l2q zZ_2xE87UKN=bUVry}xZ=E7#|#T<=%={@NW^(reYSFKzwBxwXsFa^?nQ%?!wD)8Epq z_qJ*?&y#?*E@pJ5ZPUNpSH!HN$vVC{X{3G3Y zvqFO=nw0Gb?KzU?*JzS9%cEvPV#GwtsA*Q=eb)c~|DQa^ori&eA-yEX50qfBgfasI zL)P=BK`;L`F}|P9v}QhY^Sg5{Q>HEAU_9M!{BI4T(;?YSyt1GDbIvj^x%utOl?O?p z`LB;l?ET8J;0q%&$Lv*1EL;NOin{ha_kaI=zg3Ivtli9;3=9m6N#5=*r#^2nd&a=P zz+U3%>&pI&osUbEE4tm)n1O+*zA7Z5Bq*_5p`a)~Ei)%op`@}PRiPrcfPulHcWS8b zwc7?9cZ$2@9NAbt70x+v>+4><>OXuM_Uj&Ri(f46E!NSSLp{OZ)v8>@D%-R7Ns@A%s?=`%OteUq2(3V9o}r8=-NZ}ZeGKc87d95uhYS}ILK zrcwH;CSGWcS_Na!yHBraghu58HEsKLq*v-%Hz2-Yxvg>flnd zw_63;iw_rjYrR*x^U!~yYlBvYcy;%RD-p@>`(phpKOeA}?{+6R*ZU~1*14x9KLq6t zbH4})3$<=NHsuh<`khH4HwDgp`1A4Ig-5owWeWP6j#X41s`$pq&>ti7lhbXFqK-n# zpK|>NOhK(K8`hq@!qv9ml`VG9oaJk)(38zs1_T#dDC;nh-+__`DUwC9XzA-Gc0-TY6;%CES)Ai zBI`GWI29%y+SYV7cponGJe`mIgkaE`|6RG8H9}d5}^s+=%Bqm6Cs!F(OUc1%~ zX0Gj4pT&Yq6xXkJW8#qQSbNix&1O%6tZF3R)IB%jH5Mqdcic7J+jc>wzS;PJ;^BT4 z9gey->FIrKd=}hFQkV2MFY7-M@AG@(y%(!l%U5kb9((R_<-`kLeT&0pZ-12i{c*5@ zb?b{q%^}Lxl6>d$IsY>4J=rz;=Jnuc1_nkhPZ!4!iOb0e6Q)d>mY9;1mYACS{K1n) zX%8m)o}Vx|{6Qllo7~YeYu?DHrtV-^@0Ax<=eTeclm7ZyyWA$KG0zVx^vsN`Y~98* z-!FCQIyvV3`y5Msb7O0p7fvi(+^K2b+|UpiTSW>=Cs>foQ53Cjd$nl{QOKmVn@ctz0C&AizjdHUVVGg z($mw{N9@enx|^eU#ftXj*Jo{geJ%gSj?B+u`GIVP3Lie+-Ie>hZ|?50_aB9m1=tz? zH|+iWP2Qs7MZp4#W)=Y!*7g=T%c?g&IwvOz@jMiq7E5-?Sp=W z7yaADz`&qd;u=wsl30>zm0XmXSdz+MWME{ZYhbQxXcA&*U}bD-WooQ#U|?lnuwdnH za}*7^`6-!cmAExr{Nmirz`!60vLQG>t)x7$D3!r6B|j-u!8128JvAsbF{QHbWU39Q Oyz+GQb6Mw<&;$SmO;@@A literal 1770 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy~fT?j*eyqCKhI9 zPKJiAMiz!nmd*x-mQI!iCg!fL7BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*Ar)<4axfhr=}E zznB8}>yNR=dR$PQ7ri4qa@ynUOq&Li)h}+;&FVaIVMT7VeUHR+%YVxYqOCUMK7VxP zusg?(l8|NZ6*d*h|KQGlztq$3g324uXPS<7>aLF_+3(yUTgCMIc-2S!5Vgx3+gGkw zR_}iMSmK8x7mk~pycqad>vPeQ45K@h1`18NSL}VwI(g;4Bqk*O*{8a3Z)n8n*3}D_ z{#BLurQ>A#eo^4s+=5e&Z{@$^x;ypV$3+v9m)-o)`D=HFZd~Pl@$$vB5p(CC*pc>e z@wQY$j)jh^6b&QfXQ!n^7GrUY{(AG{g_BpQ-9ENFxbpSJW&ax=9g%Rl$RMVk zuPhU^F`>|)iEKH=SK7l8+pitv_#}bo^xHlqYEmHm6_9UAx#u{AG#Jim9qjj;!I&xi=(B?MkXO zc;#la{Cv8my;@bg|Ge@gf9A=kTW<1w`y%e&_WjcPr&;`&nt3Gqb4ALG8K1N}wjLAw zs9uo&D$hFA|ALr~&dG`IPt005^Y?VI*H*i(94m&X~mz~zh~2S?!T+IC9F9$VZE*cx8{Zj*_ZW3YESnC>HIo8 zPgi`uc<)r>_j;dsRx-<7g(b_9JrtO?}KYpIc<^P-p3=NJ~K7FgG=L6L~p00i_ I>zopr0CncMk^lez diff --git a/toxygen/smileys/default/D83CDF3E.png b/toxygen/smileys/default/D83CDF3E.png index ecbf4cd365b037a69b45e9f3a46016729ce604ff..e63cc585ab827e5ed0df1b9575f83c2300736f4f 100644 GIT binary patch literal 1888 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 Mp00i_>zopr0G8ZgKmY&$ diff --git a/toxygen/smileys/default/D83CDF3F.png b/toxygen/smileys/default/D83CDF3F.png index dd5399e3b245990a66d5e1b0942b0dcd0ca70df5..789498ba451d8d66c5b9cf42dba095a8cf9b872d 100644 GIT binary patch delta 1614 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 literal 1601 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 CQ}n|C literal 1624 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy#|IxE(Wf~W-dl9 z7KVndMow-9W|mH-ZcYZ~mWGDrE-<~GdBr7(dC93Tdowdrte|>L@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{vGwT1JnoY1Jl4x*IH|+i#IVh!{MZI@o~&6Bb|DkO z5;7LbTHT-fd~OtvT0s1#XLWO`&)vCmN4?0jck#JTA@ys1|5{!nE8`@)q4|u=O|vVO zj}(t^8eLv^?Vk0+Bc1CWJv(LbR4(WpSE=!zct)$I-=6HenXqVbjJ%ZcMn}zCWw5VQ?3}8#pQLLTcT|eo2WSWCsWjc*eEK~5vmJ|lyY{`% z-Y>HI*owKAUL?Fkif@B+zNj5LMVdyNaVp!Ai7G*0OuS6^aFY)0fSfzui}O&d-BO zHgHUISBy}wJaDU3db7ji6Gs?$?l!Cyv)g5}OQ0%m$=SssQJ#W#MnHeGk1c zjaUBRvPsP4-{OSxTdY2Com}{3#kx;+m48oLo9^#fwxD@hh0IP<$u?c3@4;@1O#3u= zgLT&=B)|RKvvA?`isYWdDeU1tZ>PLUTC}#ZtGm;|v)dueB1w^5Q2y+}dm6cUTH(9f zFXexGEv_E)f8D0kh3eCFYfhv+tzLM}SiEnmcZOBnw?DtyY^I&o{46S6%l`VV;=i{F z#`CgN9{b;m(U|!4(B%cXc77k{MQ65kJxxk`cG9!_WzUnhTLK?n&dzgcnonb(G@^&hu0Ff$a^m>WHB+|2?i>pfllT-G@y GGywp;Wn$p~ diff --git a/toxygen/smileys/default/D83CDF42.png b/toxygen/smileys/default/D83CDF42.png index 640daa0835884a3a0d7019bd8df0e4ec45c34a57..d2b2b3150f7b480b04cd2734aca07cc39d316fb5 100644 GIT binary patch delta 1587 zcmX@dbBAYwWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LMK~pAgso|Nk!yWZ0I$uq>EiYbwLZ zX6{4P9EYkoPE;_R>Eb_B&2hbfb#E!t`e?4(bqrTJxHqRU)bB_Y*`C6?!c%xxI>Uit z(K8L=>w|?)r8Ar=W;jqJax719dkn*k3WjY7!dD8om-#VVXp~qM%&;zyVQC=4x&Vet z1q|yv1y?#FN@mtl#6z!H0brGX4feHj*eGVDuWSn4di#+rMXInN?@hK1IA z%Pe`9I0-H^;kneo_o9BC%H0LB*ZM?WtyB53U-#WMjoY&%-Yk{7K1uxR0ljYr^*(G- zyI#WZZJqqB>5?zjs(jm}{d~Icxq9I*3&r1WP`Nfy{Of+*Z+o;pEEK&zL*YiJ$fF73 z?MQ;@|d|xE`e!bGwez7mRHNUN{m$_5P z@L_@c+iBugE7-rZ^4>^edD_JAZKdqJ>Ecfo$$noYb*r4=NSg5HsbY`ox$hM-e<)(y z=gD@Xis8e2u`8{-pXZ6Zt>?ax&G0Of;Y9+&&M1aM=?quP8Lq{$o{wdC6UT7Kk>yMX z!y$X-Ep|-*|Npm{yJ{~31A|pbkY6x^``miQ+sAWMF7LO0>bLUS18$Y?d=)`2qMvMQ zS-b98{q z3=Hfgp1!W^&)E66RM~eSI2t9G8a|SKUNWcZSzlpIZ-w_}RbKHK7Sm6k zKL14`@W8T7t8S^9E(>w1IxCt#k@aQC`S$X5(f5)YnJXt*-cuGjRxZuZapLI3`71ii zm$0^6_%Fxm>-YfW6M%S^` z8M0ETyr-P>46`371b12VNmSJKK4JUGC()}2!kWj!9NE)lXN zdK2H}i{BI+?|5xKCRKd4;G@W)qAh=W{v6#l|EF8L>Ydj7^SkH#eyz0d_v~j`W_d@Q ze|^-RFxRQ{v-7HpbA8&6?{BGNyuGPwmSwr^e+CAIqn<8~ArhC96BwA?)Yw7}oH=yr zSbd+;p~TeO!zWu+4&-L1zkhIo`RPX^gAbOLyLasR$ldU>$>|W!JGpsr>+abpDePM@ zW4%Rl%lvwWgn$hy6ZA!eq}0wT2+Ijdii+r-KA<2jEG@2oe!+wZGp5X$H0#)b1vBPN zoH_OEf+ce%&z>G1aN7DWX{hORw!pngGIaCG>3MicMg)eARz&Te#E zzH{kTU-6v1^Y%3=GB-~wtZwJLb8wkM&b7?E##h1KB619p`l_v`{Z&9&PPN1}q9i4; zB-JXpC^fMpmBGls$Vk_~T-VSf#L&RX*wo6@P}{)3%D}+f?3Ek?0|P=sZhlH;TBRIr Y9d6k{izX_nb1^V@y85}Sb4q9e0NW6{9{>OV literal 1614 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 L^>bP0l+XkK##^L3 literal 1665 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCz0Q`VZcfH-jxI({ z=7xr@MwVs<#? zTUuKdWSDe?^>jK4MKl;r+Hqknce2T@(#4&fc_tDuD^%9`+}T<1Sl;1)M@jQUR>oEq z6AmZlOP@5B@@H6IE?bxGb#T`cmHkJb%=APzl+Fr9hX*Y1pL(olmCwd&H)qv< z&Hch1%dx6wLo9psYs)8R7_x$#Ez|?A%|?5*4^4BIkPeKlhA}N-oJ$-#J{`b-+Bj z(Wzs~vw)(SriDDs9}7AD95}qxsdQ%0K_2aI0zp@cW^^asJF!u`ajqbrVCN~_Gtwe9 z{ee>~-p#k{$d2nTQJQMFVMAAjQTIiq!!?1&kDf7ZdD_*mH|c6%(w#r+zAo7R_)a6= z{`vm>_Dk>6O;8HLDMl|6(-ZnGwDD zabwI=_aiOO>a;5sRO+66z^5{Cy4XicSA8?zqlG>J=SpAYy_uzPO8Te%9{(E~v>rPj z=nwmCSMtyA@H$lkZIhZMkKdK_cZ5IxFPgycDD2&ad-@W6pi05h)z4*}Q$iB}V?%$< diff --git a/toxygen/smileys/default/D83CDF44.png b/toxygen/smileys/default/D83CDF44.png index f1114e758890ea7585251ceb30f86c0ee6f97343..73218b90f4be09b2b90a3a32874f00191bd6ed30 100644 GIT binary patch delta 1684 zcmZqX-OD>cvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{>@Pl)UP|NlSuhAUT>wgz`e+g0i5~lJ#Q0;S^ z`4tteD=J*4L|IOWvw%oZ5P4FP?YIorNlDgIqAab|sSwmvpWfY&-ddg7Q5D@;9ot%+ zT3-@XUlIe6?W{|g)RH-=HLIsS?SFIZuWG+P%@KcFqkcDq{VenPnr!yJyYT{_iaM z-(K{$D(P22^rtwRFX@io%Y6S&tNuT|{{Qs4|FfI_b?1IcwEmjm^}nt3e{I?e2ZiT$ z(yu+$-iH}|O1AyqU;V$k;(t%Y|A{qkgY;jz)GL0Db@|(v_bSBdRe;HBAKlmfI`0DY z|F@U@Z!P`bTK2!K?0zkPJ#;kDh%XVop9R{rMU@%K+oZCKQ{W`5(FhsW+;*|lr^q^a#0hqlkY zb8*|f%R7$mSvb2l_y7O@tLHDWU|?V{DhcunW?+wAdH3tryQ_R3KhFO5&-~Ay_3J;q zdGf&YCX;LTp|{_AbdAzitdRcxUE|Xy#VI$xf4?csV_na{z`&T~?OyM~(7`I}!N9=4 zUgGKN%KnUWd?Wn(k#Y}nc%|!PsxUo-Z zZr=)~nIcb1=auN6za{_O9C0|)QEa9TWZTh8ROX(!**3fn}_Q`^K@5@LQiuW7loRDM?O z^%)YH+&hb|9(2^y6p}Oi@Jr?->nBlza&hImp=UdiPu2T}EGqxJN5VUOcY-yu{es31 z95F&OTSDrCE=BJ6)@ZYTBL9KQf;;}m8_k(`m-nop+?-=i+V7X@E}s}+xWr8=bDh?5 z=ioC9T6^EtE*5JKcy`^9$-`1)+f&b?2M<}ZgEq;{*z+UaE1=KxL{557z{Y8J-<;?! zt=J;F>x6j_lQ;t5k;dlsABUjE4Y z*GJ6@YXeF@I%d7`Tc-Sb`;tFQJTJS}TNk}w!N9=q!qdeuMB;LCLPA19N>W;4YVz}h zq!b2bKRxAS;oxB7V(vUYf%)y%FP*w|?Ap0=_YO|%Z*OdjKXCHq(W__g-aUNz^lkSE z^BqrIKX0#Kpkbk6qGO|ERPQyzQN>ElOwUfyP*c-V)l@fjildIQv9`6kx&HnI8&>RC z5;DipY15iLi#Dy=wQJe7b^CmRni?IwqTIs#_APXb^9yr~;(8=>^w_dz)2^AhvPlGF zy?^oM)w{qZZjJBXzWUFw@RZbBV6-#;)RoT5A&E7nUtiW5FFQA9=|VQq3s+{<+or$U z^OJjvX`)w2$=5HuJf)=$ISk*nTZ*2@@BPfcz@S><8c~vxSdwa$T$GwvlFDFYU}U6g zV6JOu5@KjzWo&9?VybOmU}a#yfBD>G6b-rgDVb@NxHZ)EmFZ7ZROez~@O1TaS?83{ F1OPjLCVl__ literal 1667 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy>8}C2F50iPA*0+ z7KVndMiy>vZqBY2=B@^orsftdCNRC8dBr7(dC93Tdowdrte|=w@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{}3|Ln?071X=rt1d7wW4aG#LB!y8ND5M@=9mk ze(L|=T=w~=`mfsRt*s?CO+K)1l5|M4pWW0=?)=qPg;q>sSZMO?;{o>VIzNtvuGqEf zfrwh3O5Ci&|Aj*Ip69K~O_#fPr?4qS=-@3Kj>!T)Pn4;?v-oiB+JfiSjYnT@FtRN2 zu1~d7JpG*S+p^ui+#;66Hmy6Pug_Q{V7y>va&*S=w(uEE|I=lwR(l*^yg!GdE8gwb zlIpv@{Zp4+mi^}@`R$H*V5V?jq~&ck7N$4d{K1aSO<9R@jyIcPrZX?)QVlgeyvIRq zgWG-OCvr0q8Qvp1B>@O`Cqld$atpdCQAt3U|h^ zWu0O$k!@U{BkOfsnPaZjoljOZmYQ+W-%bggdiH9`jH6pCxaBt&ZCTH_P~6F_dgl?p ziwC>~?5{jx=?U4Xa@*v?e&PB3oR_5+zOM?i@yp`ZoWx$fzFce4|EmFvp;sL^I^Qoy zVPB+utMvK5%^z=7wCz>8etr4G^V=UeToSlE?T8Z7pBX&6qc{zt+kAI@@L2r;=~)?@RFL;be=p-?hwfV&?yM6}wjd7kd}!yldW# zZ8>M#KMQoeu(PSw==vGYy|PQX>e;7B({gj#5+w|oFPj*e8?)Z#v~_h|CAQ(d*r!K3 z&o23}XWa?+BT^4`{oC`nJ-_wu!W)Oy-}YQfXh&t;ucLK6V+ C%6}37 diff --git a/toxygen/smileys/default/D83CDF45.png b/toxygen/smileys/default/D83CDF45.png index d11e09635f810b97ec8fd2d92baa31ac48781280..4b3fae0c9c10a32a47ee5ccc2091a372d1afc2ab 100644 GIT binary patch delta 1872 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 OQt@>4b6Mw<&;$SnB$&hi diff --git a/toxygen/smileys/default/D83CDF47.png b/toxygen/smileys/default/D83CDF47.png index ffe08febce892c7778a3b22bd2e6c5471d4df099..05ba90783a82a8a7c205da237959bb9742657395 100644 GIT binary patch delta 1640 zcmZqSoya>uvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{e1h;AN@b@>i^!C?;0*VjXv}^@5r-; z6Az0I{g`y~|JBd`?|ynzee7x2{$J}K{=f9$$BbM5Z+-oL;@#)gEC08>__^cJQ}11` za!&r={`~)iPycU!{k^>Y;s1kgUbyXl-+b|D&GA>-yB>w?|2F03|MgFQ&A;~~^~l@0 z^KUfw+&A5R&T{oD&7F4*w%!Zg`!wz7gUJ2Y9k*Sx-FjPd^99{?=e5>e@?Q72@#q7u z?Jsq9UD8{Atzp-`2YX^!)-;r3=9lzB|(0mBw@eb;cXdj`aLVQiErOcWV5<$J)uMO z&BwPBzy5nSvE%LAx1AH;efaca%I?3Pe=)Ig@R}U`w!e#oQ&3t(-_$nY+>fvGd3wJ- zGGt(2U`+CMcVXyYmGxj?U|=ut^mS!_#?Hr8ugZ8Pbk1%D2BzAokcg6?#Bzm#qWrYX zoK%I9%7RpdirfMQ28-UQq28P381UTrEn?X;G3BI8yW|JG(8y;$UW$EI+7O&*^7&RJ$YrGpBNOor}D>}+3S>U@3@=i%<_4&bb3_A=Frt^ z1AQOfuC3R*mdvS>Bh{UIRwkb%@Lr%)g`futi^tL8O&eu>Gu15t(U?&8gkCC zf4-1vzUPkd3-U0e8I@m5);Qi(%fyX8Di?UBa0O|4su#XxuB=m*Z}=^^m1+)snfqRP?Tr?r{boWAry6fevp(6_&flYXvMS)y zqxyvlWGqi)rOPawloPUHLF9wQZ#THs1#)$?Tx2n7N)JA$|O4rn>ToDcKp}w(;H|$!r%`ceynb_N3ZEkayJ)0KB zx}qU3y4pQGyqy0=h?>HMcW>Taem&nnFlW`i#)T6%cCPG1gX} zc6ar3@o+W<;c0r)H=Qv5!N91GbWnhrUvK~|nAavyBr(~v8;?^J^DgJ({(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{^vYPR7obmM%sv z7KVndMvkr)MsCJNhR&ugE=EqyW-z^;dBr7(dC93Tdowdrte|==@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{HfYNX4xwll?tJ5+#n!&;Pf>dY$#^ zmEO~uRAr~E5mbdV-m@3b(QM zVf&XWUccK_pI)9;miDVmJNWVf_IVF?*X;j!?|E%}(kwMLyOMB*9WJ#yKb^YCxMpId zeOG;_XI;(0r|in{?ekcg?j`KH zFX+-P!2^HoSnTRc?z8l5Qmd?;CG)V<*Ij8v)8gr!c}hm&tF|*oGfz7I%w^-m5UZ9W zoe{gN1P?uWpmQTvX3K`I8++zVtt`K_-m|EGhy8L^Q@>K#hF7zmHY`3Fw2isoqJ3F! z)7*dw!I2F6PB0#Qt2oX1oX)}ZCau4oCnxo8E;T(8zv}WKDFfkFD?tWpA;$MU?Dz>Tw&Km~dQbaB`qL*Gl$_||sgvs9HcGnhB=CrBO9NAXbkl@% z=N!*}*37Hq-tE38^J40TL#`9@J1(fD>dY$MkP==0Y=eYzh7Cvhy2sbj8a}sd3+?$( zsj@lM%eW+%`-<7CCrpvLE&99W ymTz2nCob%efE&O1qVK!*^q-53-6{N2@c_fmH3rhbTDP8q>IF|%KbLh*2~7apUu-h~ diff --git a/toxygen/smileys/default/D83CDF48.png b/toxygen/smileys/default/D83CDF48.png index dd86e85d2e0005e395f504141831ff4ab6acf5d7..3e50ffc92a80dbcce19a93131fa8383587f06a23 100644 GIT binary patch delta 1791 zcmZqVUCuW_vYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{05%wZpHb$N?+>B@A^$>q{KF>OCj5`%drj zpV;QxTzI3|v;k_x3AIy0EbiwPF%U`{$U-s(R;%ATM-n%|& z|K`$5=jyLrZMk=E((|YDpFE!Z;`yS-k7qxBGWX8yzB@O1&K+quf2Q)z?Vfx0`tRSL zc;`;<-Mjsd9!`7kV9JC0ldoQ?zkH_g^vTj&x4Z7&pYZ76)CUhHKYBFn$>SLhA5OV= zsruaM$~`-?PM<5eaii_>mD-D!s&3!uuD^P%@$9*hQ)i2h94uVDH2&=A>a*v{Z{6&; zaIx~zb<+O7tV-S zy)=5~_O$(bGY=fdK5!uK(1D^ITeB9=4WB(Bc=4R5l}n;lFOOL=FMReyP!jo;IHiq& zfx)jNsNN5h^3*e$n_5!}*A-sFPG@?WQ2LPGcYhPCV9KNF#c!K zy~n`7z+U3%>&pI&osUbETc@FPDFXvjV^v5*Nl;>Wy+T1zep+TuszOO+L8?MUZUF;> zMeo#5?@f0MIPMg8$vCpHTq?9t&Yr$Xr}&RaguM} zWb86?{cSUDoL!|Yp1k_mwcN=E_nf``)@-KXec$9|yFz4xN-6>y^Edn5swvf3^JsNm zC~rQC$U&vFnf03rC+;?n+$$+|i^Cv#$C2%KbBj5pS(!Ek&Ysx5X7|IV^JeZjobXOe z`^(lQ?_8@a#l9!Yo=*w6=Tf0CU#XiRG(i8ESl~*-V|QdX&nbJ>`Td{CqltOhR_h81 z^_I_;e`>g?!9O*0YjCDdX3wWt+V7OKPtBYsp3kR$&nTJwlalSb`f#icei$YS%>Sqkokp_!*DW-t1v z6;0@7zC;|3DSXvj5I*8v@E*i%rb@dLJ#nn|k48y@!Uy zk~AyNKCd}^p#^Lrd8O~8BQ|pG+;1l2@WLVI?A(+ChuM!Qo-7LZlvTg5p-*PP#Etv8 zZ$)pO?Whlna|D{JKrRI z``?h7vvq&CQWhJ;{+W&`Lroiyo(qiWUlaPkl?bG|?9RfTeSdf~?^PG07w z4;-$l8zfIn47_OQ(0F4MPUi>ub%8(>H9` zvT57Kt+zK6UVf$<{Z>{-LT9ba+&Qr^ckkHR?A=pavz8(2bAW5=Et6+H3=9maC9V-A zDTyViR>?)Fi6yBFMg~Skx(4RDh9)6~23E$VR;I?<1_o9J1`AgHHb>Ero1c=IR*74~ X#V^j?6BX6D7#KWV{an^LB{Ts5>`zE4 literal 1665 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy^d~Xmd57Bt}aF{ z&W47rMoyL{&W^^WrUq`N=8lHOPB6WmdBr7(dC93Tdowdrte|>b@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{&&h3JZuxq4yFZ5@oBsW(%Sjq* z9@R_~P~M~J^Vq&!|2LoE?Ye_8+ZPnSpR2q1_R8LqGfShdTgbi);WKd{ky%!ErY~z&;k#eG4|g7AaJ*vj zaHa0i>{5;wfU#7>kF^%luFq5{=ad>w-;}Y-~C<1CM3&QpywbU z8l-4`+@Wk8_wkJZ*G>M+`qld}f3o^?bHR<<3gn%{OcwLp-Yk{3EVTLc&64br=nH4L zY|<1b_9)4O>22wmSggXlxI<+5^zL0tgM!%4JQDEi^i<_+^5Fa=AlmmWCC9(Jsp{?~ znW7H@SI!A=om$}5waDk7Wue2;SYZ#MgF4)*S6$<GfAGk)^!*9xn`8 z=VX4yP|hQkY2K8gtGCX4(K&m^ap@%WK6bTC6N@^xdxi?@vjdck3CL8Mw2wEp4R!mVhb+Pgg&ebxsLQ00fM3vH$=8 diff --git a/toxygen/smileys/default/D83CDF49.png b/toxygen/smileys/default/D83CDF49.png index 45f804c9924dda3f537a0129d47b5f9d5bf6728a..1110ede39d3fb4bab9581c4d69b27cadac34b7fb 100644 GIT binary patch delta 1723 zcmZqWeat&SvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{97r9xtrxhoyvH4%$T>oHVQQ}JL=RHO7%Pqe& z6vQqze;yNlsV6&np~?T?$aQP0!si)9%r)A0W}$1leArBVmsYvJsoKshvOW{leJ5x* zx5+!U$TA=SUS$@$QfZfF1=n_Y=N37;IvLA6F#$D@Kao_ynDsCQGF`PR;^M2r|7Xnp zU)?$Nc;&yd2mTi}U-Ak0U)g!+!N&jH{r?M_{#SJT@0|X>vg6jf^Z(;={uejBOV0V< zG~s`3_y2<0N1tx~cZ>XATKm6e#{bS~{~P-LXH~t9NxJ^}^nd@@|3S(BD_Y+rXZ@dA zKmS5VbB6|21v?*Hou3cDwfQ$p4y}|GJL<{bK9?$LId{iu@m*_TMG+zlQbys;V=$ zcSlaLOr7U^;O3^!`_})@E&Olm{a?rKzn#zjoPv+LR`0sJCUTNR!}_!hXXc-}xBtqs z(%+jn;8YUi7tFvA#1{3|nde1U&WT-LM4$d~``Psj z#LD?&#`EHPy;XEMgq@j)8%J zG0EHAW%WtMbPWau2KEw9Usv{L?0j6RT+!{W#taNh4OJl#B|(Yh3I#>^X_+~x3MG{V zsR|Xj1q=)py;DPduiaMQxl`OF$LQ^J=y+k%&98g)s{hoVbEu0sp1Z$IJXvMoL^r18 z^Y7O$$^R3&+VlOb#ctn@Ud@j?q_Vr>IpJloKb#k;q8z; zXWUit%_rR5aX8LeWqMZq+9xVJ>y8U#PK*tedUSG^Mu6x~r)?`TA6loBd!)W_wR-mR zi%RZ{+`|RUo*s6*l}pq+n9N+yobg}jDdn{+{IJ2MJdfqaf1H%tFsar>#v!icX@#p& z#Wv1{4kzZXipqf##ROLTx8-VS2wEuc=ygICtMi76SGArBYuGzdLnhxyuGjV0U^?ZO z@-xOA`%h2Vz;N^41V2}wnVb!AX9{l23$?bmzTuFawcw<6d;Z_Fv^Ja|lVf$awXf!g z=|a)=`cFkuZ|t8uYr{*f?=L?cJfF7amK8s*%$;666Rz9SQa4@ADfxDX<-D2S<+N0h zs)IVKl0$#k-Lug06EcZ1ys$?tN@x~$z3>q>zVj1f6yh0~-Gq0|^4)ep^pA3^MO>PR z+xM*Qz{QVW#w?ai?BU=$kg2d)ajNc1m31`=>+_#5Z;)%e{9loI%H7xina2k;efIs1 zjkb#Al$;(S?`Cz+D8GHt4(3f)j9$Cx@-1AmzL{l0iNdy?$xjX(W_q7oEtG*OJv%G6Qcl@RW3=9l+JzX3_ zBrYc>BqSuIIWX$@t;y*zV0f~**kbpNA|=j(oja>5>Z=NsSt|C_*8Kg$T=6r3=})8M zLB)lM51E<*7#DV&_^{%|j2k;D5<40^oRWfyyt1OI+?HuXIOu3wtDEcZU$9}t4wo1v z4TZ?4u(-g;(AePU@cRm?XAdYS-@b9>&ZSv_SFhc>cvDsV{DB1*?q0rq-9GWrl9R#9 z{ARD?xU$3~`PrKKo5j!0&EB8zP$_joen^P8e&nv4pIuY4UOCKbTQ+T5-@1AKw3wZh zlZAtM7c|W5+}XNx>ek-1bN4ncW@BKpxBA^|^z%6b1A}UbYeY#(Vo9o1a#3nxNh*Vp zfsv7}fw``sNr<6=m9eRnsj;?!ft7*5f|b9`ku<2KTIJ@aWTsW()^YKRbN56=buI=5 MPgg&ebxsLQ05_x&@c;k- literal 1669 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy{674mX?M_CKhI9 zPKJiAMouQqjxH9)E*56ajt0igE-<~GdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*%Sdq-Yz_iuV#WAGf)|AQKKEZ(kZU0ZNUAmP2 z;Vq|&3~V1-LjrnOXFEh5IK;qddXaknoAwxzsD^P;O%rQoOIH%`2EcHe~c#|?pWU25fwc9%o&cP@7?SN z1^yjY*?sbolwRELUs>mL`~N4Mc4f2jgsOzlNUdDv+CKdU8f#0GqO0YxwKrY>81SgfEK&& zN)uMJm3Zc@FcJIZ+q?AEex?dXi`Bu)nZ=?*_^*YDH6%>fby%s@cF~DAuaaq3XP^4c z+OcQFL{A}x>Cz7ldpw%dz#_RIWP;GG^*Nin0`v1-dsmju7h?IIc7gpwro!iL2JOSQ zzLYbaFtjw~%CO8h)R1R$p=R-_iXZ+CE;FJcX0vW8QK^+Ku$XvcLqk4uZ;Sr7zE=gOw;~v`SMnxbi*9A1ZC6BpB)mU=TyrIybw9v#Cdtf(vJ=< zEay@-({HZ$q`l}>e6eMh&a*YIZeLh;+1FXjYl+X)D)AG+ljiTc?KyEL zOET}$3d;*>3-9q7?Z4ObbKdufZtJpFn%;A7Y;NE5TG=3RsgM~%pTqM_4_6x{sZ4b| z?qEHwwr|?Lb6<|{XAE4O6SX$gW!YYZ#SzozhqWxeQ#fnIB)$6$$yeJp?p!ai@$2r4 zqO*VJzMOkeM(x=ZhJZ$ofK<;(=Vu4!d0lNkCDB@oIdVT-^ diff --git a/toxygen/smileys/default/D83CDF4A.png b/toxygen/smileys/default/D83CDF4A.png index 7b3689adea75855607d8b962cf7ea00fcbfb48b2..974715316c22fac991c74757b6e570a68cc2e6fc 100644 GIT binary patch delta 1897 zcmbQmJAr?KWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0815-_aPl)TwHmR~&uNN(n&l@GqhB2KA zVtUac2}i3N%;0Q@n&*uY^-t@>p4N#ysuF!vCHkOD_(7TQ-6DY-8QfP>IIpH~UW#YG z6wiJ>ngx!|hB3hr0~k!UWIP_od?Ju}ohuVq07`gEFfFoY*yqi#*PCI917na3iY!!7 zJ(NaJWwl-}8~I+Ki>ZT zSkwP=9p85*eA^!Kb+gBpmG$O7w)ws9VSClc^uC$l`*M%}C))m>pZNc5&;N@Pe;q6O zu|M|v?x1fweZOpU_`Fc}T?gCUB7sjm!atUK{5?|l|N8v@m!|$ZS^xJy#?RdmKXwOy z-y8m8fAr^hYHu4EZs+oSo2mQ%P|5!@9sf_%|2t9lcVGJ7?O}g*g@51S^L@A9*VPvF zUzV7>Yh-xW!uWHJ-rtS>|F_5f-x~REqtBm>uD`Yi{MZ-yb&chx*{W~an4Xt2d~9R* zJzw|la;tx99RIJf{*M%4OX~Lw(Vx@Bf3GzB zwp9PqLj89gjE{;Lo|Z6tYGe4?&;D(q;J0a#pQebsZDzQa$?&Rz;cY#`yC#M=%?vLq z8E$7XobYG(|NnpI%(Vv?7#JK&g8V=Unt_2K?7*kbkH5X!?K7p-&tdzTw~s%*-n}I~ ziSzM2HkUs?ZCL90%YS_dV`k!#i9Gh@!*)AH4q-(@8wYb8IRVzgi)B_ZFfcGCdAqw@ zo_>Fx8Uq6Zdx@v7EBiBcJ}y0(Bovw6kz@0-qi3>LB#n5AO>_Imw|h)rJ3Coi0TZ~r3t z->$MryL(<-tSS09J6gj$-JE}Z?c}MI(%Wz68YI`3ot5P_j9vOt%9nN5-SkUx-VYXd z>g<|nl_nu_P%%w%Q{m+5lE_8Ye7|NT*cF~G-~U@|l93ln#bUcl{E=k`z4zKw&OUfg z*Z;+97W3D;qLlrfyIq+kQJ=50=g@q$$qb6jhIcuU~hRstuK_N4yPT%$4j>u(C;)sp>zxiWIl7hvH9ux7Pj~lofyHf2x z7m423-+U|aCD-@NPZQ_6ZqE9A&%~hMx`|?<=WU(I=T;qk^H)G8yY%v+u-177n?3eM zuFIafc=2R~iG7Tpw7LaPv~u3=l9^|_)1iJJOIm8vQBIbV%TAave0&{wk3oquqf2ZW z%N&vPhyJG`e+kauvgFkC^gMs$Ci|y9Zgvc(1!w%@mpnGCJtGl?5m!9HhU80PRU_c)BiVJV1GR?BLyzKl8%j|1!EQ?hoRGy!W-%xPz zk*fD}-S~|~SF<$PUe=ptUtfEFL*nCQXQQ{}-QMTi<`|aueBIsZZ|`o`C%iuB-2PNl zB0^+ppN~z|n;)H%1(%m;t=?d;V%5s*Id;Y0?(F<5u9uaSx!LH&i&rnDCE}Qvl#_)` zbA#DrLye2My|uHmrK7E@joEVmdK II;Vst0Lmm;3;+NC literal 1690 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy-rTXhGxbFCZ>i) zriO;DMoyNNrp}foPKIu77LJBa7BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*{bXO+ z`*-tp9c=M+5{hH5b*F^2JZ`^+N3o|2s3)`IxQUt39*t^E|g| z-?O!{u6*ZHyJW%rYcI!LIQQpVaCiCf;swX0KAPr6aY$D$Y%;p^%-!SKa(k8~x*~1v z3fB&Nt2R(;3I6)~%f?|SB5UH5s7kqt}ez1B}hOeUQ(`g4lU;_Za3&lk2IOAvdu zz$SNb@oT+$Re-;l%*sXHl4{N59hm@SQ$ z{sni~+q8eN^6)LVE3Vw$EOnAaF{E_LDwZ#z52iCpA6`AND(PX*0an%-b<+P{*zMbV zbe_z`Yc~YfZxQxsoME^6TB8%EfuyH^LhphXtN$pjh`;arfJM*q;GJFiGdEmNTe6oy zSMWk~T9MDspDte}^H$7rQseYa+3;FhWZS-2gUP${jdwqrlJ#VI%46daz5|Po2&`as zWa88cNX)JN(DnV1*pdYkp9i^A&Mvv*>A&Co=HnaPrYo4fPMG2pm9DwUbVzsZ9*Z_qxK1POq+km-3@JjHtBzFW$?}p!@1tt z_rDfT`s%gS&i$)!TxaeJqnzSS@5#Bj3yv5r65C+4-qTypNdN57l;#ySR?F7O$Xx$k z)_dD&>h0I9S5Du)=38;P$RYPqo?hkN`LX4d&suG2jTh(gEy#4eb>d}V?Vmq(I}Ia` zHg4)aHNQUg{DQ4Fg4d_*U%|fF{)w#fUB+)>4gb!z9P>T&;lOwHpOv$xNWE<=e4J3+ d5YNQJFe|i?$LGYEmkbOH44$rjF6*2UngD1uk4FFi diff --git a/toxygen/smileys/default/D83CDF4B.png b/toxygen/smileys/default/D83CDF4B.png index 3fa9c850c785223509332639304e0a03bf03e896..f88a92e4f0ef72427f50c133c8e8abed1b61a1df 100644 GIT binary patch delta 1885 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 diff --git a/toxygen/smileys/default/D83CDF4C.png b/toxygen/smileys/default/D83CDF4C.png index 700ff44f08ea670c25f0270a3cdfe03a817dd718..8843766054458d437f24844a7675ecc5f165fe2f 100644 GIT binary patch delta 1687 zcmaFNv!8c@WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LKl11BH`-rc%=_<#_y7A0 ze@`>~I?3?=y4vq+R*$zbJl`L4XSL~r_0m5ty8nNm_5T{fuM-TvPci(v#PIo)-17tB zZ%$^sI22QVX*R>V{bql!x&6PZ`u_^UpEC@9PBVNz&hTst!-Fl3@6YF7oW<~Hqr$s` z7GF-=emQCMdXMzc9)_cR4FCWC|H8{u!N9=aRubeF%)q|%Yj)KqfeTq*&wjVNdw_9$ zs&lqz=Fg9B%tXFD;L?9rao`2h*NfsGe*WDkwzU4ucj<@sS=Rrn-^%8&ZmOQR-yB5Fd)6B=q4;pWd z>o>m{5jssr;Oyeom*3CF*MCu-x2*Sx-sRci(LQg|u1@XV7+U;#+sTA`&)$~JK6B&! zvW>y9tN1jn_N;K&9;u$Y&x$YXW9ar(ZIW!R1s##ga$=sm^9{>yYc^v#Bc}H;xBPaD zlUpN0{*|;Jim&x5RxVeUPuo!5E&R*s;8HW$tpe@Emy5l%@|A2J_)m0g(CVlcukK!P zB_jEKU#y?y=OcUe_f{;qHLFWIWX|c#56U)e;ult~Syjf>tJx|TSA0}slhT}*KR-OX z@JQFTOhNxr@*bPSJ;(VN`eS6icJ@>_M>uf&OxJ(F?Y_qGKer`&e(xKW~F zw&#{`^%_@|2zIZ(&x)DyxM86JL65%xAACMNsCn<)ajHvQZB zF=3Ii#fvir&nJD{z}eW9Y5%E6^v3??TZu2ZzGr@#I6ri8R_%ThgM#Y@igC@`0@dEG zI{D_WfKK+kWue((RR_-~{R~Z>yIO+RoKq-MLSg-;5T>05iO~}eZ&nvS5q`iilWBFL z{IjO|`xjWV_^*6f_K??&sowLv$R;+UMXm{!!dCy*dxmRg)in_@}?n{z-h-+$;8q z9SjT%=R92;LnJOIC!{2$B|5OJ$$OXc<^+?B+`PCsCzxXH+3mY?g2|?C-ap1Y9!xcj zjt3S0FmW&*ys+Uz{Ra-kM8Uun4G}3JB|d9*xXf81;^GjcW~ZoSsA;Jh8pfxiuzrHr zHHTQhsnhxzI$C;~y4vAkjGQaNLc&6?U%7VG+{A3PKqsp*r?Ctmk6Ffgc=xJHzuB$lLFB^RY8mZUNm85kMq8kp-E znuHh{SQ(pInVM)D7+4t?r1BlziJ~DlKP5A*61N7CV5VBuiHho63=E#GelF{r5}E)> CG$j%M literal 1635 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@nRI&i&b!HV{#xT;kfuJG)~?V_{tCE6OD z4zWDwdti0@>4(y;MZf<%e!>3CiSaPYxpGf|T7zgW1uOm=n%6@G4jAl^I(9STWP;wO z1Fv#+2F+kTY+#t&J&ny_fztDXRa%QvpEG25@9g1mzMhfYrto%AMumgGHerV26Mjt# z3~vteWjx=>@V;g5nw_TeWM0d?ODtZpa_I}}%JUt6Z#U}krv?jVXj+|LIpyI&PsST} z#H-Gx?b*2d!s*X5)NXA2Kc%7WMRt~v9i!_!SFLZNE05@usBx(=@@*HtQO&W`jQtmf z(mz&>wRJD9PCi)T`84y!seT2)e|;wpE|2*2Md<5tzXM*qvC74wb#K)EuC-^(sasHa zqkPNask<82C@I@~;`#k5>BLe8*^~D#wDn$fbx~7as3sVCEztG-!hGu!sm8oTDm_P> zBrd)T{4mYrMf#yI-{mTo7daQ)Fufv|@02Pacd}Psc>T=u+Xt_3?~jqmD12sqYQ5wd z^<$fsrp7HaieNvulVQoS1-TO@#GTGj^}W@9^=ZdG!Q;{UIKq9Ko=CA=KF}?sa=tF) zjpb(zvDlvTHFf<*_&a^JH0rN*+;_NY+NmVLkg12CXuAKd;<3MZ?UV5PD+m7ee_O@2 z|FX1GOL??3JL-El`UYu8=P zlIY9AYkNLUyRh}F+bQww=1K*BmGY+D5HK>aD3Z*wo_Z*9;gLxdb?@}5>bMOU-pcX6 U_`vaEBBQUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#?t{lA+G=b z|7T!eP$@wm#UchMDqv77V35y2AlXa?2$D->kV|Da-YNq}GD!@F8l@meDxP6ijo7Xl zG4WUi2-;jG3_+_3_*WJ1FUsIvl)*hSi4}q-Ml(;0X6^`KfFlM}kYdZw;LlL+$BsA6C!Wnjo*U}y#TNG6Gap^Sl{kAb0_A-0_1)LMoYXBm#q>7ss6WF&jGMt~waIl}@#TJG$^BLY> zV0e3z;rJZZh1m=n8yODtGaR4FaAqFEnKcY&HZmMr%CMu2VQVYH)ujyk`xqW?WH>&T z;l%}pXBQZzB{Hn4VA#^au&a|{Zx6%%2@LyZF|2H2n4G|{wwB?3=?7*W~DJK zFJYLJ!0`Y7|8H^GD;O9UJWGQ7f*BajbJ?$)Tc3V~W&Zv4{n`yz?f3t>+ay~5A&s$q zIY<3Tmmk}h?`QR2SNr>I_HMCPKg}7LSXem&fB!AvW@hK&6IIT*^y}}VY^g_wf_F18 zFfb;0ySp%Su*!NcFfg!}c>21sKV#?PQf1j=pJBqlz*Jim5>XPASgue|l%JNFld4cs zS&*twkz2sPV9`4@)N$Hv1CG1JU2=|WESC!BoVfLMuU_?^x`Ig`E#{R;?Ts)}u&bHi zv%mh|BKeQmw>;h%?XBE+^hjP<>`sYj)9rb)d7kCo%iDgEr_!9)vvj4^k{2HqG2Kp^ zyXEJr8xcpt@2-+cvk*C`mNs)!;l$nMk$WZ0ZgChy?>MsiZf-HBEGyHZz}bTBYj!_M zoj0*&N8=kY>n~fIy>qRy6#E`5TkfHiuVf?OKhd>8>xB3w*O06=io2aptxTUcK zev|0S1+AUMmb`UKj(0GbO%1*3x6*K_(WUHl60TuOjBV@pSgxr!-64CT`-{YS4~g>| znH7}8-D{f!^F6pZ7XFuEb#ho4z_D$fPpJV*_wsvUOo^8rb6mCxSFdpuSi`)uuG^Av zd%uxjoWnNWC6@()J6Rm`(=24puace9o!x6^E$9?}Z-1HG+!r2vX2y4Rrr6~;WgPt? zzb8iZ+xsPD5xWl6?yC8)IV)uC`*t?IcT##svbW?;@!XwN_H7^MJX8P6t4z7~CZtWQ zS-tUGXiwW@mR=Jcll7ZIoQe~hA`U*ZoW%V^J7I?&OFdUfn1gqC<88SWlD_^k`b>LD zJpc6BKfj=Cpju?I#Kk*GApHJ>-|U|lBl-?}sXxKc`)-{^-?4)$C;r*8o5w1alXdzP zALWywowpZqy=yqM_FXBrE}!D}>yAtuvK?zHC-GEN9JGqsBx6;%b$@`ve2W4BIUARg zvaZiEn5X*GpPY7L%44aMpTsjZu)b&NEAiy(J8@s<_s07#cJr3M+I~Ft+~>-P7ry!y zo88VoI{nMrUd(MFvIZy%pN$@;k9^N*iD*;zh)I>_>amE~`9ld}`+gIBFBhn)^7E4jKH zJ#w(7bH$2``7>_p_|f9xshQa!HS@!gCsVF$`O@PPX(w*m{b}TX3yC#NLX+d1z%-GP_;OOx6jDi=gWM$-JJ}Zr!_i_x9Dr zu4tI|ucfJTvX;8Egpq`#t)x7$D3!r6B|j-u!8128JvAsbF{QHb SWU39Q%=L8jb6Mw<&;$VZI*gD2 literal 1570 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 ZSae+c;@mw^QJsr{!PC{xWt~$(69BZUFns_3 literal 1666 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy%v_n=FU#$CKhI9 zPKJiAMovx^&K8!IZf?#7#zuzDaJ`;+#U+V($*C}VGc!}Hpn8q*>a}t%N=+=uFAB-e z&w-_YfQ&;GqB{l8{Q zsc-W2I*%Tupc;)=YZaK}n@l(k&Es0t{z>!2%Ih3WEdoXyO>$gZTwRkEwAaZgP1Tqn zb~(1xbjsOV_0R9kJa?yhd+&nA!*{HWpP%`8?#zbsC&gB6Empp0S@ODZ{eiXLvM-lk zkZ1Q}_#3|O>h){0zI{KRWoUjUZ>uZcHu)F#QnvP;o0GSv=i%xDmB0Lx=T9=c8ht?Z z!IPBrcYkjBKHcu(6NBE#5>lOe-u;x!IdZ3L0pkp|cg*GuTC+WJLiFaT^?i}ewJMpB z;cUF-iN~2`n)5w(?{*RPykuqd@66Lnw2=Q@U z-jjcQ@%vCO{#_5hy_|hLv$AN<{S8ZHI!fQFXg%r7nSDa=o|E(X&8WdfF`9?PUMT{_KjXTd?) zjWgz--s0*zLCS%z^#Z%C;9Y&kr;AP z!1TiATaW@;|M-{z^ry*fmNt9`G~9pzxfTXCF6l|mag zB?s?{ap@=uN&QgP8ZPgD^V~++U7TOJST}UHZJBv2W}lVY!vc%vAw2Q!a8>^Jy) zZbRrMm-OH**P5#*+-UmsWfC9b+J7MeGY+s83p~^LCBj+idj870nNAO;to;6N^Op9x zlcFZ`DNEm(b3Xr=+^eWm6VG6m?)R&c9>|p+ySx2?y?~4VYayxIW*M)X3!C4%ADy~J zcmImNw|TZnXGVB$t0|stefYD}xr9IL0Y7vPFr2^OxNl)eS1_nn@O1TaS?83{1OS7D Bd_({M diff --git a/toxygen/smileys/default/D83CDF4F.png b/toxygen/smileys/default/D83CDF4F.png index 4f4292701e8ea90b04a2c0a3c761c93a6f959054..9dec88696106cfe31533474bf3ee1be4ebf961fb 100644 GIT binary patch delta 1843 zcmbQmdy8*^WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CwolPl#*kHik$?hSV(#UcC(Nogj9k zBZJ*`WjKm-1PNL$;L}?pn7Rd|vK~S~$dpC~m6;5p3mCK)Gx)AyNZtrS3F{dW)-%Md z0il=`3^6MhA{R4+&I6&~Sq#Cm7y_n&kk3R0pNSxgA$GfWf?VL<$p8VW{tOO{3{H&@ z9)e`6mt-)_X0Weku&ZG(N(CXi8U~Xr1~zeoqVRpzm6zfo_S??B7;*07;#G*{|$HkZ@l+^%iVul?)~3(`}fw?!xk%UreFTE z_{zWem;TSW{D0o%|MM^XpL_M+!fSt)uee#Cwee}uiC=vu{ga1qR{LSC{vtZBHvh`2%=HK^S z{yJdIr+^JVBDVid*!eeQ*WdJAf75pUP2Th)YR#vR6>mZp-3x6!r#|Vv-t3pAOWr%L z{^GmgTj+)#;q@E7h0K0wG3mZx`#Hm$?F@An7`pF>O?jp@_qExAclJx(J1%Ol!gKQ;v9}gg#Hzg3g$H4BiD5?rQXp(`PK1ghwkm4EE;T1&UyTl^Kt!M#s`5O z3ic;GN?KT3ru=`$8so5P0>`(VGiDtWFqv00pQELhVdKlIes2uLJvXfL_}YG+b;sVz zE;kx-;syQM{dJic_RdHws7-sD5aXo#`i%=mY~=sVAJ-glD7YY*skh`&qPUahw7*ZD zYHg^mXWx4GlIXk3Pnzeu8fSmLXJSxr-9#}la@$O`xuL5!efQq5=BjUZlxo$XGfG=G z$6VKPcUN~*l4U-+TE|U8&&MLHpyJyTrk`9A3%s~IL|wlx*jK%ck;i|xc|e~}!&K}#yQ}-h_%*vLN zxBrTU>dMg5*AKGfJ3P*-z8BP`_T=YP4~B)z8^h*4IdGWmwa&@16;F=-cVd*abVvw% zdG>%(bF=Y-g{KsR4H^}19JP~|^_BHaN_#owjVQbO6a8g>Bl5poZL+?4`?2oxk2M}I zel4|J{dWDM*Iyn7E7VIbeetL{W#(M2_V4+ef0@39cWpFRl=5d_U=;IoaSV~ToSeYG zdRmM)b@X=-wII($f3>5#LN(&9y_l24M79zS|4EF>)ac=Dv+pr?}cB9|^18*ScH zT(oI((dSRxB9c?My1Tr+PMh>+`5Qz}$-Mgx* ze*gNd{^gs`9Fxdi$J$d@GB2l}nPXc0Y|hK#=Vz}cT!`6FaPg6<_jKL(T`3c8R;6B>l&Jb7#dg^n_8I~Ya19?85k^B`P&>tLvDUbW?H2jZXFlDICoD}ROez~ N@O1TaS?83{1ORr*K%)Qv literal 1690 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy_P1=1I*Czhp2BuS#Sz~o> z%1aA1FH4>#flD3=xjDHmDGG{>0TBVKqrR|j$+*18^>mbYBL9-(YAs^6F;lu%9MC!D zZh6k3_u2XY=n%K4Sd%;rZ4dmg}Z> zFfOuC*!TS%%gZ18W7VEbTYji>pTirsdUu(XHC*wZ=C50vYVI+!ZrfJ24(%fz&~Cj%7u4Vp{d{`eZDO&1xQ4I32SKc~zNmS~ExB zQqsfb4k5wC%(K7jjj!t~KmC2_58+*pAC)XBPRS`L5Yl4hoWb$?&<$ZfCB;c`v$-C8 z65jA4`}m6oktwHEZd@Aw`SGsT2}<`YkHmQewD5)(+DyvvG1~5X;O4EXE$4FWA6--t zGyW{REBo;V!7SISXD{AQnZWEgkE{3LDYM88`wlbtXxGH>wm9WZh`GB_U~7Qm$(Ytx z#S33d(pg=RGSxHXvy(w*f>3nhhQA`08Ft3*%2nKwxh(Oj=>+~*7QqWm0hR)0dt&c5 zdas?9e8|N2#xkbKXD4NQaK8L{==$GW8_&b4a}0Fixoff}JPplfnDoZ%;+CuxvqP_B z`-XH+5V}@+{MhM|w8B*pe1>57$lkuq^Dz zJ=@2mKr?)S*UU&y$+S}^P72+mvR-NLwua87 zkvchhSzoF@`Q7znx1hY#@z8T0HTw4io_w%5N8w7Vb61;#Y5R3~`G?zPHrA-H{`^(X aZNTuS!&abp=YmC`+QQS-&t;ucLK6UhgN6bC diff --git a/toxygen/smileys/default/D83CDF50.png b/toxygen/smileys/default/D83CDF50.png index 436b5800cda9870bf63258a1a0473bef3f841d67..b56380fa7b4eac6d4c991dac0e2f4fcb40b07769 100644 GIT binary patch delta 1911 zcmdnRJCA>YWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0815-zUPl#*BB<8jWjLp3a^<4~A?F?nj z3?~q$o4|;S zntK^)azMsGOsww$nF}$xsvTqj#E!CNs7(d+Q2QWOLTrUt46z$xJ;Vjxi6D0{)H8s9 zv?hZ~6oYd#gOVYL3&(6c3`VXDHX#fSQ6Tp^Mlx6jGZ?xsu(CsyG^R0Jeq!+GtKX&P zuB-N_PFl#Y_Y%XoM+|q~YQO%G@$5(PrlWfEHZyEI#&Gxs!`X)n_uhxT`&sqkN7nWC z@oNtlOo=2X4sCE9K+3EWtXYU)w6fsO(&aiB|>dIXzdrrHbydHk^ zn(3L_9>;DN@4v*d=N!wztqeiw49)ek8Rl;kShhoX$u{BDdu2Bql-qn%ZOc)W^#_Dz zZ)C`7V2H0|n6Zw3{${ax8yOaF5uCk&Va`T|_W2AcH4M?E4B2fA)l(RnW-`=FWvH0M zkkiT#RRW5s|Ns9lm^H=O_Gj#TTq>4-eMD0j7?|p+LLy3n63Z0|it^Jkb5a#b zDhpB-Dsl@L7%Y0HhB{8WU2ni~x428rk%{F~;hYn>l>7&KGGO4{0MhbQ{ z6MXj9|63&g@%1gwcSd`wH)bW7XRnLtSv&J~(rlh*x%cw6pXB+OBsJ;Tr4o;e4_#Te z-4(m^Zgq09aC~WY){KV%EVE~%o;v3G?q2HGyc-$H2i6pImhZm(Ovs9h#bvQsy)s{9 z`O(uh%0CO3Z|au4$Y!5@t#pf%%t2rOi6-}4Dir1^g*IrN5Z|PwrF(6{UB#zYjQ!92 zzTDwHYi;I^Lq~SbVY9!K?7}$v*s84MS7roBzAO#zQPRFNbDsS@nXo;nNBEv-dG~~? z^gO@8C@`^mvE9Lr@+GV-7wY?%6c>bOu-vYnoBP6p&&>GF&J?>G zr;MXt%!H*fw{tGH(4yLy{W z$SEOaN$FPrYT zTzlbzNwYFlc4`$`n;eu4*s*LW_yIBhD9gHt3aQT;almD;V{oEbF`)%J9 z%{$-SvMztt=9_ie`YY7$R{w zSwJAgfz|1d(xRkC^+J<+0uCss$_hw`$S5hFW?|$hTA27yaAM%aPh4GR6b>v95tbI8 zKVim{Ig8dTI(6vKp#{@sO`A7y=G3{9XHQ?h?%=6Y3nBv|LSllV!r}rWL)R`ndu%~) zaCG?n3pcLZxpeE=y@jXG9awn%=GD8GZ(p}he6-|b@E1!73*KdZv+a_ft*O6R{OsK9 z{RbvYZakgwaM4lkX?oH7lAf-*n%%QgVu$awbyHy+Py}5U8@6rSYF+;C&{6N{>tnW-7d%NW zC@L!~E&jga*~_PIAHRNno}Xdo$!Gx<69@Jr1_lPz64!{5l*E!$tK_28#FA77BLgEN zT?2DnLz56g11n=wD-$zq0|P4q13T+0d?*@n^HVa@DsgM@Ps`afQBj?Xfx*+&&t;uc GLK6TZF;P+g literal 1722 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy)Moc7G?&HCKhI9 zPKJiAMwYJT#x5onrY43i=5B6ojxfERdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*n4+ zjCqo?kIfXUZV=!&(jyV5qT!g_!!0cAV1A%!#|6=Bp-1uu&h03AcuZC8m7lQKtQSs3 zGe30r%vgAEVfC)JlDnUW{#|u^ZI=J`&O~PJ)t{E;-K{?N{?EIe*MEFFb@SPgFZVWY z@6oS&T5pr}@Yr|xrI-Iqu*|#P>-})fnO^Sx*4Kwj7yk8W-1xcqXu?sBKYky+%`A48 zoUgO-?N@#sUAuj|YPSE`c%(+brean@>8sd%T5?aGY~(*!b}e98QusfOYA^js5rqrm zBKJ?>Il-BFP`Ecbfu zl&<^}wkVu+ey+{R7^xWZ?zLQ-1QKUNZu%xN^NPyu^ktPlzaCDjI%BmZD6eLc%9hsq ztZT0OeXJ7+uF7LFdY2fJE|$7ewp zTIt%I5qGw-_vFefZ%EC&bEU52j+&RwpACJPOA1^q=FdHN^7pY%f4_1T7To1AHLr?& zwz~f6g^Mh2zo{i=2)xu45_I5y@sl^|ZKc_+?*^&WzrXKmcp@uqP^w}mX#dShZDNnG zuI&ZaEh<{YZZbJ15@+%}Vz{4w@tyh6xt3=%d5S|Ndiyr-d93C(c322 zD7>Dga#!VoDCg;mTy+!oN2=&c#xC*bXOS!2b>VGg%A4mO_n2lzP1_hVF|1xtJyfCR zGRs5Vq|Wu`QJY++xiLN5&vZJ9We?vIe&f3|D z{QIge9o$y%`QiLMhB9K@%qw3Bl!YBVsj_cflEj5OBCGos1j>j0FDjXOy4-+uwfKf7 zODx~I`&8X^u)4qWdi(zEHD)gRqME%#vsT;a{6E zgUe?u)@#gO6ms_DEV1u@4VGNG{qWh|`#-F>BBoYG>m_{YXJBTi)`*MP6uj&u0|Ntt Mr>mdKI;Vst0J&JC;s5{u diff --git a/toxygen/smileys/default/D83CDF51.png b/toxygen/smileys/default/D83CDF51.png index 677749f19373c00008fb7f286e19d52e4fc3b764..df81f7215e2f7ff5d290454f4a95d5f8bb829940 100644 GIT binary patch literal 1999 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@Iro{n1A+GNO zEZ+E;?a~(6sxG)*nRk@}=W;oAom@^#q@Kc!hGN3R7YC^w574{sZ*n%kU}ccHUL@}e zZ<80^CeOg+6L-TW?uPeWbnm<9-gMBs>7aSZR`rst>RAiLvlfcSOk|Il$R0G1gri;B zB5<@-T@a4eEAt{F1{jFvW$Bh+TPVk|LY`};G<%&0s~j^EOcEchlgnvSBW>9#Y}LkZ z)goe3BV}G8sh+}p-{0hwpV{poqsM`!XCn2dB&z?Z$^E}++5dA#{$D@$|MY?Xo0t8c z*!q7;=l>IX|DQSdr#|;baoqp22mfC?^Z&}p{}+$`KY#fDg(LrGcm7{L|Np7I|4;7z ze_+%1;@JO_>;50#`Tyje|HpUyKf3+@(QW^aZ2Ny~`~Q^_{!gj=zkluj9ZUbOo%X*q z=l_KA|LbP`-?Hfct`-0Hto*-c>Hj@T{_kG&e{IjVhJ^py=KWvZ^?zE$|C)sVb@BiE zOa9Mm`M%0H2@A$vA?f>%HkA;E%S9SfLR`|a;;eT%6&s2~9l~MoOQvOfK z{Xe7d|J;)Q^Gp9PEcrjT@LiV2|LLXwo8$i%27Zlnxa+F-H`VihVbK5Ti2u!T|GQKE z_ow}zkp4cy^?!Z*|J;CIu`c)QwI4d`{fu(@o9yvF$NztE@c)Xi{}ti?ivm6-JO4}d z`Wfc<%u)ZAh5BbN%kM#UKO&ue#kl;Ab@?1=|0%@!i?`Ji8|`yCvTrT)-rE^|a5Vnl zWc<>_=$(zhbpz$|8Zr-cRG#Un-O^J!rY`>f|9?|P*(3%AhM^QW&aV5oT8&g-oF0(=kC4Q34D(~Nt=K86RP{@R~-M`zjrK{ z*af|Jy!&~(PnD02k(Eb6)6&60T~hTx@+t-f2F4_Bcb5km{QnIY7#P?~Jbhi+pRx0C zsd66b6X|4NV5+YQi6{w5ELSKf%1_J8NmVGREJ#(T$Sq)Cu;`r{>NxGT0mt3qE;&aw zmP>_mPTcysSFid{UBRS}7W2xa_C^>f*wsw%*_Ev5@dL%C_cBe$N z>Gr(YJkN6PS)YC#=*8IP}TIZM^WgbvK39%3kp_O-*xJCKJmA7 z0)rEKQuaI*X1TZxebaQ>{o>y0i(F9WE4Z@o)yJMYAL~CIh-A3qDD|>TgssHp)&hrj zEjy}<3S9oB-R%GCc0YGV@P6BOMf1*gx2(&bwfSb9_P!|2E~fXs_L5By84F3rskGbB?jfT_Kwc3?w;Ph{s|K&O;%x0oicUW^cgc}&7L!N z-uwj%)fm(lEnc#8+42=DSFK*NcHMdn2F(o{H*Masb=&r>J9h5cy+?~dTW9aS{Ra*n zI(+2lvEwIn^%$7+4Njgqedg@B^A|2&GBjdhU}iQpxqRj7wd*%--ZC{~W@cbvVKKL` zynW~Hz57aAPR=f_ZtgZ73~X#3o?hNQzJC4zfnJ^-9v63}hB}E_ z&S79+P%UwdC`m~yNwrEYN=+a>QWZRN6Vp?JQWH}u3s0unfMUec L)z4*}Q$iB}2k%WV literal 1731 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy+%&Xrp}gTCgv`# z=7xr@Mo#9==58(qZiX(77RCl9PB6WmdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{y(7ZyiH7Z(w(u3cIjyFT#mxG*>RVCdDk??etha&lQB`pT)RY5lEh zX1=#9PMUb8s}-j|I=R?e-}2T4>O|=-CB}GZk+OmU?g3hL*(PE9p=+z z|1GWB*S|R0ce%ghMfK`G+djVY3g{1G@#=an>6QL-dE!p@3&xU@wdOdmO77j0?YDis z?WW-5CvpLo(%n{R-PKRA2-(PMw! zurto#xg+&rme(8kS(o(um}0j$ZHS3v%Vta}v~uz`3y&1@&dy_7B*-DRNK*Rm(h{}h zyXNhY@ltEtK?v7XALKE7Db0%pX&Ye>lud&OKY_GT@E$)ZQkM#+qr=2W#AImM`{Nz_v+@P7b}#z zmM)nS7wdJ`*Qs$sWPFS23*Hrr@|L}Bj7<$U^Duk(toy_gGskz_Pwy_2vuf5qxvlTs zyLDwN9xAxZSk@|~HA5nmJvxo?tkRJn9WKKU%G(1UoUUEwy2a3PZrDsRV$vZ KelF{r5}E*0f_#Vo diff --git a/toxygen/smileys/default/D83CDF52.png b/toxygen/smileys/default/D83CDF52.png index 3069b8314e6347053f78968cae382213b39138c9..262cd7a7aa97e352f4e2fe233d7a1e75d6dc5632 100644 GIT binary patch literal 1725 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#)|%3uo6wO{j)7t}gI}AHeYTEG z14rIg3CC;$+cLSZxx9|q29DVVHsxZuI|N+{^b^*wKt#NolneIoIA$9pEmcfitn8R= z5ZI|%c1X~#QK#aFuw%AC0-Ci!Drcx|me$-Uq5(mE4LXaov?1(m z!fKm^RFTmpVbzU-Dr*Fk)(9xA7EoHkE4PGKZZ5Yp1Wn}>Lq>gULIjaY9fQLpwunWB z>Fb;;cLz?t7(eTLa?Tnj@g|1bE}?%)n=i{5+_3fiUEX?LM(MHF=Ncxqxh&lO z|Np;XtN}_fsU<;v!3-Ps@4WHk{qO5FTq|!VaXo(Y7!GM6J?KTc*dKlSrYCgbA=zH=EE7#Neh-CYXPASgue|l%JNFld4csS&*twkz2sPV9`4@H1^VL1D=}inpG@|<9}PQ ze27l}b>nfls*n?B?*6u!H#|0lMV(yq=g+^zHQn)(!zvy=JiFTW*f)dHg;^1_IO$JF_$!G>!JPV_jt*iy=mrSWz)L8m|yHob8vzZo$kF1Q7t6JvX!DQy8lr;UyiX}#uvh^yo z&U>U+{L3hgxHwJFdV>19k1`Vl{#}=45VW*@CEQct9Oc0A_kExO%M`9wh1VxT*qt|2 zyv)^7$g$|?T~U^Fek(^)s_RPoB)TqtLDzx-0e0$ zmP51r-v2i?Ru?DAP!(oULMh zV*7!Dum}DR!!IT^H64&u&vU!HQ1n4|+(gY?^1tPT6idEy?ocT*QE~T<5?C&O;%{#a z(=+3OpY~4*#KdKePTXN3b&@~2LU;Pa0K+A2QibobeArXZG3x-AwWZ$sl>P&s zuDJ*2l>BL{wzzl+cigE?2HdnwV_eBFJR5tBQx`7h4;LpmMT8Vp0~H zxWV=fER({nnQbfkCLqY}uAVL)zJ1RhrgDMnhcB<*u;Rw}8G?nm=L2lsQbL%nTyBdhI9od9g4sFsPQeMwFx^mZVxG7o{eaq%s&87#ZmrnClvv zgcurF8Jk*}m}?stSQ!}1@VUPWMMG|WN@iLmZVfL5)7cmp7$iY91m~xflqVLYGB~E> iC#5QQ<|d}62BjvZR2H60wP9djVDNPHb6Mw<&;$VV;<7CO literal 1665 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDz0L*(t`?>SCgv`# zriO;DMwSLH7Op0yhNfn&mKLr~ZZN%`dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{cip_fv)1e4B0sCzqT(UE{9nCYf_FpMCBzhPD*llv!IRsCX=_zry^!{dQZy z+T;&XlD88!{Lh(~C0XNachn^CLPDCUfwbU(Em5vKoJw&^M0Y;f>vzJ6x#{zz&)bCM zK7CC)crAB1lkqC|JtzMzteC#<)x6}g%bSFqmFg}W&J0X<6UqD>DKtUs(&wEo_r7&H z-YVAobwAH7xi5vEGEU`A)hpurU#lUzApBDFv0Xf|SLG*v|9jcdritTT>#j?gv35Uo zZqHi~I_3Iv*7aMK%k*T|D|JdIKAUiUZQaDI*i{}E=IQvJ(q6uAW^B0q?DGF#IX-fn z^OP?R{1x$Yt!zotN$vLwHXSpc@oCx3N%8lyPH`*V^>`R1F0}ZKTjTQ+-j}AB9iM;f zrGS6Sa~s9bN6M#l^(IB+T0dO=+O%hV%RZS;{O@CpGyKjLJ_vBvIC1{u`OviKHy*ZM zW}UpS;LJ0PrN(c{8s^nCWa|6y*0itlm|?lcV%3ym#+#Y$M^t_6@yj=2-csXq%kjM1 zL0^`aPU-3?EQ=28KHlge(V@t5KJd{#Q)Ur~s)Pvke|Znw)E(An3vn`gGnyLNG0obk zFvHY}!!>dShc1J5aNio^16;SP?(MbB3zq(vrWuhi?cm`bjZ-eph)GD!tfuE66_Z6?s`LE|xG_%tgavVG@WB!R$ygS#X ycW3JIuD$#EJ9LGFJGnDt$O2O0B&t;ucLK6Ta5p=x( diff --git a/toxygen/smileys/default/D83CDF53.png b/toxygen/smileys/default/D83CDF53.png index eeb27c83d0958bdcfca33a61659aede52905d77e..54381310154c6f340035faf7bbe8fd2f456fa7ae 100644 GIT binary patch delta 1951 zcmZ3+ca(pEWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081Jm>XpAgso|NkH4=03{9bDW3gG!M@O zUf$b$e0TZy?(y+G5D<7MAaGhs>pmaf9X`Gre0*1Vc`x$v*0-~=HnXy}v9juPGHA9l zD7P^vwK8aRfY{;13|D!1uk-QU1nbf7X3*_o&}?P6&Bv$P#h}v(Ri@m=08wMm#Bhy| zPpgCBGA}R0gjz3#^Sr#x)=cMkd1te+pXA}0#>RG(hi4KS+W~IwJ~p;J_1xTDY;3)3 zY}>iGI@#EI*w{94amg?-HM6qzu(7S;;9Snm&VUMBn3=O#SgKiBJK5MKv$4%&XP?W? zK9h|tpM`~ifx%%Cvt~O(=@z?5Su!Eb3~r4K^V+nYXlW_6GDIvDlx$`YZ(^t|l|5r+ z@+u`cX>Gl9)GE%^mFBlBEzcVmoYm2J9Txh(vHp|4pK=>R;!4%P#SCsUIHt#ly_=%@ z)8F@hR>t@J1-}amQ&;OoEES%Us(8oL^pu}&N)E$wEsg(`75@YL{^w@@U%u>#w)S%` zugC6gKSM)a*w}nDGx@Hm`rpF*e{%Bw#>TS(0&{{`&uD4Y&rel&=HmU) zKSBN<=F9xh)BYde|G%W@oTTJUIhiMA{9kM=zJ>(9jf(icdd2^`x?gTCe;n+-Zi~63 zp>b7M=$VAb|KfuGE-wFLBL8G({;w$epO^d7*XzHD(SJ+xe^KF&LxQ*Q@;(w3`K+S! z$<+AsDx05C;XgxzKGrYM`K+Px-_Y>CyX$LjuMON>H&PhxiwM4xk$R)7@Y7K5y@tyF zqWu5L${%&L-_AB%#=(Bk%;J)iIS!iG)o$z2td zS8FyEO})C>AZPYhrL*bB9aHD01{@>*6!>>tmZ4tI()yKfPla=o1IOR@feI{BxLOrnpA2Dl-ca!}S4$zsqN8_3 zS)K=>(;NDJ7;sZ+x%D#&GLKy-`H4P zoG6pywxjr5&50cgL@(EWD4Kp_|KwYVr)A%remZ%6T+2Y`pI+i!|Z3O;mkWbNR+|y?Jf3S$NHDGU61kH9b4Tw6WoEwu(cIjJ|k+)p$nNV92+{=w@0eI`6anSrN~6Z-8)KPx%`R0y){hFj0=9+r&x5q z3lnjF^e98cKIh@q7&oV*6QnYa>|9-MC_BZFSREQr#a1)<-rk?|iTiGJ*UXwgDIW%g zdPZqa7sn8Z%Zvhy@Bc9{FfuW-h_H%^iLWvWmR=eZC!nRLt~Sn0E3!QTysK8Yg>CqXIFPmuQ3C+NnihjiIXNz znL2IyjG41c8Mw`6&zU=K{(^<2i|Q9IS!&L}ZLw_mij}KYuUWfp{f3R3EE%}1Hg8$J zb=&qGJ9q8gvv;301A~q2{sRXO9bR+f=&|D`PTDar2--WGI(_Esx$_q;Ub=k6k%2*| zUf9XmH?(ag;kei>9nO2ES0~bWc WOTl!uiHho63=E#GelF{r5}E)SKlxz* literal 1830 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 N5}vMpF6*2UngC?XdKv%# diff --git a/toxygen/smileys/default/D83CDF55.png b/toxygen/smileys/default/D83CDF55.png index 5cb9566817d54fcf4de198559a3937b921b5ab15..d3a43de2022e5dd1c1595813673329cf9266b734 100644 GIT binary patch delta 1579 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 Lu6{1-oD!MQUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@Irs4pf5Z8;l zW?kAn`^w%0SN1Ksx_|N2{Y$PMSbFWivTFyIUpu(s`k@uq53RhgecHtx(=Y9wdwI{i z%X{Zv+&Saay2exMn@_B+IkBeh!uBc0SCk!FS#fSl-}$W*k1j1dvb^~0#?Et_yALnQ zIlMUU^oEu*8`};pNI$e7^c%orA!G0e~gBOQhrU^LT&5rXE}a2{J)a%^SA z@l};4R@cn7;ht^7y~tPQ_=>XWrp##Q(88>vOA8OoPd&6Sivb7NF+Fzw+@#&J5+U** zU!S{oZq@sjdoS*o{_5e**AI5QdA#HP+12kR+JBzpbYnxy-Z-`QQ$7E$4gJ3+{8^jL z)^LRd^n<~54rKTSm9lx|%fA4qvInnM)rot{4)+><`cN65F7aG4T)PGa!@~+C{ zON-gNdgBw}Lc82JZzQQc%GQ5fVf(7Y^jU$<^>~>jrVJ+ngl}i)-A`70T4eqx&-hA| zyARY{N^C<#MCh6J+w^Yk~rw)~EI_BQ|HvVU$F z?=C*R&&M+5xb)mNR^J|9yXF4P*XnyuLC5lUam?qhyjDK`?P3b!=2LG4S$Ks-B_yS# zHve43$$CHi@oEMJ2F4_Bcb5x%*UryjU|?V`@$_|Nf5y(orOFm$$SJ|Vz|>F`5>XPA zSgue|l%JNFld4csS&*twkz2sPV9`4@)c4wL1CBezU2=|WESC!BoVfLMuU_>ZJ`4MG zkGI7?7Wa1QXk<+J_46-#ZFl@-x1#AU&vxsom2G}?>gbJCJ4>-r})OxoaNx>gCDXJSp2P7OoeL$+{fKU1(u$>l#!k{E)G}ONS}ManA(ZMM24Y zH)Vs))jsPite5`ivDH*Ge1*mI%ZK|vNW?iT-?ZwMrpdB9Js)Oi&r{MqG;^N)J(;LI zOONwCajmk^_m_CTk@3VqT=Rg!px-(lVu%b!-0LtO!}8 zZ1LiZ!ShKUH*hW#xm@?5Nc6`3=39v`xxQz9I(UB7#I5_xd3o-b`gB~^xa~2y?CP2~ ze+6{1%Py}n72cbWGBM`rjORM@+WZdnnb>H=rLJswbckVN)8WnP;wQonG#EIAN>2!p z`7f5saQO7$*=;i)7992}Vcg}t=c3#MJ@*8!CmJ4|leQ?Vt5f)H{)sW7@4%P(6AZnt z)_L@~J-)(GcPo}{?z)yH@2q8$o~$|Ymf>|qP+sZz=r@ZvckY+sR47r{mZmQGVZ#9% zuT3&qm0R}*IJol(Oi8ceJz4&T_uQ5zDgFl*36*W^^H#5Tb?U(iZ|7gB>t8B=62Dxx z#{5^-!MU&A7Eb?M6Q@#AdwI^)Z_yuB|Nam;;kL2#qvNWHb9vgo=X3sLI&!yb_MdR& zMg|5(0Z$jl5Q)pl2?-{KrpZZZiK)rYA3S;VEbYNV)AgGkJQi>0G>nJ}y?%u&>h_IW zjz=WkynZDuB`Y&~mh9XEuO#Ng#@vmHx_!&q>aMMX#Lpj+EiJZtckQaK`u$72;`awG zBZH2PUw(5;DoyqtdK%V+V&i2~C(W|&r=yHojDTqkCWMLDmqz zm0XmXSdz+MWME{ZYhbQxXcA&*U}bD-WooExU|?lnU~cwG4n;$5eoAIqC2kFF*+GjK x7#Ji$HU#IVm6RtIr7}3C1hYJ7z literal 1772 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy+&>(X09gYCKhI9 zPKJiAMiz!hJO#)d{NhA_RJdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*+8a< z^>qqd$5u^nnS3;5a_`A|XVq@*Eq{M!=EpO4HSB(T*qwgvPVuup=ftIVR7&+2GVGcE z#4zZb;fD7Iemt^mREeJA#?R8kzhjymlSW}iXnJ^zcK!6ANhR`LKO3*OUH>Gz(nBgE z;z>oU@wd|4;K@rv?%9N;Iz9RK?)NmNKTaJ2b$)MuKHkp79hbOByJtt(y*5!<`DpJ+ zoF4uP7biHqe7N?r)t)ag&v%`lUb}0bwdeE6m(%<5s^pEoJ-GF8uhbulRGVWK{>M@( zQ=grRT=R30WT|gq*AYX@DF-*F-!w6Nne)QZW_!fu1#WNtO`Xpaby@u1CZ~@_)^D=d zzkjo7YlyS1f@p+FSAb5}LG{cf@wdL8DE@qE?)~&8lN0^gnh60~Ha`z z%T~AvxBYwut7t$xW(v`v+9wc(~zi<@_;CtD3;EoSx@Jme_ z`Hj7A9bNggk`A0bzU1S{g|+4pQ*vdt#+Fa}r03n+`m&JY=*yggbyE*tzUV1pYJKtR zWB&j9R>m&vaLi}D#rNeNr|V*Oqhh{0t&vkIP182(Pn=!ZdN7AE>~?y@-SP{||L6JZUEIkp00i_ I>zopr01+UwoB#j- diff --git a/toxygen/smileys/default/D83CDF57.png b/toxygen/smileys/default/D83CDF57.png index d21ea0d97d5f7ac29c6692aa8c8edc1dc22c77e3..fafc625fa7c28b802a9a7d72cba31d58f45c7c20 100644 GIT binary patch delta 1567 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 I>zopr07uVF*#H0l diff --git a/toxygen/smileys/default/D83CDF58.png b/toxygen/smileys/default/D83CDF58.png index 948a08dc302f9088470d4eaa035fc9b0fa516628..5e40d1b731040d86c9cdfd75e96b46f336399f90 100644 GIT binary patch delta 1245 zcmaFE*2y(NvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{ct(`k)F9PHTk%#0(PgRn^P*H6?F8yetOVx9Xv~k-DmD|yH5Y1i z6>oJD&oyUAP+(5eWlPrNNZ01b(&Nm~bO;BeKlw%H*VR9B@b{1nW7h(vMWeSmF z@)Tk)<7J3eW{Fm2wiIAsU|=v8V2V^^HWy$pP(gC zm0r)w9ZnaU94|H5Txqkt(&c!$+ht#g(KIjNMJ6}_h+jd%v0W%tyI4+Uu{pO{LU1q-Dz?=6J&R1D(y{^KbWt2s6=~tl*}xD{(af< zhccuqyoK$pMV*}_Jv?Py+@$U8#jVYS%e+O8YXI)9L20{MU6}Z z@?7|LM)OT{WS-^BnqYJSG_SOAk zY;-)RxbTn?56_~c#D{_t11}nGEd0pX`ACRolIKaym6VG;`wB|xAw2Fn%^v&)K^Nfk8%=tWC*}6Bm<@`_Hjix|X?Q<+^$QUK*#LpS!={!>7#LJbTq8~i1Y7st2@(1C?(0_D&7VJ1l$8l6 z*-XlCQ4r#F@^InUqT(wMa^Z4-;x3Vw0-(-vd}n#z_WpNSW1CHRF@4I5e{SPw);^T)yMtlX(mm1@-=uH9 zsGs}#zTTz(&o*CvpzQD=-28sco0Dgy7w@ZA>{BjzWTzT&!t3#i#IF7FPs;oFHotz? z-n`z9?aZok*1CLg_kTV3v)R9<>fz4xW9jp(njdaxzQBKhkEQCb-6Kcwy?qjqtM5l1 z)2qylzxPkBAiH6DoaCO;Z!bTlpPw(^z3%+V=RDiA>JPebL~A=-PY6p?{vyDCJI3<; zslNgL_gGIa-@_j-_icOC!Rh)wX6}2ZHLO;B<>mB6smnWJ<--tGlQiE54*9ypKMSMw zRTPHYd2+hB#QNLWC0ln{#5!cMt``lc;aSY}YQ57;@e8M%!nwBTT*$AK*m`$~UUTll zt$$ZPF62L_(Q>5zswDT~mu8C+VZqE*jTW9m%6eoQc(4t z>6&W~{>*s9w&!j0b5R#-2IcZDSH?)|j#E1_!%mqWb9M@6i@!hlM!m0vi^hY?X47_D zXLFmj!{kxLZyn_*q2ub@OIqiB@_BS^{biBVX^bTsRIU~~zg_=kN%&;BxC4R3ZIcej z+>D$mRR1qjA^4rx`QE!T)&9K8|E<~OG&#CYr+CT2r7o>)^P;3Im>#JlO?^7!oZkk2 z)5e2+=IaB#&wdv7c2D`^10QTp)_*v_W$@`v1d|c(C#Oqc6>}ylo2c(Lz7hH8-v7MD z_-m6b!hh5*KBzQx$DZk0|1JpluN3$pr_;I0W46xXylGON$Ii;Df4kMsX?Ogk#`>n6 zIeEWV{d~DEZ93m{t5u#B4~>_sjAGUi>b|vM=BYc?b5-}<`29Wo*Zo_sl8yR4cwR}} xtmSwn#pJ=J`$uaw9L&FD6I)~QU!IYTVRrax!}e~DRt5$J22WQ%mvv4FO#pN|m!ALt diff --git a/toxygen/smileys/default/D83CDF59.png b/toxygen/smileys/default/D83CDF59.png index 61ab47ace77ee9aae67626dc9cce098c6177dd75..9c72e3c73bda724c45f1ef953618fba9b63a422f 100644 GIT binary patch delta 1186 zcmZ3;{)%&gqy#eq1A~Sxe=q|B153K2uOkD)#(wTUiL4XN^y--s1AIbU85kI+qZAeojZ5! z+O>}#KYsf3>Fd|8=g*&i{rdIy@87?D`}XC_w@;rw-@AA3)2A>0|NsB}``?!@-_M*m zbN%}D$B&+zK67mGlKv%2`xY(ks-HWrdG5T9`3t&s>|FQq<*TPppKag1{m`L9w{PEm z`Qq)&SzYFqTy_q8UcO@Xjsmt0{Q5?W#U&9RKYV=j=*jWp$6vjAJ9S!zz9FlfgP@C> zn2nu~y_2wk5lenS(49MXZ{50m^ytycm#;j1@?zqYRznkR8+&0}dr^Bw2@4xR4Q-~3 z`mBJPH*a6La;3AgbN~MRH?H66>aH^|<~FtDx3Cm4w-VCU<&u_Vh>G<%cI^1JZQG_y znR4;M<%+5z89637IYwhM0UcdVej$c{Ag6u%53F9jX8yc|WtBy|f(+dJ3@YjzGV;v4 z0t`+r7SpHC2zusc&A`AARubd~N{S315Dy}-VCjrf4fYqotKVoPZ{-%fkluGoDSaus z-uHI)BL<1f`mWD-=&)_`KY9KOAFNqISKfbo@8Odtdy-k6r6~MmU|?WO@^*JAcI31@ zH&Icdp1s7=*OmPlJ0F*dwXjo(2Ll5mzo(01h{WaO1P10xR##OwwKTD?Fpl_0RUI3r zslBp#3&bQvWf!kIc<|(*LkEtYJ-k{{T715M=(?q6kDobtKwn|S;@LB%%$YQ6%Ch6i zDrXdww3f}6n?G^pl&O;G2^!~)C@5r2nLBy*^!c+RC&bie-8bZhBX-d^9cVLaD#*XF*Jof~`Cc5m%pVYOQZH{6!^&$3B5F8W|K6$mEUpu?rQ+nJ)YIW>dI}N>VzySDy;b_!Jm=1q%F}z+-ku||WX|^Y zH+E)!pJ!YB{jGh$g`@RT9dDied*Pw7d%vuG-J>U({Qhigl9BrP^6~cn8xAaZFd@K_ zjp5O@sCiowOfnc47*tDKBT7;dOH!?pi&7IyQW=a4jEr;*%ykV-LJSS8j7_af%(V>+ otPBih_}t%xq9HdwB{QuORf7z&j+cVzY@q1&boFyt=akR{0K0hg2mk;8 delta 875 zcmaFGxsZK=q%0c)1B3kM|A`C?44efXk;M!Q+`=Ht$S`Y;#6&~AdS-P`7srr_TP>6P zJ(i?0w9o(k_ukC>w0(uag<0;2N0u#ByprqMts$W8vSLC)qN5YP;zh%j8+nc$7c8yB zWS1z3owT~qHR-|9W~EM}*;O+N5+sga$uF7v{`~KKveWv!&;LDsm|yN-@Qj%=UuueV zzgHFN-1hnN=Xjapn(NoC+qAoWclTop4n>9l|C_rnU%ni@ckkZwmVM4!qy83ER{mUP z_^fg3wEwA@nJ<@nEltYaI?LADdh^$+yYJq-F=1n8j}P*i=(qf%4gdMi1!7`i67urP zSFh$y=GnY?vwUA~@7?z=UMS4SJQtB(gu&&fk82&++W_`;NK_T)KDf+=&w>mV0H}DlAp$Qfg_jFxwI( zxbC$O+k&k}0+||}W!mSMNJ%6!ym<3wk8-oZ2@b^zw**=RH(vBm@7y>^VS|O=Bn6W# zOwIyQTg+M#8?Mbt+dOmP*Q&qmXV3ad^$O4Oo&UhyC~$Yd`(o2_p@ r`}NO%U%ouq9iu1y{_We3?-_n?seLtjVf=0e1_lOCS3j3^P6CWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LK1LpAgso|Nk>EFs#_PZR?(chfkb2 zclp}Ma~HSlIk;luw&m+LFI%^9!;XDB4;)>-e)Ib6dw1+Vx@`5@`W5RoEnmO+^zl90 z_wC=gcmMg*ht8iqv}^P7U7MG!m_KpF{0R%DwauGYw`^|ryh(NQCe<&W+uK^5HE&Ae zyeUoXwK)thz`@3v9PQgulRK-wcFBy^S^c$bHM#6;Ab-G=V5957g17HKymIsI&!4~k z{P}b5$@8nX?w-DM_5S0hpTB;4_4fVa=dZs1`0?k@-xqJ+KY#u9?3EkW?>+qR>GPdO zPrrQo{^`rtPoKYh{`&pog{$|UzP$P1@z-zPpS^nXZhfke* z{`TYDC(jR^JbUfdo#Us^tlPNp)vIUUzkYiE;r*4XSB@MxymQyiwd>ceS-)n>*3DbD zZ9RDS;QROQPaW9w_T|GrzrO$d`}^O&zyJUL`~Uy{_Vx2FpWgf8@r^GZU;g_3VcUlJ z`?fB*smSB+W>3{16EArU1(iRB6fMfqu&IjIUIl?AB^6}bfr z3>LjpLjyO>HsJa5U9*Zs`Q#)^KF%NOjsL#+xLbRYjoibn>l4))RI-;|y*1^&ef?DV zKc%l--&-wm|9bLj{=Gv>c2#`-P=C`r)^%Iq?z?RqZ#GNo>TbEY>gu%>%R1iLiND^+ zB4*ZmbX!`Vwa}`hi5+}*m%OswrFT)5yH<6Ef6&i&<@cRlYP&d?K5H(0vB^5_VdP17 zsT+AQiR(p!qPK6os-%R%=1?|<)iuaVqV zpYot-!i1__PcF_|z;(pL(Aa*fn(Ew~_3aN=?OHHb@aJS*0oCX*8Haf#&%Um5`LO>T z!wL-(19>AM*<8lPf9FFNu=H>>3(TLqQj$mU(aY~E8#V|kObdN`L@wR2Dfj4#`&oVm z-X{C7>MPt1-*P!HIF)&Uc)3c<{9tR9>l@tW&gH1r48OVG&rg+Ymh+i}T^HWC$8ay1 z@$=;A(B%JlS?#*vJI+txuM>NGOG5*w#~1zyOD$SAQq z;qGo}YyJJ3-POg-^>_!Du|Y@u(PKy1)m7BgRF^MVwzS*WASER&_4$)$PnU~{h>508 zGcfR&GHq&jNLc9gE7zu8pYCO3;9+WFX1aaLwyoPt%uUTaj15j~DK9B2ZFUGxckkz6 zZeA0UAOF9lbF!M>j63G8esUS>n;u=syqtb!j%n?!5LZ^W1q_QPeR^YA{QT@(lUtDx_0sEc8R;6B>l&Jb7#dg^n_8KgY8x0>85kU{JjaWoAvZrIGp!O; bg8;IQO)6y@Cn~CQF)(<#`njxgN@xNA{Eu0B literal 1722 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy-v;sMiwT<&MrnS zE{2A#Mvg|Vj!s6V#zwBru8tOFt}wlxdBr7(dC93Tdowdrte|?G@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{}93HHtw4wSH+?4x$_jLOR` zhM~^R-ccbjFHTKu;##zzdlg5Qbm7B8tvf``yS|8Pyf`IpH$j6{w5B2ILe~NfI|mlm zwa1oCKBL@UX}_-gSlH3E;`?S=%YUEqbKmE=rv;oo*mwUmFMRuK?#DR_Z_alg%5r?* zJ@5I}S*!l-OP9FrArW)KcHi0Gzil=L9K3pRw$1tHdh27tWXogZlMg$4s0q)=G)bJP z=5xDk)vNU(zIXjR_x(%j;8-+c&*ulfp68c7`fc}8N^PQOZ_VtfovF7?#3!)*otLZi z6W8M%+7GV!l0DD?4@JC%@aDFBXi{pae%e|z?}w)zt&;I8ep%$fG$W0_X@_qkPi|+n_e@-gkGmrrtks;=`{?4)HV3r)^UETI79kfBmmNsjq_GzH@ywmH*2c zsh@B1n>_OR+~j*R_8t9M+tudkyioD{{@o^jTC2YVH!w1&NX!fj2{iZ$s!cpy{an^L HB{Ts5utT3} diff --git a/toxygen/smileys/default/D83CDF5B.png b/toxygen/smileys/default/D83CDF5B.png index 0a79679a73aa5bfaec5104170afec7673a355e83..a6808e1a6e827c7e28362515c8c3012f5d248884 100644 GIT binary patch delta 1702 zcmZ3(dyaR4WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LKY-DC)0y&a}rM5Wj)WI#M4s5P0 zNMdDSVE_XuapCm%kZC=2dp0jUdt}Gd?z)uNKrvx{uq1Ax9*5MkSFgW*`~Kk!dU9#su)%zvlc`%ObC;HaADu~&u<>w zzVP+Ki}%lOZR^XJ5GKS{m$c}N<1*V@Ih zczGmW=g0W-FVm;rpE&195$E>3yy_Auk;=Wtmi_tnbM>E&`1;9VKh7PV9Uk5DHtm(u z)e~3lgx%gL{PuC#ZeFDu;gdJ4*zEOO>+WiUn%QezXXW>`p4xYEU6O?01j+7>iVuEM z-`}hFoEDK`dmv|_)cdmScP*cJDJV#96s-JUrXKfh=1F&n8+jX@;woT4rRxz_u=?%$2ei)=Ee30JItrBw#@iHhc(Dy z^#zVirZb}s3fRp1yiG*FOy-DN*yJ0@`b`NhGrmTjl1Rfc!o_3`4-c1V$yESD=e0qItzpkWi%b*?mj1}$a(V6xsO*C zIBI1sx{-9@tL0?wC*BXHZoHn8fcX8mK4c6a_tc0}qR!;Pr<8SK6o;rtd>a277 zj_Q~xu3g{DGNDKztXN(0!-uBl6H;t7DmU*B2(T->P*fkjC4BR2-W-?0{%z?uKZ@xn z3e9t1sL%1lZt9D&AIz*<&RDKYPl-(4NE^d zUV7m-&G~b5&_4#_wOxBJDfMbIFfcswba4!kxSX7zAZuV`WVCt1rj5l6l05>9etPNR z;pXN1d2(@fYHDn5b^iMK^78Ta{~Hb{FgmKSEqpNH!iM?}9Vb@2m~mrAMWQ0x#FisV zo;;ayWy_bIGi%<+1hQ?sv*%CKp+%1-UE1`i>y%a@Tj#4;w|4z%JGSiEv}@bG@p-bX zJh$%MynFlpaSE&Z&NJE?Jj>Co?A8}a(pm)otP~d51W2! zt;*u!>3JnJE9#cju3c93RlnHU+zm0XmXSdz+MWME{ZYhbQxXcA&*U}bD-Wn!*v tU|?lnFvI8mE))&9`6-!cl_(knQFOc%OlO;@sLsW};OXk;vd$@?2>`7{5m5jD literal 1708 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 OTFBGY&t;ucLK6T0cDt|u diff --git a/toxygen/smileys/default/D83CDF5D.png b/toxygen/smileys/default/D83CDF5D.png index f76f82acd9395696a8e24f85f1716618af9ce891..7b67c2f1ad28f29cd16e113870e076b13a02bda5 100644 GIT binary patch delta 1812 zcmeysyMu3nWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CvI8Pl)UP|NnbiG8q^c9&a=_e{{jF z?F;T5&fk6L__<40FI~HN=HitjC(o|hyrZ#i>fsH!clSlVI^a`Z-rQN&J*lC0YHi1a z(#H1k=FS&K{9f*|dvz@E7@Azf*twb_5b`_tKo9U?48 z9C%KyjJ&Xa+UY}^-re2!>PXJ>J>e%LnEzSG{&!UQaXD_eIP15I(f4&kPD-*oIgt7C zbldw^cRoHn@&3w`mj{#UFD5EHR^|KO?f9YDdbb?MqZ+ekT0*Dngzm%n{@_Tk3D=X)bw z?2CGRqWt5nrJrA2e*WO_}@`G9)sdTFMj;}`{QV>m4LESOm1mGf~PJwBd55k4%gScH*Ya8Ffb;0 zySp%Su*!NcFfg!}c>21sKV#?PQsvfZC|z35z`#^r6%tVrlvu7%P?VpRnUkteQdy9y zP?1}}z+llkHPn0490Q&^zeOyYCZ?Q}X_x$<7aIBO$4jx#iaefiZBho4)?8bjdg}ap z`%~usc70t`E_-qDuP3j}^Am$&_f-CPGkcxV?HzaXoLN5I?42ZbE9Yufc1ZAzviaR# zBbcOT*B?uYHa;%Q7rN2Yr9Cg`t6c2#7k$0^gwCk1togn>f8iBTB?YO^2Q5l&it2yv zdGeNRlXUrw?Di|ya&xzGG)qrZ%H6c`R9D3+KdunheG`1MZtZA$!?$utT*`x;_xGNd zmF6v_XTZ5~xwqU)&P5Hr!p5J2vx0(7eyNgJw~G6cB5^WT-}C2A?reOLVEjVQ z)w5e>KX}=8Yr0Q@k^dN@viNeL_Pa*!=De! zV^6>ISYffmO)7JpmVf&)3+7c{wter7;#qiRy(5!{rO39YDwZEUG?{8f^83X7(O1UkgX0dAqW@UVSdU)LGGxG9^UB$sx)uOfSmL zEzFHkXTgdkGScGnC(M{KXVIc1Cr&I_HEq_kc@t+&ojZB<^!NqKPA!Ow2#E=b3X2Pj z42`W1j$VFnLFD}lH?G{dbnDu^i#M;{U3_%G-P_mg6CW)(8NAGIwq5cw9a*-!H;bR0 zo4r5b;i9A7)AXY4qWc~1Cw%Lg%DX!4OvK)_x4Uk(x$zuRTbB0PJm=1q%G1|!)1HYP z*xO;Z{>i(UncVJXo)^K1!!u|;l7F=LZ zU}vaU;^C8{lv&8Yz@S><8c~vxSdwa$T$GwvlFDFYU}U6gV6JOu5@KjzWo&9?YOHNw tU}a#iVC8Rf6b-rgDVb@NxHVk-;@r)^z#utMRhf%{!PC{xWt~$(69CrlVb=fv literal 1776 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy^dz)mZk=-<}OAq zE{2A#Mvf+yCKhfMZf*u9&W5gzMlijedBr7(dC93Tdowdrte|?$@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{*1y2{pkcwL+lQT1f0|k!%-~D^j>?N6- zGS{xnO=lM4)t@|UUwbtMK%&tED`E{=dwQ5|6Z6+a@O0wyIY&? zUOc#IPGUxsPuL~HX{qZc)`m*|^LoG})+6yHar-*yXkF7%x6KtiX-TgaB-}l(@b37b zNA_;o2Tgix}7sH*7%0+$j(=gd{=%kv$e*5k#kEInhNneoIBA@3F`BkW7&Mub1X97=nW4-n~ zp1!X3?vErVh7~GXuHQZAv8Cy?4WsXeMX$ev?#K$ha7t&#G}eW;Sqr}K*v0uQ*uGMJ z+gD$KU#})ijg9%Za`Rt}llq_5Z<=lRVj0`p)81dg%ax;oIMhx$YMyrwk78Anabi{4 zHCt8WY4?Uy&j)`uC%$+lbIri#(9O?@!iN)b^(>zK{P3_?XvX{Inx&%6EPNli^A%>O z%~-(R5~#4F_Ccli3!YgqCKe@b8*&~QEVt%22(?g-`|^|_H+-H_*ljj_+mxky*=rZO zh~!6cN{e!{PW7k@4J=>1tgW-nTRkNAeo^6_C8v@uK5qQ=lI_ZmWC<@Bp2B}W!rOSx zd9HporCId!L@!~RmHThUZGHa0X`S`Ir^U5@C;yJzBPry%zW+Om6#rM1WI1L!i=Ib5bX2I+Ns)IaT L{an^LB{Ts5y2!L5 diff --git a/toxygen/smileys/default/D83CDF5E.png b/toxygen/smileys/default/D83CDF5E.png index 281ddda1950821d13c3047619ab7cbd33c91f77d..9a9950159e703e4fa43e1dae494c84a076110c0e 100644 GIT binary patch delta 1714 zcmbQndy991WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LOMupAgso|Nno!JaKEh()MJPZOO`8 z;uJSU$!`vq+Z-yhIaqpgh%|^1F1I;Sc1x_nrYQMsk+SuhBjvZmD{YQZ*b=LFt}p0N zss8P?*;f}vp6j+b+H8HI*?e!Q&i)F$UD+CY3$=G zj<%XzTM&M#-sE7l;h}ot~QzSenM^9G!D{x@YoqPv&SJNz>RB zr+9r{+KaQZKHk~(uKwJD$E&k%bvj>bu(;l2d85VpM!U_84x7`J#&55$`ttbDhdW!} z-P-u_aNFahk@u#%J(%nJcyaL4rJ)bz2YkG{{quu8Z;o}m-k$SvW75+VArBXLJ(%Zt zy4m9Ol_l?Pt^fRJ|EGt0Kit~*_CUph`L1_nTHl}V^l*vSr+Yi!-`@QB!S0va>oe}m zw7fgp=Ft+52aA1Qotypn(ZLVbmOtGXdw-t&lU06q7y3NjTmSCHnveH(e0sR=%ftO| zPV_up8FXis^~1$(PuB(An(O&sYtfsl%idgF{&H{G{e`ag=GZ=2>GOO;$h8?R54IG& zyRr7mmM!me7-T{>H44x{nnRf`@Xv{ z|HIvFPgncjneX{%N9p?;YhGTQ|6q~Jqt*Ubr#QbpKmWs>EiblZ+?eWczSI8y|No0G z9&2M@U@#~N@&hFy1_qhhz0d#tcztq`P0g<@eC3_LZ~VIaY~tpS1{q(;1F26}i?Jo% zeHF$ObbYm0$XUMC^$ZLQj7i?^E({&4vK|Z!4D2PIzOL-g*!j3rnL=aEGBPkQ)mDW> zlmsP~D-;yvr)B1(DwI?fq$*V87BDba^iBIOraC? zQzp+cTc^0&S!<=ZWW{@b{Y}g2Z%%6C_Hlaka+-TplSabjBWM0hnKdQEXs=w`?pIAE zZckLB4|r|sv1qV)dFHoh=YyT^7&#+l!Kq9O#Pcm;=7(BaT;Fia&bnSO zXx*Orw{zx7c<`AuzjHE{FVxLA`o%wPozU;{AnTl+hiZ4#eAq0p+U&hEGkf`B?%reD zQfKCvoj!NZ{NU!NCL3p8=zEoR%lAW>^F!aYZ>5BjPdlxPDgClwiU_AWPi<}A6Q-YB z5_Se#`ZU$bm=|g8z9uyFg{<1d^OlQ)7Ry=Ps6X?KbBBtNbkNO7evfWRfBMr{!!S*` z;HQ0x)#HZ67RL@|`qcf1y*%KIo_>OFw zqrxs{w;|2@nzOq8KXI;86PZOOeU6rpf21u)9CW zpZ51g{?py{yydU9ACJ^~TsdLN>&ee@w$1O-e|dYo!*-{uNBLK7w3^xX=KrKm?A*d# zYd3Y;dowUF{PT2i43W5;oRE-`lA8Sd!IMYN9zIP@N=isbcp`3KU}9uuW@u_GF2cmc z5gic~dHcq#o7NUqmfZ}jF0nDOckkHR?A=pa^Y=&t)1!6&*43|D$Jpq2P;p`6LkGsr zjuRT;*I&%IvExUJi|2xdkRXRmt5?^1WkprFEi(ylP}4qOYJJmG-CS3Hzk`^DLU=^T zo2a0uu(-g;P+k5aci*+y8?$3;os)x$yO}%|Y+Z47>)yqi7p>mCe7dB{h3jGgZ)V+@ zZd8b;R8;3;VDNPHb6Mw< G&;$U&D?3O4 literal 1686 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 Q1F9rEUHx3vIVCg!074pphX4Qo diff --git a/toxygen/smileys/default/D83CDF60.png b/toxygen/smileys/default/D83CDF60.png index d25bedc83729621051d661073db694e83e1a86d4..27ab69ed348526ee5c2b0fb052df826f421384fb 100644 GIT binary patch delta 1684 zcmeC+-OM{dvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{tOy`!A)J27# z?zF9o3ZL%k+?kj#-PL)ztMj6YN{GmGS7%Jd#K190#U@SLI?v3e-pO-zXzc35taYim2fF&ExjI)^ z+GZMC7FyW1yZbGSO4?P^SZ`~eZDLhs3rLt}HhgF~*Vb*+Q@wBYEK37H!! z>zeHB^UZCWTzuw+$FI#QtS`2-@9^|r5S6qtF{{+ZvBx`TPE1n2vs0C|U6rHz|Ns9F z>gMt=Ffcfj1o;IsFfe&6d-U^uzWKR7{As@$y#KvFSMvKI(_8PH<%~;TGjeUxWbUp> z{CBb9>oz847FG_vmVe(2xcEfmG~-VHc%AFr#I%Effq^l}+uensgH_gpp`L+(y~NYk zmHinzAD1fQnb0}A85o%At3o15f)dLW3X1a6GILTDN-7Id6)JKI7#J*ir-u4oyKTU6 z=e3A!6A#Ce#}*T}zOIh__G9kEPmitgew)^9Om*PlQCL3ze*KdCKcTBV-``s7_U-7^ z{J29ZyDL6^xLTg8RK5RhUM$O}tG)G;gre4DZWFsHvhC{i6w_@7jxE}FGw#gC7U$3$ zp^NKwr|fmVs=F$Av3b)^rJ3*RzS~aO<-+2Ud{44HFZiR8{KP$X9m*c_Rll7Ovuf+4 z$p$;*oR_$kg=TiBzh2}J`h@%DqGhewC*Dmsb)mb&=K0;pTN1bN9_Ce3{8@5R+162H zgJykFTAi2gr2wP7Qf=C=+e)f>=D%UozUh91@ri4A+*h|IpxO-1qdoQPTRm zI1EqCVEUuD^dqAM zZ;IKHO{#N`Os(gC^0l{yVViQnFZ&dW_-Lc*98TWMjMdnwyh;UN|}Xe`{0869&zqea1U97SAe8`=(=E zVtDTc*N2v?i@0tc`qaGc;6cHk-KqB9qJL!_p8M-<;WY1hIhB{c>X%r~J{$Z|_3san z7xPx^`k^2imcB&!bM&M-#6RsM{*d&kyh@1CEEHH?n(@%H~44lGDe>=1e=u>Zn_h7TPlR%8fnXvpx% ziK%f)3Mwk!N|bKNY}tz*U%)y(7?*r z)XKy{+rYrez#xxhJ*e(LXvob^$xN%ntwBCg{5=B$gCxj?;QWc&vRn)dp00i_>zopr E0RHXN!vFvP literal 1672 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy-rSsj)rb7rY=S< z&W47rMiv$>=1xYAX3mx-mL@JH&M>{6dBr7(dC93Tdowdrte|>L@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{ z)wYGYq5QUA6qG7HGd?;g&e6s0c+r_(Zcl|~)Dk%xjYVrUDlWeAY|6|SY9wi{0FyB7=KZZ z>fNZ^liReu@A9kL)31AMD|5i-y1+@tm-h83#)&9?(6ixt5XyS=O_%XUBf-5tA5Fc+ zcVN3|e3;Wi{;cKs9xZI^X0c{zPiPX7IeqBP7KMPDTmKs^c=p&!!$Or)?YU*^F~uJV zQhV&#{;u!*XvjSAM4k)#qoeQb>r-w;nn)Tv>ov-s>TJtD@8`is%x9mSFV3D(6XIkw z#nIoUAw)%L*(dMYDg|?_HGUjQnEUsEvZO?%c)pzeN58ms{NM|V9%C+y=g5}xQ zKb+XPS@-3dk0=gl3km<_xD|V zeOv6npB@t<`(*owGdoXAmod26{HT0m+KT{H&W8>YB8paIm~^}F)J59vXWZcURPkp@ z{v3CubB+NqH~N_GnrvK^ZLm6Q^NG@!D<#Y?Q+^qod=SMDe($Www1+}-eZI1&sMG{) zUKqALP-A*WXQS3r-qmX|Uj)<|dVSBi#mr==IY(jt>>!C<%|^2B-!`c zto>x9?}p&n4A&fGKE0k2$J5<3{hRlS;K^}{nzP!3jvl*rB~$EU!55$Gj$X#xjp>iw z`BWbpNf&$E$vUdW#-@^x=*$~ zx)avmrnhFZ(8;L}Z+>4qwd!w~Pg-2PN8=026SGsFWT&~FS$ikrFSkl#JbOa!#zIlM zqW>@V-K%Y0d88oZEAOSRQrtW5tX&?z&u(Gd>-h}K44&#L9*xbDTtIb$r>mdKI;Vst E0Pw+l5&!@I diff --git a/toxygen/smileys/default/D83CDF61.png b/toxygen/smileys/default/D83CDF61.png index f8a22802393b6194f5b5186bc091f4442b91cd2c..edb01f78df01254650c28dcd2488822e1d0c40c1 100644 GIT binary patch delta 1464 zcmeC>dBiK0z(>EsdrDW`fAHYTm(QO* zeYkSv(vKfM|NQxV_wLPa-@bl&|MKSDTNR7qFJ3%#|IW>-MJZE{N6)(w(0N)Way7%9 zcSjFC?wNQ|C3`pH+UsR=F9kQ97O`8%nzT_kb4NXk{Q_2oR>3W);`e5TKA0Qz|Nnna zP18vX3=GyKL4KfUVVn5w?L@W-{cqpC{rjkuZ9>PyfA8P?dpD7-zklL~e{bG(ykwv7 z_QN(d&Mh0+Hu4IIN=Qn{2nuhxbMKDZCI$uu#w2fd7lsa2Sq}yV2KEw9Usv{L?0j4* z*1}FD9t`yiOtn=Z5hX#1e#)khq4W+mrkLW4UOl=Pev3jx9FkkN(y<{r8C{vTWO=!+&JAPPi1@&xXO+2Ex(kXGw#@XTcv{W=Eg;H+U9U^GwiKU zdhqMbEyW#6!(Y8|>4_gYs%zL-4j>b)PL8-)Ui+O-L;sHJnoP0?s;dt&hO-*JQ>rB zfYcdNqIq`Nr582Uu<~RJy0D8BNSyoWp0P-1uVY>B%>(CbW;8$6-Ff8;hYs`1CKE3s zkC?v>^X{KufAEq+=YM-oLP)u5@f_*p^_zakl}A4q`Vrdb;3=>OZGO8B2#woqn4u7tgX;ojAPstxnmVK(3CK ziz#fkk9=!hckrO#&+c6NSHAnSc1*q}m%j7cb4R7?>Su4}+Ka|t>-TTWJ+!i$KPa`_ z&9Z)1z4sIGo_SrB5x!Rp85kIrdAc};NL-FRcUj0OQN-cl+}_E#x3}fquKLfOvFhh* zpVyyuM=|`LctS+0w~yJ)XzDqG!ULzCePT7^vp5>G^U%|ehAd54Ywu-jyXC}EA{c$= z<+duJL%X+o{%(Hgp%$(oxZ>F4mpS_)RtY9Zt@*^nOFb- literal 1549 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy)Fh$E+&QsMlMD! z&W47rMi!P9j+RD_PHt`{mgX)7MlijedBr7(dC93Tdowdrte|=g@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{eZ`(1kC*HBwq|~`*n3#!+*_TaRd1Ico&S8kq~42ti2>)#k^{oR6GX&C zA3cAXuyOD2AC|M1ZagSAN3Y`VBiEbRZQ?x+Wjy8US{E#7QBmgR7CyW^KVH(p@}^_f zrRO({!xApfDf(c~tXyOwXI1v4cjdx`ALh@GmzcNz_X&gl8QO>c?)U!pKjp{ohu5m_ z9lgG97Y9I#v(yJyP7{S`@SDBov`p@e2dDq z@_#A*_d4ZWxp;h@$e$0t^$s78mzU79Pd+%0ub}na(d|B)x80j{y5QMOx3<0E_C_*Q zhYkz;pZRqCw6&7fe&Pzu2@O|ccmJ8++}OzfB&Y4i_Wd)Q_iyO@J2@dL|G_(ng2d-_ z1qnt;r+pms6FNFqoi6;;*|9z>AmBsY&res?#L|`pOkP#He};slq~rxdvk$+XEq-4w zDbXxvSGUPCDC*Yx^Y`mlAJk(=ic?5qUFxNg){+$Q<4^OW{SJSQJ58LtQZ=YvQCoX+ gTkK&$(FO))22cKbdj#2YNmdKI;Vst07r67P5=M^ diff --git a/toxygen/smileys/default/D83CDF62.png b/toxygen/smileys/default/D83CDF62.png index 62a24bcd97ec65e7c285c45c9eccdc6309d6438d..ce1b4922e18b4f35a18243a0a979ee3769b8bbfb 100644 GIT binary patch delta 1522 zcmX@gGlyq_WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817l2pPl)UP|Nm#(F$CD#r}zg%xp@TH z+Xva(2in<1xp{b5SO(eK`&ip}np=2VT2G1BFY}RW4ppl!_m<7D74fpL^s%<-jnYmq z7w|N<@HDq5bdxIel+JOI$a9hCswkdNU)@(*>8;7-tHZr)>clOJ=694AnMtuUriGRy z#hFR5m`SmiOS9Ccg_b7A7bQfumlZa|`?V(dmZv7x$NTLsH#*VmP?Qi|o0Zm<5R z^jM=^Q9|^-`f7^mXaF7H~o#7v6i<@GCvH?BLfdHuR%<&E=a-`m{$ z|NsBRIy2K47#O@tg8YIR7`|LsRN^Xr@!q0)awTUjeY+g$djCv>#NA8Rzy0IiVRHG- z@ed|ppLa%Fzw`M7KmSjWO+Pm?Yrhg*w`w!XDoz0rF-a+D2~k18`nC6_MCdMMU|?WO z@^*J&=wOxgU|?WiFY)wsWq-!b$EC`7s86Jmfq|*MDkP#LD6w3jpeR2rGbdG{q_QAY zp(3|{fx)78YN+?7I|dwgio0YS*;p@yR}FHZ=WzRJ0aPhV?8?lsTZ z>-%~`Yp$+c^^mh&>BbSAU^Bgm65?Ef#ykkyq8V@#I zre#a5Gbq$s-Yx&qa8ZMAYUtMBET5GiFMkodll zSwTtMy?&9>y$NChPXDV#I2hMDDc`6HTea9}L&d9HMUfmUF6qFxt+wBkxYjVQ{nug1 zxV?NfhrY%(-X)g>f;(9P;%puk*z7JVsMlL(T2ZFO5nK6x^XG&`%9c0I6g;2wa|35% z*Gv0XMN@C=pL{FvCD-?tp91HHF3zsCHz_E%UZ5D)yzQde+f^su)C${V&s&xt{6b@y zman9Lut}Wj>ve(G7U(aXGfXVJ-KWrmd4!G~Q!W zS~&4Qy{pH9ptBufN3`4%yq+j5VNuoP3b&u|Tly!j&R-6n|Lr{K2j6u)%aEBPb<%(L z=>?ZPG$ewIdsV_!mpO}`X*jg@T`jjRpW^rHj!Yb~T+yFZBtLv;Dh=2qGh@%KcrSMxMC-K{LPo!(77O3yDO|v}z zTw&pB&tkLN%O83F`lvaf*QxZQz`I%{&?^cWmCaNyz7r*98;svLOw^!4$x%_;|8zkPoGxW0l0i?F~qK|x*)9xgt? zX08tJ4<2o8t?n*f^^U9!EhQl(E(yVFo7EYTv zbuFJ@!j%hWE?zo!=IrH*ty~61uQVBd{$lGXU_AV1jm=`FWZ@kRD7?R1^El+&l- z;uWvy7jG>)F{$z3wR86lUOaj7=E zRdP{kVo554k%5tsu7SC(p-G6Lft9hTm8r3|fq|8Q!Ge{)&A~cg8glbfGSez?YqmdKI;Vst0KhtUe*gdg literal 1605 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{;xsMotDUjxI(n z&W47rMwSM~E{?_)CMIU4hE9$a&M>{6dBr7(dC93Tdowdrte|==@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{9c5Z$$kU>vt*EfDRG+Kh%2nn4 zSFWsZaJj7G9`5Yn_&(140JqHhsn*6i+^4k}B}`JU-`*PN?vsA*=T%+q`%Bve_aB|t?$9pb2 z(af7=u|TbH-TLMzRlfETlZ@Bj@81kP=$fV1HqZUZk;CRQ3sf0r1hMWcVe8t%ut>vc zp@xiiWd0?Ut?yM&<*$zA-#1k>`@Tqim8DB2cRu<1 ziDPQRYPGtgJN!SkSSy{ckvXhjP>~l={_@n;s9rZ_yZQWS%hK;G(&(Ay|ER*hw0P3p z>zRu`ewcD+XULz2UxocvPM9VA_x+@$lRMr&)0>?%Uy7$KZ`QKy-RXfJHIpW8$*Q-{ zd;dN(@5&&+eQyI5?4GWEF6*2UngCA>RwV!c diff --git a/toxygen/smileys/default/D83CDF63.png b/toxygen/smileys/default/D83CDF63.png index 361eb8125a7b18fc2c09ce2d1c4462cc33f05795..9cb9907e3e3be9773d97621053a78465d6aebb39 100644 GIT binary patch delta 1595 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 literal 1773 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy#^LWmPVG&W-dl9 zE{2A#Mi$Pdu1*%l#*Qv7F3!g07BIb@dBr7(dC93Tdowdrte|>L@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{~jb4ojC0LLCAZo8^>u zoZ#B+SY~8xeB0RevhCfgS3~#7rI}o03?-$=iKDv-B3Xeq3^s>nh{D+cnN!-L2-o&RFDmkD=)H zAj{*j+mvJ9%T5+Q7FC_@dcbw@U*215m)~7}uI}x>Gv=ofq~!Lehj0aJsQk6t;x(bq zso+@PTQmED?0dccIj)$@Qn=2L92&MZROPDa%&$u;V@+mfF$ZFL>9UMcL?<=&=kAxA!t<_?xzOo9sQH zI^F#7UfUNB1e$DKCl!1jK!^=tud6@+oCV~Uay-WB(d{2E zJYL4}AFVm}(5iI*?r%E-BM+{qf46Hz?iyvgSHepfHuFpqww<^#bIg;t#l zm_OD@7QbG5_ti@EYo0%63Y+{}V-g(@=vBpWMqS;*|JZ~&g9)id%)fdzMsHHQU$jl` z=M?u|<0_Fj-8(xr#O~FW-+KGjvnA~=R&y?Jux(D1ce3FB;e2g(UN6%#L5nM{`PqNp z@0l#Sz_8x({3qVLj9WJP=Q*tW0$!FqFtLt58EQKB?b?z#e~#`tH+ktbr{f-*Y%O*% zF2CjCb?j26SF+E9UT2}Q&wVRQ-k)l>QpkLgYU-2sTUXo1!*7jnjr`{Vm$psaOLD9f z`4i{wylO7)78Poq_ve7s{r^8sFPL64WuD3tQ7QGs&7V(N%sP_XV-+yL@vA}he5>=V zGCE(Q7raqXxWf5X|J*y9|F>Qq*E79#C!M`+ef-ML*B%=DT$%iM?)kdY>ubLMZmvBg zEx2sWo^xBOZ+5z`5X$nd@V-=LdG8a~bggsK?kg((3vXa#c;8-Rzw+IAK~M$c>FVdQ I&MBb@09i$_V*mgE diff --git a/toxygen/smileys/default/D83CDF65.png b/toxygen/smileys/default/D83CDF65.png index a4809260ec8ef277329a6b5825eb4d0f1f47ec56..a8d746f0797bba85e550b3d8a3c4c9cb487a89a0 100644 GIT binary patch delta 1715 zcmeCv-Pl)UP|Nl>4xN_yj?NjG3U$}bX z;?W`nhxaY{JOV@9| zc>VVEyAM}x-r0BT^q#{f?>>C8egDy8XD>c`_2%L8mj_Rt-FNKttM?y|pSyJQ?8SY@ zP9Hyi`O(YQI}aV-y6?!PTX!Ekd%63_sf#!6Zryiu_mNX~pT69(_Xq@S-FM{H!)JSs zo!)!w^wA4fj-J1A^Wn3zS8p9Sd2UNRNYUXfdk=3vaBSQDW1IIK*}Ct@*Ka>QeEj_8 z{fD2we*OOQ=kLFNzkdJu^6lHxm#@Bk|MBTh6Qtt%k6+)u z|NQ;)_y5x`{;#|9f9>u6t8V>Yb@TtRXMcYF`SJ7D{m0L~{rLI+{)hjY@BRAz>(8ej z|93t7|D^u&uW!G;{rvU%{ikm~fB!%H^#AFX-+%r2|LWVnH{brg{r-RBz5kCs{eSWG z^@mSifByczENPeLMcpp0 zn$_o*m`*$3HaViiy!4J>0L$zdOHZD2y|!YLOKHv*-GeocCcWSHOKwv36pj;adsF2$ z^|UUpKL13NZCmua3(xuV!b&$g%{jUCg__|#*9vDlS2e~}3+m%;s0D7UY0GV$`rE4b zaP1%KkJs)_d$;3&>W(>j`=%d1!DM%A)vM)~X8cN;7Vcaq8X4f%{qM2ugQL~wEgEc2 z&be+ZeC)p&!-PPO1@(p(Ul!Segg%vlEoY~)_s336mJCK<1Ge6vy9 zb3qR&EwnED`ncZHpIV=3zjg}L) zmTO*RvFuvy6?*KhXm5C+wDzf4lUidxt#oSkxqHMi>B3jb$=px8A9(f%98oZ;_u&@^ z4cdAuP&b#uzI2A<*&yHD-hO4Fm(3GiozV2~^sEv}mp}2D`zOO0_k>^eDGkeZ@t(aX zBXj!E?Dd;2zVy(rSQ1q}>0ijF5`%-10XuHJh+db(QCY_)VG zF+}2Wa>4;FiJ3DZBW@jRmTo2Sa&h( z+Py0mZ(hB7`S$XatXsl&*E{_@vchw+lA8ZaD_?ev1=h94&eTV4mQKI!Gjs9sdr)}^+Uyk#y}fJa?rmPoJA;{Fx3q(1(HZwr1_lPz z64!{5l*E!$tK_28#FA77BLgENT?2DnLz56g11n=wD-$zq0|P4q13T+0d?*@n^HVa@ bD&=tN@K4LxG*MBVi-Ez@)z4*}Q$iB}e0JDP literal 1678 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy@sYnu9ilQ&MrnS zE{2A#Mvmr&MsAkoE{=w7rfwFlt}wlxdBr7(dC93Tdowdrte|?G@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{L&pQEDnJ>0;Fh6V1*|6K7XTnsr{MT)5Z|#4-dGxTeiGRNNGh^mj?U^P2zlR)> z2xsB^Qzrc5E`!9$qbiFosw7Qpf6ucaln_kj%-us!fW_e4f*y7%TLhTxKc? zX>}=@)`ppygznYxzO-4danT9ylD2m`w?rn0XiodLZPkNo`~tzuip3u$8wG6cf2=Y4 z?T+GDrcJ40_gOWkygXt3Pwon1kiwdG%#~Rx&D&~P0$Gk1XV(enlpT*)ZJr>p%zV=6 zQzwL8xXW@nH_bI)koBeI&IFMU0bZgBCXC^(=kay5oZHeqJ%?e&xQN z>CYPH>ixWV%?`7#CSP~iqW5A$tbFR`tcrQ@7L^`T55L;<{P&3^mpj(+C>&jOaE~kl zTPu(Hg;SCoD-M-yE>4iUCMWRbC1>O7DsR!x0k1rlzPq)mXz@hBh5ac93h!)8nbinTu$sZZAYL$MSD+0817k*jPl)UP|Nj{n7(Seu0YPt%O@^SC z2m7Aw?s&Gl{{Qo{|6gAG|McYV%M0JGuK9d=&ev-jZ!FEfu+Z`I>6AZr+kV}f z`SaeKU-xGHz0>^hRMLfo4u>ZgKHBF0<6`#To7KN>HU7C-^Zi2Bqb+`i`wh;{G`Tp> z^5R0vGc!%zZFl^CBl-XBod4JB6JBqzf3nu<|E;Y5532v)OnD9TUE%t=)!sVqoUsK_l~V6f<&8X7q5wgJbV>zY+8 zk^(1=7bd-4|NiUdQ)TM=y6?JEs#`fe9ILs&AN#F$dR5h@M7B-R{x@E?T)CE;yOm?E z^hBlHO)F1v)vxm73US>t!8hyHj^E1&E;abS=A-V?L3ytVWKI9D!L?Ekq84*fvq@YE5>`^RC5@)e_yU*}AT~ zL^~EHHr+Vz@aAOhC)x=iqBcbj9MoS|n{7QgWyQ;bEGB!N$uD1Uaq82SU&mj#KbUkv z$0OC#sxv$P#Ao(Tj1l$y2cG_)znjQsF=IYF=5} zeZ|=mUgzbk;59DczUj%=cjCU!?~V6Pm2?)Fi6yBFMg~Skx(4RDh9)6~23E$VR;DJ}1_l;Z q1_r5oM|Yy=$jwj5OsmA9!^*%wB)FD!qM|w%1B0ilpUXO@geCw8FVW8c literal 1521 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy#^*`uI4U=CYH{Q z&W47rMvew9#xBNgZYCDyW=7`b7BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*Fewc+Z9-< zO6$unt5)-`P7Z3@SzFn)`M+vH(Y){KeyJ@EB2ER9B&Khl{yx>6E5~l{WHmj`BmtlN z#n(d>dd?q^p67Jl;K@WLTj%ra!s^BgpICNW%rw}}T~p1Pmd%}{(UVV=xz5X7@o!GviH$e83o2_wE}k~Hw9O|X zN;axe(zaB5p|j=|dzI}bi7NyLW9(ThYWLyZw2%EUVi>PuJi5IPpfyYJbEQ3 zNw3Pplc}yKRQl^zp)T#&{^#Q2=SshH*}7OfBy_o~zJd#z_FS72|1W#=Y-HqYU3Y3_ z>EGkUQ8I0^!F4N+RkGO!zR-}m5cYzLaYmQZ{8;P%$5uAqH&#fkPGNl1c>MFn+s7vf zi#I1HMFehn_Uf14yGzZww;S(waQ!`8&sig$z>u2#tXX4PZa=6H_H^}gS?83{1OV`{ BDN_Id diff --git a/toxygen/smileys/default/D83CDF67.png b/toxygen/smileys/default/D83CDF67.png index b88025d3d52d2da8db732f2a43323664acb95c27..e64baba09a01c5e2cc2dceaeb30c262f7c3fe0d9 100644 GIT binary patch delta 1605 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 diff --git a/toxygen/smileys/default/D83CDF68.png b/toxygen/smileys/default/D83CDF68.png index 429f44e62dfa7cd982746e2438a97fdda3a934f6..0e238054246cb7ab21cb2b187033294c6db85755 100644 GIT binary patch delta 1605 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 diff --git a/toxygen/smileys/default/D83CDF69.png b/toxygen/smileys/default/D83CDF69.png index 54efe4f0d330ab9fe603cfd2824134f881fafedd..de6d759dec34ca59928bdebf7405e4d70ad37726 100644 GIT binary patch delta 1630 zcmcb`)6P3VvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{L>Roq z8GIxdEO;3<#Y*`}Fa$_3hAJ}Vm@-7FF#1@Fq??OGC^OdwN;3M0gXDZA8N4MKJwzA+ zr5U|6*?lA#eIyuM<(W6dN_mSjL~1Zk@DucxWSHQ`AEL-~xKg7pKy+`W!oDnpO|eqD zQsuTK%WjI5!iwUxnUYPI^X^w`|R7;g0$ zclrvtOES8tv&QK#7TYqW88O9ZF~%7SyGby*OED(svOJw^_H4Svv~aODFTQvkCQwB1 zF@&ozmD{m*_z293mAct!xHnVbYLo7>DdwLR*nM7T`)Pqq{ku6fPbZpQZPwkFrSSj% z|Ih<_W-~A_7?lM1fszCR7#Owfd-47Ii+$~?`}V2oe)w?s;LjI7K3<8r^8MGZ!v}Rg zeAHLnXWDh(ry1|NRnHk17#Neh-CYzY+8k^(1=7bd-4|NiUd zowr!rTdbpz@yVy(&)0uy-k-PkMXdXm6?Qv+osFImBAzd2_j}pu6J~ka zZwnm!_vYAKv0FJ;v$8{iUzE-7{u;r=KKocwwDEDC=2aFdi=55tS620}^Da5=7|;1^ za>%~lcee+e;u3t&`*VT(t~X7~tExU3HSd-7zwx={%C+3wtsHZuCq~~&37#tYFJ!LB zimr-{<|{KFOI4S@@jB(o_U!LV*J~lsL4}Q89=5zcIs6~6mdu(lbH3B$NltCo4;!q~ z<4AY;m%^QKa+)0b1ogR=^XAoa%zMqp(C@_jQ&IV%pqPNh|2M2L4yz_`e6*Z7>!5&% zT+w_k4r#`umskDX7>cXh2wU>&>~oeoahX~bj5qfw&EcD`#mumGMq)v2%G-pP!&0x` zxOnW`^lxinSdxOp3!YirXDg0uT_AGVu5u^WoBNJ$H$3I~p7^PG{?Qp*EAyo!cGOGz zW|&OVy_~W+GVEI&$28sDnH#5d&NJxF+Go11_~^xpo)a>TJA9m#p{=3kbHl8l;@cCZ zpF$CtD-svw9)6L-?0x0SGUlYFhP^&tPBA8L$n$)~y+g%_J1BCJ-=ka7pZ+-6F}xPs z@kidMcjLi{GRcok*zEtT%eIW=6rFy>L-lNE{nNJ>M9Lke-ut#EuuE;q&zl|$n(Rr@ zWs{is^&>W`yk0m-{69DAQ(^YWZp*Gu{Bz*`L!OEgA7(PN`0uWGzBKuYq2q_mWlQ3} z&$3ghH-Fn+rF~y_NATX-J5P<|#65TJ%RHC$d+m=qwZBDPglX*hpF=%9ZL zxAt`{WzA5kKg+=hR&I-Q+s=-w$9|#IdJaK$)jfvpFXay zaN^{lb0-d{sN|&NBxR-LC1$4TDxEr_pq7`Mo&Ns8izja$y?Umncue8t!L85x;fRlkhb*o=NvwK?#4Ft9Q($h|P&TYgJwI|BoQYKdz^ zNlIc#s#S7PYGO$$gOP!ek*=qKxvrr}h@pX%v8k1bskVWEm4N~O<#Ur!bmZozWTsU@ abYRm^*H@-LQBj?Xfx*+&&t;ucLK6TSw1x@* literal 1626 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDz0Rht&c+rdCKhI9 zj)sP=Mi!=q&aQ4276t|eE@tLVMlijedBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*>4d3jOYlh+`)$EWRi={e>)VTV2K;(- zrMWWx>G>G``9AC)Toj9}*p4VmEHIjRmDk8ovQf-;!H&7@*RD*sE+dhlB$S+>D*O7I zT9(*(7KPxWQt@h&IeRt;%#`;1`hqo8IR2^%-&5@qPqQZMWD#eap_Rh9Zf|X&OR^nT znDFgo;e4OYS$c_n(zxUKby=LFsDSgTJOp^u zem(qXdUr{Mg5@fccgAcpZ+2|3pSPo4yZ7WqIfL8xvmO@h;{Tqvto%LG!riCL6g+pI zwe=L{G5epoa+yw}Y{0@uIXU}C*H<0;XGCh;%~kcCvi9Y9)y$60e-edV6W%NiWRGxp zl=AM|0_)Euo(mZdUVO-M=#2B?pw`DfJ{)RY)aMTS{k)l?gIb4_wHG;U@mn*)AVkwR8@iF^Qr^|i)AqS^gtY_EbyBEu!eC>TV zv0%nI-ri*;N<1H5z12Fis(0ypQ=NYt3pku*{XAdzYZ~wwlt>81Ncbf^tzP(O=fBD# zC9XR=Z`mrGIOk?ss&rMfS#j6;OvQ&Teb>@I+bl@5G|)ENX101o=-r+Qd(+QKyYD1z zxPEqRSiHD|xP*wot>>zZ`AqUG^R{2F|KHTnYWB~%j@y92d%@qi>^jBqpwiyc)z4*} HQ$iB}sv=Tx diff --git a/toxygen/smileys/default/D83CDF6A.png b/toxygen/smileys/default/D83CDF6A.png index a73950843d51048ba02c09bb80dc7a8c403f49d7..cd6c10ad1f4ad111a1cb14e4d66c4cacf382f884 100644 GIT binary patch delta 1985 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 CmD}+E diff --git a/toxygen/smileys/default/D83CDF6B.png b/toxygen/smileys/default/D83CDF6B.png index b16a6e04ecb9ecbf3e84ff35701a1316ac3ed6ad..73ad91cafc1c98cd33a17aae6372976fc8661f8d 100644 GIT binary patch delta 1639 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 KelF{r5}E+XheLP( diff --git a/toxygen/smileys/default/D83CDF6D.png b/toxygen/smileys/default/D83CDF6D.png index 622f296172189c72ca86a0472bcfaf161349b6ba..90a201acf9704bc0358a28f514dacf49b4957831 100644 GIT binary patch delta 1556 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 diff --git a/toxygen/smileys/default/D83CDF70.png b/toxygen/smileys/default/D83CDF70.png index f930ce7a6d3a7911033c29e2dc0c44bf4661808b..4101f4009facae0704e7c061723217c9dfe2e32b 100644 GIT binary patch delta 1611 zcmeC>`NA_nvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{|AGhex4>;eT}Wdncz;^`c_&^^$Yh+5bB`{|^rSU~GI=OY6(m?+>59{`&35 zI(z$hf`Xg0wa+FdzCC;X{m0MG-@L!|@Y$hrSMNS~@%;6>C$HWe14qvsEf&Uk!s z{*!adpI%z`{Q8!cw|Bp~f9S#Csdx8JcyMg?qtlC@Tv+||>ZTXBcD=fP@Xg~BZ=apH zxvT5l!}=4SuOIw=X4mgSoBwTJ{eRt(|BL7TpEmK<=9d36r~dEl`d?r5zbNxha^%kt z_b+Z{A1u_~XbOLEHGi!u{?^^_L!#Hms^pK8t3R%u^x@!&Yb#1$XbF5=Gx`0^0~gkm zo?V{%`qA+hw{}0dwEn@VCASYvzp<<5%I3yX3)7$9-1YF{x_c)U*5BMeY?zQAU?>Qt{S5^LW*kssU|?WO@^*KbQlIW6%D}+DUgGKN%KnU&Y*lfAjwm+rO;$iQeVe;?X{D(ymVJ-WXc^dfUl_ zd(YmM%|0`s{PKj5>8qT(`1G|l* zUWU!B-f!OK&GvqwAt2CutH&bcuBiU!o+oeFHbu|5aGg_c>&q3b7EQC~DXrYo`N6@) zRg7_!Li`D_z?B=@-tcKIpI4c5?|*N_lAE*Iw0-8B&itTk>n5^c<(gGxUcH)AC&m>Y z)!3vo_vO#WXA_S`yS;IUTXMXPGzk2JPr@2`4gd%2k-#Hp5GWp~~>^!-}xC4`O1n zzufE;*!AgO`tysOeNPgM56|^}c377mNm@IX4AF-(HaZYki@OqNr(K&64!n!(z@8+Kv zBl-?}sXtL5b~iRqrnfs-`DbRiWJ#3}m)y$wnQoI-3$159Q0Tey>fXGCM^&Hv+vUNq zka?qN{v<|z={1X0PcNJt{+pY%h#KcC z&YC47GjV^^H|PBq!=>L{$?xAj=lN&Fg|DYS%h|TSOaJBZ*YzzZ@+%+ z+`WSrPtNR@KO!n1QdC%4{Qbk1Pv1U@rU?j&e*VnQ!6P(%!jwtVCQePB5FQW`6c!lD z&Bw{h-QUrZ)X~@3+wJe+a+9=h!Z_73b30#57iKUAuSj=GD7f7cSqvzW&e=&&gL!O}1_+E%_?( zg_ozd$Jghq#6kv!>q@p4ul;Vl%D}*&TH+c}l9E`GYL#4+npl#`U}Ruqq-$WVYiJT; zXkcY*YGq=nZD3$!U}kWlc*%1V9l7}_nQ4_cbr|f_lZl?FsLsW};OXk;vd$@?2>_3N BD;59% literal 1549 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy)MoMj*dpACYH{Q zj)sP=MwX^VhK|OL25y#yCT`}&ZZN%`dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*I1Cy<%i(^Q|ttFGsW(zw?9GidgT-_GW z-JaQ(d{r)Kt-a8_H-dYs$^*v_m+Bn^zg%h-`NOQERiX9cD}Tk;$Io0{S86QPxLWaH z3CFc8lMb_+Ipv$@bR4KU7 z#(rl_qP5z^%*|D6YM%Z&8uGJ5_2-4f6RO{@oA-A*!_i4kX0CjhIp-jt%$w;d&=!!=m*X~HB(P+EqUCvs(uK8MGjo=G z4_fE%AI#w*qb$PYsPN#YiVw2vTyAIhj8z13zm{b1t*zBy& z#*AjW9%gVcN=3P82`(>P_suAh`E}sL38E?$`V!|4{rn(q)?Bd zSq@D*xztMEZQ)@4VDMq$z4{%>N16mSJ#M_MQgpKHV3eFg>HMPyWHA z`7B$)RmC%GOvSv8oBJj_bNX!k<3luS#m5K!4K_=5@gA9@>^OGic63h;I z?d#*?ny$KC-WVhEi}gt_@6nq&NrxS-Onz0dx8HmFfz{9b5;l|wEn*hA=*(Fz`>-vC zWu5ER-V2)*nZ#p$*wtk7+A%IlRa9ur{a%;XRdapGoT>M}%=?lUtUV_xah=Y+J6v08 iOE15+{(D27iHG4^q{#2rhbrtr1+=HDpUXO@geCyY2t@z@ diff --git a/toxygen/smileys/default/D83CDF71.png b/toxygen/smileys/default/D83CDF71.png index 0db1d71108715d7c23aa408d1b2dd00fde6cf76e..dce933803b753b27d2c29392d6a589af1a68695d 100644 GIT binary patch delta 1375 zcmaFB-NijYvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{ghNnTzD0|SF-Nsu2Xa2XEEg!CR|`~HK&(yD0nj~|DR2yJS8#8A(`z?kIi?!wT) zD(k_(z`$PO>Fdh=jGd25#ah^@#Djr>sirCT%n*SKP@vSRiUJ^AXT9vw}64c zqIYVj_p~_%Ja=A;ST;>eIVsc5w&!$p|VQVcS|1=Y$gek3?WXaU!!)*PX!QrRcMJS@MSkv(5%{z&)iw0e`6 zghw%Y;`W!5T^MJdT9q~Z%8Ve%m!{z{u3}3r+t%-~UQ=y>x@wO+~SUA{wq`uSfvQn9m-UpR1Z)kCK}Y`KeltyZgTKCV`AZO*a;;TIaqw0tG~ zgH7UGUtiG34iGEq3}t4Y8Fa)j>A_diNzzaB8w|5~&U8sJ3H(o3tELrD(sbcbUwm)0 zkWgS+PkjyBG8aB4uYgO73cm7I{&TQnJS{onAHU>+gYUYYW$^g*KH0r?)5WJA8Wu~c zq&(Mo&EfmdtN1j`{k~JT;**~eDh(G_ZZK6p+0ZV&rg_rSBTtV03u5{e(md^R?1Z4R zd1kVu729O3&Tn79WRqyRkVh_EIB{|s@3|BA^M4;S|E2e^UazKl-qSnr?3{P!n}lyJ z)RMcj-M{gT$g0`=LA~!%tZeqFeqmpDrmOMP)#b(v3=Hc$T^vIsE+;2Au=n)nEGjr~ z=*XD^C$!Y`6g5>RkG&r4^YeRNMR%h1h7q2>BNoQpc$iQyQtUi6ANWF|i;b(&nQnP2wnmJ>}>=`p=Mnpt?H2e^Kd&Z4hH?1vZ%&?d> z!{VbchsEq2Gj{CSxqEiSjEX8tZUc_0-xW81{;HfcW9G~mKiN49S=bgfB(zPqV4xx( z*Qlw`qovUFu$qBklh%V-TQy}F7#LJbTq8|?>1-1f)wviLJYD@<);T3K0RU}T BWj+7^ literal 1504 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMliy%t7Jrlu|?E=Ddc zhK8<2j?Qk5&c>$3#+H^APHv7yFuk66#U+V($*C}VGeP!3^cv&UYvo*&npl!w6q28x z14{t`8Tlpo#Toep3eLf13L4>=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDj~6 zXldZ&>TF=)>|zA+x23s}tEID>nX93(xuKDhixO0C3OQk>Z>M0R4@w+Ji3KJU;;aZ+nV$A#`BX~B9~7XC+y z9!tN53uRw==dTh6lg&#e5s@B6(PCn#H-=EeTf4S^s z`CJFC_5Tn5Ffp57q*#CU;mvCKYJIuZFDx!AnXKntI&$7U=EiO_1-oCIQ?F+KK52UF zYMZ*vb)A0>KhKIR$`)E-9<`#I?O({L7cuu)^IlrL@bYtHvkRI!>6wQ7>^j3HE3<5RUpqlO?K9me_l!_+xpK{h&WzwN_;fc ztX1?P$G>x)5my|q@Ek6b(O7q|GNa)6HQx<~u1IZNy(-*x<@I-G>^WAgxD~!);?d5S zb(}f(Hwn)9?5Lc2``Ogji%(Y_ds%YJq$_^^g0Qto)83V7daUzOymyhY&+On*k;$i3 jueWM!DBJ(Jo{^1V>CyA^w=ir`2bH;=u6{1-oD!MnTu$sZZAYL$MSD+081LN8NpAgso|Nk>EFzh>d;l_ifOEzp{ zAOh6G)$BWYVdI{|JCB^+ck;r%lNYufJh|`Wg`G#vK)7)7#HAZoZa=tk`@!kU*H2%% zwq(P$t^1B`+IwWlhHY!N?On5d@BGypH|{ySVfUfA%hxh6Fif1iX!+)y6K2e3U|!^v~=KYciI>++h{7w6yJSn=}pm2cmF{QLX&%j5H} zPcMJIyT0r7f!?R`ipI-L$#g%{m{(b%S{l@(#AHID1_51JNzyJRI z`~Uy{|DV79yng@X{Pnx%ZrpwH>ivf=-#>l(`R?<#`_EpVynOTe{UaATzPcPZ9Eo_TyJr4r|gI!6G zA1DDZFmQ452rx>^itx&_@QboaFnLcq{qpzkGwzxfFN8dQ^ZCo?;ve5qOG{OM|5j(q z{*lXPJmuW`4;N=yGDmQPx-l>?FeZ7syD)UH%6c#`FtC?+`ns||W9Q>iW!N3GMwfws zsi`U?q9iD>T%n*SKP@vSRiVD5vLIEVBDa8n!J>C+X!N$(20VYhYwcoDJ~=6pkMqa+ z=U+CTe&6F!w6o>*y*6QkNoy`9r<{5}fBz}|f4jaeDwn;u_}7zH=J|<1v3n|iyqUdD z>GqDhdCn}KZuU+RyOnb_D?23kM%n!CuMtervyUZ38y^?u3*G4H(w-OeRW5e=i~7Fa zeL`o{SJr&roxkvksFH%z=Ytj{H%0Y7_dGewn_~WM!*#xuYj5Am5@a)1a=n!jJXQ2# z$Xt;XT{VicuWTvgy(t^yx%b3@;{DYVP0vmYbSXCb}+C)>R1jZ^_lt5Vlb8QE^z7 zTWi9{E5AbmHVA6yEWX8E{f3o0M?31TSTR$ce6Ci7HazptM=xwG*}g7FJIlh1J=_QZi(E3U%lcSOWOSkXMzmmj} zTE{EoaBATOUGG|boBNaZ+x{Nd{wem*+#la8PN&-NPb#XtH0SKM;E$@czeQezt=RQL!L&^M z^2EQg_P2Tq(gckskX)@2Kr z?%4F~srjS8rFGz-`hoLDkMb{Bz|_+e)8kb0Mxm>dQ%B(pzZrvvVKa-1%h4mMDoa(C z98Ga?VA@nv_KmO4@0{Mcymy~A`S5Uc$<2$qXSc8JA7i8A!7eTyFU5t44+ZNd23|DW zSoo1s@f43%r{_t{m6hY}s;Wm_m!>|In#IEtB|0_qYC=G8 z^m5)UuI}6SE!?{mxlVwYa}FOT)B4d;?1jfFK4IZ95{5~Kz>qITHeL$>6xjy zhZ9dTF+{K%C`_|^^_YQyLAAs+q9i4;Bs|qBxhOTUB$dI)z{p6~z+BhRB*f6b%GlJ( v#9Z6Jz{{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy(Wg{W-d+!1};V} zE{2A#MwS+C7LF#C#zrm{ZiZ$~jxfERdBr7(dC93TdowdrtiXC5-Q4i%wQ?>>O)SYT z3dzsUfu(?ejQo=P;*9(P1?ONh1`yp;U%Vogx=fWzL(CABECEH%ZgC_h&L9KKeW zSnRhjv@~#XbvCeYb}<6^+tS>~)zaC`%+=7?+|bC$MG2}mg`65#_bdknI|cwm|!0R15>-Fi(^Q|t&+*!8Nz`gZT~&fj3ys9 z(f_QFl&viK>QJxfQjwSr?o#chfC+jm=8F{8#q2IojP2xh#1@q0_=>!pSX3-^^4=+75_{!rRNK#}RL zsk6$r4_|Wv6#O>SGUnXhcYpix2A`HrjfH|@6JN4@`(0(WLPIddyn6Bv-zOZ27q;*C z^K-vw#WVF)cTMk$2C>!d^m%ok>z;1yr<0p%!US4A%N&mTS2QO#GD6e@i8z>udsxM1bt=-JU7Z_h8@5XJo1TT#qE zi{4gQxW`1U8**tdDDY}%ph9QC_#;mOP+ zi`oM)eh8ZOkd2>8poZn5&M8w@^jJB2TMsPkPR-KuC z?esJszPl#nr;25kx93h~V9jph+gTe~ HDWM4fvCUkN diff --git a/toxygen/smileys/default/D83CDF73.png b/toxygen/smileys/default/D83CDF73.png index 5b7dcfa5e9bd808a6b5cfea7697af2e4144f2e32..4b8c2ef368151147e2fa2967ac6a1d9b9f4af31e 100644 GIT binary patch literal 1772 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 diff --git a/toxygen/smileys/default/D83CDF74.png b/toxygen/smileys/default/D83CDF74.png index a15bf5984e5d62aebacb7f71d202760833b60248..368e073cd7ab1f3febe5fcd0242eda420bdeaabb 100644 GIT binary patch delta 1304 zcmaFBb(m{{WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+{NpAgso|Nk#rzh&#*L+37C-FxK3 zx^24;96vR0#o9&dwk+SgbJo(;<&EuiofB$1ddr(TC(d42*FCAeYwFyl{^?6MZg1+J z-aci{^u?=|Y}mGV!?xKg*3MeGdd`ZqvzML}dg0dnwcGYSdhvE={h&MAmn{VCv{^jS7 zso%PLzc0?#ox{Muz?kIi?!wT)D(k_(z`$PO>Fdh=jGd25m0PEwbSVP^Q*Bj9L`hI$ zxk5pGQGQxxPO3slWkIS!MQ#BDgGKMu(786Rw z)uma}7v1gV%9(w3$yxE^$F%mn2)nxB&_WI~!_yObyX)87OZk{~Bg6JU&Lg4s+ipD* zvf^TKS!|}n7g=^Rao)r|cOAZU$X3015H$N+REP48hnka4&E_aS5}YEmfa_Chj?h%8 z^ApxiD0(%M{o3EWBerL!nZzVKiqR9dzntvCIQ!J9tm#)~1WCR$4UcgZTViZezsGV- z#pw>&6O+#r%1$(RS6|ES;G);}i$(d~1Tg`p|J5QKj7yyqZ#+xMVsqY5@iJFQz)a?d zTZr|>=TR&NZw0>UpCMdOJ-zb-)26k`bK7QksW4P+Jd{`;`u0goO!k+Xtpd9~|4V;< zv9s?xJwS@WnKX^Je>AzoA`%>#W(@(40ce`DER-5r4;c7v>V%*}~#XeT6 z)6(D1eh^taYtifreMze>P1!hW(Fd)l^4?8ur=9dZUb)chqxnd}@XJ@zNzzZEA4K+E z%D=cnyTEXbA%_F+mt~@Yih3RfUQ+dzI$bN4%x%=1xL8p&>Qd8=UyU~VC;A^KVq5m# znJuHF+_lhzZMoa0!(lH=EQPrER@P5+o8+(R*REy3%(Z>jb=@rzPV@R5nKziBN`OjK?vrbFyQony=j>xLn{6W31QmppWd%qBWb)swDiV}@_ zB?bnDNuDl_ArhBE&t4a5a^Puwn5^HS#8h^$k2T=U-hKa#J+GZ*o$dK==KC~8l_Lx% zL!=(@9#NPq+xO2vtocsLt78iGv%Mn{Bz3vun&sCp$!n~WoKhgH<&gQ>vtd{8;^=j6 zB)2rXt(24eGTCqCT*;np$DQ{i&g}Lq{3#y6U$4#gHQo8ZYu$xEGXrnk?`z3Bzs#am zeV4tb9$(b_#m4Cj3=FCzt`Q|Ei6yC4$wjG&C8-QX21Z7@2IjhkCLx9fR>r1QrpDR^ r237_J3s(L%N70a*pOTqYiCe=(<}c3O6BX6D7#KWV{an^LB{Ts5S6F7- literal 1376 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy_RlfZU$~{CXU9I zW`>5YMi#D4CKiSU&Mqb{re=;#7BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{L zT#`-s%d9{5l-1WMbQ>Ew9Q@rsUwXqug$;GT<-+px`kwCj!^B!9V%;&)4av-&&W)*|17A zU1ktSNJ*P}=ExbQWfPBDXPuh&=g(L5nceNu`x$j~)<^{j2_;CyF5J>{#YZu~=YcT; zhe?IPwgia-2hS-UK69+=K0`vu%Fj%^3>R;R8a*<1XqfLW{Dev1tb>Y!VZ-$4?sI2% z&u(P*NDlt<{e64^+o8+@-{tS8&fi~i^26WP;hH69^X>QiF8%&4R^jhIb*}@;#p~jC z+r7WGHM{e_q{Q;Y(gkmCebqEy7km2G@dFmTkunMg7*q?6bezauQVlBeJYD@<);T3K F0RY3M?C}5q diff --git a/toxygen/smileys/default/D83CDF75.png b/toxygen/smileys/default/D83CDF75.png index cc30ad599f5118b6faedab6d64964b64feed8196..e1fd61416c170b9348f2a32d1aee0ea3b25c614f 100644 GIT binary patch delta 1752 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 Wzc_bKR8;3;VDNPHb6Mw<&;$V9xC=@E literal 1598 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 BYfbnTu$sZZAYL$MSD+081Hp3k5E-`eYX zbGO6UL-WoZoPOcL%?lT9y?y)Z%(0y(4{d()=Eu#OPjB3KeD>`1`ZH&)A3S#M>W#b4 zpTE9x{m%Yl=dNCT{pit`PoMsO{`~*p!!K8^ym|28^T&_>pPZiZ?(W9_UvK^Y_2}QX z+wbpfy0e|Frkj`9ra!xW9kuwvZeK1_s6?Z+91l4pvzY1_lQ95>H=O_Gj#TTq@SWP9+`;3{2Hk zArU3@L5bxG1x5L3nK`KnC6xuK3Kh8p3=9^%Q$xKs-7(;}Q`{xv$i{N1&_+3X`YN5` zKPDA+eaAiH)@XUVb+oai{QCKqy|#OKrt;3yU!ENm6W$$J`ZOu$>YZJ=Ml-fo?!K#L z{N%2Q%GIT>CM-JK%~dkH^wP7{j}t`WOIK&jNDN?Uub+{6>X_@hdnsSjEHac2NgF62fep=>{ITV$Gv)a@-|P(c8i7Ug=4ZV2XYr$nA^GrRSG|3tnboc3US;s zL3dG5GVe{%T-o1(%e>JlH@HZ%zQ-e1iqK0!;RX{~y&ERW;ko%!62 z8(u1y?YY%y`%H=BT4PB4k2wvut$kbgC*(3eH2D#t#BiZolI^&0Hv8j8QH#&{wOp9` z)n2;atAvGpuH>^ABRMn0DM`2Z+Rc1rW zDm@4H_B3gcwbpr6!Y8K9JJ7|F(9X}jXG+dQW&_nCktHskQ3B!i6MjqoWYBR>_+_8c z;8q&0;=Z^ri^cw}UbA1gQ~g0z(_qh{&_`t}nyeQTtggP7B<_3S?^O?mh0GgGtxq1S=#Q#@e~6xN+gSS1dDXIsY9Q75Q)pNC!UKPG>~AppfZ22drw$9-`16TSH|x7|J$N{ap#@+;)*SW3q3S- zryBlJS`suhW#zI73L>GCO{a42&6_c8?dA-Nvthe0|7CSpab2G0t^tdnWUqR#`6(r{ z+4|E~TXee=z0C>A6q|7K-M7dGOhxTBe^~1)6pjS;Ocr>4dTRgeyY)ZLUsu1!a*(%5 z`boJT0|SFRdP{kVo554k%5tsu7SC(p-G6Lft9hTm5I5wfq|8Q!3>}K kyHGUb=BH$)RpQq0QZSv3fnlPuDi;HTr>mdKI;Vst0FOR!X#fBK literal 1403 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@r-129{<{CZ-0i zu7-xLMiy>nMy}>AMy8e)hQ`i@MlijedBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{JbagT{a5XYCbTo5zb#-$!baS#aHaBoIQiAGDAt%iA?G$YEL5Tw?vA~3a zT--oRaJmMiaXSS>=1I*l6b61LJB>7srr_TXQD)dNT!zwB5HhK4-FW zaYQ6HuaXkeLb115=JrZim)F%f%swE0ab4+YGue@h!O)K{CBH8fFE}@IpGAM3=euMt ztFofV=+^px*6XiXlO5XgBef1Rf0%7ucGSJ0!CxsUV>{>jUdx+rH%(u1-fhFSZbwrt z-&q@MD_aTzPZxnhfM6nTu$sZZAYL$MSD+0817ll&Pl&6-Q=b3-|9}4c-{C1w_%pe+ zuY6If78nUQ#+w(>8$@tXMxyrC;y)}8N`?~@&DXO|EG2?y5+L)n&+G=j+4&V zEV$wH|Mb!IXP%|CdMpKhgVt zYQ~4jdjC&N{=X^X|3u}N(|!I=Gx)zH=KtyL|EG@pU+nw;+U)-ai~ldse7+>&|6+~* z2a^8ps`=gE`~PI$|MTttH=4iS82Nv_?*FaHzkA|eRi}QasSoD9TUE%t=)!sVqoUsK_l~V6f<&8tT1ijsee| z*CLip7q82-OMcJ`jePbav-77wn~MGpE@{s&Z{3?0&cC;R5p5T1eKOXq{!514&L3xW zr_A0wyZ!vWlc)Cd-hOK~Lvem=aFXJ()h4lvS032*+vip?XO2myt9kHoo_3`hM|7Oc z@{0QPyi1O|#S5QN&-?kd{Jv9=duKz&758I`r`J`?3|GlFpK!OReP3C@=5?!0AMxb5 zbRMY?ekl6UWG2gsrW(cBE=P-aZ_3sO>DhihuxJ0=3f;|1dBkV1Y`^@#y{bneA$iTJ zIR0OK?SqLw>oXFDREvCUHY$MC&TvgnH=&8x5JlQ77RYk;-H^q zQ6Dou)Y@Wtz-c>cL8tJ0|KHeH8%~hPaofecvgStXg|4Uel{>xO-1mIDp``V9$fkcV4I&bpJreiJKE9 z>Av)wQ>$=3{|WO3nZ}p@Cp^^IeLFzrY;*F=KOc6lDcL0?rMEKBEok+$?aiF;8jjul zwri}JGNSR^4ZiR%+-6<>bA0* zIJdsbIvUlUqg2vZFUIcvWc{?i7xKScZL|J*`?22hk9#~`{90x?`|b8e-oHL-Pv{LQ z{ph@Djo;G4zxTJ)F|t;7t#Jz5XTre1aN5(wF+}2Waza8vLRw;Ka(c>>2TvZQrLcUe ztoV85>Q6~lmR1SLgG`5{W<}k4Wwooy>Q@4zU?8)zahJ$x5p1gVV>e;)8g|!Ymc>4D7>*w+U5+Xc83`rj+T-flT_R84hlm5sHd*|Ze2r%#?eJw8A~pIbB`BtS?hFhJswJ)wB`Jv| zsaDBFsfi`23`Pb9nO2Eg!vmQ} dF$M+(NstY}`Dqh%WVjd@JYD@<);T3K0RSAB_=W%g literal 1599 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>6zKmL|@|CYH{Q zj)sP=Miyp=1I*k-xlC z#pG*w)BAXNt&dJFzuFrW8cXS$aj$pK(RMC&erqE(^<4?W1T0+bZhFCLo6+=nHlr;>PRnTu$sZZAYL$MSD+081LL*;pAgso|Nmb;wgU?~edx^ojfb`_ z-?neao z7fjzD&+xMT;DQfV_P#!`@<=A@{y2subEh5Hz4h*`Yx~zMI-Jb#?bP&d*SCK-GWSZE z+{t)`!$}PLc5XR;=ETVz>rZAed^=F~?c$P;`?_w`NuSSTJekRG^uX@Z#}A)BxZ^?z z!_R#QKaMwl-d*vaMc{fF^MzuD%jeIWIdSCt!Rz4Y+=l%Tmn>cS@ zzjAEHy89gr{|{RI+voLTi|dEkqR;zzuV1;iYxRQXlNkOV)%}0a^xqES@5?1W%ocul z_vX1%$JWf5_;wD%|5IB3k81zlr}b}>?BYosAD1vJn$&S*dfAy3J=gcGzISHd?Grn$ zY+rP8Nn2b(d~bK>_RSkk9Xot*&#om4=hxTP*8KnfpY4@X9|Hq}Oi7SmFoVI&`#+vd zP(L({Y0)G5d)A`w6<6>6Y4d4|+3C|Q9$$D9TUE%t=)!sVqoUsK_l~V6f<&8tOf1jsefz*CLip z6YEn>+VSyLOkbr_{KuvHh21mnz8tFpwe z&zL0n&NO_DlJOnMdHnf&n)faxv42vry_U%Nc<(!g4JVFHoZr!5zJs;pLH#x+HHAYy)%_g518I-1Oai&F4$YT-Q&lk6HR}l(MSll*x?Hh)Z4B z^5_uTCZ)D;@4N}`8u=G1=HawMb-% zi)R$ex_ye@`JV`HnA7<3{{%*l-_u=1or+S7>l3nf15*N=iFfiQmba4!kxSX7jkdTm)ls0ilV(kuH|2!F1Vs*^6K77HJ9+l>`BNhGLV5y>oFZbP)2B?EDlQrp($&?-vT9X! z*6UZNSFJvxs&bGoJENt6ZH2SstXa`fw{KZn%}!tnioI*QxAw2_l7lu3A9;)`ZCwJp z7#J8-OI#yLQW8s2t&)pU6H8JVj0}v7bPddP4NXD}4Xlh!txOHH4GgRd3?@20Tm{yk h1JjY4pOTqYiCf15nMkpTit1bp44$rjF6*2UngIB)4wV1^ literal 1630 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy_T*nW=3v~Cgv`# zriO;DMox~7298cHmS!fdu9i+t1~9#zdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{hhvb;Rj%cWPdWvOnKSnD0P@i+?+V;$Y|D4vPxOgA4lBo|TV?`^D3Eq@q`h=kXiMSsNC2 zzrP%Mde>E9Thptv6!!1zI``dE*t+YI=EOv1mEPNnz z-$mn2Th`YD+6U%_uXI~x9=utk)r>2iY`rhIQ==tvdkdn z*zWZ#Cp{0h>-=$K3!TpQdCvX&xBXUhecIqA{Pgq9Dd|l`D>*_R&lHNC%d2+vA)BRd z|M}|=PPc8GcKYdtPq`dF1_U2}x=GzsLV>6^PKe+j{F-uS2VACDsPMakhwZN&?JKu5^E$g-o3t@lY~zH&4@Enfnp=a9bN&%aVDSIU_U_|6`G24i N-_zC4Wt~$(69A!zZ>|6U diff --git a/toxygen/smileys/default/D83CDF7B.png b/toxygen/smileys/default/D83CDF7B.png index f690c80036e71042ccf0a7774bce17b5dd91df98..5fac44f5103bea8bf40873400989cdae68a01f1c 100644 GIT binary patch delta 1481 zcmZ3+^O<{sWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817l2pPl)UP|Nn0}vt2&61A?v`+H$Ko z;mY2%x18B-IkR0lb@1ZK$q@D}XSSQus&4fc)>j> zKK=aq`TF@|SB~#~bN1ldxlKQx-g|!k#+;q`=gSy=A1nNMs{8dq=R0+5XA2mv>{@ZXp5gzIpx=j5J}otV+RJ^thT-()g%7$I z{vWmZf583wdaVz$g&%Y>>|Hwb)ij3xCw2cHHu}F^_4_J`4YRsGEUaf(Go$;+^s=*S zCf+)-`QDj**Y~YDy`n26HD$)MDF^oMI&tLChIOku+FJkr|NpVzkUs+hgJemNUoZoM z*XFmc_v%hdI=uLVuJoxduP19wTjhA_RIr@y8wLgj#w2fd7lsa2Sq}yV2KEw9Usv{L z?0j6ROrbGn85tOus;fdGN`ey0>lF%$^3yVNQWZ)n3sMy-atjz3EPAJg25y^U!0|U- zG}eiQsnD{|u-jd;ztExVkG2c}7dr{croqH==k?(oi zau2P1r8^J(Cwew$b%<~B49Qxf7_Ip9PP*KT@7o2xheQVzHg*;7^b+*0A)FMDG08ACq*dWrY@m=7q6yVo@d=5ug!Z2Z4lynu0~ zlhTc6DOqgJ7b@P~c2UT&=-^#nmUMn6N7Gj2tMYd055D;cKV(jccDLZQo65oPC8_P; zzf-qP?$~i{#b$2BlJvjlk1ZCGJ?NO;VmZC1-IrtOjBm};cOUpCdxg)te24yI|2nbW zHD`Y>ZEUO$J={{!yQ$aY<(gSj_sTb=p1r*#beCf53XjxU zxWmA4&4UlGPV#=z{jkI>sn9?0wn1ylfjKI9RhLWL`JZqp7x3$IXZ|Q+TjtW&G)2Rc z`_U`*Pk-F(7*9*i_{T4KY|$?5Ns9{$vsmnNrut2v7I0ySTfLU&It{_? zuAk=Ec!#s@{KM91;(A-&S)UEfec#po<#D)z^wDc)`B%D? z8OhxHulj{s@j%!1Ly{WxatsU%hdo^!LnJOICnO}LO`MXFn*97hN=ga?lb&X#WMrge zWu9D|-G+vyMLHZBnxDB>cyMuX8n0Nja&>k_R_5y$uU?)$CDCCZAtgC`#;lpnqNkz^ zBBCO1-?(+tS=6e-$im8U_l{jVokgoU3@fTCfB*RP(^)ji$bf}~jkUd{zRgk8%E+L_ z-NnuI_z@@3DkFmI2Nt!P><$()Ffgc=xJHzuB$lLFB^RY8mZUNm85kMq8kp-E znuHh{SQ(pInOJHY7+4t?oG>X~@*G7+ZhlH;S|x5BJN0CuCn~CQF)(<#`njxgN@xNA Dp9r@v literal 1574 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 G5}E+{fe#%3 literal 1593 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCz2*j{X68&F5$HM`%ESISi{std>W7*1HUQemy&<*-#j z+H0p}ADvbdyzSE6$2Yx%-z84EX=!P_f2O7JbAuLvH>ziPe?L`q-?~Dd>v{fFLz{VP zCTf|t|Mloz&bIB>`g=PU=Dc_i%b#z{Ie}@)$B6&`m?ZdF9u%B-cua1m@|ojJ$F${2 z86Q-}q(PCoDBLPd(bmW^KN0)q{!^=3=t3*(+~w%KiCqbam#fhJ?v3H?!PwRonTtbJs39 z)Vox@>w<~P)3VN8pKdd;D9taqp?_l2RlTT$#Fr{xGB@b>nVT-SKH;%Izx%{_Im>R> zo11?8+8(HH${ZRNnBe$9eSWJ`=CY>NV+9eBv8|hDolexau&Z#J>c{>4b1w8gDdOz2 zDzr6Tcva|+-a5s}XO(SDXD_y!`{bZ+4%Y>FuIUe|mitaPW7G3WC`2?MvsrYi!sO^f z{VQ`g_g#3_o3mzCwWZCX!_1q!w^_IdpV4W&H^azgcC1TQ(K?oRn`*61Thm3iB-Ty} z-e!?~_V$CB@!S&r=|xx4-G9_Ggi0G`xXv&;z{=%+{^`fJzjONjy?uD0oR>SgU6 zbA5dE(=t}3R(wjA>%4Mi&%B>O?$Igdk1wmA_mlnh%F26pQp__;v?tD*r!OKBUp`%b z*U|YoTi3`vdHL9V=HYjX8G9f0pV41;h2{R9S38UUW(C!|g{)o~7GxTbdVTUVeTC2d a4U7zd|3&?(etd`m73Q9QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#T5Z4!B zGoJ-bd+ayqs$u0to4RurRi{i!jvE#n*2y`jnYmv*?M2v3IC>T|4H-T5n*>LXJbNFy z_gy!wfuL*Vwbvb6t~<6|acH_|QwKrkEUMt>lt~F39XBk1*mzhc2Mrz6%!H#CVKaYL zZ2w<%;AiEI|J^75cU<}3f8}fZlCKF%|My@0-+bx+>|6h*T>D?N^Oe`+`|f?Oyr%vy z-t~X#jsG?0{!hPt-=*{aocsT$-}ve?=cYsZ|1Hn{SDpDk_4Xb6*8h`l{%^hff98#^ z-g7RR*ZrS%`+xP>|D9KFSvIUth})!@dDN!*lzG)zlgi_I#lN$5ZqmrOZd!9zuVSBS z_G8b!PXTk@`OWwgIOlc9^#Aoo{+I0dU$XO6@U;IqoB!7x`xdkCTg-z0xtqU)&iP+< z=!SjMQ@5V0Hg!Kk=KoLG^u}$%|Ns95)RSu&7#J)|g8V?S$^Zolhs_Sf{hh*=Fn=f8 zg8Szh?k~)H^LBOE)o`)b4fSiawbn&kJ=CDRpnl^X_+~x3MG{VsR|Xj1q=)p zy;DQIH_b8Nx$|4ZvgzV=J3i)$>8o^#|7DhTE%sSoIZd zhb_4*5Zue+pzmf8BOhvQkuJ_VcP@u!_`UsQa$Y4W?6U>m%`uiY(wmUztj3YIp4UZ0sGf_Wg7=J~2-5H&UB=jXcFYQ{T5gi2P)Bc2VRDPG!UAvya)Ne>AzH z@}}T0^GB|YVjbMf(^zHZDerXH$8;>RS^31`_7e-=>*+|Iww)y=(2&#A1Sblg(yNg50Dv?5gL#iF2KjW}oPFK}OcyiAkgQgTTx8 zsqDTAiw<+Oc$kD7<+#lx>QPkS@-OXXf0g!q(L0{`-{brqU-URty4-U1+t`mge|^+^ zu=dEcqwJoGt!DDQy+7#_JBw=9nn`s@6B!s7etWt&hDcmaez5Jy!_)?*I6Ln=wmR)> zMn^?A1py(E4ci3M1U7EnTrMD*oFpV%T2i!4G<`xrVOeSM^e-PKeEIU}+sCO1A3m4! z6>#%#@$qq$aC2~eP3GY4?g(*}`@+@H%bnce@8RR+=jrS1?d$31<=>G$;naj_ z6Q`a&bL!NT=~E}3n~;>i#?<;|&Ye4Za##gsH@3FA8tz{Z;JI?ef(XeIi3zEpfx4Qy z!NL0aYXd?PQ&SU?FRxg$YSpzXtJbYrbtNr1IpO-sm1|dDPffqRcKzzL7ZcMTT)1p* zkaOYr1A{CBvkT@A6CSWI1sfOp`4w{q3(Rih=4D_ANjCZZ@yJY71_lPz64!{5l*E!$ ztK_28#FA77BLgENT?2DnLz56g11n=wD-$zq0|P4q13T+0d?*@n^HVa@DsgM@Ps`cF yz`!60vLQG>t)x7$D3!r6B|j-u!8128JvAsbF{QHbWU39Q!0~kTb6Mw<&;$VJwCe5v literal 1639 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 WcU)j$w%Y=#Ej(TQT-G@yGywqAeSBU3 diff --git a/toxygen/smileys/default/D83CDF82.png b/toxygen/smileys/default/D83CDF82.png index a9c0f5b8bcebd357a65225a3c56dc0f8f3510a68..5de5f4ebd03e9403c0f60ee79644d7a26f4bf949 100644 GIT binary patch delta 1607 zcmdnS`+;YIWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LLFspAgso|NsB*>-#^4p~z7v-IVkH zqQw8RA`obz&wn_Y!|=b|etKI){emgI2lwwgaA5!JzV`mQ;sujhXZJQ7I(Gcfk)u6P zM$JJwMUFx_mVB|Atm&ql8K#^O%1nL|jQ*00kxEP{dQ7>NY(=&lc@}IbdQ1^YO#YIL zJ`#)(%1j`e`uhI&Hvez8|38P}zq0bDgBAClzWDa@*Zvb{51c&r@87@w|Nq~sfARL< zsq_EOwH&*C|M<20&)_kRERd*$Kt zFF$_XdG_YurQ40sF3Xp%m>6%HVhXO%9&@)UTnviZ^>6|CtdHQGbh7aSya}Yfq_Aq_9FxqzSz4PwZ?|1k1cUv<~cp7kxfq{WB$=lt9p@UV{gMop8 zy~NYkmHinzAD4=?uv3W#0|QfCRY*ihP-3}4K~a8MW=^U?No7H*LPc%?1A|5H)KK4R zw+%S%ycV%-;^BDm*ka?>*VVD#e#|}ivru;X-};vNU$g7$qH@`b zi+??NWuBiH6uYPL$D7&fly2|1o9E2(>1OXFv0FJ;v$8{iZ<>^%Ko*hNQaMgsQWyYSK@cJj-mI;%;ZHmEw{!zSrw-T6S|%8@G?stIQM4 z^Axylm>3${2Tq>kb?J4YM5y=$$%*|rjM_Ji9x;4cVtia%=-~fd%nFMxIMmLV_+kMo z%d7f+CdCCI8ZAHO%$zk*F~a7nov2C&gF&>j^#V;?M)ex?dx3|ksT+FL{ zMY+H;#Y`nvE&b6g_D_G@>=<4P?)W2bd!Ic}d0zuz_O^3;Gr&t8*DF`@VP zKD0V(?R{IiSgbwZ*>y)I4@;G8Pd$qsJY>z@mSS7-=~2C>qvbP(ElQ_ft8~q;SDxY5 zwBS6GkK3gsr@fD7Bsb-py%qS+ov-giyw2~9_fLgOzq^v(zdPpGXC=++=4Wr_>=%u{ z)bHPz*Se~kKPb6$Qs0~VlRj~a-ssvt{SQY90|Ub+PZ!4!iOb0e2?+_Q4NL~j6FYn7 zHg|ST?&Lg@kix=M@Uqab@bd?wMu`MQwR$_XT4v|E+FAu>6$OP1m6V*Eq^umB92KQg zClr>g&reCqOH9d3%u8FcUjLjzVopv9s|ZVywsLZ^CtEhRbhP$_1sgX`UEAAg+B|n} z^Wv$z0T+%PJ9X;h&DMig&)z+J`QYh;mk-}Qe*L_CgQ9_kh02w?$~rbmMp{;CW_^lw ziiY)?ma3+@w#vrZ*6QZ{iu)IASg~WtmNjd(>{+yF)vh*2t`&>6t=qTIaPi8WOHIRP zIl75Ow$8C(-rTfk(x$Fevv##Do3^b_kDFo6d{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy(X?krpA^|CKhI9 zj)sP=Mi!=)=8h(g#>VDuZf35o#xT8}dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*p*AzF1&qc8vGY^wa>`3-XVP zj!4y3h(AtM-FWAw=8`?_cWbZoWY3(x^)_pw>R%a+eViFvqj{G%djH|uK8af*yVWp6 z+af62WC7b5vw2Fh=9~#xqHWRPv3SMPj2!cx&xc)|_}UIhY_(EIi&POwRoOOUe%V2F zuS18_n75~k?vxV!RJ3WMhUUQ~XFI=c;E;ToIiWMs;*MO++}H!J&6q19rCyx);=W^< z&kwUKm1KrU*5ecEuQiCgb*xyXo=|&{b$hD7?=0Se7q1^mylLLKo&EmX$qTMeb1XHP z_VMR$?~M;ilG4(o^rj2%yDIvjqrq3@`3b{>2*W-1d|8-9CW)>*arLR=@~aO&9}#%b z)04C3O)byLkj^zeqI@DEN|up~)9$bx-mr0IGh>UBD=AnSGW`xN_&JUo0SpEFeJnJ7umW{mo#eJ0cXZ1FC3QW8hvB~NbyX$GN*y)cS1wTs`O;8QuTspBZz$?Y% z_yr{`k)Ac{*0yKpBT2#*W2Gt~nTu$sZZAYL$MSD+080~1$(Pl)UP|Nj{>85uGe=X(hoB{I$P z6hcOG+yvohmWv<+)z5GenBgQa&4GWi9p6M7-hNB&UURNqbFOYv&TdoAPGgQvV~#dM zb~tL$V~3+A9dUnjxNnp_<`H0?(UzhACBy3{eb|Dj1%WF&s|hxmKSm z@TP|0|5BOPO-!$A8QwQAJg;T?zew^$CBwxG&O5~dpSNcJ|9SNP7M%wL4F6XvJuG1O zzuoZb_M9u(T!)i+KQHk3dpQ1iCBy$c#{akK{yCiVey;1GB;JR`od4f5{Qs5y|L5}m zPgi_uWBR|t`2S}8|Ib$3Ens#1hVPRYp5!sS zDPVX}&hWp3@Bd=CXJrh3rYrq_zv)RS!~ccy|J#LsHZVNRVR(|m@F8S(=70+g5m$qBmaNJK5Jpv8pgTNm-W>GhW|fB z|Nm1BVHN;toLZg2b5hVwz@-Z%f=y{$ibq`Z{p)=h5d zLq}v7ou{68`QzQ`DGm;MQy(%gFfb;0ySp%Su*!NcFfg!}c>21sKV#?PQe{1D5wVDY zfvLVKB%&lJv0R~`C_gPTCsm=OvLIEVBDa8n!J>C+sQ0Eh20V9ui&!>Yye`vzuwwcu zo#H=Rj_^23Oxp86D@P|%cWZr0U|rq+z`LU);;HXKC3c;CrgT>PxMOO3O0eM>gA+Z@E;bd5PpvkO+at*r#cwcs z#j)*cw^_EYVPV=MDE*LKfA@m`fB$=F8@_i47tL9)%6Hc_r-yew+(}y6JJagV%6c2a z0LhO@`ALbrrr*Ne1fA++d-gZCwQ7~_${h|zSH`i~9#*bklvNen=$#P~B=OKxyyq16 zA<23CxqP~}E+w;nI#S$oSoqNH_Y5CS+?+T+;fVAhCZ`ki-aMP3AC>P_4!f8q8^m|L!C7Ik&@z{~LprSQ`yNg?R8>>v5?M}yi ztjD<98tQwB+$VAfF%{3*@siQT@#RAy&P0uUflfk3IgK5{1)fhdPF$F&({Y*q$eghU2WQZ<@V<3mn%#s72VHVmsK5ZQ)XYsx@W4u-9HmdHcftb;h(A)e=(Hf@xal$PFDT;TL0 z=8oF3v}e!6riIz;QK+|*-@mnY?cBZ1izjd9-p|0?ykNrW*}L19PhY#efA{+N6BL>^ z)Hm#|P6#OIC|$PSGcBzmKOSEW%=gD);cd{X-)|)_Rfy3cCSwlFZa)n-!Ibq zrKNMSn*YpKd@Prjou8q3SV7x7``etIpYH?*Ki7}g;Stx^=opy!anjP$LDSbq?9AG_ ziR;AMRavj3W<_OhDJ}WhV8c-O&o#2NGRBF4fkCyzHKHUXu_V{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>8AfhK@#NCgv`# zriO;DMvkTi2F^}Su8!tzj;ozKHmHSMh$D?7}xK z0jGZdPP$xNyXskxyPcP;--J6yN)?6wMms*fC!sAH&MB&XCXaDjXcEf~^UNfbtpev{ z>n)8x#y9L%U6y1ew_HtUX3*Qu8IeH?pFQU)Pqq#4d)FLnRU^;)_}tX1x$m}TGroIc zBs%HNE?wU~18JKM&&`Ru>UkoP0=gUYH5BZQGKIglh}j%f@-XWB+YMQc;cfZ22ZNdf;4@-@Y#N^WzwG)X{~S7NcK<%-jRJ31C=MHF*S*r%BBe4o>VlkL}}r!Umb zUfL!%;iN&!6WflSMch%(F0yzCDdgQ*TX5%C#67;%b%J-oeFHa{p5gabRz01~aEU|A zfh(LjZvQ0Hm>|YyUI~45`!4#}37wyIu=c9X37boGU;Jc@Fn)XBh7HSqo)8nUcc;@Id<SfQ3iohheoB(x(`@x zvM`0T^6zPiyyx`(eZzy2D=JMpPbv7-wQBG^I`8YC@1}Kn*NJBf`l8s6JZJvK;<3^H z;a;^6=wOba%vQ9KP!sZh%c}Rc$H1>*h ztIkg>IZ|_O!^SCpn!P(#cPTI4oU(iB6{%NC6H?Qg*L%eGzjS~9@#V}G{pKHzw_;L- zUvDz~!)w!X@pP-?O~#bai+WEd)mmIApK^QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IMv(xY5Lee| z2A3!Xr$`3J2nL5RhW`uD^Gflk_W*U2fOhzV4CkQvp zkt*mGF6);l=o0?_g(0g&uw$ug?`paK3=9+2DlpvppWY}qd81-%IWNPj{~b$Yb2@~l zZB>dc=4JcOP(M>TAf2nAQ;gxp|KJ?%&gHTh4Z>l$JQjXTZZT}(dAym8!bSfp zG)6GAM>5Pw2Kh^)4wz@=YPq!+cUdu z$*z#QOI|#4Wlf)5dg$7EezEB44BucB+oC%snl8^K3dR?HCHte)Rb2#SFuhb8RZQDfEic>?kc4zzSlze9z&a*FA1?OfI{6 z=FMXvlkBj|YfPDKAD&hE8JawIwFK|f7THV*kGRy8El&<{C$YANd*@Af->~o&hga3A zPdd$>PaS-@7nwAyOcX!dB^Q=)^~T%@chwWTo_Kh4PT8Wcu1?{*`6teZa|gcEpJ3>H zvyP+B?ctSzx*J;3u~ANrlcg#>ib5Azhn(EkP_)kdNs@f(4=t633!OJ+O-(s)nEl$s zNl&Mo-2Atd$*On(Q~Hw`A1ii6%4G`pluURa`DdCe_qpWOClj15E}7J9{7L+9-8u7L zS%+djy}fw4(ngx|ZuzX&8y9QuTOz-o>89qkygColq{;6-eAHhUKh?3T?Q#^$Dh39I z3!W~HArhB$k3Dy43SeNnaK14?{LmGdr`t@vXGp|37WT(XmD=;axngHYooqw*J@=32 z4`?#L`bWek=V}nrZJpVI<mdKI;Vst00)X!nE(I) literal 1502 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`Gtf;oFf&vz zGto0NF|ahT)KM@pFf`CNG}1RP*EKY-GBvj{FjRm7B|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9ucp-l$oBHmzd*{pIn-onpfiKVyje< zTcDScnPTN?XldwbWbA5a>T2X_XlQ8RW@Kz(VQk=J>f&T-Wa0`l1Djq$GgC_^Q*#po zLuW%nLsuh56ANP}V<$IDV+&VDS3@_LUeCPZlEl2^RG7V)nJHFKy~cR;S~(Y`CYIzE zh2-bwz*0a!Mt(_taYlZDf^)E`o}of`W?o8uc`+z@z+rFYl3J8mmYU*Ll%J~r4qvNG zEcQD)Iy*Z$8=E`185p`685&wTnp?P87?_zlS{Rxd7#S!*^`?*$X8LvtHu|8%fs|Na zLP0KWASO6ngVMO20wVLI=B3yw6)D-<>EE(_%fP_I=jq}YQgJJ(q&MvV>x{+px zkMS{GGyeTgPdLzM%)^z7%i zpYqM~78>wRu;*RQ{qH}s(S#5CL=$%XJM?mUzg2L&L5#)cZ-Nuzo7eHR&3wA0frpJH zZo%0Hj7!S1UUPTFRcf9(aO~Wf|DTTbM=ZEesj#nk%{P1IW!yy#72WkSWX@li^_@pX z<_yo}MfVIFekc^Ku<7ES@sE*H!p`RJe-VYdC9iL#w`od?O3sLskW`BgOJexw_^4gX z$j32^|Imp>jjXB1b~t(VZZAxGX5F^G{@>w^i;Vc0J?>bwCCYlpW|=toaIrjE?Iw2U z;k2?vJhHo=O4R-Nae9LM9|j*|10w^SRe#SgOkrQm{77R4x5U|6NeRE4wk2BJ@oiS3 z3w&2s+Zl(Po8;zMSe{qQ^yvK8YqKS0_5D&jBI&#L;1*ti?H;EKZfa|MG}y4waAC%~ zPc`QPHU!RjcyO;ikN^A{YYvfBjE|HzEU9tuoA|rHqi4`&aURiL_(N*(MD}knVp<%2>IoZu*=k5^WIp!)|NrEvGiD#+ jnC>y}v;1;K21Ygpn`em%86r=sK_#xItDnm{r-UW|KM@yv diff --git a/toxygen/smileys/default/D83CDF85.png b/toxygen/smileys/default/D83CDF85.png index d9a4273e84612cb8820bfb4e6aa864bf9a3a3459..2b96030c11f40c5c7d043ea3c77a611d91229c59 100644 GIT binary patch delta 1746 zcmaFO`-^viWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+080~2F_Pl#&@3*)hiH&*4ieEJJJyeDuWO!^gJQgx7DW2wI)%dTaNJ)qc9SZr@$U z!Eolnl`R|$=dWDf78SI3^PV^F-tRtqa_7P0+Yg_<`{2>xQ|I>{Id$~>mF z|MAn?_aEJH;MjrVXD(j7`S$&X#hdqRKYV&JC*yJ+#zTS(mxLK^h%g)!V7MpB@KS=| zy#&KYNrrb43@^kP4hS$Dsuy5*EY9#rlHrdu!+#lu-_i_w(-Uv?bv#_Z=E2F653k+0 z_vFR>r!OC@UvvE8jVrewJbwP_?Z;1Fzy0|3v zo-Xx{k1icMKd0)+&N+`R9lvw&)ZUe|cW+p6e{Ij-OI!ZGz4!m`pTEy6)W^}tk~@JT>R;+m|+Mc&K2uXVww9VnG&f_f_|6{0{t8 z_GQ&qxNUVP^TSHFdWHq!#ysEBukjtb>ALuwU&|Ej*YVQ*UL`E-a|NHxF_tsZ(>S_C zeoqYRxAzOwa&{i7omKN;vu8@~TwiA9a!%nlQmN9JlR{OrtL2(fPv+fnF}}#1mUPa% zw{%^SNr{lk9fN>9PHR|?wsPKP;yXVtMj@V&xh+wHOTNtZcEZekKK1!3_q0r;{uS^F zO%Yf;d$DFq-vQN$ixr&HH!AqupKzW33A2HGed=WPpQPZQ=CG6>%z0e=W0I zeKz{z*5BVWAM9;;@u+#p#JN1}&$ci5!`L#bt6+oJk9GzIMt)Bh#}J9j$q5Mw35lu6 z*OSweQxh8)`21>_ot2%LYyJ2HrcZ1>edEZLGj|SMI?~nMJaJ-U>+M^|uARGg@7}@s zYsY5HZk^b8{Nl-*N3Wi}d-m$l!zU+BpLp={>D$MzpTB?qzP^HihDFOnMH3wxB_k~> zD=RfKJv+sT6C3q4Emci*ZIx|}O|`At=TCH0FxTI|V8eW$X4W3~cFajNGzv=hCff_paT#c=PIL9u0+i^_Op7w@-Z3^3QYc zW?R%AJ0rPS&2P4?yVtD7NVl?gTfzipn8)WGbgE=aOJhCi+CBYD{H~mxlRkBcYB?~b zNrsw!ohmge>Xwz(E-Qw;2R;8+y|`M+z`&qd;u=wsl30>zm0XmXSdz+MWME{ZYhbQx zXcA&*U}bD-Wou%gZD3$!V35bM9#mW)bmZozWTsVO>VRpGj}(7DQBj?Xfx*+&&t;uc GLK6ThfP$z1 literal 1771 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy{67CW`@S*CZ>jl zmWGC|MvjICE*37vCYI)IW=;lXPB6WmdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1n!Q!rsaywJ5VJHN~wcKUV=9zE+u7 z>^C=bHLx@>u{5+aHFPyHG_-JWG;?%xbTf2wc62i_G*yD?O(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI&8|0OBMz`!i+>EaktaVuuh?QD^9k>mUK&%D{a zdG}3Uv)yT%r3=ergIz_E7H9?41RP-({Jh|ct4osCLG_Od{r@t1E#z-?ijH~_tiL|A zTil>2BUf0pDLct$(b-+eM%iYYx8J=yk>7Wl)%ynz zT3@DjzZdl|`T5XrYJVED!xXjEPd+W1ev`$3Ghu>$S;K8zGgmJ=m5$YK>`H%~+V}6p zivu4F?!4h!!^^i&s`Jpx&PGGFbCyqTSykL+ICa`Xckh9TJ9l1GulmZhaeo4TT3V$G zuaZ>Q!XTbse-f6&CjIv;;Cy7Iw`*Ff+iKp$7hUeY=@8doP>l^&iJH4k}X8>|}U+ zYx=#j4w(|!;cMreiEzEVWXb$%0ky2FH#Id#iG5;TXkg{tFR*4h!|7wY_Hl`wKcw?7 zY5KnXENQdUrg=Zz#x3_jQtC;g&9-CJ3(wjf`uux;^7Dn0J$U;aF5f?NE$%=UkHU%* zGj2w&xcS(9v6I`{%yaxILLJY(DI{GtEPt=}%a~Qh^8e0`d*vsLE`;A`_o))7`na+@ zL;wDLyA;=XU({9{WNpfCkpS|zwD`|WC^Ow5F(+)L;c~ir`e13X$ z`~9Vp3g_Qn-XUA9aB$Jo%K}rjT=g?pGOIPW*>HXOt{orJm>|hzOZYRYrQ^p#F~+f!O}9hmBH|yB&hoF MboFyt=akR{0Q}m(x&QzG diff --git a/toxygen/smileys/default/D83CDF86.png b/toxygen/smileys/default/D83CDF86.png index 3c07fafb18baa4ea316db1ada2125882e79f7d5b..bbe1a2f223b955262bdddd83848dc7b0d94aa981 100644 GIT binary patch delta 1577 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 diff --git a/toxygen/smileys/default/D83CDF87.png b/toxygen/smileys/default/D83CDF87.png index 6fb75ec6dbfcac739584a7d7ea261ffa1f48b1af..1af4546ad31d8719c14d9b2a5f5c3a5ec18b7d9e 100644 GIT binary patch delta 1558 zcmZ3*v!7>zWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LN`lpAgqecOG86{g6S##N5Zj(#PY% ztp^N}Rw+4A$+=PIZ{BB+b7nDjWRQ36?8@ru&N_GFUSyhUeM_BvUZFFCwl9N<-|D56 zYnN9tX!tSc_!o#r6LMmG&0byh0eXqfYK=Ikl%nz$fIy3d{Cp z*RV_<2A!zW*KUUwgtWGK2j_+|_{aS@rjuTi@bLP&^qRzf$8{D=ac^q%W$;Wqb>$X= zO$md44uf5BN?q#z`e&2=f4!Ynml|3U+uRi#P#ni#U2@{`4F<1zt*pGoa{~UK(*A!Z z<^S7F|G(Y&e>>^ValIAu{X|j%d014xHpuWRoDE#oA=|v+#e6-7+2RY1kYwj?mu$=DnnNNjQSZl{qxe^ zAGXw~Z=Jfh@Y#NQhKyM*t@V}D@|{{64xhWs5Wm@_tF3-^(T)v~40(%^C)cD-u4c$x z+%-S9X-+}q%#y=rE-|FdwLI@zQ~Yyl%ZxVL-t{Y z%p(jHn+~43@c;k+OOsCecy+yVv$i{7cBftzkC@cg;1S;eBm$=UZ<;`8;H zb>Du(I?mbAcYEJ4<2H_kv1?hD&A(rNVfznJU6t7-`tx!OH@2sS`yHEhcI_>_-pcy- z{l^y@ACB`qJSldQnddE=l?S#*sOs*sGE4gy`h88CAfxMs&d6PF=A?X9S}lIqEt@fo zd-;$2*>CkWx-~MGW!YAouo91Z*L>2NC#V0$!u7IqiY`tQE}Zb%&Sz4k_(L{3Pcg<- z3h_6@0#6#Y=QdA0ZdH8T;=bFRw%b-R;X5pr*PlLo{)>dpgUOMhZzoAzj_CQ)r9DqY z`_fChx;@r=D&%`)Pjp|Yi1U$nZ^n4w#L>X{0Y|tmF*&XIe~w8cKue@)E#Fctk*4Dt z^SB*1yb~~s@J&3wm7{U%!YlG~*dP4U6Mo2lWLsMam&H;KhEGW@2g*X;ip0bef4S)@ zu&evu^9L8}JNuqIxU4+q^oe#~jZSAVXo1d$>Tq~RvCKA461qaWj1Q*>8GtxvvdRK9$bxpijd$E3l;2o}IqRRCK zJ015iw}ok#l2$!Tpwos`0 zo|23Lw|_}D&;NCc&)wl&zu)$K(YxcWPT$SXUd_GNrT_Z)a)+|6rL*~i`d;;vRMdID z5TB>mRiV@?keQT4MrYsL*jne@*yQBc*xdTyVsBpOY-x!t+qZ8mEh#JAW>m76kzsG7!-95e zQ!WMu2GtVRh?11Vl2ohYqSVBaR0bmhBO_e{b6rD|5JLkiV^b>=3vB}fD+7Z(mi3@w j9HAjMKP5A*61N8VNO8vZ6BX6D7#KWV{an^LB{Ts5oRqEu literal 1578 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y)GuM1};t}F0Ll7 z&W47rMi!1nE^bD~hHlPoCT7N_7BIb@dBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{EfeT3=B+Bo-U3d6}P4YdHV-DO8m9Db8p(cTesP_ z>gVZl9Tnk{GFfWqmZWgdrDugW7ut-q{VGd36p8!@yZtS}=jA8a6Fsgrhqv5IxjZvxUZQ7~iIAs8 z9^cv$v+jn;ADUF;{MmIc&r(uZ+VG+G`#I0`vJuvTW)JUv@CkV7RJd1RzQ5sw%K}D6 zljTHu&VG4*E#QmSb)yw?6*e{`NP2uc@^|f|hzh}tHh&f}PE$;I$m-CwH1yis>$~FT zzu1s=FSfT(tnb71`rNsdtNzQCIiADDxuQ+%8y|LQazjuxm zhN?{n3-uM|zrD?l`!DN^ehB KKbLh*2~7YK09EDy diff --git a/toxygen/smileys/default/D83CDF88.png b/toxygen/smileys/default/D83CDF88.png index ad51677127d391bd66c401a2b9ebf66250aac20e..f208b55a95347cbbfd741396be9814f304a8cfb1 100644 GIT binary patch delta 1264 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 F2>_0b&}9Gs diff --git a/toxygen/smileys/default/D83CDF8A.png b/toxygen/smileys/default/D83CDF8A.png index 7d5afa99ac185c24539b386fcdf180e52a4d59a9..ace4f9ebc64a42e5f5db26e4e653c3a360346e9a 100644 GIT binary patch delta 1532 zcmdnNyNqXoWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EW`fPl)UP|Nje1D*rbN6_!-O(V14> zi#@{kSExT-?|5y7@vbJVGp)S4n>5d~@;+D~Uw@{R_e?AARxhj`5y+*W+9zvuSzxy_#+ z*?%8)y;@}W{hZ_95AMI7D*b+C`tM8Zw@o_#FFE{t=6HXt;*LV0MSeU->t&jCm}i=^ zuk#mO8zO$JUdD66mH*8`@3K0=XI#H2;-=Mfo+0fBXZ4vkDb2T~yxjUPgIrc|`i6*$ zMBQ12@I4HUix>hnGeqrYs65ROP``!2eI^Q`x_eS8|oXb zYJuqkT374q8yXspP1J5+U|?WO@^*J&=wOxgU|?WiFY)wsWq-!b$E8wl`PWA@g@J*o zt|}y=Bq*_5p`a)~Ei)%op`@}PRiPrcfPulHcWS8jraJ~4cZ$1Y9NAbd71}6gPhX`| z{KuriuJ5>KoLlH({*E@blwUvpve$OUUv?{+{_-aiTddRzZ5NS^3t4b`&1AFscv-bdY24Fq z+`elzeL|qZ0qL!hbtiUl$Gv)a@;1-8yh7)A^Bq@~Ef#xJkXI$=`P8?C{g37*jsVt= zk8%X3N@dTvTX;${ecp`k^~!Q9w=H|bCHlNb>JLZ%L)H?l)vM+@C3_{dT#qsAT2s%F zUiGh{IOF3qIqM1HFAd}78T>oW$Iu@m^OMtUkD`u3%b#-n2TVb&E*rk4tZG)wuz9*o zfMYga(&5!}ZzStACCGYwZ9mVsWA9~`8x1+q9Lo(|W8L!;3=9m9JzX3_Brfarh5I`NGBj6fNefQc>lo<5y1nU*ZOREzb1lD^|NkS4 zclgHtQuXC#aOz;qY~0y$A*0l4ts84q^Tm|7EfLess4aHnkI3CwvoKc8R;6B>l&Jb7#dg^n_8KeX&V?=85r1EU*SX1 fkei>9nO2EggMV7iriqH`Tnr4Ju6{1-oD!M<5Tl|o literal 1720 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDz0Rhl#*UU2CYH{Q zj)sP=Mvlgo78b5XE@lP>hHlR07BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*6f$4>(i(^Q|t(@RokKjZRyZ3Wu=UNsQ z&x-ds!A!XgON9?9$ zJpIEmSN`Rl(s!@S%CDF0dS5EtCRefJxvkCl;&Zj{cd`g73QgAwKfLLN!l8X$eB1M{ zx_TN#*!C!`Q8H^wrovQRkUwaM+|sa;vVZj%BR`goW7f3|q{!S?d8p1I3Z!VMl5 zR&83Pqv-0)^8X2o{kt6QAFaFoWc@S$S!Zi!uNixLmGQGxqS<={ChrhFQ6g5b_WVIR zvk=eOIVaBSTdr-vIm3v{G2EQPPP8ERuiWz^IgK%r;)j<#$qmxp!qPO))8QV!kt~1q zbykO$6Q*D0%5F(FxTCjF-M~I+%udv%MA;lU-Ml>U~(=BkZ)}{y1g{ZRT?? zUy5!GKU7}oJbj7T1P6|^WfcV#|C(M#XuS}A{&&enr_Y&d?6-ug=ypg5T-1Ej+wsg; zt@TT%+DK(e#LvNt25S*S2@`*WzTVS=RJ}7(>Lx~ zW1aHqV769M!~Of&tMBJd5X*Dty!=>*!L1^#mGO+vrVmHizP;l#-e|huvi*6XeBr9* zL)?WIgoA`%2hVv`&KTvB(#p7Pis9udX=AIbOxAOZ?5o%$Lqr8HK2VkaAnNU7e*DlI zo(Yf2J+!RP{Qt{slH=Kaal`646SG>MFE#Y+XEj_u#iLw7Zed%rkZitFO{j{mQon^| zE9{7A BezpJr diff --git a/toxygen/smileys/default/D83CDF8B.png b/toxygen/smileys/default/D83CDF8B.png index 51c96fe3e8f09607c17025cc66cab93674aab221..fedb6536402d6c03fae448b78c99222a2e105b77 100644 GIT binary patch delta 1493 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 literal 1438 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy@m$n#zvNwjxI(n zu7-xLMi#CvZf@pIE-ua{mTo4NW-z^;dBr7(dC93Tdowdrte|==@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{I|9G5T+Ji|O6C!>*VPy7X<0#m4b%i(g=F9>EgB3EfPJeiGHo48VI=$xJp5!R2 zK(-A<-duYtzt3~*h!;EKA2=ob!X&YW51%LGs-0>m)sHvw(6BOI!+J?U;f(&_56=#} zvx((-1}@@xv9qkr@y{2pwm1b|rqywK5(K7|Cq(@FBbm98fmwLn{L~4SyJsby>izsg z(t%@xLb1`io!-V8orWIW_tQTF)F1iq+Ftl@p|%g z@;l$#{yp*G#jRjAwm>;H-pas)E1Qfy6m2xt(BxV-ci|RemgU@LXLtYk@%jCKsZ60= zJn}nQC9mDte|o|9nX?~WIG)b)EoKtW?jQM4Mj;Oh898L;Mt}VOcl~~zTSY=V;c|BA z7g#R2ufLZXAS|4)!6zsEh;&DAHyLSx7jbk~JG?RzIM4p5EWnB7k(4u4@_kzn}paDGQ` SVEGnMiRtO;=d#Wzp$P!U2M4tP diff --git a/toxygen/smileys/default/D83CDF8E.png b/toxygen/smileys/default/D83CDF8E.png index 734e849fe34127eae38719eba412c0a3a59034bb..a3dcad2245137dbb30a46df75a0d3845c6a14293 100644 GIT binary patch delta 1527 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 diff --git a/toxygen/smileys/default/D83CDF90.png b/toxygen/smileys/default/D83CDF90.png index 7a282a3e09a8f9a0b74723136fef987467ae68fc..17e008f1217b447de157bc8c4ea69da4473bb97a 100644 GIT binary patch delta 1461 zcmeyyeV==RWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EX(%Pl)UP|Nl>icQC{*VMtoVP`{g@ z?+8QfZieVZ3^^M?WZW`_xMd7kYZh0$B?j?A$%S~ z(5#i4cW&EvWb58TI}aS)uw(DuBgc0gJihDTu{-x4E#I(p^X~ndcON`);mWGbJC<+Q zx_radg{wC%Ub}he`Yp>hY+bf~%i^`0maN^p5KJm;xPJ82*Vo^FAAR{nZtYcuiN~*f z{`uhN-zPu+UiU^`rZ3?r*1x+v-9oyPp^(#d4Kx$uiw9K-miaf z``+Uxr*Awxcl+YCTie#|Je@b~_{D2m_8k8I|NqjYBN7Y@42C5^e!&b3+%0#0-sL&R z*U|Oh;g@d{`WX`sy*-fdbd!&yWW_V@pUrHIfA+Ak&;7fUQ#^I^<0zI;#(4}342((M z?k)@+tg;>q3=Hfgp1!W^&)E66R5`YGs>m}i)HBspg+!DDC6+4`6y>L7=AG0#~Q;fe;`lUlW>+UoxQ zakYP>73E>}GS22^QWF2^b#r=jXGd?1mHK>7zTZ8)ZO^PWmE9@1leSa_HRf*gimEBq zIrC`syZT7pcngt(N@*`Q6;C?by(W%_doAk>-8o5lv)}41T!o`jr`NMwhhL?U=mAWBKnL73C52AMKRN6!e20+bJdPxy|C> zqSyD&#dVK@u0qSdVi69;wNA=6UabgmZB6)i>9&(bM7fCBqFJrB-<7!5@UE@vu4H&8 zpR42(kdy7PTw$3A=Yl;y4kcEXzI_rCQ~cpgkif3b|F(WkSfp%u<4eJFqfd##Orq`o zSDcPb`tO%@=#uQa)Adg`+pTuH{_2iN!G_Eo0_PoXXG{*edhpFoj(MeTmL&+k&{(GB zDj6Sa66gB)ibi&TSW#ynBm2y@j^ysf>(@Q zicCp%RNenEcQm#!Y!b&Bs87#J3Nx;TbNTux44U{p&J z3o}boW0OdDZeU_$X880$3unR1nb8puQIW-mSt@dJ-oBBQnOl*_RC7*mUEVvnd4CKT z8TIq!I8u%RPk!HO9>1g4zlU~=p!@OESpnshMe(g`NTg^3RZJ(!Fp2G(CR z+~~oysqiCbXAuWuW$ThBQ$k!N;yoi(b#0Y{LwQeyU%znX)VY&aFS$jmUc7pF{sE3s zc82OV;;&?%B$YETFsPQeMwFx^mZVxG7o{eaq%s&87#ZmrnClvvgcurF8Jk*}8fhCC pSQ!|w-TTmtq9HdwB~u}-61R@>W)J&`it1bp44$rjF6*2UngF9Fp`-u+ literal 1526 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 Tbx#*81eM92u6{1-oD!M<+9pf@ diff --git a/toxygen/smileys/default/D83CDF92.png b/toxygen/smileys/default/D83CDF92.png index 485bd181713dd88b28bbc19e99aae7b080a039d1..557fcf4501ec7b68f78fe850e9025f3fc2b4e134 100644 GIT binary patch delta 1857 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 D%s0F0 diff --git a/toxygen/smileys/default/D83CDF93.png b/toxygen/smileys/default/D83CDF93.png index 5a601fe20bf89a57d9b01404fda712ee499a1ac3..8d6ae233851e4c74dada36614c6f9330e85a373f 100644 GIT binary patch delta 1368 zcmdna-ON2fvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{5JVZ0+od!m(pxNJYHDg? zY+_|C1nJKJt2UjDdkcr6kA?6q*cRz<>1;SMZN@jE$lHLca2* zojIGn%H|f=YhLNlXi@Y3-3;{%42((M?k?XPASgue|l%JNFld4csS&*twkz2sPV9`4@G;rE&1CBq>MQodRIF>xNnD+Jh z_g^<3mroE{fBoaO+dovdF&Y~5i2VBdS^RH@|3#-wp5Gsx6cetFD0`ZcbMsF9s<)SB zeE(NrV`cp2s)^1`)$GYj&Q9h^>JGj1XmxU;SbXXEtR2b=Im8UTr_Y_76PNNeDI!Dn zK+Z#v_uFp16SCuC30Zt?F<<2J#~kugYnC*=71aI`aZpn{*HX~E@NT=O(R-yk0sfO* z8?;`CZ*vVPvN^o%;Hkss?-b78?{>%ShE+XNc#FmI(?`#LlL%E%+q~-5QqyG*d%ksP zuT#;!_0rC6kM*Joc~R>L;wuYfryA7VW^r)U>-))~9CtxX;L!h_TvHkX69sZg4MGnJ zD8Ot0~2>cZYIfweQ<0F)_t2 z-Z*j8-%b5*RjG0DqD)fNl48lf8#o=il^{nL=Z-3T#{?E$e#lIyx+s+1m+_3Mj=n21vyM8*KGMsyn@BDwE zU(DMrySjegExpOWz_8TQ#W6(Ua_rfwLQMf8tq*mN9n8}S2t0D&K;A)yjRyDrC)%_h zlzg%~()j;brh?CZ0z{sEjZ~Bn%6Z7ac`<@n_^6tg|APxz#cHhk3Cyq0mMvd#>R*8i$Han5Up6aBh{q~7PJ8+Kt>2-;HMbg1 zK8?@6sv{z)Jtf0N=lI4GOi#V0>YP`$zG7GUgyqCjZpkF6U-KTk2bE*0C9V-ADTyVi zR>?)Fi6yBFMg~Skx(4RDh9)7-h6Yx~rdB5A+6D$z1_m>H?(bq?U_j`|%}>cpt3=j; Ys^O(zI@?4=buI=5Pgg&ebxsLQ0Bn^jBme*a literal 1463 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy~c*7u12m-CXU9I zW`>5YMiwsSW-i7?E{>KK&LA45*E6rUBrz{J6=rW{W{MS5uQ6V|R?bDKi6!|(A^G_^ zuoMuGkzbNuoRMFk;2dnGpb?&#my%yztO<%9aM)Y9q!wkCrKY$Q<>xAZ!`CVki~W|4 zPG*LV&Mp?Fj>aH=JDNHhJDXcN8CyDUC_(k6kP~M5b_zE7pu~ZcSYSdyE^Z(u zI9-F%xSawb^Q7jb*eVq%+1tq-wcO0W!1&zL#WAGf){-FaY!OF+e`(9otJ0^vkrVQ* z(P0#H^b~6n=hteg2(sC+BV4hg^U0G~fj^A*Tv#}{xIFi8{$q@<=u}$GzF2OOgy=~< zzi$_9jje;fy?JCXd%6VoEUgvm*6&-jev5Ya@(=dM;`Z14yY9bO`KZb@US>Zt?n_(r z4$MDtd#r8Rd;t2ruz z?07o8x;QhBCJ9bIcO~Q5O}PMAb#cwF{Y?oQ8{v;P(zl)kl6SY2Ppwb+Av=%&l!!1fH2kKbxl851)7+FnVN zZ`I+R6TM+4Z<^7D1+u5FCkoD}J1=9^wysq9>D@H${UPk^XM0}$|E@hhl7cm-y*qI7d-BD#j~o&wy?VKX>85yf=$=her@ay@Ff;FA synEp1*ONO6c0YQ@VQq16%U`hsh8Ui``~`aT+@PY=)78&qol`;+0P@=wFaQ7m diff --git a/toxygen/smileys/default/D83CDFA0.png b/toxygen/smileys/default/D83CDFA0.png index 0ba4267a80dda8d20411694eece7ef58d19f7de6..7e28985bc3a34fa3418259f1d42f70bfde428970 100644 GIT binary patch delta 1428 zcmcb@y_nTu$sZZAYL$MSD+081EY0-Pl)TKI}b13e#p@LczJ{S-7R?+ zZarXVf6^LbI5XGz{LT9e-B0^xFFM!Zb+XMft9#zL8}}F{KC6HC=K22{n|^(Gd7>vm zzjyE1>vtGtJp2Fu|Ka(yUmjjw)RuMn+HHpUFV?JDV!Y`3|DT`#AMSWqq;RoF{#mR3 zqcV+k`%kW`^lF&3?9`Q849j2spQZQz;$nt_ui|FySXAh8;_?lVb+5HIzHT}F>GS7L zo)5nK{{8#$qlam2lULS+9=~|qqJHb!WA|Qt`SNAumYt_g9R2a*`~7=&x9{9Banqsp zWasXs>yBNx=DX+Z;%)mMzx$GN^h5d9E6diby13A<@W|_k-7gZ>oH=s-YQo|7IR~Du z+_>@h(L<>&GS$(#C!c;guy@ztbC+{YeJDEdrgi^~cei$|-MXXx(7nTFE>&LmSbyf_ zq0<+euhf59(j9+xbJf997v{I=&tYI-U`+CMcafN#*(%4tz`$PO>Fdh=jGd25m02nJ z?{o$Rrsk@Uh?1bha)pAT{ItxRRE3htf>ecy+yVv$i{7cBf!pR7aQsadjdfySI$1QQ z^XF^Jzi&QOd%7LB+E#nDxQFQ>({zQufBrS^I~uxWf=&I>>W?Yiowho=i;TAA+NoX_9ccy77i_QY*`heg%Sy)62yZR;ko;pCcCWuCp68p?Yg zby=V6D%sWFZQd@LubjmGNy+q`ve2>3z6>r;9z9Ukdc>N^sQ6{Sd!vAZXh*{8#X+K? zO-I*lZw=V+P9w*~l)L(w64x5ukoqGw4BO3pTlht8OI_0J&~#WFqJS**bQuZvJ!6%t|{Go2;C$T6Q zr*+32nK=mULQLzTR-T?})Hazhiw9 zU)#KelN08CxDeQE{7F31{=0PT)`xn3zs-7j=ewiR_sP$)%$7e={QE=ngxkVhKNX)Q zq%S%AIo@eMgT&OXwSShM=w@JGIO*x)7$R{w_Gn}@W1s}XgMQ-^5sQDU>CBm2>shAQ zth7wSuk7Og|ExCY@AjDMA5d+bq&Q~}Q%IIualPt#Cf)UyrF(21R=9DiecpPss@U7Y zIGJg-tn7;t!E@(s9C-Wj-p_Y`8`~Ay4hi;4FKSY-Ic6Z|e#9lH!%1uI$!7wef2!-) zh+o#!n5um>YHy>yZ_d{E)2c5z?#BIpTWcWk=(vZ7wZSyI{c4j;{{EZknVvi&_}A_Q zQ*^?!tIXQ#&rVakCtJ<@+x}U>l>3MOw+dGs)!+Y3B}RdPfkCyzHKHUXu_VnC}Q!>*kackJAClk%Uz#utM RRhf%{!PC{xWt~$(697Ieu>=4B literal 1492 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{;~fCgx5~2CgQq z&W47rMwV`l=B{Su<}PloCKk>vmN31ZdBr7(dC93TdowdrtRQ+F-SFzQaxO|uEXgkl z$G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z>^F3Dbv7_^G&8nzH39qE+`z@j#mUIn$ko`)(#%8&syBt4Fw?hFu+ax44y42a6AE&1 z12Mtr8kENE6cCvwH7~_hsYuD*&iwHfYNX4zBoh8o|nKc`p{;y~IE9#T6 z==A?(GiI*Pp0DpJykFuY|B8q9%-qVej_jSi-fr*T!wRi>d?9C8E?&^z^j;?U*2(`X zBXxE+#HflIikIh3a9qK$LL!G*I=N=S>Ic$|_l2JQfBr;1bxOO`iMG3K^*nEQnhpuM z_svgxDt?)Bt=_+#Vp`|>`Wm`s{$tEdNRQfGmC0Bva(KP|o&IgN_TOCg^(EW$qs{&A zEdTmXk6He&;_l%oY|EG(QvW;n{rWz?vDxs+dTnNH=EF|eY@dU6-uwNjJ+4_ML7hv5 zS@z7M{O3Dk-X|IqvuUOM{QiGFhxKF&W9KCzzr}l%ZEW^9xX$|&8vy<&oMPA=cw?O#=&JaNX31Q+q&2Wxj*TKGs>OlduM zknP)-|MfO+4t#iKcQUc|`EO~tynq|gw=;ITi@w`m@J498RD_ru|Nn@FT@?!jkNj$N zo+Pfe{r#cYr@hwgkFcninEa0a27io1=1cwUEmF_7zLu%e$uRu;+y3#vU~vzxdklbu diff --git a/toxygen/smileys/default/D83CDFA1.png b/toxygen/smileys/default/D83CDFA1.png index d59c5e566736da61a0c85a69340aea29cba1d39f..0413512a77d929b1c5f3d9e9b18282fb355e9f03 100644 GIT binary patch delta 1492 zcmZ3>^N)LiWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817m!EPl)TKI}b13e#nq>d)nqR7j8XZ zD7wQ?dN+B)n_~~Z?+QGzn;5s zkD>knL({|mzCQn*oaE{+#WftwYB==Y$D5(~;o0kV7&;zT?0&!d&Nq|xE1O$Z8@6A~ zXggNic6isFZ>O)_W|;Ww!i!%4>)tr_U)$NSc6!&Y`CZ#jUAeVn&-u$&u5iqM$uR3h z(6k#*gKnL;d}E@`@m+yuE>2y0zP^9uE{z4dG!|Z*vUXR{nTa;Xj$gd4zy9sbcfU&x zf6QKS_wIj*GdE7^-uTbmeNxY+$_@9AoWB}% z;8Wb8Pv-LAsQtSV_xh+qrKX>W!;d7TWPk#O4=JVg*=j!`EH|^Zc3k&}b z@crUi?|t~prP2%ED=vQDdFA;P4$ga%rreu6W!L5BhfZItzxH!Z-_qqeCXH9V9Xxg6 z|Ns9b^98 zf};Gi%$!t(lFEWqg^Jt)1_q1XsiEGR?ig^~DejVSWUFWSRA`f&J$;o<@t=~@p9^jL zJ|%@l8YxIgshmFl-u{X7{Aa#z@@%80%{cT(%rw?JOmU}trd(O5L?wp>f35J;u%kLz6FZSzb z$gr_`ydgDw&lz`>`h4>VcXu3)x88E)S+?#Xo^{U!BCnqH2|p5?W3)i(Q)`aV_EV-Q z1zB@e?W&#=6fEhr?D{3cE?tl1&nrs2BVL** z`6>9D74K0B+;d%)L2#++D`&S&in((+;WlGJwiYvk7EV(;~DHVZ2jJ^g$B+|AC; zCkdBd>g7IJoY)j5^Y7&8(4_xvLVkg9q`$3#N&Kj)+`LrxQ%@Zktt6^GrL5WY;AwNS@+a}rb#Kgn zW*wgU>uurm?D{yBqT0)I&VGyjsQUMZ=nb`y*gq4rRvTZs_%~i@Kf{(OT}`f&46iUS zFud_}aSV~ToSfjm-s6*S;0(_~Ny&(a$jF;F>MbpHTHLtV;3LMCCnpzYSI6k6$e3ss zCnuN3CKzZ~$mtoVnHlKGDH$0k$Yxo|>Z+P58fq$&wf=?5vzDEF5fX94suHtnAFpR?O|Kot>R69j%a<{yyQ{gBMTUJbLo#*}I3&UOjpA z=E;fr2QQz#ef;{lynw(9@h6YpK0PTQBP1mzCnzZ@Dk~@_EG2YCKw4aU{)8D*=FFKg zW0I7(_~{AL=FOWoapu&ylc&yROj&05bP0l+XkKIyAa< literal 1581 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y>3nh2Bya5#;zu= z&W47rMvmr|E>12kF6QROj;`izPB6WmdBr7(dC93Tdowdrte|?0@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{aG z@H(%$G*!Q|Tb)OCX+}-@Tc0OlZ`P@*%M%`Ji%To3ODj)ce8R+9?jI4Gc#u7W?a~MS3^DcM7QOFRyzieY;QU&c zU2bsiLi*1|hd;5NU?^&Ax~x~rti_52XE^$H6MzuqcmV>=cV z-FIugf}XJU>CaX_x391JJNw+pPyg@#uRNNy`pwU@_|Ut5pY@+T&(E2!_o~w;`Oo+N zg4qX|1pg|(VOpo8vobJdDZBm5ztyZ!=sbNLMMjLc)9Jo~X=g`&74uXs2d8)*=Ms-@o@BjBW zFOs!yM}1HG`IuWr7IAq?^7NgNU*bDu$F5zH5|aC-PP4PF&A)HY7;!4$!|!70C+vC! zml_(5TYuXdd~x5?DJ+|UQx9Ih`}>Bi(}PX_>;I?QRJ>yHJSnYkfFWePV(_*_vywpt NxTmY1%Q~loCIHCQSY!YI diff --git a/toxygen/smileys/default/D83CDFA2.png b/toxygen/smileys/default/D83CDFA2.png index 3e8437b4b3bfdf7a0b19806a0bc0f71595a921e3..eb1699ccd91b4ca0fe89131d1c98f0c3a2197f33 100644 GIT binary patch delta 1358 zcmbQk{gZ2gWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EW-cPl)TKI}b13eyBh3^v!KE>-)FA z=<2(0>w)d`b172}zpHQf?`Ltw+wT0$`+oB;<B`o!fM>#n`t*gY|>sBH4)BgZdZ z-~aH#z6bA%I{MpYEk1VP+Nt^%pU%Gidh+?l`pGjZ;*-w2`tsjGci#NA2%L5ompMiGHKdnDdF3ne*D){IDGE%-t!kqTiU`>(?e3zOPX7@9zXH;=kFJa zVu#OMdiv|n3Rc#0!n}u0U);{lcJS1N|NsAcPU{wCU|?WO@^*KLoqOMak%57My~MNL z*OmPlJ0F)Sw@yRpQU(U5+NzL=ttT>SX?kH2feBvo75@`PT#e{cUqd)=bel!@<-PWDy% zw&BgGlQ%Z)42@oDxc=|QijuS&t1}}ud0$iWte>qe`a^8>lB4Oz59!#wo^{ngX#t0r zq4)HulXc=!z9mIu=pM*ks<8eDf80B+-}_eZPJ37I zeS&{o8t>It+cE_bE^;W&Tb%2xwszIx&D&i&)+C#s6$oFlf`i8>u|;f$uQ7}3;#V!^ zM^C)i_rm@LM;_~XQRV%qMa_RW4JLQ+@g!V5aFOTv1_s#}qTh z5jS*`w6FcIZ$Hu7_xK#spWv1IIkvj3W}5r2*+9ZKO*UeUyxH!R;9`W$#GVBP6GWzYrEjw2nmA$k#(JlXA);;M1& literal 1436 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbOWy++24hK@#-t|qQ7 zhK8<2PUfa&PG&}yW`+jF#+EMTFuk66#U+V($*C}VGeP!3^jhH6Yvo*&npl!w6q28x z14{t`8Tlpo#Toep3eLf13L4>=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDk0u z=;~}>1?qMy|$YmS!eOP`xSSgqgmbf{i{XaUdlYm{5?58;A)` z*Pt|Rr+~;jsd*{3N<~WccK)}s-!m{Up7C^X45_%4w6o;7BC}@0)Bp8tJ95{5SP`+b zO7G3KzytfuXYOCWpNEGn>&N-y$9lZK%KW^1_xqM_`)_Mi)E&RMKSzhrQGvHfMrVhK zPxUJ6-}e?Bf8Jl_`Raah;@Us|=X0)boY5^RT-q&fp77K1;ju;P;{GNpZrq=EG2d>V z^a_cL!}f`KCDK7Nzvuo+yH%0-t^4zF>#K9?x-~UtRIHdARq^h>{j~c3|GPhISddz#9{;Q~nx7O)zY!uY^$DeX||NldGa%QZnFaG$0@Ail8^YgdHTWsi` zEvv*4!n5V!e1kS#hk~fv?+$!kzU^M3m-)BY7EJ~%wwnqk+Sf6@*r{9pKQrUSs~0aB zb~fEv!y(;JbmxuFnKK$Nci*5r_Nj9gg}q)Sr3x zenc z+uHf%|0O2`Oqg6+Qh3qWDDCQmf3=S_FT|uAd%LwdvO(_Rh0B$>X;B}(S_gbFFkkLi z$vWZVo6F^eZf+)Q0frjO8fEI#+&b7F^h}ShOEPNclH{Ma%OU6!8;1eIocz}{uOHj~ P1eKSbu6{1-oD!M<+c+Q9 diff --git a/toxygen/smileys/default/D83CDFA3.png b/toxygen/smileys/default/D83CDFA3.png index 493f9f4df140f48c1c9ad8451c025a85fdef0cf3..215a5a46578e954a68151cc2ce21573cdd69ce0b 100644 GIT binary patch literal 1577 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#^3;-5ZCqV z*G~`8V<_A;Jw%TT1ksL5BSTmNLqVa}b*5I+fGUBQA~5C+HrVGRt0yBa&X zs+!xj@7z71sTw30*UC`1YuTE0oqZFghv+GGA7Dsdl9E#hmg;6mnm&8}qE+iR_D-4B zl;{Vsg#l&}0}cSvi$jWmfngdr5O!R=-@X09p=UqB!onB|mj_10_V2lJ?A5<@cfK%` ztm)l%_rUW%T}PfVq|V*7|KR%%9};Hoo^oDv2G2KEw9Usv{L?0j4*7G2(|pBNaJs;WXFN`ey06$*;-(=u~X6-p`#QWYw4 z3m6zIdZ&hZZ@Oc^ai_RT#*vNXQ=!eYt=^&2EbF9fCX|8d!F-n0|xmnVcwUlrR`rmwXj*KD#`ybSy5 zAB&(h-Nsdoag{>+3AMnL8{2bQrv#t3+3@ba+nvifo`+f0 z&OQC|!PeGIWW&letIE83HK$IDD?S>W;%IgF^YPh-ySDe-XtX&gbKY3!Sp9uQfr;IV z?GARBFJW!DP~XF(xFAG><@Ow(vk4qx{Cj;_4z)Gk=*Uw47SXK`VeR!-te7cpy^&y? z!?w~Tmj!}5Sse7kEZ+35lAY6?-D_tp=oEg>zFcnZ3lBas~fqkj((Bf6QlaA zHpp5h_Mu(Oy`PaLtIXa{XX6Xw6@O%U%k0#W%~@sN>Nw_^<_DJu-wN~zon;gjEE4DX zdR^ePjK~wJWxTd0rYQAkx9ziYFKVpe)G-n?Naz=1R49=CW*W%6_n{$!=^O29Ozw<2 zg>Lo?!AkDVUR##17JlWetW%V4c*^1Pzn$m8i+5d*GGgXPo#c;RdO1@?glFYMw@JH& zE-&Otb9fl`eOIg4^a;Eak7aXC35At50xF*W)5gC~!kJxonu z`SkM#D~seo2agCA6@^m=IGB?j39Vr2nG}@4bje78QB9zM{sS_fIDgi1(+ zCMR9J@{mc2r)O5-N6x5(Mi)1?Tv@ieOP)*#vE9(HuW{i-!)DK@Dz{~t5eIa%Z{KNZ zG*rKSi7Tlsv25N}UgpKQ`(&>~RkORR`?0yDi-)%yzH>*?#%AxHJ+(EGlC^*T{Qbkk zBC&_Lxyjk-a82#sy@!>R7B6DdRFm1G{o=(91_lPz64!{5l*E!$tK_28#FA77BLgEN zT?2DnLz56g11n=wD-%;~0|P4q1OChBCZlM`%}>cptHiCLuCGj=fq_91WJ7R%T1k0g mQ7VIDN`6wRf@f}GdTLN=VoGJ<$y6Iq8RqHg=d#Wzp$Pz%r*Q%R literal 1482 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy~c({CT?aXCXN-2w@H}b!?660&!}x?L1Isn0jmF%YJRT_Kv9aBh z6A8U6KmCEx10I3Z4ZIvNbr(AvJM7yY-gRHV$&<=+Da^j3;GnjGSP~Pf)aD%p-}}xm z%zSsGA%@LQal-!{9bb;g7f3vjS{+@`>18x$-hDQc;1Fi1sS*j|v;Wnr=-CU@r<_XR z;+isj;-jR*#N>{Q*~bqYIKwqbCBl-t15j;Z}%(_!(hPi1jwJL>=b zVq()XbY5`5JcrGu@|#WS_kYd$OIldwovPitTTtT*$0WYGn%=}6MJlW!^Df)_9+!O* zUA=8@YQlskO)i&f6iV9#JvKNQi|Pb67TwPmm?yW0o$u=z)9DVc5+rokrdabV@p7DX zqhSX7tXju7Ce=mj6`C8Q{yXueHl5n+eASrI_ae)zGG>JX49~Ut|EL~QJP9gYJzf1= J);T3K0RZly1#$oY diff --git a/toxygen/smileys/default/D83CDFA4.png b/toxygen/smileys/default/D83CDFA4.png index 8ad09887381c4c78eab68c0c1523e19d1f635c7b..8f12411b5ba52e05c9ba6b7550a3fd0438a21caa 100644 GIT binary patch delta 1497 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 diff --git a/toxygen/smileys/default/D83CDFA6.png b/toxygen/smileys/default/D83CDFA6.png index e3a45c1441ae009c5704fa73602921a9e2b3f934..8a0dceb8d88922701c56a184a2fb52b6815967ef 100644 GIT binary patch delta 1278 zcmZ3+wTf$kWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H-NWpAgri6&DkiosVC9CU)V;=y}JY zW*>@}u`g`Ou8@gagL*dxbglJoU+LSj%)4o^XTyS|6&I6MT&z!AcHX;bF+|9-VF5$} zZc6{28*e}V=-YiGdfu^*-+uo8|NqnXUmw2yjGBGurg~H=n%r zt%o`1nR>@g1_lQ4k{~}&STG;~&u7n`J@|^Wr`%zcW4k%bp9H4}F5r6Anky)JI{n7G4WDMDzms@=xAS(v?Y75k(;pP=`VlCn z%>6@R^{V+^$x@Re<9LsTrZ`*L9_#qF4 zuKpa8rh^v8!F_zo!VFmYpYPFUsXtWJaN|stQ1ulh&TDKd?-$86{BHMY5ufmu_i4zJ zkWCC1x&;|;2XA#Q;N1FVv#?@`=-<~5Zgw_4dT`k|=EREjSdO(8kJRtM%lT5!fce#r$l@3Iw|nD?cAy!-yrixL$PnV?#WlNDN( zRuUPu*kadg0{k|E)~Fjxa7z%zhxydPLKP zYn~0y&4u|Vm0hN<{30N?%|>w2;+vP2w-WE~9?)Fi6yBFMg~Skx(4RDh9)6~23E$VRwfqO1_o9J26-&&L3tFRAvZrI cGp!Q02Kh+w_Y)P>xfmEcUHx3vIVCg!0HNJdoB#j- literal 1318 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y@sZyPA<-_F0Ll7 z28M>NMoy-#&X#7*Zf2IIF2`Tsp%Z^AT{i%AozCfI3CtFQm>HR)y52D@qX|NkYOknsF$d}O9}!_96# zJBG^!{|*WBOPsU+%@KZD!uI`*`V!`4t`f!{3O6<0oWJjXQbI~f%7do~te@@cejlB% z_mfVH{ptVv>kVYT%WvN;>3GSbYQ{fC%NG^j4!(AnUDxz+4zsyLn!LjY$D$Kik^lZS z&e*1Kb>hPZU()9n)l)e{jp1;thc~uL8^Tg$HceX|A+ci9$z-S;=*DN_FM~6r5 zR}oKM)h3?wcl#K!-Pp4n3};leFxCegZs3$iNYmPpEaq%`M!$gd|9|5-d%lWnXUurW z+TZ%#zTWuF{yz#?|y85}Sb4q9e06^!^kN^Mx diff --git a/toxygen/smileys/default/D83CDFA7.png b/toxygen/smileys/default/D83CDFA7.png index e351eff983450b71b05f4d8a09b052e77ed00da7..3b364433632f003b9a5be7b054c4a2c027ceb89d 100644 GIT binary patch delta 1356 zcmdna{he!qWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081Hccgdr;yuUDZa;i_ z$>!a2R&U<2_we46=N4|(Idp2p=3VP|9oT;0*p@@5>R)SXfAshNURt_w z*MUVFcRn#Uzat{DcH5rS+xJ2AU*zSz%*S_ugYyCh=N%D||JBu7_8xxo@$;JxpAMY4 z{691E`MVGQ{{8>||Nrkle@>Cod-UY&RU2lFH|#G9=P@uaFeZ7syD)UH z%6il@Ffg!}c>21sKV#?PQn5;Lb-BmDz|>R~5>XPASgue|l%JNFld4csS&*twkz2sP zV9`4@)N$Kv1D-p-MQocUrku3nW8Cw)I`-R-xrUqzTej_g$htu#d&$Igtm2Z!0F&7JY4>Zg5gB&SuZgWvfqIlkayIZ``x9J}}6sd+n^;-W;ymuXH1OEjMh; zII~T(*ydq?=xmoQuhk-_uds42&G|Al!Orv9E%EzKE4^JDOrFgxe6dHH@3Y{^+q^0K z?=~d2KMBg!wd8DmZRiq}5x4O)_^{eL2$&N^zAIsrce((RAIddgE_{`?Mt9Np@do=4p*J1n0 zo!)Qmd%oT9lJEP|Pbbgktckka&c-LUn7jATw$zzxX7--DXMS*VZ)TGDLfNZlx6FF* zvhC39sGFv0$ELN${0zI;JgLpYBI(8JY!&kpe+Vd#wz|jqd>;pURUHIH_ za6{9)Y`(KCvaJ8h*!-Ah@~+JBtXD65%Di*$ME?U-Y<~aSdD7p!YkDNXzTDvFyY6)_ zi&Ro%f@&pRR-Cqx?Rvy>BItCSd6r?zr9MX{4=cgwV=9&(HXN{yHkxl$`E7r|gmW?r zjtHN&ofIM_zp}A?@teuh4)!iO#yjnltj+2*4a%Dj9+dpqoofF*`d8NB*k5ler+L@c z%c{Kmw`Aw6x629hL36_uu5tKiwshbIdAd?qN*WcwI7GS-W7=uTVxHBCBgpNGjd=5OuFkJL2} z)|~vT${Ld|b!lc!-_K92ixeLHyYSC$-|s(+S8vqrwSP0Y|2v04{K*%B7i!)58Cu`Z zVthZdah^Uyz4&Z*rYtT71_sp<*NBpo#FA92y*)&m6or{6N)78&qol`;+07x~Oj{pDw literal 1463 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%0DjNyz7?v^fX_HPO+yD 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 a120_11DQy%iHho63=E#GelF{r5}E*!yENYb literal 1669 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy+)?aCWfvCCgv`# zW`>5YMi!1HPR{0rmL_Hfu9l7_ZZN%`dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{c?DrG!X;s|0g*FBTdAr_RPE9Kqf zD8f!MV8P1@BUx>PW9lDzO~b~<`;?Cm~(}G-n7`f zVr5QV^1Q?Mr!&jMpZiuOc9-YHtr;rOmJ@bc=O27`hC6m|ZQ%*Z3mmY>O?Vqm`ZW?p))0g<)>I}l~6K@;%m3U^0Nv~c|Q1{Q_ zPpPhLWOHq9G?Pwf(ZprRES2l#99Sr3Rx>Tmn_>PjMTy=c;kVyRWP2z3SJ`3B6mJpP z{2evd`KG)%+O=B5=KQ6Tp$V0O=Q_VDY?C=xzv1Ofiwn1ZNgoK98N1v`-dJSE_QM)U zOVwfElq`Zpn7w_k2j+4MHa$B284u8`ss zjgJ;x?eC5qJ-N~4#UI{N(Qj9MJf4&C@M-Yvb?$NAY#SotB}$&=O?H0B;dCaer*qOu zmNjYIdo<;ip0<^Je&`GmMk$#rMX>qSkJ;aPbzPT}>@vc^SH(SSq2_F3OFMsge zr02a!V8gEba!^wXkd)pcFS`g7pK^!4ef7ejPEUOD@ERdBfI%JPrP zEf)w*;fi6A>CE=5Rh-D>-#+z#wOaLrYyX527}~}EzWC_Ar3_Rtc)I$ztaD0e0su1l BZlM4G diff --git a/toxygen/smileys/default/D83CDFAB.png b/toxygen/smileys/default/D83CDFAB.png index 38e00dd65d74394f632d780b8789506e38008da0..a0ca7dc6f33d7c49873af5dd503fe3197e1b6e5b 100644 GIT binary patch delta 1134 zcmZ3?HJfvSWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0814B-LPl)UP|NnPxPI&pKW%JtTwaddV zo+>)JFZ2J;Y2V)U9^R8ar9Y%F$L__0wyWo=?_Y2H@o{2eoLPNnkp7O%nTK}grzKfz zT9fAPqIUj7`Nx+%@zEv~B`$~d6fBt=bNO`Tslz2_j+XAq3=Hfgp1!W^&)E66RM~Qfa;DhpB-Dsl@L7%Y0HhDJ}CW5Dz0yJiuK^2te-e8vy5;n;)0ryIGZ2d`8Qa%ewtvSOO0$>zw&h(^PY1N|h~7`Bm1J zJtw@kHB8NsEM%y>H1oT*(81!njP(M684K*!c$6$*WqD=q-Y9TE%fzYXoY&bE0y^`m z=5ukGF}0?KR44ZDO=!$@U41{v@4(x|zO3>Z+tzz5S6+6CB_M9k!-Cqfw+~_tOI4O> zdF=f3Z)>rU;RKl+w!5uqc8{VQj($16D@OI(`z2*Jiduh{{9JK9>CLS@{Jb)Edi6}| zr7};O-OQPG=AQY%%}-4x`Yu?!Yi617p1T(+g0puYJK_;}V(Mx>J}=LS1xF6NE>;mg zG5x?oiKy*t%ZgaE=B2Dv)4KTlPm`nu%ic*z9YW;+T}}VrbfGE!rY|h-i@wdhdG=h;0>1uj&f-5^wGIe}P2Qv;d|SU? zN@n8z+uxY)pDO1pe{}n?&h(Eq9;d!~7MtDnKRW%(+wcX^3tv5Izj9-)kNdZJ&!5aL z54(;<>zVN}Fw`^Tc)B=-NL*gqf0D03fyX6~F_7`QEbGtz{aal5=JihA(ms89rq>Dn zg*t8OGG?ke{erC;r@I&@c%7DV(3-W(^}@=y*9kn2TN2_7GS);h?0y%>$UN)DY7^08 zG8}%{Pm5J0u2_~Iw3uIci}uU%}>cptHiCrEjwt@L`8Kj1_n=8 KKbLh*2~7a^0qoQO literal 1315 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCz2=r~t}ZU71};V} z&W47rMwTwl=9Xrr2IfX478d4~E-<~GdBr7(dC93TdowdrtRQ+#-0G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6)wqG;nftHn4DZF#`G9(%i_^(%H?-)zH}7(8$R}392`RoG{b3Q?StoB@U#-0uu^y zaRV{I=^B*A?GzB1Cp9m{R;ftI-tPG(!7c^{#$Znu$B>F!Nq_$Tw`W#u=xlT{_`tSd zO|;<0c-Kg&^^5f5_Zipx{I$2SefH+Y_I6>ty65kL*~BKK+185gVXOK7Ywp80JEM8q z*tB>)i^;Kf-`)GWOHuJ$OC#&D7f(O)^W@*JO?~hvCHcXVlvDwMCus@K6I!JgEmOPA zute(Iyn2?>tNvFEb`<mdKI;Vst0Kz)VJ^%m! diff --git a/toxygen/smileys/default/D83CDFAC.png b/toxygen/smileys/default/D83CDFAC.png index 6ddf1db3a1df08bf16a8da40ae383a88b8ffb829..3effe3a9c4bfc1db323b11d64a3628ca37f5df00 100644 GIT binary patch delta 1464 zcmeyw{fK*lWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EW`fPl)T1wd*@4Oo~WI>YO-v$=dZx z)~?^Meb?&ETjApVk*@0s*KFRlWbOJjo3|#V zr6;AOuiXOTuHL-$%!P~Vw`|WXEr)1G%E)wZ^Yo95hFD`07iu0IXcFdU5bUMn=c;65 zretGw^32&M&z`?~|M9`&C#TMyi;jzDWn*JvViFJ#ShHr$?>~QDym~DqC(kb^Bvmgh zy>j*HfB*llUcFjMN}5kVP*O(j`KwpaadD^5oqzw~!_((4cI`j7ZtIS1d-rYLwRifQ z`PPok8My`f4j-K|bI$+&|KGfM!^g+Rz`(%6!}IFZtKi_^wQJW}1b9ZYlt;E!v@M-& zpBPa;Z)(H*X;yKeo@H5{71`#|fu&Qr%cl02gw^|5r$^gl#THIzx6X{w4{+D=bJYp- z(DHZXy|4G4fq_A{B*+gGISgva3-4<%>E*0^Ud&=qy6N4%s@-3{9jx8=XXW@ z#>}5SeE9HT{Q;Byhff(87#Neh-CY76=lSemU|?V`@$_|Nf5y(orOJBRB4QB(15<5P zNJL3cePX#nK~a8MW=^U?No7H*LPc%?1A|5H)KJH1w+%S%7I(=xvax(BoO9yV*S&hx zf9e7zeYBWYCbj>Dk%C=~!?OGK^%whpnC7aKOWxaaBPq#z^}3jzwX<$#?Jjv%dna%E zL7tykQj=ZEq&#Qc3`QGi$m!#TPO6pJV$^--|d3sb=7m8+RS6;jGb?izPVCs-;(_KOgA+{#O0SrT|HS+QRn@FKP$Pb zKA(BdsTc+ZhIO7Ujv*44lM@^m#KO$V_%vou?w;Kq9`1j?p}%orXS_pz$BhdoZXCIC z=8jW~V~Ee4OQ&ufyLRo|y@P>HL0yiwPTo0l<;abju5Mv|agCib8_yrO>Kh*D8R;sT zHD`DG^6A_A``6E(Z$BgD12+c;4;LS2>!gIn4G-!c6iB>0F3`v(aYbpdf`XLKF@qJy zk0~}+u=TdMq)a%nVCI}Ao*>CBmo6zPFuSCr8D^wES7?lpWe_^)q<2hRBA*k ZaqD;>6Dc-PQJsr{!PC{xWt~$(699_5h0g#0 literal 1522 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 QxfmEcUHx3vIVCg!0K0AvLjV8( literal 1778 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 literal 1359 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy_Uv?MivI<7A{6E z&W47rMoy-dW)`OA7OsxwrpB%&<}kgUdBr7(dC93Tdowdrte|?$@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=KIv@P{8Q&znAr*j2`7$i$%ZY( zaZ_5Eeyre6TJd9k`Yans-e&&Z!z(7IdA74JKh|21Su$I6S>+Z+9?_Lyt54{jS9S<`Mj{#MRnajZ2dE^l>IVleB>Gqxtaro24IEpKhi+kf<+!vf6*a&lQV_xf)5 zGOaFBzxHQUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@ICiVcI5ZC|z z|1&T!7+KgLBYjf~eNzitt;myY{FWkDnY1e_K&R!F1yNrV3C2Kcby#4UOs}JX{-I=p=RZ?c2 zsim#DjzNBT_1bNFp1u9}>-V33|NcLI^=|32accm{pau8 zg-d*bLiZd#mXcjy=jy4UXP{+hZ0+b0nV9nA#mjF$e!6-4diVxBe)b|HCc(nNSxiz& zP0!H8#?I9*;LV3m?>~RdFDWxHHFx#)GqSW%)X>y3F*CP!Jb&f-%lDsjjZE}S%*^Z@ zb&X9G)HS3P6xDSN^vo=ZDr;(*S`^jPi>qq&O)b=Q^rhq#`Gtg4wX}6i%<{@A51&4N z`|An3g!1cikbkNmqls&7OWgAOFLd?F!Xf#~y!_aG6j(dBTd#r%r{2tv|FO_gc5fd|BzK zwbOi$8XQ(MHWU2#pjS)(Ud6Yx8yVRRH(gBkp;dMg7xQWw!rvv_Ra(C9VpdQ}cdu;{ z%=h5tSomLt)yZLH0LP|zKBWdM-P7+aV>;ByPq>vS6%z=%*@{1!d=$8t=Gs?{N%ZN<_9-F$(-f7`GRm-(mDO!(sfBD zb5t^RBrd3Ijo_ZeEqsKDZ~eqO3ila{#kF1@yO7Yt7`X^6_Vn5Fx*9rN;wcFl zdYZc0>sPE(#rLeXriWp09A~ zfPz_J(D5gl!OMN;+9f|*b2EvD=aEpc(X(@>=gg1XlyJ|(^b literal 1723 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy_QZ+re+2vCgv`# z7KVndMwUkAt`-)q=59`AZqAmjE-<~GdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{UpGr+BH(S$FTB;?!qyGZku=%zHEO`jdqzsXtr0>!%fIP1t$o zzMpZlbnn-B{>vx&r3>`BO+5W{!@hlO{O|88*H>&%(vPU#{O7BEWAWbURzLo1QoJZ3 zFS)bNXBoTiN;MWHN0Dyz8$bRrXRp?dh>1CJ@7_7RT6X(2d;Ti1UfKMTdsX(${t{b} z_ix^~tlBA=KFOD(U0H-m; zidWpT1H{+0Jo|jGOyXtRX?Lr%0?+|cO2$OcJ zUBWflnBUNMzfz=&lHucs~eC33=IJqvyZ<}`R4>n$PB*0|V-adz}=fo+dV|D3#>9uXDw>D#A01vPI{v!dE` z%Ae=69jel9_PrLaBW7r6d)6fPj+C_X{i)tNEUjL>U7nVf@M-UY`361)c~@sQH>|tQ z_iEwK3Z;Y3emvw4NvQZC_3qBjZkF?#Zt6UH{%pgB9S5Ryr`+h@J9oZ8*U`eqPo8Xi z^!>Le*WRw9(!a8jO5bc^4!x>zEKHN@$%2z|2RBwOQ{2D5|L@)S(?v5P+&tM1FVFP! zU!pkC%XIsp1pk=2`qo0#zV2ne0uD;vy!YeS?ai~l$QI;Odv|F+ofx(=Dns3LfBlAy znyfQ@6(my5vhn>W-4s3h&7QA)j{GOoe{n^HbDfyDM#-oFNLv-%U2w4BDyTZ~boFyt I=akR{0Ck?1@Bjb+ diff --git a/toxygen/smileys/default/D83CDFB2.png b/toxygen/smileys/default/D83CDFB2.png index 8fc6c037a2ca1ed11ec387c77a6ae993684bb784..dc90beb64e38b3cdbeae4dcaabfc5a11e2dfc677 100644 GIT binary patch delta 1684 zcmbQtyODQ-WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LL^>pAgp<&#pdyeEI+X|4$xXxby1M z^T(Ir=*hzi5cJ^g*#~#e-o1Hh&(4)Qw=AvSym28K+PHQu8d|$@78-i>?COVi_r8C5 z`SaV`FCU-2d2!>(!wYX-T>tX^{?`u=zI}fBIX&f>oZO$EZ@+(e`t{@Eub&?M`u_U! zho?8L9)0`r=BM|Me*bv;_s_>)_1|B9{rKqH=O=%Dy`R|MDkmd(<>EmZ8ObRVJKn#( z^X>EVKfm7n{PyC{@Ap5yy?Xla{EbVy<>ch`P0h}q*<<7Armds>`uX(_Z|;2k@c6-< z6W1^9vT|^mG;PwU<2#lvU+fzg^6J^O7f&ufdvxK+gR@^hJ!@$wl9Cd=ar@Hs%ZHC2 z+Wg^7{rykxpFe+m>GAzDkM5oR{Nc&93p=k}*deE&pl@P!`OM~vr#BtiyRM_H>f`%| zSI=#`e0GbqgVTg5{TEKIKYwD~^T!vTK0N2(;-RXp@$SvNrn)?FF`+Bh&s;jc=lG#b z*DoJhzH;%~m$&px%uXL!b7;@X-8)vEKfUYODQs3RJ}59SFfb;0ySp%@ao40UFfg!}c>21sKV#?PQnCE& zBbvg%z*Jim5>XPASgue|RG*)gnUkteQdy9yP?1}}z+llkHPn049RrR##a%Lv&$Q)_ z8*eLHdCKUxfAjOl6?2}Xh9sYC$jF%BW556Zg8ZMaZ+X5mTIBxu z+N>C1^VLp}Lz*$kc-7n+$-0vgVgq0G&)_bop5FO^X;ZDMm8{`bABHbUZHK=`Zf!2+ z-1=s-uwu#6zvs_w7G^$qFm1{138&g)Io3w}n>>B>k$-cq@Og*t@So0KCw6w#+20Er zn|CkcuID_x?X1zuHBoc-<~OCUy}V^b=tA`~Izjon= z_|8v^QHW=3{@?jvS@>sdx3vm0nRCd8c2|P+l2YfBN=8mU4&3dDVA=x}8sa zeyzaR!gnNUt_r(c+=g@0a@zgk{^*Nbn8-X=W!0X(B~1111`ifqI?v?eAzt?}@2F7D z6w{S2rz{a+cYmTk?eC5JFIU^FzutbV_x$4?j~Bm|SF3?3F+?>jy+iM@X4D;Sx=w6d-(F{+sCg@JXroveu3Z$84(#FDXDrX zv3JJ>779v=$_h)1%S+9gFk_0$!3m3|&YJda>cp8-=T4qIJ$}NW0FFszTBmgMGi4 literal 1683 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 diff --git a/toxygen/smileys/default/D83CDFB4.png b/toxygen/smileys/default/D83CDFB4.png index 2090a8d335b1606312ea556c174bd613830f0c50..6f383e744e1c6b37ac6452fa5b43931065873869 100644 GIT binary patch delta 1407 zcmcc4y^4E+WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EWrWPl)UP|Nj|a;E%DUZ-zcSGKqNVv=Q{%pd`px?K&kr8_`T6t9!-w@Zo15=xYP^?`zEM~A_s^ei-@bkR z{Q2+SzfYBwKHb0n@#Dw$@85rZ{P?Dh&exA0-@bkO=FOWQU%y^8G3D0|hZ- zlDE4HLkFv@2Ll5Gdx@v7EBiBcJ}y;;-9c+~85o%AszM@4f)dLW3X1a6GILTDN-7Id z6)JKI7#J*ir-lY@nr*=I=euSVi}J}ymV5_)tT+Dq=Hu>cp-BR9zqyPPbRu_8x~BH` z-%sto4?@?f)yHmi?|n0Q_5D39o@E~&J=~ms%Vk^L?z_7;d&bDULA@n^|tTU$o%A0mdv_Dj_=C1%F)9-dq3Xk~j$HTr<}wu2T8HZRZoPEe}Y zCd<&{#QZnWb&-;;!i9h5m_#mUnK;$RO`R2}SYh+VPE}+LJC}D@bz*$=L8fTu)%F(r z2j1RX*1|7xTl&(<2UBJ;oT~3OXWHg|J+3J`V&OSIR?qN@`}zD-*(Npnr|-Bhv44k} zN8yq5*mXj`%Y&?Qb{(p1*>gSJuq^kUIWNdu8$P~*T5MFby;j+Urg{hND#Mad)ocDjiY%zO3)AIk$Y#LW%FUUMTrs=cq_bT1F zeJwrCtN7Jr7oWMjP~%*K&ek`zi$!M#JWEz)RM{)Ct!M6&1Bcm{Pd_^)3Itb zWANd!?U_ru?t~c5>f08~Vly@E@pqe5EW7G=6digV*Bt&NfBWB++rPvfocpEB{`69N zzDXsum*(947W{F>-yfncre*B zBqXG?F}tw|B%~##CO=Pj{^U_wVjIVXPai&glESk%AdJY2vgKCLuL`h0w zNvc(HQEFmIDua=Mk&&)}xvrr}h@pX%v8k1bg|>l#m4QJX%X(0OiO`UnpOTqYiCcqw Vr1<-Zit1bp44$rjF6*2UngC9OKt})o literal 1495 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 diff --git a/toxygen/smileys/default/D83CDFB6.png b/toxygen/smileys/default/D83CDFB6.png index 956bc4daa1ef04ba41d8242ee2e262a40f2cfcf1..685a06f9dbe3ff15244cd5fb6ce8e024bff59a8e 100644 GIT binary patch delta 1107 zcmeyv*}yqLvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{{7+B+Us?Iz z-Ti-J;$It^4>~%})Yad4dw(q|dLl3XL|*=pnb}Ptp$j}b^%r@0&$F|iXJ`LEXU_k! zvj0g*|D&V-`}==!ad~NE^s&17se{A+|No^j^pY4D7(_~f{DK+e=Dtr?KGk2~^X@aF z$kUtCzTdDjX2|wrU|?WO@^*J&=wOxgU|?WiFY)wsWq-!b$EC`7s86Jmfq|*6DkP#L zD6w3jpeR2rGbdG{zNE4sRiPrcfPulHcWS8Pq&WsWdwz>nHcd=9Y14l2$Fx;4pZ}E1 zmYkM)L&f?^!i1EqS0=gs`ujQj*TML$vr4YYnQro5_wR#{+RdZg?dSJp3g7(o_~Q$u z3Fq^sOu3NcyLs!0WgFJosjuG1VkXvmbX!uNC|~GCPnX?!lS+G6d50XYcgtt_>}hnr z_N(rcT`nvx$@e7N^>#lpke^tyqw&o_*|IkiVpeUPG}&N>obwV_v(U^A_18-rLZ5Ko zT(Ye7{Did=PF?6OS@Zku98#vOzN`tsUFrwFT`zYQwO--$->&Lr%WOa;0TwSRC}jEOOSbnmZ>uB5{5!hiCb{ z{blF;cvRSD`;{+LmM`?pIQnq@o;#}F-Y+S;QPTdq=;xDk!?4_Y{Jb)2+{C3?b59%9 z-^?+ys=m*1-po&UH|N_GdCRV{td{1=&q}>MLn2zN=xMMkzn7HKLqX^Gc;%gr|F|Uj z3Vc6kWknQ5C@N@OOh2*tZSIVP<#$9s8thy2m1ho{k#x|_Nq&z`Nq_p&Si^M9xZszZ zk;BcqtVa#y<{Wymd;P6TsVXTvEA4tpY*PK&7uC;TjLf?E+D(^l;hE!(OdeK((Z@WC z9z0|{pL4S2)sv(Dog9B1IoLCaYkA*9?&QRihXcQHzF)xl-{F!|Th19R?FT1v3JTo* zx!sKaJMG{0hkAd%&3Sq$zFFn2|E$$F?L_%s-d^so-R0_8_LUo~lx1)3SAEI9=vG&Y z>72U2p$aGAt8hIqbmv zu$qB^LAAs+q9i4;B-JXpC^fMpmBGls$Vk_~T-VSf#L&RX*woI-)JWUFz{{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMl?z0S^#7DgtH#+D|A zhOS1I76y)%mIfwn2F6Z~maY~sy`Fi+C5d^-sW5vp!S;gn8spV#|$Z+XbkeVqp7p8v$>^{v8AJlsjI7t5>#&rIbo)6r(mNGN*qXu1tt{a;s#=Z z(={lK+bJM2PikI@tx}PayF!Nk9Jow`Z0)KlAhdddEhYE(t5Q z;KN56c=GNhB}|yOGW`Jig)0lBK0M=PKK%B!BTs1POAB6y;O49Y=jZeA@XO6G*i$jF z=Q4wd=?b=*pN9@?NIrhIetW+B(M*O}QXi%*qv#pdz9pFV1Z(||pXo-@5*&3F<$cc&H?wbF zB5(79|49A>XX)iq6S(ii?%wwC@7LFz|LguTS!(jCZn3XpY5k@-C(D5$y{WVB-~an} zV}I5sJ<_bqCIS61$ypPs&-@km)kv8sr16NeXrLjxm2M6#j95#Q?Bpz_Gm)z4*}Q$iB}Vgt0c diff --git a/toxygen/smileys/default/D83CDFB7.png b/toxygen/smileys/default/D83CDFB7.png index 4fde005983511d9978060c84edf759f7fba13824..421da920ad2dd4930380d267280f7df6a6ab0392 100644 GIT binary patch literal 1606 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 diff --git a/toxygen/smileys/default/D83CDFB8.png b/toxygen/smileys/default/D83CDFB8.png index 584ba696eaf0748d0fd627d1dbb19d899bfd1e94..649899dcae5d5bb653be9b7a95ecc4c742f26439 100644 GIT binary patch delta 1509 zcmZqY>F1dsS@Ch>&U>cv7h@-A}a#}gF>=LkS_y6l^O#>Lkk1L zF9rsNh8GMBr3MTPuM!v-tY$DUh!@P+6==i2z?c!>6XN>+|Nkx1o0<(c85kJ0#&d6t z=iU&_u_2nHD=Vr!EqqB^(c(bHrlgPsP5CY95wUh=3q$zo7l!av$NJ5w%V|!Fm>ah^-u@LVt zk?1gyXf=?8qh=i`2x`)is?n10660ckgL*g%%-9;weW=R%YK8pTT=NqXQrBf0t!c|% zm1VlDqjX7o$^1aRMXg0!lI3@%D=la)yxXWVuQYsKQ~u^;t(%S3|7TjSF7uz$kawoo z`2SSXf4y4I`<;(XNLyCudnQk9k-zNvIGwLe%DCb=MZu`5wo_BAw0>s-N_Vj}F5H{Ed7mdT91CRo|NsBN z>6h3T7#JK%g8V>H$6&v|zJZPHYy;cbiyQ^_+0I^kz;@Q;CZprDj~B&{{8hJCSn2R~ zbHw|Ni~Xc-Jz*2-fAafQqK=ZZXgxm{D@$6`Q&5mFCV9KNFm$lWdN43Bu$OrHy0SlG z=i^dk3o_)CU|?XXsS1fG2}&$iC@9KL%gjktD5)$+Rj9}9zsK-Qq4e zM>dvEg)*mal`cCix1WEG#k$Aa_J8zIVSaRwIq}!ezwEW$@t55;o%-_uS5!?fpVSK%n<6Z-Y7;ZKD}bhU%%$}e!=f1(U%GuJBxSP{PF1*WX;i9y(%^+ z*(-I)^_Y($krU2+`1A4EhDW-}WeWaA$BSJK{djN9pj1?NMP1@!+m;4_U-uU~aA=5f z9W+x9HkN2Q>YXp#xZ$CK*`iyWw%3(7uJr}gADPo|d$&)E_=H^9mnKI-lo&5`i}L+m zouyv?{87~6^Li~8=6<)g?)NKEVV^DdF2+dSOmR!nFaEf9Lchy{taa`_+!tf_Gty+0 z+572id||xek4$fwom#RvtL$4H$2`;g%Zp68Z4*-`)~w!mKGufUoKq^Z;=-O&L7Zn> zS(8}XuY2T8$ZuqH`(Ua0PTfF+wQ=`^Hy0V0=B~3Ys?RudktLeZge5(}>q&-3=d>*f z>*^HF=RXnNFsJe5{|Sd9V+usvA3wU{QFlXYuAcJ31dm>mNq45s;QP?#eQNEyQf^&6 z#q-BqnK{p{78`CaW_9tSH(x4wGT9J1JI zCg1z}Lcdu5>UAx*@W}efz`(HD)5S4F;&O6Af(w(*8a|#AOgVjiZ%#1DoYPy^Kzuy zw46VouwFn!Mo4PeiY0591Os^cJ9@hMI(xgrn>kjftEj0iU$QJS!)a^A()5(H)aMz8 z=Nw)zdG_@9fQTjB;ukLK>uKs58?7XYL{#MM8@Fy6GcyE#lJVW}YSU!~ z1_sp<*NBpo#FA92{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDz0R%{hHg%7Cgv`# z7KVndMovcN&K4FXu9hx_PR^#rE-<~GdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{*-r>eR^_ z!X^u+1WZmyh$s$kd8jK5LtEd4IeYu9+02die3O^% z3Xu&esR(S$-|Tm*rZi{Gqt$t#yvj^n3BsFwZtgg7clMflJkq%=GjwAf-F>%hXG<>= zY*R<)(l2Pg;&%?KC(qQaQuF*Gl9vg)oH`mkX6o#6*h0T39!uOOH^JR zdm~xbDM41_YxFtJ9rd+SIzKRN%x&;<_EY6>i0k37m=|izB7R+}woIsH-J*I~8>@?s ze0Ii%cB;smDQ-zR#vi+`UH? zrK?|TdYoo<;BrF)>u1$l0?I5dZmhng49i^l+NNZ9azA_}UG(3U41Lb0*1jv{*5*_Ee%+CYL$+mY-DXFG?%oCpU93`A*x)%8M|QRLBXH1Q|-S= z*KU94_xIb5)0KAmLc8{5p3D2a_D59hZ;=yjiMxI%TwURJ>EYjKr+S8uC%cv`IL1@S zz`zjT>Eak7ae3|uZ@vZt4%fgHO4sUEl>V>2YE;1)oL{6iUE|o6qcgrqOqN;2^{Lmm z{yEPik99#@YXYOQA1waPXLp9DrpNGr`(EuoGR1bn*E*Jpf11kiKW<8)Is*fPYKdz^ zNlIc#s#S7PYGO$$gOP!ek*{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@r-%&SsXTCYH{Q zu7-xLMi$0SCN7p1h6Y9!=EiOY<}kgUdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*z_8KN#WAGfR+5N_NTPyxM#c-H4Gb^; z^E_d86PutZ!pK>8^S^qY-7kGjP444IJ~;l;(UmvoDB86# zu3NWHL#XA4#>F2$uiG2gmAzD(oq2TH2=d#Wzp$Pzt#Bz23 diff --git a/toxygen/smileys/default/D83CDFBA.png b/toxygen/smileys/default/D83CDFBA.png index 77ca90e4fa8338adfd476d5f0a794f1369315768..e19dd827e20c99a6b9d5f211796235e5d279d20e 100644 GIT binary patch literal 1583 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#^3;-5ZC|z z|G!_)aA`UN0|SG;sW}AsdYb8*nm^m9b$X$Lo3rty=^zEqH)&sA?e%0i14R9$=?pjK zGhAB}a&fNE`}GLH2dibStqZ#_10?c(J;Q@l64%y-oSz9Y;VD?}&4mot*M*&*$#8Wh z!{r$u^k|LZ%dHl7HV2)b&Tw@W!<88f&z3X1*sT5bfdAR)AokOZ>Mu8GzTBkw?x640 zSq$&ifee4ToZ;*oi+ghzZp{O6A#ON2iEl{(!{uoV`#TxV&0qrg3XBhQF)S@)n34$#O;H@71+Gfp)cob%;IG%JJ|FjcyeH|$ou*&+tG^vLI?UkVLy!-FXeFg@G#F8LCP!usR?MTSm<@vKAAg2KTvdEs z{qv*f*P4G7Z%$46^t00Kha+R{-;eIU_L?86-@~Dqe(-CQ`01DbVlIE_3Hi6bB8`!W zx#XX@h?uyj052E2{|hY@1wkH8)~)-ir!p`wFeZ7syD)UH%6c#`FtC?+`ns||W9Q>i zWG9Mf`?9v!p}-B6Z^2h&{xzn=N;-&!#Sw&Ck^$roCB~ApGKl+96*Ke_@fhh>~?2o&jP-eUZ%UE0=UE zI(p#sW_9rs(+`}AN^ojyuzM)Mx#Ff=quv3&AkQBvhKJVw(C4XGs9fOrL_vjJRaa>@|qjk(p9vYevrS3FeDMfP5HluUP6x@>da#xAub zU$1&FXtp28Dx1W}uRnub^>yRP*?(18p9b@ZT{&mG=GcXk_s<;Gsh-9#Wx||;FL~ma zS0_XmvAaKcKke^YYOLMM1Tlr(szFMvd{f#9b9WSl$Yjge_ z9rTZ3$D^(Z2h45x85kJ$d%8G=NL)@%NJvO%VHOKBW1GMr%(FqgXaPq-QQ>9 zKUi62K45Y>q_imMkx&7nr)H*PWTIu|3+BWm>MgCcVDakZ z`Z)<1OU^P28kl(+v92bw{6^d`H~pZtaAzTXHA>;@WG2GZ( zg$hbfpE*_5ZY`J5=I)m_PkqAEr%!oV>>P}1|9<+*%+$=(%ZjuXh$-VPIfTEpd$~Nl7e8wMs5ZO)N=eFfuSQ(ls#GH8cq^G_W!@ zwK6r*HZZU|6H_V+Po~;{$|z4)KbLh*2~7aQz@4W6 literal 1540 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 Wi7AzZCsS=eMWLswpUXO@geCxLYns>q literal 1655 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 SR8;3;VDNPHb6Mw<&;$UuR=TzT literal 1635 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMliy~ZYnjt0gqE=I0~ zhK8<27Ur(zuExd|&c=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDj~D zZf@yjVdCs!;$j5yx1+0(g^Q!BxtXJ}sky7UqY_ka3OQk>Z>M0R4@w+Ji3KJUQ8j(=HK-1-J#(v?8N*p>1h5kD&S-A7UlJ2FdVv;ROr9C|Eg+#d7 z7v~k9yK^buKKH&~yz}yJwd`?x&-a|KJXbvL^S%XB9{q2)IYIHr@f%euuC^Rx|Gjhr z&l0xOGVX^RYb(oS1cS6$V;voI-rg4Dd*i5aPTKo~)Aony%t0(H>v_`^!*d-E+~!?m zr0kftYF5IpYb__1d$+PGcOQ7hciAv;({wS>h%hY=4daSy!z3* z*mGY}_1N8%YWNkObo}5nsSwS}{?GUG)$Ra|xgT~HtN)$DpXJNHvBY$eV)G;U4>cb< zHyHSGdl`iZ1jJhQU%0dK;>!H{p`MX*16Do#8RKNVtp1MRrPP3>{T%n6xb0oK#W^5Q z#c1NIFPk5v^%cd5bLW0D?pvs}YIgwV`o9V`>r^zh9zODE!z!KIZ-pML)Q!DsbWuOx z?o|FCsoIVA_dg8fs-CeYvs3&V8%uHVgH7L@#NwH!e#^~FdJx;v@3L2c`K(T-*r`wX zmAA57s?rQ4^40HF8mUd+v%;FWG>O?nii_Qzt8@e7XSum6Cd_)aRjgtS^92=gq3(@E zGY^@a`G4-{lwzK&UHoHgqBw=v?QHs|=2Ts(-0ZX?m&f?s zGE`BhlsuZvAj;%>;A3q0FAVPD~t9vj0+|I~zk zoKW*@`t_iyy3^m#r+b~hUa8vJwyTl-oty`9t@{EV*xH8tD>eKZ-oVI^_BL>$`}R#U PK*heNtDnm{r-UW|oBw4< diff --git a/toxygen/smileys/default/D83CDFC0.png b/toxygen/smileys/default/D83CDFC0.png index 8bf69e29752a198823504f77ce4a7e5f77572406..a877641cc293b72320abe0e494ef80509fc5a8f8 100644 GIT binary patch literal 1926 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 XL{#pqFv`dA3uH8BQV0$)^X*= ztsVVSA=2wMZ-4Oc(UjTqj~+h>k)6MM)sBNF9zT2D+}-#8|Npx$-ZC&Si76mDQAkYU{Jm!fPoC?WJpJ~ayKg>z@d%7~{PbCEOZ%#A`|iJd zr)_L*u!B($L8IK z&R@B{dc)@1_a1!w`a?!hnU#$rI6A&_!em1;i<-9X#*Ut}q6z~u3m3jZMNsgS1o?r2 zl>r8{uN@BkzF*_=p^&TEd%g!8xURGJd%cLxnivKK2F4_BcNc~ZR#^`Q1_t&LPhVH| zXY71jstmh>*61=YFx6FsM3e+2mMat#<)>xlq$-qD7NjavekL-|0?L!si`x1R|aaj{e^HapB8S$@#l*0yr?!CSgzFS6OE-zvG{By-O9 zyz0t5ogW(PUBwtzHRxXx3tVY;6mCfArXDDjL4RVtVjncH1`b3oF;G zDqD1H%Ap0Ij%zFGHwQbsIQ46Oue++%3CHv{%jr)JtF;9A{1%+9o%COA>)}hH?=C-4 zwp;CX?N?n!!h>~*PJ7&Ly3N|MIxY46?1aeACTpXNFLGCjos8@)>|10yN5x~w!i5!6 zmkM=p%T5zDw5cv?tYPIT_6fXKAv(FjF&Yf;0Z{OD+h!D|+&xg@x$}|J|n-T%H#MgP>plOu$K%DXWtOYoZhw^h^>Mg@ zbnA;p?IFt6p6%c3IsY=r&geQUYP3p+fq`M7r;B5V#O36KfPg27DM<-wiKz+6&mTNV zN(cxFd-(LRyHG%A@bwEJmoAA3h%|A$c=76`v_w{>w5B9?cLx)zkI&gNx;ksu*xJhZVm|ofoUx59Sdg(N*&YHQ|wG(3t)I7 zu9+#>A!)$4AU@^klR#F5I!@kX2R4Q^-bxQtYLikK7#LJbTq8qOw6?n46F{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy%t6WW(Fn}CKhI9 zj)sP=MwU+I7LIPtCMM>t7A|g1<}kgUdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*{-06JpI^-nU+(tj**q2}?|D`qn*6%nRdnlr8z!g! zje0-$nV6D<{!7lB@h^U7<@4VkFQ?lzI5W?< z|KYj2)dk)vDk>H_K0PuGXI|^c&70mYV^@=LchywI0&$zVUvKJjy?_4?z5VSkd)vYK z+~m*y&DP!f-Nqs#wa-(L3T{+{0ghZvj^3;vz@ z`hT@~&cFKK_nQA7F!-Urc*URjQ+t_n7`SzK_}<<-x8eH{%QpY@@ePL$T))gK$F^zH zCLXq1QatkSbm$^HK|rRz~isQ+sMYp9>YV%M+L5GizIYJQ>X7--y}Hu6ibDV$N@5M<^s ZU|3pyTq}b=PZ3lsd%F6$taD0e0sut7N{Ijf diff --git a/toxygen/smileys/default/D83CDFC2.png b/toxygen/smileys/default/D83CDFC2.png index 9bdd6b44467f9226ffe4642ffec2ba3add2614ac..9e35a6ee1ccdb1eb6e00632ed6486649204ebdab 100644 GIT binary patch delta 1719 zcmX@id!KiLWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LLOvpAgp*@BepfIQ74k;dT~-t*xi8 zZ`A)*1~`InPrUzs@WPE-{eHJP?N7Y_Uw{AW|9xk#zFD4r;{E@-U;ghpdFjOa|0mx6 z_x6tV_KrUB{{N}>|2G~wJ$KF4Wn1@E*Y}o|wohKU>->lRy(w0;jgzLW-=CRV57Bb| z14!eU_y7O5G87j#cWgLST-*{KnHCY1e(u(Tf4l1b&$9md=l}D!AO8RUf9=7OT_-R7 zub;_q_sjoRKmWh|`G4Ed^FM$6zWw<5vp4VWef|Ia56I$%hDpzU{Qvg%|F3`li;G() zPF&j9IC=8qWmQ$Z=Pq4;`{Vz^um2Y;*wWcK_xiK<6;&NgO;cK0CVaSc_`w8=CmZXY ze*eGg%*~35uA-urfB*mQ+jlfOySAvLX~(33|GQ)VZ>slt*l%&?^S@i4|3Cl!|I45M zDJdnzMRi}#F1s_uD>b8R%ktT;=KKAh$NO)&-Gi$~E`R)g|J(ncfBu}`zC1gpdUCe+ z|49u0Cv!il6MEGp{cKC!tA|&9{{8>s@Bb&?|9^OL<-_*cZ~a2kx?9(-T=IUZ-v2&^ zpMA`)HWog)cJTd=Kc9a8uYdpR|A*iI9~UtE?_~Jj&+xv1;XxJut9gEpPpx_O@$={3 z|KI%j|Ki90cU$YewlREJ;`QY4+y^h8zyA6E<UwVy!fq}im)7O>#89N`BD#PxeHM$H8Ox0B(5hX#1$9xO7uDX$+wR!%_o_^gf* z>8$y2$JF~N;f7}nPV_jt*iv`LAG3~c!t~~UA+S+)kL%f=sJA2K>^5c3-=VTulVN+ z7t}9b`G7ek+TDWJ?kWevm!!6X|IXYxXt6uv%FWIRMfItF&mUVXBzw>?y~Xl*PkRry zm*HD>{qV-W)+;!p*A>iHlmC+=6d9d<`M`nLLoB=9Zo17%5j}ZoUVB31rrBl}eZDl! zoblN^dG2Zn-nlHC<~$Mh^CuhNd!-BBOB*Y;hJ)cNiu zG51Za--7{Sv_Rj>9O_Lv9_%nSrzXlajBeCNVI(xrN!DV>0PIG zp3>f-sinEQIwGRN$iO&ybw%ax6~BJ{WZufg!o)h`>XqguXQ#u5bd{7AFOr;LYO*yc z>G30Bp~;higDzi^+)`>}w0To;(dSRxT-{yXF%n*<-<;CcTD@v*cGl}x(o(Y}>Sskq z-M(dQwR=}})$f>1ztjzU=9pBTx{`T0{mh&BriI(y%9OKh-ud~Ne#DLy^@kQ6eRuJ! z_cSI34I2ly=}z$%7#J8-OI#yLQW8s2t&)pU6H8JVj0}v7bPddP4NXD}4Xlh!txU|d s4GgRd3}*P;--V(fH(x0wGp!Q0j+cVzY!emLxfmEcUHx3vIVCg!05%|Gq5uE@ literal 1731 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nujz0R(#hL+AImd=h& zhK8<2P6ie(u5M1w2Cn8t28M>lFuk66#U+V($*C}VGeP!3^cv&UYvo*&npl!w6q28x z14{t`8Tlpo#Toep3eLf13L4>=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDj~9 z?C5G>WMXLPX6fu|WN2vN>|$Z*=w#vQW@_PLXyBp*)tf?2nCaUo*yw{22U22z2?e>h zftcWQ4NBv73W&^;nwMg$RHS5YCx2(r9|i`d&z>%hAr-euf^$742a43)tM%W0Z)Zu~ zQs*3>vthG@ejL5>iTST^ilQs0r)yGx3u|NMq`#ib!u2kKEKaVRwFxU7qd2EJY1N5j zO^TmceA##Irt4?7S3jTfcLVeJ>T^%$J)iU2?t9F$*Eap%`ePSd3kjaLI*8SF(Z;)W z^}mAO?M@S##i_+{iE~E2s?8tY2m2SKEH-p=f2i$p^6eCl35~iwPxgCg?pv|A?pyI| zx58ULmz~c~eQwUJ;+kXp`qsL0Q%bX(CEFZ*Urk}nd%w~6pR*p<)CK3FA2?Jjh?G>w zHa~Lg;me|@zsdr-PA;=4&;9-4@7fcpib;1adnl%?=-_s)%~fl*i(Q>&Zd}*nn^=^_ z7in9~xH_C!fN|-OyNXY^y$luZTsZLd+mh8&8zpA`;jYav$l><>VQbp`>-JUAtriyp zuU&AsEYzXF-=x|RE--D|{gz)| z=kN2v|LjRGzNwB2l;%F&w>$c2E$a%&Z;ktJPrdWE{cQjB*UBek&Is~W`Yq<0=Hu_b zc!2@`$7L_h#?L8c^p@LiDe;n3`TY7_`_2@4>zsB{<38}wSY0*v@?({0kKVo(%b%CL zgE@JtXGErEyT^me2f~g@ckZ5gWWfP}ea$~VJQB6&bDrYY`o=jkBPXXVM90Kjf6{Xte0U` zi)$*^YdpX1n4cx{vhxQ&b19uMTeQOI#lZ)^pI6+t(x0)9eQBUC!(IzL0pZO-Cq#;N zl#4s3Sh1Puh%?DJZ>f#c}T)`B-y+y@UH7I0tY zpefXp!G2xp!NTnc@lSsq)#f?$LR;bJ8$KnG<4Ok-+Op4Uax5-dz!6ZP-YImqWy?0k zDkj65oM(15KE3DlL9wzxO^&grG9e^tL(^BiEz-Wr4E-79PduN`z|0`^j5j^?Zc8et OTJd!Cb6Mw<&;$T)pOR$& diff --git a/toxygen/smileys/default/D83CDFC3.png b/toxygen/smileys/default/D83CDFC3.png index d8c9cf3fac55c78a6bdf42b10efb0240b2897a94..f4721f10e45fed51bd3ad00b930af675de8f806b 100644 GIT binary patch delta 1398 zcmX@by^wo?WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EWZQPl)UP|Nj~0J!4>C2+-h&(&LRb z;0w^;2+`tf@KcL6;CntjKFdbpW>?^~&VUQe-WQs^FV(u$Ut?vxz{+|%+2Vee&;2f+ zGxEZBJALkU`rPU8xlrM9q1pR#ix)&Sgj~fTFhRnQA>#@|%oz~Myu^@ufgyN5L)u9Y z8N44vhU{ku+078J6NDHT7<@J}z(Bf%=>O9zR@KD(-rD|aOWXB&ZOQvw?ElRapGolF z2sOFR!}?2C@>;v!)eir|tgQd-R1dPU?q+5Ep(v^wyPRR(Gld0@7+P*HOnJzVaGW9Y z5JUTIhU#k!?ROYzFMzC={(vF;AVc(FhRz!d+?ovL?f?J(KeFjj5Ca2)SxJx|D3sIQ zzE1r7^UJHBZyp3Z`&3`=aNfTDeLdIwHi!F{4=CI}_0coo>#0nUcdzv7-mm-V;E#89N`BD$k}T57-zOnChxRB1(c1%M}WW z^3yVNQWZ)n3sMy-atjz3EPAJgMo*e!!1L$2W)aKc<(7QL5B7&VHLgGI?|tUXCZ5?j z3%K^DY`u~t{p;^%@xLdQXRGf#-63~YU3~w})}rFF&mV4@N6y?{xce@f@w4>nb2bWG z4xP2`Vo1X6TD7+uSpKtUc8?3necX*-*>l9h*|97 zVDd`&@r2a)ikaam@_7d3r)7WLI#_>p*Sc#R#ybQ}RgcZ)Sl_Yyq>6*+lkT0KAv(Wj z>`tDRJ-2ef-Ti)hrriu`DjQ7fQZ9mym;&$b5^ntGJ zT^0>CC+ED^7CKgbmr)?lV}adCkCG*BDes17)?0RZnxzp>-eb2WWN?L!H{Ctppam}qg@=_K%vUg3`FeP`IXZqEk zZ~HjsZS}jnD6Dnfg-ss4I=`=;-u_r6(bX!hBRsOFUO~@fMp!{b-xH>vTozkh`9GHP zgrteKei7cPG(*9rrRDgA*OLSfTHor~AltZdLhyu1lfI~&yEoxE{}bj7GL0|)Phgz& z%1p%l^MeRA`#*ZxbJw-Bd1p~d@` zmaE)$JlXxfmFZXg5l6A=RbK**PGPv@Etm70G1dLpN&Ynw%B#OvKT~Vs+G1g$RKNL+ z``(M?tmT((Kh}HxagWD~U&}0Kzuo>Q``g=a1?i`kj`E+}WHpuV{ePif%;%cBrhHo{ zHHm?NVWy{xV~E7%pe57V_K4J9YD zZDog)JeU@J9J5&R`zfR^P6???%}zCaf~_#j<76|5S?l> z)zQSq!S(9tXkJf;yT`Aew^uOGuwYSIuuyW<(zT1F#pL)zFLYlpXIO3C!qMX5CTVzx z!Q;Szm0XmXSdz+MWME{ZYhbQxXcA&* zU}bD-Won{rU|?lnkji&-Cj+vE-29Zxv`X9>M1pHsCn~CQF)(<#`njxgN@xNAEb~;z literal 1482 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy_S|Hre+qFCYH{Q zMuvv2Mvex?CN8c97N&+KZl=1I*}ZxwGNDklH+( z)#iDL76l3X+9%m+e*Nk6d~&t6je%R2P4=Gt`A$QIqulrF|9S1KdUxWpv^sxHT1-jM zKP%~ITSsAs#*QBS4V$d)JWu-n|Gv-1|HhpE{%@bGd+z@GPaECC*$?}?VEeRv*X{pb zuBGlMe9PI~p&@?wxb1e)eg9Lf3f7o4JLlKrM$3oWH#z)%KA&f*RFe%) ze7=o9!q3mwKk7gJ|I@*g=hULjQV-YpPjT7t;Pd+b>ihrvTmR$tcX@Z_t*)W=4N3O$ z^$9;%m+&r0m;dp5{zbtRYK(_jdwO~VuI)+RbwcR<%$@${f9Nf?GfxtdKfJPh9$VVx z?aZyBAKjE?-c(N$5ZmNF`=Do+cjHTb=EEoX71Hz7C3+JI{)MVFvz@PD<4)j^t2*)F z!4qaSHcP+Yw%zjojQ9NcHvi$j)&F@sgZ^*ao$%UMkS%dt7+WGETbu3oIA+%v2|HsJ zgFSUYCk~uZVeonVBUp*~@%%`hQ-7Lxtn-@=>Gd4+|1LCv?V;L^St^?B3OnW3v0C~X z7GxV4yf|WL(D5q8h(+?e!VHeaiyb))W&-U^ANQo+I`V{h0wV(>8^brjGfP?)pRNRz NuAZ)bF6*2UngE5M7YzUa diff --git a/toxygen/smileys/default/D83CDFC4.png b/toxygen/smileys/default/D83CDFC4.png index dbe988e836910f8945c063366351a1dfe6549321..19b88bbeb567b260934f391085416efe804c2bf8 100644 GIT binary patch delta 1667 zcmbQqyM}jyWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LO4opAgso|NlQ+P*&xoQsJpo?WI)d zt->&S#n$G;i3v6g3)U_u@ZH)J-q8&oFxh!=B^9!orS@juCD) zm!{=ETu{caXx-Hrg$mM=78VxP7Up}~QywlT1BuUG@o+)exyiYbl9Cb<5)T)YU0YDW zuyGGWR#;eAU0oey;PTDF!ou?M^8Dty4Aawug@u&^&7|xU7o-0;oMDgb908-ZVaot7*1U`FfdTikhQKgU2=1N@}Xpg8O97VvUGHG6q=;H_t@mO zWU6W?E7{1%?0017^<~(9j$yH`+yq$-4UO4{>-!&kzEyZGQpHnNt3a2b!;xY85r*C8 z80M{Em^*=Cxwig96=7jvGjr3Wcc;3X)Q!30s&Lemq1BRM(_V&crx{l7Vc2klVa;ZS zrIvly>OcN^d-K!9ysP1HcU)2*1~9BJU}$w+&6k1tGMU|_1P3W+EQN-S3>D9TUE%t=)!sVqoUsK_l~V6f<&8tS=iu>sGW-y*dv zi!aKwC+vCsz3S%i_mfT>Q2(>=us4HenCH?>!TTs9?70R;TSbC5~x*f%~t{X}F#3!>X@v+v-;4ij{863&hJ+ z-t@1OowT{T?VMlBl+|zMtMjW`&2m1|QBr^ICcmV(hv8>-{qWw}`&am)*A>WXzW<{m z6g>O8Z!^nk7p^kio5yCPhI@177@WZpVU!QD(S&uPc(gb#-n?RNKY+O(nA#x!Gs z*DQ@~5+$|ko-q9sijWi)EVfbEq_EVXs8Q;L@jfvpP5szY=P*v4^{$v( z-%RoB`CgU@MG9fX>XIKmG(De?VyjWPQC~wt-Xde?e(htcEzgR{Hnso0@$hpEwI$20%?<~he6D`;L+Blpd-t-iPW^&~D_1>zx_0g2*Nzd7KY9MZ1$zYpfvmlO6E=Dlcw|IWu(af7 ziAjpe3QLR6pTO~CW^{y_>hdMamZqnqr9L-^h+eJrI zau^sGR7+eVN>UO_QmvAUQWHy38H@~!jC2jmbq!5I3=OP|O|48#wG9lc3=9rep5sN) gkei>9nO2Eg!zPuojT05sxfmEcUHx3vIVCg!0Fk7%O8@`> literal 1689 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{0Bc#-=7tW-dl9 zmWGC|MwUiqCT?cVE{-lHPDX}KMlijedBr7(dC93Tdowdrte|>L@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{|HNCPI@o$Ro=YEFs5S_*Kk7MJ66AH?zM)e)6 zODkq>pXqmfS@G@d>lY`r@y1u|Km2{)bDQUu&u#P%f42D-+iDqTYImUFS-hcO`R1DqHi_>*BfJFC0nObaF}hfd$(h zE`1!3w(3&+2F3q+GhQ95{?E9jtoHOh0hySqwm-Z>uHG`%PFX7T`^%N<#xD!cpJkt< zJ)QB8t(tAm!~3qQCvFx>o^|rYxiFJhUoS7M?-e%pSoUf?DslV6Y{kDMcH0Y$L~Z|; zj3-L3+2d3yVHsHM#bzjj)cwdAh%&8Hov&&~wSWU6Q2TKOaF zaqr?yjg5?(&!#c6ADa<gz%IX8tXoVTVMa{_KB!xYHJs+nVRg>pP6`WcgwYpW+7ioCSEW5&v54No06WM z>p7W)?N)ua1*T`Yq*U9?ir>-@eEx8(5Ucm{rVO$5Qyf-xDQuQsr~iE8#Z6}wX0=_H zZaB1t>5b&$m4U0XRlP7khkXwGgpn<{*?k z)4R8fQ!E`!_Ug7RP1!fKZsEe*mEDKGHvBbyAjfL4NOMunBm)Hnwt!ToO#%Ba|584{ YaAPw^owVkebD+w?)78&qol`;+0Ffhzh5!Hn diff --git a/toxygen/smileys/default/D83CDFC6.png b/toxygen/smileys/default/D83CDFC6.png index ea79487931dcb8abada6584461310f0a71d2f549..7ede172133be2e65f8af03f3a5d0a7307f83fcab 100644 GIT binary patch delta 1522 zcmey$Glyq_WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817k^mPl)UP|NnP28{g<-xY5UOs-5Bf z41rg>at*xIa_m^ybR{-){Z?dhP$KE&uQK z|G(Y&|LOAopU$1xTy>?shvDSLvj3kB{eL?1=ZU1Ro2|cWvix~8_Wz?P|34i#wXytC z7sKIMp~t3p9hu~Oq}TdTm+7G{lOw%WMq4_@DIsQ>n@Ui#w~ zS?@1jyfs#QTFHNjZ7<`JzYEqXGB7YOCVAJpyD)UH%6c#`FtC?+`ns||W9Q>iWmh$` z>|PeofZ%tzS_+xh6|39wwkFIZ-sAIIJa^uM(a$&JMC8ABY=gnSo z=5|HBja;+jmprK{uYyXaE?O-vdcy4WlC$Z@k80Vy4!gR+c_D|M;pqvz-8%PDzNPKR z$ZoiKMD7~~zNMV^qOjRJch@y1nPa^ElS1yfRJ7W< zsxhuQ5Pv}}aAhG|F5}e6=WRB;_g9V!&ha}asdVn?rurW*7#CW887P0Ga?aG>C>DNv~-Ji|I zCB`fI$n=!ir6sYZ+SU76&d$1Pl6}_2|3;+B{j52kMOH7Id|+Z*>!Z+VUK)Bi3ff5( zTc5D~cbiICKhD2+x~t1C%ET|?P+5IDA}Uit1bp44$rjF6*2UngD{I BrM>_F literal 1653 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 diff --git a/toxygen/smileys/default/D83CDFC8.png b/toxygen/smileys/default/D83CDFC8.png index 4540605b898edba9447b8c31ce4af229ea870f40..d804bf56734b504f5099ac514cd66da3b2fd50d0 100644 GIT binary patch delta 1812 zcmdnYw})?nWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CvRBPl)UP|Nl$$FSgcrBJh-{1XQuiJ@L9326ly8gM@b4>v9L6o>-h0;Mr&%= zu&@+nGSrc20vRk<=W+|4WYr8YI0UZdNk=ul^Us3 z*0)@`bnVTO>Bp z1OtV+KAt~yv?8n6%k#py3lmHgIt^rNv?Owrg(4++pS^tj@$0u`ef<}!b5=yUM@#X# zu(MSfY2RKl`~8Q{4`02zc>Cd=IkUP=6f>2C!X$W|*jP6t1WzfiJbm@{{?iv1PMMk@ z&+pC4?!m{qJ2Q4}PTG>SoBL+Wudc6e`Tzg_%>@gJ85kIxN`m}?85nd{J^k|I_p_71 zvJ3v43jgq;@ZaxmvR~%k)BCe{$sfJ@i@tnO{Fq$(?sLMqe-|kD0|R@Br>`sfGj={MRhB*W872%2Ox0B(5he9O ziRB6fMfqu&IjIUIl?AB^6}bfr3>LjpLj$MXHsJX4T*S7ChvUg(i-}*afB$v!ak+!% zwbvE5bLXhv<}ftqN%{5nv-sbR_{(lZ(;uGg)>SLp{OZ)v8>@D%-R7Ns@A%s?=`$PF zFH2mh7rI;IZCuEM+c~Pa`z*!6KKg!N)n?7qRi7ZZndj!t6L)9l+~eV1%RWOl=F#1E z+jbw3VP)DBC_S+~r})v+c{6KT8sCa(f7#mPoqH==vF}OSa!;-IN_QsuPjqe2S|Ps8 zH6+XCa9R7*;(3)1?A(>#z1+6+F`M}FqFp}%<&?R9goK4!Pdzqea$sEX(a;oU%fp|K z?>^l9{mhNV`aLJ4u4@Y)yP?itR8;syUE*WgrUrq}^EW$iWr#{0+@=<+Ezxwm_?{S3 z;$g>}6w^-IZ%Q23`oi`sRo3PHUcI{+pZiOJ_4H>p7w5E7SjKY73H z?*j8*>mK-3RnK{9Gmo7!cD_ma_DZdNYx3ta-PByBXXjzMYx2_%f2LiKUpKF-r@mv` z7Z(NwMs80R#}J9j$q5Y%YH4C&W@UVSdK}Ydw_iVb^XS#HcboZ7B&R$P|M2D0w~t>x z^K*!>aB^_6v$nUiwYs~wxpp(K{%B23di+ROX!4}spvx|j5=U5?k}ew?ZQfK|^!XF_ z5lIOZn`Ulfp{_1(uhXZrwNxdSELrXJ$hdxWcGl}x!ctPRQzTMm?{yZAin{G}+uCaP zuBT5VMds92Rb~Hr^-EaIXO2na#74)HN6SxL$-I0!{mdNG>b^}}%+0UvRMveK*YSBV z$D;aTlIa5NX*#jnYL1=?<&9l^eN8BPrE~J#U%b+v%}w*}En3O?ZTh!sxwp$j-rQNb zc=hu9g!+t>GYLsqX?cm6skzD7>F*z8ByccTmIsF4W?k9Cz`&qd;u=wsl30>zm0XmX zSdz+MWME{ZYhbQxXcA&*U}bD-Won>pU|?lnFwybhDijU5`6-!cmAExLkckwVsHo1x Oz~JfX=d#Wzp$Pz(Z6HMe literal 1843 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy#`LsrmoHwt}aHd zhK7c&MozBgmTray&MsyKM&{0D<}kgUdBr7(dC93Tdowdrte|>b@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{1_LY2UnAvwwfNb7G17mjnHWAAYF*Bflp1_K*Faf9`gkD|MEy ztMS1$>z~$3Zo15wy8f)us>ElPKi|x5-BdK;mwsce%=I@Is=HDxj@FW*nB_#z^4dd`pVg!=I8zyCBVlfT08#VfmpwF+i*E%^BHxx$t$ zjma4^wzUd7zfTO-?eFiapSn2QO8C>f_T7u)^X^Mcy%jxMFpIEf^&1weKN4t4RC#=Sf<=u)cah|# zMGe&j;x-e-_UaYbue{lL_v%F_*5B?)<(Hb7p09Fsn#|Ov zS|Op%7TXT5;_k5dzb&}f;6eLs<%3T@K6;!S-=NI7&M)?0I{UZ9@3)#yICbX1_r>fJ z_m?m(UF74HBsSx#%>I?Nv7gmn$B5`!GR1K=-R_-tgIS~G=md$sZ2l$dT&ryQilpU^Yc~m zkPawe?(VKU)6+jQo9U8bR?3gdH|O2E7xplW@7T=14fhzH>@HXMcTGXA+bq$@xV7`N zW(&v8H8V9lNaY0q}@R`I{(XXT2C#HLy5L@M!b9BSULy@t+ z3!Z)pvaWm>J}Z6uox`IwyxqGZOX^e+XX_i@CCB$|TRv&Jp5N?i6T5Q%I$2mU zwK^yptk^B_EwppdJk|bC9)-$q_cvRuFVdQ&MBb@0K@#;nE(I) diff --git a/toxygen/smileys/default/D83CDFC9.png b/toxygen/smileys/default/D83CDFC9.png index f4048b83b7b9fa75b11f6ace18e7f036c6c7c9b7..fca8fbf7ccb8340ff51cd4fe71a10e22261bf6a8 100644 GIT binary patch delta 1812 zcmey#yN7RrWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CvRBPl)UP|NpngsQkZKwLL~PJ?PKA`1>8QpBG#H-sJgfThObOJ}=h=ooqC{JUeG!rS0zG zdh_Q?+%A?d{oNJzd1J_nH3637F55S2{m$huzMO9SYkSbkHG!vF zEp`?f-&^0bx6JBdx81vqL6@4<&Sf)wS!w@zrO%l*%XmUFTP1O!nE6Z^$CJsX zuhs=!p5%F{$L`r;*RvHu*D6@A6fhi25PLb(;`PSR`jd?&kLTE4sNlI(!+N8H;Y>Q~ z^Vv4X>y7U%h`d=cbf7HLd zTYXITUz7gv?a#k!d{2E=yZO8S86R;Tc4j8Ij1!;!eSUVRKiS7jS(KaAxP4y?0|NtN zlDE4HLkFv@2Ll5Gdx@v7EBiBcJ}yXe&66LOW4Lj4Xt>_7u(NA#>q&pM%iDfCp>5CXwk5kl?k;)p%$0T9S*y&? zS2u3xTE8pY)aGFTOZSY_`V;3|-`z|3nii1}-Ei}e>b|#myS-m%2nh7v>aa+e+cmw? z?Nb@^P2I2;*{suVm8@>HXp)|%v~o}Phqj6?9i|Y+eG_yS1wEGCWW8ig)w9Oud!#>3 zOUss8XHclOe6svY!$l3gsi9kgvwT+ee3_+vPD%Tc-)=32hXnx+-hgeTQ76RG^XQv#%+zsWmn(4*~u}_v^=;(_*S4# z=q#go%QW_MU0WG=EhF-T>OHBd37W1l(LA-aeNUKv3RMWc*~(-iqx2x~;ifbb??vTz zc5__uekI_2EF!?F!h$JS$=$gvZ==GxI)(4%pBN+h4}7UV!O;6|okriWV=E{A*|0mO zM2fSX)j4a)#3#|6F9TSO7Zj}CoVW0(+LM2`JQx--Z!|5R#Krn|=Qf9lOx*?1lbV%3g`ci_Vg4)Y@Z4W-4Nq6vuye-x z&)R%*PM7}6=fB*RPlbz*LGYd;ole5#|L&{2v z7bUYaGC4VJEd0pX>3LFfW#-F6JUmK^4i-wzjJ#>Nv+^fvYZ4F7BUivn$BCVeneX=e+*!#jC9XGXZ$+n(uyFIKOQ|PU{tP`G z?i0DwOY}t++t#bu*VaYvPfR|qR;oB*yM&2Z{>@#vzx(FyHrcwRw7%r)7fBwT-X33{ zvuAX5)~?CTdHaU3uRK^Faijh_1_lPz64!{5l*E!$tK_28#FA77BLgENT?2DnLz56g y11n=wD^n9~0|P4qgH*nwJ5e;`=BH$)RpQnl5?sqVQBj?Xfx*+&&t;ucLK6VwCmp!} literal 1785 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 diff --git a/toxygen/smileys/default/D83CDFE1.png b/toxygen/smileys/default/D83CDFE1.png index 18ae9e645498e14ea38e8ff9477f458ac439afea..197c598cb728347b950e6c0fcaac774e90b98222 100644 GIT binary patch delta 1579 zcmey!bA@MuWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LLFspAgso|Nk>EFob3)L=?$H6v~8V zDTHPzWVg%oE>%mal?=^N_@l`lnx&B0BA1jc^I4VsyE=P)XqLiNJ@&I!QkTr7Un#Nw z(g3N*ZIeF~WBOQ;Ze)jSlL;uT; z1@hrF3~@aS2M)}9`uucSCqwlNhTs;4{rl@@JbilN>DT*_6$}YY49+DC`}a?M^5kfk zJWHe^Yhs4PzI{_3KRT4D%U^6Fw(s)PtgQwH%NbVZc<$dj`O(7zec=ubc5-a97`Wz& zGt5_En8>`ZF?!LWg8h3ZF=S~mv}pYQ|9?iD8z@8!N`m}?85lIrpSN=P_3xaxf$qPx z^Lf(i|Gj&3Lx<(^X_+~x3MG{VsR|Xj1q=)p zy;DO2x7|_T_?s>o+r-Ec>!)^X|GdlR_4bR;J$d?c{gbWhPtFWv+*#o0yube6BKeOZ zx+=3v^ylRmZfs8t_d7Q2?Alv;y_N6#k1sZMj`MY%6uZgH^OnuZ1KT20b@y4BrF{(j zzNSr(&vip*X$^FfR zWcKATTb{be94xJuS30?;yTIATQ;czyLi`D_z>|jUIn7hr=j}|M^WW`G+f6H;@EI1% zPoF;jMI!Kk+U8ZaR85yH?D^8AeNRRE(n~wJd$M7B`a5J#bYH3H_mKFX%cP(r?q1s@ zsPDnevGBhPtCPdZ0FG@|Ggc*Vi0SXuWjVB~`9?>UQ1v=jfi=8K>+8BJ8Q#gKD(N_E zGhGra5ZuM$pzmgpvwqdwIo;WPcDn_f%J1zjlbid(gU`(N&d#2i8!Z>Q4%dGunto&d z=sXeH+$5JGrPQa~wy%DlTT$~LRs-QbhIL#j5rH!xlq2}Tql>Hy zn9d#k!Q#VgDHXIxRk!1G{)w;5KUs3p6MosJG`N+gi?~02@Wf-^ZM9y#aHm76SKBA{ zM0VX~oLA_v^GW3P3y%bfZ25#7Uby7Qsh@0UXMJ{Y(#tC+SO0Bg`gO$7HA!e?{UL?L zcP1|Kd*sfzi1Sg{Dyi%g!}&xWa>t zZR?F4KU$6)nIR$I<&%&Q;P2(<>Fe$7&%SU=2pd~(&zUuE=FFLMXHSg6%(Dj;tXw)b zJ|H3@qCO-hBqAtk+2I2xPDI27Mn*=4#>PYjM~9zZuzLCZ3pcJ@35kilbLGyZOFRra zV$BcCdt17hfq_A_#5JNMC9x#cD!C{%u_Tqj$iT=**T7uY&?Lmrz{=Rv%EV0Dz`)AD pz|Q&#ABu+D{FKbJN|**QOdbAdIh!Ubs&g?gc)I$ztaD0e0ss;Xo38)> literal 1649 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMliy+#&B7AA(~E=Ep{ zhK8<2j+Pddjz&hN#>S3r#-=XLFuk66#U+V($*C}VGeP!3^qS$-Yvo*&npl!w6q28x z14{t`8Tlpo#Toep3eLf13L4>=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDj~E zWNhT|zM=x1*(-nWdAVrJIYXv9W=rs}fXi3OQk>Z>M0R4@w+Ji3KJUoh5DNz6s@FA*231znw3Bl$a1Y*_*W_I_EokaJes8r}4| zoa|@E_vD?nE-&9*K6}wa=KyxT@5e3of41Il|6O2_#-sSAAM2UE_U%dP>fP$7#cA`l zqNaMu>-DTl9G8E;tD=LPhWLeqZ)Ez+b^XTllOCnnbrC-YQ7PDn%2b6Y_(M?|JNM@mIm(k z_1AXsNU~SUluBHDsr_91;_iqud2wG`c0KbgnHI=1{5>I%zYJJO7 z2j!k+P68^dhI$9g-yQn#`P<4#?fUhSCvI4?x|&?>+M;3pEt$f zU*B`yaF@%|+U7(2Dfe%o{Vkb1bL|$sKX>wx_j?;t?Z3u*O_mCrySZIrVc@HR|4+no zuFD*s*A-=O%H%-Itcv9tRjN7u32U!$S6>x;);#Iv)q*clj#`o%wWSt**j3GNus~+n z93>42v&2fxe@BYqpPh7UcfRqGv*%C}%beg(W-s-EnC~s~DALkcbI3FNcpc$zuRtG{};)6qt9ce|dS)tR#6sk;_G-5vkP^u&|CU{fVU?v9VCjZD#&7W>sx zt@~c}^IZs0Q(D6Mz-`?LK9itVOV6A!cwz8PcG}9*O!pott_aOx;;)x>*mA!uqb#d& dJ;#1l2?m~Q1;@qZGai5{0Z&&ymvv4FO#qvfX@CF# diff --git a/toxygen/smileys/default/D83CDFE2.png b/toxygen/smileys/default/D83CDFE2.png index 4c73dcbb8a8dd8b2d057df42349a4bc4e8f4fb61..43f5c163024353879eea71a34923805c98bc5e99 100644 GIT binary patch delta 1124 zcmcb|IgxXMWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0814B`OPl#*xCVHA zI}e<{d4K(m{b#PH=O_Gj#TT&lcBA77Zjz`#^h6%tVr zlvu7%P?VpRnUkteQdy9yP?1|u&%j{OJ2ljC(j5biJ+DP8n|L^uJg?AsJ#CfD=RYPH z$6kM}nA{ti>e-NyF=0;K|36dfegrR7yH^phbxrpsyDc*U!Y(aZ=3D;G<#&aRTrDrpGkl4$j@&KY zur2OK*q*e`-tVr`l0ToJEU{x0_6-X{2 z-~CKf*k?a{*P|>Su!}~pVRKLAnqL#DkaP7{TPn)~K*PeA|W}m%-d)uSj*Qt}# zgy!0sAKZMTKJ!zall7*q0?|V&nhtS$&y-U9IIVR~ZP>-;DNZhRM=o5y?vXbkzwx26 zA@gS6`v%GjbQ@p2;8jy$eJ|90D77#|NzICRxx=@%lzh+9N2jEVemmGPUY4}@D{thm zcQ<#Ja$P}}x_!yxd3xba$5c%ZbtF&wTc*)ttq`ocId9!j#rh>>GAa#Agg2`CO=9Bb zKC@T#^UBHLf4Ny-3bXTSR!-vP;W}}gY4cN!-$HLR_U$_sqjV>7tJ>x_=}+EI`+Ft- zNRcRftKX_75;YsAymX0L% zmKO1=lN8R%XconVdiO0V5m`|YtSkCz_hPrh?OB-@%KF4?+4=lk*{XZ=6RZ4-j!PL9 zKirhJdJfOI8}^6Z7q0osz4UO_QmvAUQWHy3{TYl5jEr;* z%ykV-LJSS8j7_afO|%URtPBiN`Ht>H(UF^{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy~YN{PL^&)&Mrny zj)sP=MwZ4#uC9&-hNecQW@b*#MlijedBr7(dC93Tdowdrte|?G@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{@`*^6{YkS?NgiPt!^6=i=YM=lA{$I|q|N9*-yX-Ez zsEBCCmWK!bvMboX_!{%$_jh@z&QO6T?O(0Goj!G1DD&{+_I}5Y2}U2a3}z@UnKXw- zs>z$tGr(re-EHAfZc=$}|Braix37EfRGeXxV$Y#JHap&2R`+)_i}2a~liw{%a#n|j z7o(@eWBEx;3zC+Zua95D^74~B_sKoJf^>bP0 Hl+XkKZ}qdX diff --git a/toxygen/smileys/default/D83CDFE3.png b/toxygen/smileys/default/D83CDFE3.png index dafcbb0daa0db02743770484048a0ed36357b6f5..8214077f804627c5ef8dbbde3862954399d76762 100644 GIT binary patch delta 1300 zcmZqU+08XUvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{*UgzOGs}Jp2_5AVW|C-|U2Y0V{`tbaLUCSQdKeKm zcCTc}*!b+x#eG}n-MzN&z|Lil@15SgWzMb3JMZ5*vTO6q8yC0Sy|#DP#_3njt>3kI z){To>cW#_|`Sj|?&tLA^IPL1$bvxEix^!Y$cyg9kNYsuElP{fIzJ1-q3&$2WPMH^% zTe5go^R{&p7T3>e+P1d;|NsBeYt@%BFfeeH1o;IsFxc<6-_LdN^8D>HUaK=OFfb;0 zySp%Su*!NcFfg!}c>21sKV#?PQf1g3v__YKfvK)4B%&lJv0R~`C_gPTCsm=OvLIEV zBDa8n!J>C+sNdc5u-q+MTXRC|;5L>bzIBLJt$8q|d)u6j=9v2LiIdLk<}m-@xrrly_2;2&9bSFqH_Rd`&ls26Jips< zyCAy#F_-xBlAS+h$~Cjr7_D9v8C39Y4?OI54htQoiwOMTl$bgO9Vew+3u@r;)Q^R;%rIC9XBR^=s=M z+c13ZHxp#j*k-yU_<*J>lZO60jvH~Y)~w>!Wvq6Kw7gqXFKaX9g$JLV?V+7LH8)xm zjtbhpDw=*{|KwR4O4@&O{akZ?)y1t==DbWfQi?~4Png|W5?ZQJt>=^?yIb=Li)GhJ zufSuMMS8<8+N4i0RpH)!B)XApW?N@+w`2YFa5eK2+YdA-a31O5Zunt*VOpcq3)Y9M zbDDUWw_OMVqh`Z(=T$%!Uog~F4Y^9{RwPkg=M!LTqm@$(H&HXEJ~a+7@2Ro8!;E_7kJ zRLGLfxAp6|Wj=XmzuF*^%P)9jWr*pq(k1-6eyAQQ*!H*Q&)seFzqs8Oz2lmHe)F8) zuN4;lp8V{Ung7wlzrJZsn49$KS@V>|*2=Q;?hE~5K3v^3A?29EUIqq+ot`d^ArhBO z&$RP3IS9C3blG7L(7an9pWZsrL&raeXGO>lwtp#x&r! z!CcPEb}v4=X0Q8xFoMfXyiNJ+mR<7GR`#|ZXfeE9IES^~ELpfAz9hWq^n0f4TNPX0 z9=m-(>c-Ey4dP7q?=$MP3d-*4bSh%h@0+}M!jX;ab6(UeG&#L#ie9=E<8_<;qDTK0 z>UTxYi*Hold+*E7fBWR?tMUvB{QuOw+8u0 a@%Ic243Z@i)s(mx7(8A5T-G@yGywo~9$7>H literal 1542 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy~eIaX0EO-CN4%! zj)sP=Mi%DAPL|G2rk2KTF2>HzCNRC8dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{eW&Hbv1HQs3ps8NV|xzOeoFkLv);Pq z;?1hR$IgCayLQ><&zHJoGb7AzF4<$FEX_M-hr#VLH?Jv3?Vj}ZBkRMY!$+Iutqn65 zxz2WX9|NC!=K|xpZLNF1yfS66-MRC8L9Rjz^YM)Vr&6NMFSoh=-~9ZOx1Xyu_o>SB zWC~^_EKYxt#Jyq0ylK1{2bwH&l&#DruBbUqOOr2e05RyZ&w4Y)xHRWy6J&yKcRGd-P@cZox|?mFMrV zXK#49WvSBzHfr9S?(d=@Gi~bP0l+XkKS_w*h diff --git a/toxygen/smileys/default/D83CDFE4.png b/toxygen/smileys/default/D83CDFE4.png index 32ec6cc508579dc3ded6717f99d86c8491a64771..28cec2535dc40f802520e28bbc47ae45b86d1247 100644 GIT binary patch delta 1356 zcmbQr^POvgWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H-)lpAgso|Nm#GTP#T|%uu(0qY6)7 zC|lEVPE+NarpgR;i$Y76UF+u+TDsI{s9S7^&WE6jr+3}FdgRId^Ggy7x2~VLAv*uS z&ZUp;p5zdbm>C#9-92jG_C@z^9kKBVI<$M`vqu;AZkvDa#({&omOZ(DX3y5S`*$pP zbo=<8&9iS_+WzHi(X%UyKV9p4c7E!+HxG7io^k#BrghD(9}k4?-ZJOb<(<1WPP=+` z-NpK2OV_pf?btA7=fiWjqr)XEy@_Q*Bj9L`hI$xk5oveSTVIPO3slWkIS!MQ#BDgGKMu z(CA5b6nOqz*DPYu;pFUlZ1_w+^yit6mzyNlUw`atRyLKXsj`Zp=6ues{B! zn}x_hwX~O;iYL{d?OqedBfXY&hVGoC+}UsS7A|&hFv+rgG@(?B@3Y{^cIgvx8=d=Q z=M-I>CS1sJy|zQ;v)&=zk1CNYE1GH)qg_;w%WUEelB;^wS6J7(gLiYO#5#jQzv{2R6x@i5wYC($E@QJ>q~+b7`nNVSUU=}C+1}aNQ*)!m;pjvAS4Gor?4LYq zLrMGZqMvKdue!L^il0{|N2*@$Nbv=;TT4Pq4XX2+(q)%x-e9rnTIm&dETik#>Wq0( zsl2D0^bEBhC|njOlrjA9OX?)=C*2KRswR!5%)No`t=@AMH*$%vZaX0TEYszn(7HFA zYtj>>PB?mSdPRxo?N_|c|3rAhyvCRRCooFAIhWCQ?BL0XHQRlqV%N2_c(3BGSC-r? zvfWwi4r8bHyjpHuKE>;g6&PFmTF*XKk^HdXfQ`mXKcBd_`l<{3O;>n$xvk=0E} z=n21tyM8J@eR0mC{rUcue+-NEbTv)AFFy~IT#tLYIEF}EHa+uFtdT*0^})nV?{}Ld zO|gyU-n2yE{eKs=!t=K2`)0~BPw1E=ax}%HQP@{=s^9V@{~b@H6#q=yJmGZOK6Zhw zrA4dn=1tWO?cjG(_xry4+N(`>f<;_hU!E-5%UBy4%5I?|^{C-y`^06LR=u68jol3n zYW}OMKQlvk#^t7I$Fla`l=6Ni^W|RDulGx8~vupY1pHJjwRvVmLbibeb za?9%S&ow*~UK_7}!0uSEoW1b9kNmtuiG448?}&f?$*5^4f6=Cc&x?V9LAAs+q9i4; zB-JXpC^fMpmBGls$Vk_~T-VSf#L&RX*wo6zLfgQ=%G|&pk7Ye5HzRc9=BH$)RpQcN YU?3kU{(hpOIu`?jr>mdKI;Vst049TFa{vGU literal 1557 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy(X6CPOi>QrY=TK zj)sP=MvhJL@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{p|>9#Vpn>5sDoosQwR6PT>>#T?qAT0;W*kV{V{MNmzt|o$Pzu1l~OHg zS=m;i(Mu1eEZUkW+;;NLYun>z3=UR2IP?8%`ML6U$;S>_w*PbBztSl-@o;EemN1vpX>EKdM|tV_T7K$mao3(y7lTMtuyIHPj6iC zSoNnkwfRQfhIb8FAO6d5NAeID3ag);y~ek) zaIF%%{@2F*=mSy)e;?XPTCR?eD!R0T@nJOcv~@M}7&%?fpHn_?M62WGMUj}CV&@tI zuZ-t&HqVOta;nF0);TS!#NQwNZoIn3H2drG4R>lxdZj!SdW@6(m6jx)z1JOQc%Z?zF>l2SDMj(%nHHPBU$CtB z7`51?PwLv6y)ULV{@<&xc#+^UrOgwXZ@x0Ed^t00Qp?)Jd|&;lhnXcao{QOkDk+*` z@a+d%{PkPsmaDmQyp_rR7Wd`Uj@~ziMAkZ-nE6%m?EeFg7Nxyks=VUo{V$bgG*+B$ q>Au;tbY_gG>F@fufbI|O4U7zZN1}oxd0PuX1+}NEpUXO@geCwIl2_mW diff --git a/toxygen/smileys/default/D83CDFE5.png b/toxygen/smileys/default/D83CDFE5.png index 05da811c2b1801e5e88f001d958c618a1d291f5f..a048c9ca35907a688df6958855735a290b80e68c 100644 GIT binary patch delta 1270 zcmZ3)y@+dqWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+a8pAgso|NnQExp$Vi7sQ*w8FQAd z-gD&C|1{s-hfh6u`{{o?NZGDKCm+53@IS=5e&@jx4_?0e@20=wz_Gi}U;npM+P44b zttT&kYm08(d*u4VXABt|KPzxQe)Dnn;gg&99J+Gv@upn|FW!E5`{}Fg`;TqddEnfQ zdp90DTfbx9>1(&&ef@Fi&cjW+53bv`_vGc9;mKKEAyH><+}*Hq|C+6Pj$ORoIAva3 zZpq0jH`i_7J7dA})%9C;&ph%gih+TFwIs+dn8Ds*zfD;8+A;C{E5#YJzJy6JIZr&FPy3!x68k3=+jq)B$NukPR!|amuV18e zZ-SVB(|=2@mWHr}f;qO@R|6Fz_IxoD$)u&xA&U~#?=Sp zL{Ic{_E8mbsQlP+`0LfJlZy-29^CBh_~P8}>*ty~`<^5iADZj^?64Y(*0TRAPRAzw z_seQuYJF$=>GayP-gRGVGae*dEKrPV-gZ&#?W&V+YK3jG{em}emWTvTUDmT)Q)>O9 z((4-80b)g+q0HoGlO+}Zr(KZ0I;U$!M)29w3=9lwJY5_^Brcow+?H|*5O7;=oY}7X zJTRzJM0I7w|L<8hB0rzq6(Mfllg6C>-PvGuGBcywf2O!;I&oD zGJE1;yB8EUmrBIEpSFC1q3-FVFVEa`ZudRjaYpra(Ec|+U$8qmeA{+}@6ca|6B8Hn zU1a<(F54-5@urfBa vGBMLOFt9Q(u(Q6x$AGLMH$NpatrE8e|FoP<6BX6D7#KWV{an^LB{Ts5M-xeI literal 1442 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy{4w7j;7A8CN4%! zj)sP=MwS+C&aRG5ZYE}CZmuTICNRC8dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{crza#>9B`1Zm{$7k zK)~@&f1d7UV|(OaYt|i6a_5wc$djiJna*|3diZ8<;nsq0&F%cM8)lwT(pNvfVByL} zfuBE{8xs%ev9+<7i+ue5f1`ExQ~Q8t_xIQHY?^s*g;D%|LyzUvO*8wK=coSYKg_a& zk!Sus1A{kv8YV>6vN-LQZg}eWYS$^-3%(m!r9SjOe7}D`56^siV}l4y=a&Avt{WN| zRQQ9~c?wF{O^owy*MHgHwTdgote$929$ev_8$--mqt~RSKMumLCzs!}4D?WJ56r54F z<2{>jtCX5<+2q2F=&YneEyCGWn^x98cOt^3eKqd?O9jm?L6zqq~x zqna-t1e@D-Nj1A2&uo(xU6xRly~5xE>xBq|Sp||l+9$jY8g8=ixVzqdzogz&i36Dv zEN*cBZ}793xt4L6@#ijyq(_qvm~65*!(C-7{=nn}_W=i%HpU5wc3;_MTr)CB6qq=L z>GFw33Z?Z8QvS~}4rESX)X9(A&*pwSOJ1*SdH?(cKctVeckZwK%%&XUP{qi^!%)}n V`^5g+d{Al3;OXk;vd$@?2>>1X1*!l5 diff --git a/toxygen/smileys/default/D83CDFE7.png b/toxygen/smileys/default/D83CDFE7.png index 4527782380bc334964680b962698324615d8f316..612e467cbe2681e72c925b82c605df53aee07258 100644 GIT binary patch delta 1245 zcmZqW>g1XrS@Ch>&U>cv7h@-A}a#}gF>=LkS_y6l^O#>Lkk1L zF9rsNh8GMBr3MTPuM!v-tY$DUh!@P+6==i2z_2dBC&ZOu-+zWZ{~31vVc7PIVe>bJ z^`974zhhYbl3~$PhPe+IX5L|#dV^uo6^8zc3_a&Siue7m2WjZP2o>r%2bF-EV%y7L z-NBHu;Q#;s45yMqh`hYjQjX^V&E3Ea@wTBF9vB)mqbI(6r&%nSSUJ~R7@;?I-@O<{{ z*>kVwPk3A(ZDU|yU`+CMcabaNQqX2#U|=ut^mS!_#?Hs3$`l%NmXU#hsj(^~q9iD> zT%n*SKP@vSRiUJ^AXT9vw}64cqIYU&;I!EWJb$ihR;F6$|5>eU`#fE5pTxrMS?g3yvQo}$yt!)E*V}Keg(l8F zoVhadYPj~kw8G~6?#Mk)Cq+c)ir-(w?#5zrSSj@Nw#<#yX3?8i#g2Jxn6c%9?YoUj z3;jA8a%`+VZ%CbAv9sUD?$Cj<$6WPmF9gkAzqO;@Xy?4cDWT@Zu@n6_o@~fkA-?V8 z9j*C^yAQ8Qj@z1i=e^=CzU`?NdIg@k%f02FYHn)q4=wKx&eTet^mJ=U%u%r^my7?D zcvrkMGxAgLH~UXvb)d8H)J!N->JlN&b7 zQ+1VOat*iLXS3)lScgryJRvxdae=rp)4KEPWSgR^mY(x#IT8A0 zeRFVU=c5OgCG~QzEKX$7?ECTL>8cI&;#&`&5`AaQ68%-K9P2@g0k)N5Aj5t+iZv;e!dU4xc<0{P1OfqqSN7>gxLz-M%Y6XDc(h@Ey6f zaWYdy#X)1$NItK)NBSZs)cF|ddzFOSHKNWOtFHZEaI5d1n4IF1_PKIz4jdD1lzBVL zPN{zTTlT#t%UR1W-F~cd{hy7;lV3|LcfVc!==85|nj3sANJP2#-nn_Tv&ULIc@m&zyBoPA=`YzZpQjm zHuZrYY!xD=v6`-lDP0rGn-k;P(Gl$!WN>PEhU2QcabC|fS8Ck0ICApil9i^9RhGSb zFz3F0+p!N-yDyh~^vM0TWy>_l;9GKW>+J5WW3*eRsCIwTOOA(c!e_KUw!iRMv0NwO zD+>bygKCLuL`h0wNvc(HQEFmIDua=Mk&&)}hq{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y%w&P#ztn2maZnQ zW`>5YMvf+CCQe4?MwTu{F3yH7hA_RJdBr7(dC93Tdowdrte|==@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{}FMG5>qFQ1_E=ikW-?^mBL`0y#Y?POgQkAmIeO5r25 z3Q{N787D~1h%#X4Z01e)e_9}K@v#R1n`S7U2%6d0_*XvQk+q9pN82XHED4qf1(TR} z=FGu6cJnna5#T@ke{LvG(4jp%6X#huT;A`P`;#qwy;a4`hXwv3hhI5QpZKIfPlL-O z`9XI^dcqa24X<%SxxWm52R+r&!A*f*T MboFyt=akR{079*`>i_@% diff --git a/toxygen/smileys/default/D83CDFE8.png b/toxygen/smileys/default/D83CDFE8.png index a8586eef824297fac42dc8aec4e348c2e3a9a0cf..d6ef1f54128781052f5a9c4face1f92c4ae0cf1d 100644 GIT binary patch delta 1250 zcmdnY-N!XSvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{HZ~YCKu}+lWtBF= zffX}eUp}~h`E-VijV~|m+neOQZ^_gb=XP(5cHX;qO8wK*JLdUWv^tCLUO4H|@hx2r zItP}|cy;N(?)ek$AK6fDqPS~r-@OBC_AHwG;q3=Hfgp1!W^ z&)E66R5`YGs>m}iFx6ItM3e+2mMat#<)>xlq$-qD7NjavT0Z-kdD2+t^oEmcs}7vl6cx1H_t)Rg;lCb+Pn}g!|2XmLCcRU? zE+jccdUnq;ySr2P?cd;)Sgz-)wQa4fxl{LcJnC&Y6X9~C zx%||g$fs9VB`qvtzuda1{`Iom8_%n{JD4px{P{ttpUk_7C*37($nA6L6P@BcHLgg> zKQCg&jtl2}WPhf_Nv!CpN{o)!@k6418*f-w<)X&#HCc<~^0F<~6&RkHK6~C%!A%YR zih>utGp8h7I=xzLhw*xi^oW=((t9%cdw5T9oA2-U;W%%{IAP+(M)~(0p;K5{=Kr6= z8pIH)(6q&Mxs(-4|Mfk3oV?O)$Ew0MZ%o(QbMTVm*XVPMJL)e?`Ouh_?QSV*nX1q5 zw*IIzo$8kUyA**zInG0@6m(l6&8QrWrYK3a!qS@JXo1l-_bgmD~FF z3a92Nhg|j@UGO?v&HTjn1E(w(R%@~H9eLxwK)3N#{R`6%3sM49H-z8ecHnkjm;ONH zgq}y4=PL7cdla|xKM^*V+j#lEBJ-82u?~Gdj%mjIxzJm^ONLXj{R)SAU*NaP3k}XS z_-ysLS0gri!mW)H87BGl>TaAYQt{)!4b_?cUU6Ubbv@=+UD)$fqp;DL@vaPu4*Od@ zCi|(gewZH|62Dzuc~_9Q+(K_*sRIl&vb9H z|J^?mx;6_xz3^xH68nQ+x+YxV|Le@az_7^E#W6(UvhJDNQceK^tPfr)L@MoG;qf$J zk(0;0|7T~d{ky)@@cu4|Mf+dYn5fR`+0wdbrDMF-E-9WF+LO7RdBy7AY(3lRy7}Ww z2fr}6q#1UXs7ALtZNex{^kKS5}T$4-wIRm|$U zwIx+oPFY;BfyvS~{=t;c(EYD>t@3&_*)(If|MjgIEB<@^H8_zd@k`#sP|Uedd$%70 z1A}UbYeY#(Vo9o1a#3nxNh*VpuYr+~u7SC(p-G6Lft9hTm8p@ofq|8Q0o%O~%_ur@ g^HVa@DjAG$X((^@u%D=?&c(ps>FVdQ&MBb@05i)sD*ylh literal 1459 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy=DfcZmupy#x6!q zj)sP=Mi$N%MlR+qjusY%Zl)#{PB6WmdBr7(dC93Tdowdrte|?0@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{2+s z&Qc@rXidJ0K=BDjfxu)thdj?d3ec2rN6nbaM}k}qeI^{6?<>439`!ah^3wgnHg z-mS|iyFTIW%a?xPoBNDkmw2lg$p<<~7_kl~rm@2aoMx{r0WS>AUdCt)E`9SZv?;_utw@x7B-#cJv$#AbM)s4Yo77VY+x3SGn)}~x+3+>lxLn%Vrgo`E+2iaI?k(qwFCQ|I zUs&`^|D1c5OkVz*1q=Tr-qV!%Yt`s?K4IEYro5m%dHWcA?7q*+o4+DzfqhnFL7@pb z8(cHhm+usl*c@`eH|p{>|In=0XTJHdtXd`dGQcNu%PH}VEwi5IcE_o0Ovrm+p=4k3 n-lAVQaHXqY@A41v4U7!Tx3UjR*qzZ0Dn>nB{an^LB{Ts5l=}|$ diff --git a/toxygen/smileys/default/D83CDFE9.png b/toxygen/smileys/default/D83CDFE9.png index 54bc6d108ca38187882e15f02e803dd1751cfe11..55f1ea24bd059d522774c45d05d38bf8acdc642a 100644 GIT binary patch delta 1358 zcmaFQ{gZ2gWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H-ETpAgso|Nk>&Y+S~Ww45DwIe0Ti%|HLa-Ht&CX^y>eRb5}O(eRJs2f9E6pJ{lL+?RmNX!hgLT z*R~vbf8xgZwL72hJ^NpJ-MLlUpYA$!X~Uk^2QQvkvGvjRKpK z@y7f08xI{{u>Q{a{g0o&Jh5oQos9>M&R=_L?cVU@EU%ELV{=#ET)nGt%DlMTl5JBK z9G$y*>*NJT=B)bv|G(3U8V3dj2H}z*zhDN2@~0jZ^D9TUE%t=)! zsVqoUsK_l~V6f<&8X7rijsef#>zYL@i?7PGAN!#n`t!`k&ndek+tMa+hCFoLbnkN7 zi|^;-zbMaNH8o|boN1T8-oFCwr;pw|d$>9O(v0o@Dr~HbzolQlvr*u(nrpO(=!>xV zp4mE$&1xH8q`i_ztPfympRx4xG1qG=Ho26_e3_PDS9o&k`hBfSdW0Bru2g@TkSZ?o zs`+HQ^a42brca;$&M?B^ctq&mM^cvsmQ|aIEF`u)gfW>Rk<6*dPWCsO?YK~ zj=A8shj3zJLUpsX=o~Mrh7Utc(Id@qZD73(!Ob;}PM4%m2anxj?uaeqsL z-O&WMs*CTe92H}aGHrh8@!LeEyr#H!C*Q6LuEd?o|MvX3yJh~X-hI&p)2piQJ-rhz z&UyE~N%+>kVK!y=>sU7hcIDN1OuH%h?!!O*h4uE#+q>3ue%>U=z`$_8)5S4F;L|Pxt44Sg4c=Z((MZerf0Yaf&EPe79V9|njV}H{=F3(kWiF@xp8)!Ivcrx9Yfq_A_ z#5JNMC9x#cD!C{%u_Tqj$iT=**T7uY&?Lmrz{=Rv%G5~Pz|z3Vz<}-Ehh`KVx%nxX dX_eS@fHaghd)QA@ROez~@O1TaS?83{1OTJ=bBq80 literal 1519 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 literal 1409 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 AcK`qY literal 1497 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy%r`;ZmuRK#x6!q zj)sP=Miyp9MwZSljwa5grsggtt}wlxdBr7(dC93Tdowdrte|?0@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{aG-ud${%e+F`53h}UG&Q2ii`0I6#*h!mpizK@P{=Wky>+M ziWu*#lACvDzTC4|#JQ|C@%h}!d6oO;MXD`5@^$YUjfTjU+Z$h&8%;3JD2$Cc{6E)f zO@m*-hpe4@jOTOq{`<5ulJ9W|llJsuH(VI!zGK_7;@E~eaxO~O4xV(*<(Vbp(<_-K z7_~&C;e+9CHNM>;j@MH+l!ty;C3bzsv%^*aOmg$~6f;gLp5e^eqY^x;;@hsSyGIRM z^&hOi;Fv38IHkeZDfHo?qsLANiG|la_%rkJkGn4yd|kV$@X^)13cC-^v(Y(SQl;Is zlxNpD2D>xUA8gJwc*3N%sn}pK-z)jq25e^(3KLU`Z$Gq*HC@%d;$dR_zGW+J{Zb8$ zDOE`5fBs8of|=k#*H4M(WZ0kQEl`j9p?72IM>+qEf)^lVy=B$!^~)o^ z{d*dD^6J4&LJR$`X6URITw}NA-%IP#2aa5Qy5{FIWuLtG`?ddEi}3rMeNxX|`jJ=5bcsYyj{%($gwKuB2 zDHS7$7qRJmkh*l9bP0l+XkK!`(1U diff --git a/toxygen/smileys/default/D83CDFEC.png b/toxygen/smileys/default/D83CDFEC.png index 7b0d510cec5b63a09fc680c7eed8938816100c8f..a2e4eb8ee4bc248e1ed0427b777d76bf6b362c0f 100644 GIT binary patch delta 1198 zcmaFCb&GR?WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081Hpyp|Yv1{>Yh&4`03Cb?C&kdyntG zc)Ro9@$CnW-FfzU+rFc>AHUeT_sI2!&o=KlbmiXTO}h_Xy7O@3&I9Lf-e13C|C#G| z)@|SS?(2`W+xDKgbR#@D%PS;m&DK4~E?#e(GA}N-WX6K!tGDcS*6u&Wz`(#*666=m z;PC858i8f};Gi z%$!t(lFEWqg^Jt)1_q1XsiA?JW*hMQxvp8oqI_~vtxVI$`1D^lAD5@9w+P+ZcZf$K z^4hv3TNlp1Uw>iyjxMoDzPrTzx6Qb5c$2nz^66(`xzmsB`F{NI#hHogmnGJp)Qg<0 zk~=TtLG+qQV)3%vn}4jT4m&K(=(?dJa@U(VDW8>AiywB%W=!K={$tzOvgtDxJ1|IZ zm9t5iE8PA&;S(S8ZRM~R8=HdlZWSr<72R$3GR97 zPL%q7u;pWpFhfs_%-_zg3dbmij-U4<8DZ+Iep!bu$-dKmve|C6+x1s>OgJ`Vwg{Y8yqz&Q?CQcdJ2@tn zzF8I^{6u4!maAlbut}Wj=Q)AbCg{8oTFk_2#4CJUta;sfpS%hAjjL6dn6|Mosm4Ay z<`B)6wLrX4E-^jBK7vu!&gzGn$U_z%W=j@LMbGX-^-uYq{Bo;f_O@ld{9lpTDR7f!7Mc0=C|198!a*Yy(|J&qGtVbQw|(vKc;xH zB<*C2Qw^h$tiUNugt1RUem0nSF-k1&Tq+|t8?vN_U_Z#(S2`T zy5;xlj!xgz&(>efo!8a=^>Mg@_0cP5`8D0k-K=c>tA63;^6GL*KA#H8o}Hd9jv*44 zLr>onYBCUEz7PsGs$oR)0Z{or@sAvWno?9FE>Gkmow;-iFenVoca?5it9JWOt{Zu$@R(3 zyYW+^MxZUP=&NP5TUS-hl8Uw8!yal|+g|u9F4Xq&55{-W5}_Iwrj#-;FsPQeMwFx^ zmZVxG7o{eaq%s&87#ZmrnClvvgcurF8Jk*}8fhCCSQ!|w-TTmtq9HdwB{QuOtU(g1 Xj`C&?`-zI`Tnr4Ju6{1-oD!M<5UUe! literal 1384 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy_N=s2BvNXjxI({ zj)sP=Mi$0Kj!q_Kj>Z-)CWeLv1~9#zdBr7(dC93Tdowdrte|==@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{hq{cuVIN?3*tzFX#_*(@u@0ldA%M?mLos zPSpHuqJ{l-kJ7DEXPucUIemT2^$8E{noi5G8q4+V&6)NuBavSNe@9~ NpQo#z%Q~loCIDoe?EwG) diff --git a/toxygen/smileys/default/D83CDFED.png b/toxygen/smileys/default/D83CDFED.png index 0ae5367f6f80e3ce9e881c500a745a33c2c589ed..81722245300eaca2df4dd14ea8969836ec6d52c0 100644 GIT binary patch delta 1243 zcmX@Y)y_3RvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{yMo{b@ceDM~@y| zzkdDL@e|WF9@=^2?D?DbPoF-0?8M3Wn-6C!J+pGlzD>IhHmo`pyYPI|nv;7E9Y1&D z-XnF%_sNloE3ap*x^njVog4Qb{r&r|Wb^IQ*KYq;5&B=6(Xs!*sVlc;pL%xU@{M)Z zULU`BegDG`$1Yqu`}*sV^H*nw8Xi7(d0~B`?fSik51+X-Ezn|7#ucq<-{r9uG?#TK~<;c{E&qkIH%aa<l4XUpce%M#j<--)>xOao^qM*l; z+eE|8RX%Gh+%H||zg<;Ue22yK(?{i>8g6RvPYvDbo#|sK`P4K###L;Ju}%FR%RLpR zdt^^cK2vCGoOtIqUqerf%r6$@djVnsN&kw)JQ#xG&w1yB>szh4$Jno89QuaF)X1TZzGP;p| zbNAec*I3{$vt)zRa=}k6(~3Ci3r_TH3fyqakx5GBg~P6eRu7k*JSh3|_NM*6diUKf zm|pkW;&i19JLlc`CgI!vhSWsew`1KD=ySJbLe`4OPe1(AU1(p-+LiMA!-sAL28JF_ z7sn8Z%dyAU`I-V4m@hnv+rjP^BJyyv`67*~|IVBqpO|ywq83Ok3(Gf&e(SgWx_|xo z21aX{UiHg2Yc`%a*TuU1UQYRM=6ajd=Vu&u#JGNZKTSyCq)VU%=c;AWY)cPK=}Zk& z%keFjIn|~ue137vk6CgoQ~E!|d3C(_cqZDX<;T}^(LOy-g1wsZRqc=3YnfK7tY>6k zU{Eb_jVMV;EJ?LWE=o--No6oHFf!6LFxPc6Gzl>@urfBaGBMXSFt9Q(nBjAO7mAMD g{FKbJO1KUT4KD@L*(NHgb1^V@y85}Sb4q9e05!KwlK=n! literal 1348 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4kiW$h6xih%orFLBuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbOWy)MRXmM(^tt|qQ7 zhK8<27RJs-PDW1Vh89i+E`|ubo_WP3iFwJXFncpW_CoYp;MHs8T$Gwvl3x^(pPvIu z0Rb8LCHch}`2`Bj!Db2?;hA|U`Q^o$py&aIy_HL9QD#|cid#{Bt^zoGtunFLZ|Lai zY+&SQW(;yY$lvA$E>12^M#e_2#%7jgCQ4AfDddEizMX=NJ}7Y@B^H=akc%6L2~O9b zG;XJW$ULceDYi;QO7?alhkyTOU|=lqba4!+xRtcC^=uP^}dHM77^`4%d z`}^zbKR-MBn_pgk&#$N1OE{+g`Z!;7g~XDFN4v%M|Npo9=dbH0{pa-knSZZtU;R7g z*LUB~S#_u9M9{)b`y09%{eJ1oJzj2EeQ066l6~Y3-R{M?;pZxT#7}5#R$gf3km`1S zlKUNpm8;a^7q?DUF#Gd++v6o$=Jb5nw%frzansIicb_(G{C|Jf@h|WH*O!JfFYD3~ zlnYZhbp9Xvrib_c&)%)Sg2~tRH&cdI-M9a*%Nxx8G`|pgmMGdbv!$Ci(BIqc zu97Sy)fp+Snz@KX=~_z4Mbj)LDIRovZk8g>}*-bB6{- YhILgU8p;hvu7b)nPgg&ebxsLQ0G$c&@&Et; diff --git a/toxygen/smileys/default/D83CDFEE.png b/toxygen/smileys/default/D83CDFEE.png index 55982b6e686e5fd9ec7ba1adc73b340d2273c469..5885b2776e1672c6a1ccf36c57fc47ebe1579b7b 100644 GIT binary patch delta 1454 zcmX@heS>>~WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817l=>Pl)UP|Nomr#F|9J7D$ONlm;=H zMaAaIh|ZA{TdO3xR#|j|is(jF(PfHa%az0?gB8?6G|Z6{pD8I0VT(yf80c!Nsj0X- z*qR#Zt12rCiikw{dAr%$wiainga*0TSeY9ePL~j0EGfQJN_?4=_!24cGYX>jw1l4; z2|qUzer6*4Kv(3vis)fwkvA5?-+e^>Mv47P5c?k|`ZG}Uot^MuRk25A!hges|JN5v z{%?}~-zNXRMB-nx$U`fUuihg6bHx9*$^D|908`c@kfJMScW}{O?u#KUekt zBK7}$ioZfc-nxnW&y@H-L-~J)?EhSe4?ZF{j6{D0i~KK;{NJhYzeVnUsnp*n(HmxB zyA(vfc#Hgr7XOnd@h49Fo4@E&E72WFVkZ>p#cpVc+%pn=XeRc+RP2V1=n*CHDH0M3 zq$CzfNi39>m@g$cSwiCf|Nkqu*u^n0Fo>4~`2{l+sr>w@+I!)`=g+@>J%7FH=O_Gj#TT&kRh`b0Vz7?^6SLLy3n63Z0| zit^LyGjmcEN-7Id6)JKI7#J*ir-piOx}(5z=e39>)3g?r;~RT#F4a0MW3M}-|8e1@ z>4m+FidTMZG5GuEU-Q1B>oXK@F0KBU(%qRmE!I*p+H8APyUDY~`M0mlR9wGI@uXhl zY?a)3Aq%3{OcINiW#9Z`m3!D>j%Fo`BRa8sew(T%UajwbxL_;8=GNdfx!Vt`e$xO{XpE6fk|Len(w|P>c=UlkX>9;fEbf?9f*LE(GehNHftDmI96ymsNg6^V8$$U5Y zH2Z6x^%d^x-NC!HRAXI0q2Kg&`GM|0KYlc;IemJ|g}GntrTNvY znwtIii}jS{%oGC(Z}jiE!}{(00yUkx5AA&IK5fnlS$l3-V`J>$mWq15TW&MAtl~>M z@4R8n;@M5U3l`5ivz?3AI7XgOO=Fcwky7dtV_v_0-W`SejLuPc&|LRI{EYFE)E_4QEGD{~J76-Qd4kZCEgmud9MQLU+v!;2|5Y^+W;w6i{Q zJXw@rNW=ck~SxlHTIXoaF=$P(-6^m9aTeocCA&#fUhS^z}uV1`+ zxp{*V%MBi$-X33{vuAW~C@{U57Iw{Sn_1a6J{bW(X@o;hTa(=xIhV9GePT$_Y zef?Z{ewT*p_6KerwKmYOXyI2nATK4BCoCu_S}%K;Ur?aqYG=2)uv6JJ$-(BfJWQx;uH7S8N&9+OK}HG4q#wlP%UwdC`m~yNwrEYN=+gTe~DWM4fW8_Yr literal 1485 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 diff --git a/toxygen/smileys/default/D83DDC00.png b/toxygen/smileys/default/D83DDC00.png index f7982a4c6f3b1fb223a1227784529aa34aafc7ce..7ced00254fdaf5aac990525c23ffd0b481e788ba 100644 GIT binary patch delta 1691 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 TWJZG!sG{(6^>bP0l+XkK0EL6m diff --git a/toxygen/smileys/default/D83DDC01.png b/toxygen/smileys/default/D83DDC01.png index 6d16b88de8605df06992cfddcf9f52af003ad7cc..0a276c7dd2a3cdb58bd050e2af5780af6587b4e4 100644 GIT binary patch delta 1565 zcmZ3-bBt$#WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817l%;Pl)UP|NpnIUjAb9re_=0Jzc%> z>AE#fSFL!yas9LPYrlT}{PO9OKQpI4x_jsG`n6A1F8}uB%kw9X>L1*`_2AB}$M^5P zdG+%1hYt_$-2U+H?e^8n4{qD?;Lfe<7te2By?p!XYFSodV* z^2f)HJlekPl9kAHss`tjrYx6hyd{P}(F#?^0MKEHkW;{DsV@88wG`SAYT%}W>m z{{8#l=FMA|FI_xw?8mpSXAU2H{`k?S_wT-a`SSbsuM6wfoIiHt{_UHO@85lN@6MA4 z_ikUg{B+%#?W>o+HhApKz`)>D666PpLk2K#p8etVmseJc-+eh%%5_62;p;6fomF2; z9Zp~VHRa#c1J|BC=h(bKT*~F^r}`hps~gttchG*y7H=O_Gj#TT&i5r?XJcQ3`~txArU1(iRB6fMfqu&IjIUI zl?AB^6}bfr3>LjpLmemGG2qzqxy#0pjpb6Yjq>Yht7Ja^F{wzq_WGlm^zKue80#-y zQ1IDb|8J3ep=qv4`OJHJZX_j{uU;3^vvyYWc3-y7X8GH1r?l;v-L_;`h-^?vMPOs@ zX1`lCr8#RJt5Z>%_bH|Chv)A0?k!-T9u&}-R~60C;>*5K@T%XNjbe@)_@?~Q zea3Rf?o_?Y4~Cm&6a1WgRD~G!Mm#LASzT73x6bs}8yAk)%Kx`2OBN|x-Z)e6eA3Si zoC`%S*MBIQdSn0OTZu2ZzQ6o*@cgR4t^4GqEOun?5Lkb4TcFz8RX1<`R;##XDsxl}N`ykyZA>gJl8hCs@DD%J>j-+*AK<3EBuxn{=L8DAH&Bo+WsTAev87i|Z27{o zK`(VfgI8KeSQ%f0gQ(7eW%CwIoIO2WAZ*4GPBD$33zx2G>S^j~ufM>``6BdHxRIoU zRA^|Z{|k(8oRrCx_G#GIln)j z8iUmuoe2A^i9Z<_7*tDKBT7;dOH!?pi&7IyQW=a4jEr;*%ykV-LJSS8j7_afjkOI7 stPBhmto&_`q9HdwB{QuOy9N%Bj*DNMyC*8Db1^V@y85}Sb4q9e0J2{s3;+NC literal 1582 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y_QbK=9ZR@ZmuS- zPKJiAMvj&yjuxgy#s*GC&Muag7BIb@dBr7(dC93Tdowdrte|>b@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{r;B4q#jPd5-u}XYB6i=;ExvqdqyBB7 zE;qBXM2EYXCKuiAbg1PSth=)IA6Lw-f|L0P7ey0t9B17J1zVC|R_8sp zoo*b;v!L*>XWl+z^XE19%g^t1ws>AJNnz=uJDnUyPM^APQf5Qk`)T*y+W72^nsaaN zp0@&P%cM+t1#iDoU;ggy`z^Embq~*BV%ns@A^mV+hM`J(+RfeD|1us7duiGqyOV2S z0LKP~8^28j48IF;en>W)kSeL-d904%dU2y6+nU>VdsHWvpSgOq$e>w$qGIW{7(01M zNn36uvp@Px3%^&}-dLS|pXZn!r@p-Hza6f=_$drS{G7t9gi^N7w zs^oHSysL&Sh(qH?b6Fxj+MoayPLzJd)dC+(mk+{mm~K2!IFzxD_*5@3I%SOtj_5hJ}}-EpdwI^XA6$PQoD*cPo)#)_C{ZDVW1T|t>$dm5^*{8U z;i+48n59XG?}3%a2ZlnX>yja!3dbrF1S;lM-?N%z=iC?+ut)y@L*O3y*JqZNXn{&_ MPgg&ebxsLQ05}Lt#{d8T diff --git a/toxygen/smileys/default/D83DDC02.png b/toxygen/smileys/default/D83DDC02.png index 4ec1cced15d3decfe09cf25245eace69ce6ad9e9..c59ce810b76202f7952deaabe2b1f75230de3da0 100644 GIT binary patch delta 1584 zcmZ3%bCYL+WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LM>HpAgso|NpOwQrlZ(alX^*O26NZ zY?DnXdJh-IJeVJSV_NWqZtqjgZkKv}@5~B6TIaMbUMs^wq<(LA3Iv_*^gB>ucD%*s zc!TrBZm;W8f-d!XpJ{PE-RN|>+4V$&!?||%^Ibj{yZz5~c&>_4TNS0YDMfF8h5f21 zwH+n4XWBegMX9Y%(L2-QySK=4Z@JBubOVU8RZ(h3>KxXm8Qq>0er;;do>J>IF`Cz= z1Z~YQ+*e;_b7hkMg+8BkaaudFP0n?CooNRdba_(1@doEscbR@4`BhPBJ>GJ?KJq=@ zaua;yC-}&Bc*>rvw?EhJ{%}d!nO66s)i%%9WWU^4dcMQse1|v49SJ&z%B>zP&%8fB z?%D+3Co3{O>}k3?JM!M#sCV0IZp{e4J~jAwqto@N^+A{W{O-?-x;Z`gN}tc$t(8|N z1)r?5yE`ZHaW|Np;EnJJfnfx)jN$PX0H3`n4! zfnojDoSc;%zxUg#e*AfT-K6c$>mN(q-eGf~#rEy~#ZzwYzLfOtMc9_F!SmV|y?xff zDfrsC@7~5Yf{AaB-S~EJZ{w`b3=9m6N#5=*3>~bp9t;c&>?NMQuI$g)`M6Z9Qe0i` zF)%RIRfR;91SOU$6cpvBW#*(RlvEa^Dpcea)H5(x^iB=+-gL)+*Qf^SyTj<%e}kYoEij_=uTd(!I4vxOGUsw(r_?i?1bV4SOz_H6kDm0sVr z@WTe1&T%Y1UXj9`anj6&Pr?6|#k_eO_g?cc^gA*CR8$TW6cec5@&6rbjKiu493RbQ zWF>G&_1Df9;&{!N^zy3T8$)rG8(~X+9e&QZWACLY9~d{)x>|_ZrLr+p86JM{>%^^t zJK99A-RzuDlKMCM@tPwJ1s7hLh+91pyxtNd^X=s6(1ibdS?#{zJNPG;*QF(2yH%H& zkYG6Lf`(4?vXirJu6XlTK&Srp4qxL*tD}5HndTZOi%aCOa=I?O)iO0vEJXe_|0b5L zU8#q4+b=Sh-_SkN9$fAMd)?y}Tl1G%G~j%PMT<-GvPD)3;Bv&A)TBUUka1 zJsu36>`AA0c=FlQB%Gaerrm4aUwx4!<$N&DjrZ%d-q&l z@#MLKCy$;zeEQsR1yxxA2@x3~EoE)>^$R4H9a$hFCnzZ;Dl054K7YcDDRY=iLR?Kv zb#0Z6wXN07_2YR{x~m)$12@KdW=2-FwyE8ajk+~8^s4FB(yzQ$y9C-_n!YlZl#r5~ zZ8E#wiRH{0-6#pkpSo+;`lKBIhm2bT&1 z!%uHjZP(<$6$}gvswJ)wB`Jv|saDBFsfi`23`Pb{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y@rN{W~R<&#;zu= z&W47rMi!<9hK?>q&X$fQ#>U1LPB6WmdBr7(dC93Tdowdrte|?0@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{5#_bdknI|qNVLV zeFpXOXKcB@d+fO9QOD_4`TEb5oL8$h&2sl$|2O{7>1g!}_Oo`dZ?A0UjOi11-5~Q@ z@sgsR%kIV9jq(j~VaYN#D+x1iXIGUUy>`n@NTk?KqGvlAuw~GI`tSt|@D^1|{e(`RqLcdJ2 zyRQka?4<;m*xJJ@a+WwsG&vtm4%jSNF|T{os`oq}Uu>H)?eUS-Q;)5Z*p@C*BmW43}3ppI*Dz zRd+_IFz;TjSHCu_*ndN}RHv|>_p9*MH#>D*tL0wxR_1ZHU2AwPCh)^fYnAA!j~jNb zOw1|LF!=O}V{^N)ylQ0Co==NACM{Y!>xye$^6OZ;=qNs$_sf#4t^EHaEl!H@SR%B3 z$KE}%UOYW*-y*Lzm6aEjJ<9y${p-=W#Sblyuq8e3nH2cN);=%z*9zW>{p+?e3NN1< zymisP4Xc-b-g>*}_SW0s=e|UhzMd3myk2#g+q2hEcQgLC+E4HM)}g=1rrJ!8^W=QC zy*?Z}=Lh;$?l#kNw=Zrv5*A`S_0{VwE1uW?W#4fBhwcG}^wVl@{imL-0F~XIu6{1- HoD!MnTu$sZZAYL$MSD+0817ll&Pl#)jpIel#+yDRneO+u^Y|Wzm z-H}mZsDFKFLA1ZSg`u{dh7ttTLnsJoVW?eSS`g*yW~{4jq^lm~3o0Syx@D zySAVzH`zo_(@007ySAXSI?u({%uriROHENrO;KA#L0d&Z6A8`B3YeD_usG9uezNnd zXiEqxOigU6DE6>7j|y;)3HFSy5A%r+^@$1cjP!SRv$s%Jl9iPbS5uaEurO9vlAjkA ze5R=}%h^F$K|0IH{!B~L!pP8!xDZ2a)rYOEe_y=#bK}O*&h~vx4Zp8l`}gYAi=Ljc ztav|H`wj6ipSNveU|@K)c=6Nu^FHs|wKXw5$kA$JT+HS4^aW92^P|Hy#m9y^)ms~C ztNFXx`nlRBgm}k?dK+u2X{#&xINKB?M<#~(_`29=t1J0f8!Rh{+E^98q$JAM#^Cj; z&i@DJ{XaPO)#5sL35mChYIaqG=DAqj=t-FsZTbKI|2f`AI2afh%u0g%KoQHp5T6)* z_RP!oPHJ1e-`&0I&Nqz@ec!$@cl`cc|BrL#zkfb=uAWUvHq;lEWa9Yq`$H*X?W<*e zdFL4z7#Neh-CdZ{xNA}v7#P?~Jbhi+pRx0Csd8-XRFP+3V5+VPi6{w5ELSKf%1_J8 zNmVGREJ#(T$Sq)Cu;`r{>b>cX0mq%^;%GuLb=@kDlsj%xi?isg6%iFEK zqm3=`*U!J~wcX1zm3N;0^6aRX@b1Xcr%5?i@9fGonz6ld_gyvPCwEO$t}cBwVbS4k zu9DfMm!7SDoFE!sx;kq{VgO6~jMP)dT;JVG`I=^tp?o0cq0s%Z?PpcLX$U;%y~Sgn za@RcW)ytE&c~Z7pEL<-flXW?eyU@bi)-|Y7_+dR`eU}bXh~u6Kx{HF6d2h-FovVG; zSy?ar(POKrX!r_?>6ah4SMcaB2#ySWDo6bn%I_aFm0M& zHr;Xgw15js(zHC+d6lp?Jf2jvy82#{`1A?CUn?=T@Ey6f$CJ&bCP7v;l5gssTk#qT zl-W7donC(IGvK!=P-CdK^;jHU`k|@qvqm%1PT@cAP97BexjNJSigfMP2Yz3_y?AOf zzqe(b_1VbW_g(rgkFR$y>ss3_AJqFM#bVEY?-%07*t#Yf{R}E+U|@LU>Eak7aXC35 zA=QN`$A{;Q3=fALv${32xH-SR{Qiao6E<+n*XWlJZ{9I;XG`nSsat#3*3aF$Wamta z<|$ix)-*4kym|BF#ocpywumq<-qW;b_3Yj4?aQZcpVVYy+`Or)fBpP=hlGFvj}`0t zyNsM8cC3nWNeLc z8R;6B>l&Jb7#dg^n_8I~X&V?=85pqL`_PP{AvZrIGp&-r$Uqv0j`C&?`-zI`Tnr4J Lu6{1-oD!M{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy)Lef1||lsCgv`# zCWeNtMwTva#;)d;re+4lCN54cW-z^;dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{DVIo#__U~E7u_hW=_?P7fM&1MfQFbnBi&L=DJR z@q%*A$%QMd!xlTnhsA|H`&`vFRdZV9Zc%@pu#nLDxRPqgz3XON5)o@)3Xdp|+3xYq z=Dae8K&QIZi;Ojrg)1XexK4auDdf6$_s)&e`Yt@YJ(FkorytD^Vm>mtPF$dW?bJ@T z!zKni+?FbwEnMoc-rh3Dm(EwXT>Gb@HFVADt+lEgQ_o3rnDNY8aWrpRxs#G0d;5L% zXlv^WY1WmhJZB$p(-ADE1zB`02-q2UR!1D$kfS&dtkWc_pBDeb=jK z=@mM*pBBAe!KxFYcP8h!gNt&uMGS|W8E4sk={b!L8m>lI{VHE{%0Kw>g{!x=SjRce z`PhgUbX43cJ+Prj<3%#w|nKdaQMlws20xcUc{LhYM= b?q`)?=nV3!RX3}$0@WCvu6{1-oD!M;8A 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 diff --git a/toxygen/smileys/default/D83DDC05.png b/toxygen/smileys/default/D83DDC05.png index 94cb5f04986e6b9b7f1560b1ce3ef48133549229..5fcfd9efa2671ea706eac1e84e8afe5f8ef9f4d1 100644 GIT binary patch delta 1615 zcmZ3=`-5kKWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LKkapAgso|NnnZVZe_*CNaR#`vivi z_X!O5!WfSDFdX$^coN0%0*s!9F+2@pm|T{%V#~o5TMjPSuxH7JJ@Z#@M@ENEUYNOP z?aW1MKc_JKnX2(;n*RUwPXBj$&(GBUu{`j{@}U14o&QYL`%%yEf0f+sGTvWv82&G0 z__b7iL6-8uEal(J75}eN_+LMVVMSM5l#gRkjMI&&#{ZAT{Xdp)bGpgW?xZH zKI?yF>;Fk}|0iW_+)|Tx(3=A_Dt=*JX|CWJ)LA@l%FSwrJxLm`npX=8DxLG6n>gSKw zS=sV`|H}4FK6n22<44&UVJE*H4PC;vSKh)@fq{X6G0EHAg`tC0)`Nk8fxX1j*OmPl zJ0F)S=b=84P6h_1nyQe9lAy$Lg@U5|w9K4Tg_6pGRE3J%0tN<)-l?IE+in|h+$rvo zb7W(=R5<6xt*`Za^{W5ySrp0~-~YEaPDfIajjiS7_w(^zlzmEIadwrq zc=GCJ*K((}R?2R_oonEH&(}F9c9mXG$;U-Zchlx>`T52o;%Iv8YN<2{k%Nk9lADSq z?lzyfSCa3RP=Z-uXZh~iuY{^aI6k=Lw#XahOYg3#`eej>Q`hfBeO;4x?yf6NGUs^x zCx*niRy5kXsxhuoh&!PcxN>9L8$QkFcApRIvF)w!+-fPBzQSVqA*8R@Q<};n((?$xVI1!Dpp>W@m~`j+4gGE%JL}SiikrU>33KQ0=Um560Ok zYxAeGvAvU0JCZ#mcZuijtgvro9MeqmFRw8bwoOc%ShITLxzL`r*)6h}5*l&!sViF^ z9b(zoba=D6_=)fXjLs`wwe3%C?35MQR>pc379bGB-XTxrtk}OVE->fB*o~&{yU(qBNu;Z$3{>EbG|ivH$gdya?m5j__53$+uKE|f%rT`oUn`U>B5qVZ@L{N$;I{tN16JPW zfo!Zr>ObyIZdU$eez|Uq`LDc#b6>r^c)HR?nlrY1*5+H+M9W_uU+xgrwYHl-sP|2Z z#hzO47yNS-yXuWBGhG=N7(RNsIEF}EPEK%Of7Ro2MrVyq&Kn(>IWc#7Uh&vS?U@yI z%W7ABmDR0ZY&BAUc$nJUj;Sq6dnPt*ncA_iwk95@L)Xl zdGF-r#l_u|v)i{$?-7qs-9N@g$AgNB3mp?5{;QkB6C^k>@S@>H#lnxA7ds~k2Jv{F zT(?ehW#-G5nUb38)}8d^nHhQ0a%bgFR@R@Dt*$pCXV&u^Rb86;R8(~8R8i5VsY_Ll z@>~kNYPz*_>({RiimYh^iZSw+p7{X|0IiKvM6?gI;g13e`lIZov= z2x$Br5xQ2I<4TBQ4#Ve5E^`#(9CtA=FsPQeMwFx^mZVxG7o{eaq%s&87#ZmrnClvv zgcurF8Jk*}8f#k_7+4t?ELi#597RWNeoAIqB~~3^4Hv&ScTZGQ=VD;+boFyt=akR{ E0FF=T&Hw-a literal 1701 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy{4`v7A7W!Cgv`# zCWeNtMvjK2j*iYIZYHjV=0a}t%N=+=uFAB-e z&w-_YfQX=;(Uktq>uvt<|9*a@1>WPGnn;Q{t?R2haRY zdNpf$<-(n3a(A6Ay?32e^Xp1xdy7BkKiAKDen(^d_3rizBG1 z)4*c^hi6O1{A&&?)J=ppX-11aW4NG;hIj-q)8Djufz%==J?-K0M`7O8mjQ$M{mF#X0$|o;*iD{=uh! z%Sq{5t9{=exLw_AJ5}(}!p7Ay{nC|p92Pv+&a8Ip7Cmp(Cu`Yr>6Y0xE9cC!++mw{ z?3lo}R#EZ~B<8I6KUIEYntfd%>mJ!{3(1-FFQX`1|5$_FB&7l(lE# z&K@(8d*r+9UfIjNf4{01#F`ywyUh|Qu4s3rf4XLWJmBnUOI1U z`OM2@M_te9Mk$K5K1j?_=`xL4ks>+ahhO5f57EJXJ_n8RU3X|m{CZeuyt(x@+v1;f zAubY4+gKH})!92aKSum|y!W`UM6RD+dh(mTP>G)4Gh$hq&y25b-KVc^IGyvPksZsb zMa}hpEsnmGy=8EqleMUBhh%#5Zh^8%fKoy6ltDnm{r-UW|8rY0s diff --git a/toxygen/smileys/default/D83DDC06.png b/toxygen/smileys/default/D83DDC06.png index bb771a6b18504745a4bb774ee68ea683be541551..df6bdd1164045048ff2de589f5fa5d158262dbee 100644 GIT binary patch delta 1727 zcmaFQ^PG2rWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LLy*pAgso|Nr-9I8MoNo>Aa3CC8~d z&0%Jt%lZbdT^&9f8oicOxGyMmn_KKUEzfycuJi0-m$@ab^%Jukd(#|_PxL?7=eN7l zXG@FM<`%D=?Y{ead=K^c?QHj6RPHvb$Ypl1>&hCBxy3F$=??QsT>CN|CuKRUsP>qg z?KC;ZX=<+XgiOaH6N8RV2|GD0^yK8g9qqoyCkGy#7<6J%@X?7ur=|p5S(I>PN$T-w z5!>2*_x6QM&vTwr)mD)xa^&pu&vo`XLrbgQn&p*zI(a@ zc25XDG$Vd}t@G|K|JBvbrx&Ijo|U|{&2Lwy-}b)fqq7q?xA?BC_Sn)BxuV)*eUtC< zI^Q+*-kUo^<`y|GuXJADa1H! zqHiyby163d>hiSp^`85>eUD8FK0U?%!i<0iYg2D5jC{N~@5+LhI}5|^ERT7-G4tM< zv=ehvF3k+OxghkyRKM#BA|CB1e{-n*;>@7iYcg-HNWL~VAkJt=y5h2ysNfRhU{_jUQMt9CpxE8+kD|LYbkIl#cckX91p2TC~%#DVoA+x$yIl@}d2gHhit;y}e83_WTN)-}=q}&a9ljcgv}sZ|gs6J}ar*r}}Zl zzWe*{@;%PnA@q2`q)P6a3=9m6N#5=*3>~bp9t;c&>?NMQuI$g)`M6Xpy1Z3CF)%RI zSA|5B1SOU$6cpvBW#*(RlvEa^DpceaFfds3P7Mv*HphVDZ@Or#6ARPHBAMj+e^q|= zyZ^*ZDm-Sjt@dhh57Wa#tqOnt{A=EKG<3^^oJ)HvHXS`8H!apuGTLl=)^3w$d+p`= z&+$~6@_Lq?yrq))c?r|)q^Vneyt*;tX!zaDQehS%2i4MEZYrL1wtGz+kMvsB8M<>G z-8Q=&>*U_pka4Bzh~n#zJ!jli^35mMCbie^D=RqV8tr#PFy_1Bq%*rY%#Tb?5nRCa zsWnG%s?7Tdxf4EJFfL#7IrB*FjJ1agT04t(-TC9wFUXo>w0c!+P_oo1kM%nq`E62~ z`|k0hyNO4Ccid>)iOg*nvYslw2g{}eSKWfyX(A%MtGMrp)N3#Pz39J!@(j^EVQc;f zp5*-`YanGZ*(j#PxPo8itW_ZMUZ*&P@FOV~6t`7m$%w5;Ze=%J8-KG5Vk>~zOsFc`eu1hP{+P6f0KhsUk5Oez_GFK&Ee)u!} zfSAn818twIEF}EPEJtZkeE3mQX*2Yp|N*v^W^T??Y-0cXV%9zL-H_&c+zcJ=Gl@Z`ab+t=+AA1yf< zyv*t(GiyZU+~vOW7cO{VRD3Nb+pS$uu}fGU6fwgIJBJ#FdRrEDQ_` zswJ)wB`Jv|saDBFsfi`23`Pb&rODE(1Pg5 e%}>cptHiCNuCGjgqM|w%1B0ilpUXO@geCyasuB$V literal 1647 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y)Ld!&c+r-rmiNg zPKJiAMwS*9rY`1gCeG$2&TeKV&M>{6dBr7(dC93Tdowdrte|>L@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{9 zh8G>Ul7l!Ft=Q^x>1FAX1Rz=U1s~@X_F&Il%%s~`xDl`?yKk6JxKc0Q0~mJQ)bKC(u}QH zJ)T@yI$lLCp+5^(-Y#W0D0wL+xj0~v{PYR)wDbJV%0#-!g*v>GR&UrObNF4EJJ-)` zq3QWsx1JKTxSFxuzsXwtQQ{xB{#v{dlUDg-jj5fxqxT+dcC5{{`tba^b@UXb=4qTpP1g!^UwwV= za`Eu@uX3*aezS$1>}Fb~m0Z8Fu;}_OULH4>nOc5D+peCEm~`v&-yH|arX<h9Y&F!EI;bdclRfA|^)&~!;9%{V`pxme zAEeG27v89UZvHoY&9mjIg#{Twwf;_1Z!TVAFr8=4?Pd+fdE(yHJTg)Oo=O)BYRn`> zwYScCoU-`g>#P}Cg_=j5B#wDUKm4&V@lWyPr`flq?mdKI;Vst01|z0E&u=k diff --git a/toxygen/smileys/default/D83DDC07.png b/toxygen/smileys/default/D83DDC07.png index 53b5530431e1ea6e73ae92640b43bf5b7743462e..69a3c7440081ab2d0f6bba7b0148f46e8a40cf8b 100644 GIT binary patch delta 1502 zcmZ3^)4?-AvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{^)H@1*}i)D_SMTD z+`0YY+0#!S-ru`<{Po3Ys<>|u*KYo1w zckkBww{QOb{rkVY_0OL_uV21+UEkgD|NsA=-@jkKaPH;vXMcYE`ts@H(+BtO-MIex z`LoX-K79W8;nj=hPafR+@aE0GfB$~{`t|+Gm#?2c|Ni;&&+p$qe*F0L{_XASS2r(Te0b;fqq}#0 z{rdUl^{WTB>u=q=dE?Z9{olWS{rU69w{KtHzkB=S!TrY<&p*3+`^?cprw;DBw{z>A zom;oBUVdcbx=TB@?@KL+U|?X-E(!7jMH=&r{Y@`Egk)WL8CvhKzg~9t)e5#Ne9tmA zolRQ(_R~@4g*#jS{K(kz-D+)77y~Gxyxmi>PisYTyoR$E7uYQnV*I@30QPb@6eF`EGaGZd=KO@32^Y`t12n5|IbgH?O+2)TDk{V$Y{8 z?RP5Lr(W9W-II;m)88X|qWj8@_?ZUta+wsA#oO%;c8E`5ZQ1aD9&1oTh(Oa?zGYex zO~(_rw>m_W3rI)$CY_IBIe1IqtNS_Tg8I`gKNxOa3-ELF)8t~4XIG^J(_NA-(CMF_B{yu5M}tu zUCsPN`GHxX9twvY&i!EFnuac}iSF3fD!Tyre5#b%-EsW~<>n-+gz$$O;W_;Z7C zj?gM$IV%eb<@&&H#rrRYOTW96e}40v--Qz{{Pip@t)IR8QTDgT;R@ERFQ2tfah@B< zcfOwUFVpXnUGr}I5dX};z_82H#W6(Ua&iI#W13iQm@J#hfn}?gpYPK-keHBhg!6e} zq4ew-pFSM=S(ElmY+BeKj;Ghm%*?ixdHiC1BP%m^4yz!O)1hyA>!#(slT%uh6rkuY zEv>I#oG-6k$k|cfuw-ZN`uV-}4ha)PTpX-!sHvD7vD2|}VpCF(mV7E8n=B(OKA*wV zMepK4y_0v;EM4V}B&Vc=c=&psKXK;Nxxf@AZjGnUG*>@SS6wE$!inVy*HzOllkV=V zUS2aL7+SU|E}kHHS&@N(LAAs+q9i4;B-JXpC^fMpmBGls$Vk`Iz+BhRB*f6b%GlJ( v#8lhBz{*kAv&;WsOu}!pQxzL#lYa{>gTe~DWM4f-QW#Q literal 1575 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y{^vY=4NiDuC6Ao zPKJiAMiwTfW{&2@mPSU-hL&!Y1~9#zdBr7(dC93Tdowdrte|>b@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{nii7yt2SH;>Da{|o#VDf}?2`Rph!@~pXO1&7mo7RT7ut6kaV(Yu%Z z+`1%l(~`_duYI>%-FdFIVAA2Uw(;lw?knbO5%|+xwN=P-!%EfAX=0N#*QLCwdU)ex zFiQ`kWuDcsUZ>DQ}g_r5=|P4~(Adgu4?HsW!*_WNw^80#Hs4~)}HVtP_3=d?Xv@p|b*l@b9x zy+7+KTomr^ZT{zWe^bELKb&vFWn5Mqe03$n^Ua3`yle^IN+z`OUD#;B@h4628>8Tc zJ$1jHXq=NjGiQH+VM5PQ$qx0&2{YLurm^cPwb0&&0z}B6@Jf?s@VbKt;EwtDnm{r-UW| D@gG8Z diff --git a/toxygen/smileys/default/D83DDC08.png b/toxygen/smileys/default/D83DDC08.png index 17991b36cf8c32a78a52579100728119a00cdfc4..2c239f27b54f044a12152668d2b8de3920b30af9 100644 GIT binary patch delta 1585 zcmdnZbBkw!WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LLFspAgr*8`mG&zV+49hx;~f`1JPW z`&UmdpFDP8+vb12zW)01;lt}^&+gwjuyyl?S5MAuS^9Eg%aP4%>aWbLcyQ^&gKHPJ zS9)GqGvUtamRs`*uTDvPeRRdIPcNTr>^MHR{>6dC7rTPb^+rEkQTAkc`Jv5gZ*QEm zcT&mTjq4%k(Dtp@7gp}wxc*d6?B$b34{Y6Z=g`JO+qa(Fw-e;1jVsTtnR;zS^R+W4 zkL=ohZ0BZ(uHze*KCIs{^T(&RXOA5G_Tlx{_b*@GIuDV9lQ$s`Z|;7& zd-l=lx?58duJ?vq?+L!q8*;Tf=;f{RFCN_d_v`DQyN7@5>He~@=KYe)kIVDF?dkur zt>x8%^mki3|9*P@b@`M$U3$KmPs7S$KsyLRdC`v*S`P5E(R z;jim^e_Y=BX=Ck=0~5a;n0&4$^2evQZ*HFXaB%MJBU|@wT>o`P$M^mHS6263nHYC} zL)Vi#*H3R>_4w?*?}w&;Iz0Euog0@=9Qtu_^V!4uuOHj<^!>a+;?cw<|twGvDn_+bt`R@D&!zPd`Yn;L%?&IWqLEkksWHJs-NX z-6n}XERnarCmXegzhCx5_Z10!e~I&Ej1MMuE88FJ5N}~^x$*xl_l|}TMZug>gZj{e z0w#U6(Ht$M>>D3mRrxljTOmR^=vyoV7UlmL);mI8+~i$_H<|W zS?v~ZD!=wW$42X-piD{CjAF@;8#ot=wEunZRBuB)yJ_M}zV8Mq+6Rd5SZcc$Kb|oyGA$e8&E4!rQ+Js2M z&+Y2wC&~{PoMG`Gw5n}ec(UPWFB_+Ng4Bt?2|}uwjxqln z*5yAD-Y~E6<^KtZI;*UeEv3ZO-9Owl%Ph&{GzVvr%!nO zT8Xj6ul~rh!pS@p6$h=njOJ=pZrvZ?kj|HuWV%c64-5B;$&=fa-#*INu0cfPD_9@Bc9vb!z0A$R zX20qe?v7_&d0pR*doVCC9Q1T?43W5;oZ!INSnC@3oQK5cMfS9L{I?)Fi6yBFMg~Skx(4RDh9)6~ z23E$VRwicJ1_o9J26on0_)s+D=BH$)RWcaK;L_orma}Q1qB<7?gQu&X%Q~loCIJ1q B6IlQN literal 1595 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y^c<9X3j2VrmiNg zPKJiAMiz$7M$U##ZpJPyMwV_)1~9#zdBr7(dC93Tdowdrte|>L@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{$qlo965(N&gZSEdjf7W-u zm4V~O$Cu5UvTAm^JMgfi>&5=iZ*noZ{><_b=NrK-6|-l~d>CDP_jzrBg|vg=m;I7C z=Nk5ySi0{QZe3mA49Ced<`dxO|e?)s$7|Pb^|d3~!hqyx`1!i9WUR z{7Gw9UVMEmrl9BP?1L5hO4}zpKKsnV^^wD?Rc65|g_Z?NG$w>j(h6Mmlx=&pXQ*fW zhGXAnoqM-Le&RRACz0GTrG8g3HaIlM{9>`( SsWIu`2ls^?m3|%Wmb<1|n{S^a zkYM=ZC5P1WjS_hmUhR5UP`U1n>ePJJr0xZ0%nZJ%t=Ph;mCQJmZ}GWmA?bywJ93VF zl<-~u%Ja?T(^{R60)8An^*+Jd)iy!k6i3i|RvnooFT``tIcT0dam0M0?;75MZ@+dG z$?CW{J=EM7;9xb+#nDt*>-NWozx~g!xCXebp2o*|u!Ut>;(_gDS7vpe{Kusz$uq@X zvOhW1)^xYd6{Sf+f2&-qwpGeI%-nExm%P~BbZ(jBj(2A44|7a-{&w}RI?u3ov-zyQ zF5O*Ub1~(g<;KbdckL%7#%veg=BxQy@%UA)rfJ)*?Yj~7rgr=K=d(4YYMESk`q`G_ z>DgZfYaT{=|NiK>YUOdKRdtVNwMk34nc8U9AKdr8@a8RR-mI-@w!dp1#~*pUa@O}( dXZ0D`7$hyQUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@ICiVcI5ZC|z z|1%^QFs#U^b(F7llxN7YWN7eWX!H(@)GG+m28mQVGt{^+OpBFXlmU`0vSXMTZ<%Ks z9HPwtl5!TFo6ImRmZ98aJIgQ67MqvCurwWnIs+N1-5GMN8PZG`${ZNFg4k!oOD{@iX!mEB6ag_x zet9-Sr~*TvEQHH2Ig)=)lH~kUkZG~%49(uMOS5E`W-&B+Ni0m`pOeH8EYC1C1|(bU z3?g$f8M5LS3cV#3rh%MV;mFVx#87C(5Fo?Q5x@|mrcyVHAuF9_Y7GCJB#=3Mp$rov zK(YZc49QIlrIQ&ds~H+S7&-$$E(ud+2#`@)3i3EZcM!<@YG>J{S@O%XWfx~EfEgfL zAXYbe@Xtw-UX&rbG)rM=Cf~dihI(iqKr|wgQkx28Hx@80Na0$StGKPyyffaSDOzn; z1@pWlh6-nfS@C=;GG%vH$ZsuSm=XmF|E@r`SqTi2BN=AIF?0q(+`up?LU?X6|D0ro zR$u1nak7gu6qaT&YoFfro)|NjC<+}ANMFm#p#`GJzvQG3T1&)+pH zb3CF;`OLyypCdb86xE2&pBT0E^dIqg7k2feY+n%i`v%{+ z*V*lBZPz{9+_wGPg9q2|gfg$IU-s_ngWq?)|5*F}0oU7(pFbabnz1T=sbcGwqqC2E zKXSnH=Xb^6FNK_foqytse|}irzV6#+<>n(Ve|%(C<#g|S12gTPs_|nRVb+}NL8rFEnr}<=$#sR zaoZdNj+*n~UxbweI_5u?`h2bU*NyY#i)o#@6xj`FHu3((AKKU#_yv zN|_(?_o1rKrfzZm`L&nTHhn$*_@b-A{JR>O3y;o9%bw_)7+vcZmdu%>Bh{UIR3_gd zOnSHH|4mD4!wE7uR(D(bYL1vL6m75nR5bm@{>irzFU!6= z{dDsDqY+yx<)thldi6}WZcj_ybTg;q+dj^Dxjw?XIp40xTXvNt-(2M0l~d~s4Y%n$ zITh;4?^ZdF5pH|JJ6GXAF~dy9(2T9yDc6Y=5a? zJ-cD~rJO&!bDs z-N4e%&RT2EdANV8_x#1mGqG!3#@Tdr1_p+Yo-U3d5|@(`7?{(<*i;UjI(F{h$)g9( z9zMILsHgW1?b}kK{SvIM!w!Ab>I~NoZ z6voJ9uxi!H^Ya!jT)EV?d|sbkK!d0M{R0>FR- z=eZ3`=elEJ?uzM0?#lVuHTCn|Mkcv=arf-@)%{~^bUgUkxq)e6;zPlSffspr3|A*T zKhDVdNLXmH#H3&eiJ;4uo~JUf8g1TGtPpqN>}h?5pjPefF7Jkt%*dZ-xHh<8c~vxSdwa$T$GwvlFDFY zU}U6gV6JOu5@KjzWo&9?YN~BuU}a!%xbhq?iiX_$l+3hB+!{8ilx<{SV2}ja5S*V@ qQl40p%HWuipOmWLnVXoN8kCxtQdxL1)rNtAfx*+&&t;ucLK6T2JM&!t literal 1802 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 diff --git a/toxygen/smileys/default/D83DDC0B.png b/toxygen/smileys/default/D83DDC0B.png index 9cc51718aefa5d777fda4690e63c38d9bf804d29..878e117e06a92df9b1f61cd372bfa8ead0049e40 100644 GIT binary patch delta 1658 zcmey!vzT{+WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LMU2pAgso|Nr+d-QZt79gc!3XBJK0 z4B-Y;%*g0lo!q$s!cFP`G3p^4WHO*)2Amt!xG=wOdFhljX+5h7r)>_dnhBALYgy7b zeZ$msds91?7fs&+5%e#gp3t_mY{II9wxv1UD{|VGG|t$V)4rs9!m6D1B{}U&8m4dT zUa%d;-T-3Mw=b!fxH_bImVfzl5Wjs%N$<+)DQjz|t*@E7zH-u<{EnqL?MvcX7S}D^ zS2TTd>zZQ)Q#Z}rcYWuX8~ZQbS##)O$;_<_kKWyP>gMUI4-cKYy?)=Bw$(?vH=LNZ z>uPM%qP2U@9lLn<^4;h6pS?eO<6*~wjeVQXOxkv?dF7$H#rr01tv@&a@U4RB+j|%7 z+@E($L{rRI+NDBrg+kt`J0aJJbvxKnOkSB zKf3kk^@Hah4qdpL)w_D;-fOL^j#kdtvT*B(UB|BNICgcIIoskD0|R@Br>`sfGj={M6>DLq5)TFjrrN5Ih?1bha)pAT{ItxR zRE3htf>ecy+yVv$i{7cBj+5>faP0ZqW#h=k@~PNH`Sr9_GN1pLWTaht{ZUQ&cIwH7 zjEo6B`|JNLk}ovPRVkOex93JulKJX&F+FQ%-Ok!w@~pQ0PTux|JU_FfCcO&U>V5H@ zE99@9Acak~C>pwB%p6d?fc}m?3p%e5|x>sD8;at`}CHTCJ z!MW{%-%V~`DroL3wyeMV$D?16HAidps<}bQUa3p2#}ta+oN(^Lo{I8_kMjhrCx}Nr zu5&$9@tw1wBSz*cr`sMyU4@o^#UdPxOPv&NTuaGfb>2|%Dpyg!Oy-DN$mAQzx=jgh zHNHll;oMO_rSk*hrnSm*+h%yFFjN^HN~{li`yeJJ`^(KvfnA^eZTo0B#RErJWy!p9O#kD)W z7kx9_4MQ_eN%#g?wy&r%XMs=EX^3N3NPb`O5s0VU2sjFZ+}RxAJfi_vFV{ zJnC-5TFvciX>!h5GV#fpBQF`=-&k_z)tcyxjhvOWxhf488gI<9KH1RDe`c}j>4lTc ze{-{b>1<}rwB*Wtz+m>%M=tH=W|o7&=LAeN1Xy@<4rP1t^_{q{`+MR27rS}OUu{1g zS%2znq3xlW}YomU0U^=W^;zvUl8+VQTO`q}#83=9lsJzX3_ zBrYc>I57E~;aPE@nYH!wsbl9Fl@6RddiHSls@C(9;~N?U`6MJ*_U>twXmNEpdPG&_ zAXAXtzDqntb^jPQ6@B9Ba$sC~Kp~ZF>VpXv3_`co7nJh)>b~tbp}{uy#f%#}ez4p< zvgFB>D_crz+5YyNxv=KVoI88|G#y&>NQjxudGV!9pSn)1dNu3Tu3v3#$!u)Gm)n*- zn|5v6x4v`h@{EfY6zk|zR~gSf?fmkGHiE)!ag8WRNi0dVN-j!GEJ@urfBa uGBMXSFt9Q(nBjAO7m9}5{FKbJO57S=3Z}D7R8;3;VDNPHb6Mw<&;$T#4ly_Y literal 1649 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 N44$rjF6*2UngB;nI`sem literal 1710 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 diff --git a/toxygen/smileys/default/D83DDC0E.png b/toxygen/smileys/default/D83DDC0E.png index 9080dd0e2a17f6f61f127f2773302e6f48f98dd3..b5774b484c8921a13d322cb2106e1b6c9e68eb08 100644 GIT binary patch delta 1485 zcmX@h^PPKwWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EXVrPl)UP|Npl)C-kOxPO8n@-kbnO zVU}v!n-iv{SRDkO?p#2GhH+i z?N#FKlw)l{DB4CT+D0kL8ic|v6+XJPOP8(^V^qOJD2P!cfYw~@t zhC}P-r8ufgh}2r0ZIj}t+!~~@EW@(UU1ff<$<8vjL?^A{P>1Xgw>TfWmZGG29ToFB zDq9MZ;(hJyjOw-fs&gjQ<~8KR1-n@O|Noykn6Z$7fkCGv$PW}J3{MV)y?Eg7ckAVP zN54m#4nBUjCGg+B;Lxw%F7$L6>R!B1s?YiFi19wfFr{=kcEz`6Xmg9vzm9Kr|tN*NXd9aw}^`#Er0<-5O~U zDaTZmZS7vKcw1qqg}T&?=lj)OE!@`jh)Z;FjpUb- z{sT-_tu8M5>jJx+%h%6JNQrxRZo`j@c^4MhN=Q!-&$HuGYx!@-c;Lj13H9>*9icl| zS?vGMWz|p!^LFPH< z4`@!FReD{?zr{&mhCywUzfr=S2nvhI5`S^^PGDmy;6`5>t~~n9Rb;*x1KRmD|=PDn5c*2NsCPJ$x%GxQ^$s@d_6=S9f>URR>r@l9L`kN=goS z$dvVwv(xjWW>x`X+Se;vwuF?jwKTX@xw-9Po7Au@EN$5|vl+Y$v9pvsxx0flF)%Qw zmbgZgq$HN4S|t~yCYGc!7#SED=^B{p8k&R{8dw>dTA7$@8yHwv85qp)xxWiVM{a&f fW?Cf<9aaVgF9p-tCMv3PF)(<#`njxgN@xNA4UBEQ literal 1613 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nujy)MpXhHfS%<}R)# zhK8<2mQF@4mZlbFmTneKCdP)AFuk66#U+V($*C}VGeP!3^cv&UYvo*&npl!w6q28x z14{t`8Tlpo#Toep3eLf13L4>=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDj~H z+a)FVdp`($rfu)I&xrLjNfup&pr4m$c3OQk>Z>M0R4@w+Ji3KJU+eep3{172E{-7;w^}B8`v?b$wEaJ*d0z8Y(1wMV z)D-x6r4-kgTy5>-ayiuWz-e~KB5P&O*clRcb}Fsm(Uwn8(i1tlpwqCI`@)irA5Dwc zq873muFcHSti9luzV3V9wuhpYC(VnW+5fkkS*-8QaqzVC^LmCEi@yZPJ^X*sZ>P&j ztE*SnKflpn+`ih@)NSv|eYZMfMDKfbA1;my=dS%8P!=RJW%ko$UZqMirT!T_K6s`n z|Kr0Mr%!C2Xmj4*_e_QFz61_;zA3lo=!NI647+i;Uxk62 zEWUZ$zcUS5&L4xf&Au(XGki(dQ}szocGmkBc${!t8^^4vsx-C7d0OhbaFLCB+mf`u z+9&Rfe6#WXmf40ow3h8q^HyJXw|v3c!);u#il0KPH>`ijq<61q!Nn@pGs(OECQbO7 z!nVX&EoIxnn@rtXnPbc6r+#@|dtP+yzK?4jMV70-5xW#|M$}fh|Gj{kaa^qJ$_fAK zY_GVlSYDmmU)1~3B7NP8)4{2=Yp$jrUU1b(Sw!R4dk+!sg9^)PIghv&6+Adwp1v#n zF4vY1n$xB%U&JZ7NF}vonz8OQmC4z&&3O|PPo^jc_$n%PO?YwNFyfp^RP~07=krBU zn5sR_yqm|zU9ta9`01xCEk`8Q9({Y8Z$Y||_S4r3G_~C`Jvl=y{(Si@7_qcn?CZI_ zFZodqUvy5};q&~4`!T_&&VaB4dD-;U6Ao_w{4tkZqh0M(-?nFuIrQ5_BhGDEyXQ|> tlGh|F_v3198CieSbp!PBpZ--oz>smrj=`^OuQI5B_jL7hS?83{1OUojTsQy# diff --git a/toxygen/smileys/default/D83DDC0F.png b/toxygen/smileys/default/D83DDC0F.png index e74447a809bf82e658f89cbe0c6174c01415cdf1..69c405b08e7d1ee7efb04ff4c9468a8a0886a5d5 100644 GIT binary patch delta 1518 zcmeysGlOS>WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817kpdPl)UP|Nr-Fow|EfDJI&sq!5BO z%*$ReJq?D^>mjU#6XF+6h+i`TE((hqrc|Jv?Xg z>ZUta*1vds^zP;LvnSSPy?A)|#)VZ!J0>1(>Oaykanqd6IoZXV=63GcI`#km|Cu&V zmNGCf7?%Y31v4}oofFw3eDQ6s=m)W$%j;jRUwGiP*FDMe7rdvv zDlcVyRwMWE!)&hIa~xtr7#J8Blf2zs7&=&GJs21m*h@TpUD=T%n*SKP@vSRiUJ^AXT9vw}64cqIYVj6a&jOkWk-Ri>}CA=hlOS-ea=`|2NyqC*dJwJTX1(Q!7@+f+6A=@41C^T(%OkTpkZ^{UvQWUtgE*JD14L{2#O zq5jXuXB!^rDwiqv8yzopIrQVbHG@)7;T3g>k8N8T1b*FL?7*QR%5~68J=j>H>8N+U zaN~xD3TBILb=qE6;<(lqRDWbn!|mNZE#eb$WnY>c2~lFa&@IaMdv%uj^G8vO&+D~Z znETz{y5Fxvg?+Z*yBH&RGsP`Qzxde)vev1;`*2^3-OosqRc7y}v+;%Tia#>F zWp--G=B%=BbsY0d^Di$l<+e>somjJaV%!&(pP6ctEZDmbjZNKi3HzB{V zP?vLGv_)g*B83}?bLU+&Ddcoru()y&Z=#`N*2Z~_niCf*9xmFXu&z$weEt*R4Rab_ z{-5wrXV>mU7WGo%%iTU54trT5$;s-RwPfOxbw^58G)V^RxVkrQ;ZfBmKX-UAEEL{o znm>t=U;l=?_q1Sj{cqx2FU~LuIeTf?q`&)6@Zmrt!x@F%NYiFL{%0C}@?y;%Ii@HlOXTPni@UXXI-4wVY&(6cNe%0icAO7hsjDPm7 zYwnDOlqC!d3^zSp977~7CnqGOC8j2)Gcc9$g^8u9vH2wn2OH~6VAwu$XQ!b$bARj7 zsatz>1#aB98Ey5!;KQen+!j`ryLVVvTXJ!5eKg|e?&#XNYiD&u#jeWEPHzrl4=>N^ z-#>m;R{Z$+tHS%VhtY|u)Ac8R|7NeKVEOg?^y$jeMj9Ggn(VCY6%{RQt*bROR~Z?s zSefnO;#OJV!s?Q}YIWnA^b42zI(xgj{V!ZgYG9gsWXY2!Q(R;v<~y338tLlVhKlPQ zSTTL#%$ZBiHwYMFa^ SiHho63=E#GelF{r5}E*)lfzd4 literal 1648 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 ZxOH6o;@mw^QJsr{!PC{xWt~$(697(gyT||l literal 1547 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy~eJtmS!#{CYH{Q zu7-xLMixe9j*gaY&Zd?I&L%F#rZBypdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*l--@X3+spJbH z>Hej?>(~DE3GFPnw6^+=%hfA9%i1^Q-PhgT_ebsW=~=rj8R1;n=y?W ztiIJA`KNOGI8ViqB`T-C8@50Cc;w7S51$Txm4$Ja+r3_EJ5Lm@y-=ybar)Dw539c< zy9GW`__lek|2JMa1r?F?d*koy5qSTf@B2Ho1M(aWNgrfx{Fs@-zARq1VbdPDp3?A} z^%Z>;CwVNsYsc&Tx+&<=3*Q6E79Wcns_#!SFXoKWE;&cUUCpQ?CO(arT(pG?};Me_gv diff --git a/toxygen/smileys/default/D83DDC11.png b/toxygen/smileys/default/D83DDC11.png index 6f143a6784cad0c085552c0ca9fa3bb49d3fffe0..7f92df675b5b331becacb62743e90d871d0655f8 100644 GIT binary patch delta 1479 zcmaFF^O1XkWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081EY0-Pl)UP|Nl3ypR#A`R0ukNKtBOB}2EUI2Uw`}>`vUMvv*RAYa zKDTW3($>{WTURVrC>i!+m&z)HP zHq69obhu|IW1? z^QKq)|NsBNtuQ?X1_rf~AirP+hKReN1trn{{yF{B{qXp~&-)J)?%ck=WpiVL6U)oi z7Qv@a4DZ~2WEl9Cfq{WB$=lt9p@UV{gMop8y~NYkmHinzAD1fc(Z?4iFfcIHRE0#8 z1SOU$6cpvBW#*(RlvEa^DpceaFfdru_f8FUoOH*4W6$R<8%H*lPsKLMucxh&`TWP^ z#^bM5drqdsm`-B6ctOEufBnBj@*lHrdCW7~Q@Qcv5xKC~of6Te+i!-wJoCE3My^@% z(@hhVt4p({FS^^!l{5S7lC$E+4{Ggu5p{LLp@kfLhNmY??Y7x_^3mCX3sns_kEq^z zo4MM%{)L8sK<}*%i>0 z@aN;JiAS^D-Z;!l>3iPM^6|bogUgdgEBp-}@n$kAewn|x-ho3ylRQ70edh>ae}8#Br@JX#deU4X@p2a_B4Ewz`zrvC@TUfw;EJxA3ce=Z6Z%~qB&~Dz;l7x6e{`fmXK(jrX7}e6e`I>g z?9`IYS!LhKIOdt=U#?$d%59sNIvt?WiC*i@H+yI4gr#r<(1#bWH zZuKUHx?V8_e zvhxcA1H%}PI&-n)P0Nz6UwC=G@RnNM zbYzOT<91AKSz6jNwPRv;Vt9CL_Dl=Ac1vuS&5cxtwledFUhmX_A%$7fcy zt%m2E-XErQ$MVwB-pS2lYSKH$6X$eD>7L!bw6uM7{}vreigSpTILge_eCA9OQ?qKl z#KCJuUTrN+EKbf#Boz53^KthZ7i&g$guc~(&=N{&08247*tDKBT7;dOH!?pi&7IyQW=a4 zjEr;*%ykV-LJSS8j7_afO|=aStPBheSDrKCMbVL)pOTqYiCf1em9mW!71g;I7(8A5 KT-G@yGywpXiPT*H literal 1634 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy^fZShQ=l?Cgv`# zCWeNtMiyqSMi$1F<}SvrjwUW{aJ`;+#U+V($*C}VGc!}Hpn8q*>a}t%N=+=uFAB-e z&w-_YfQG4*iW zhxBt3(=LXJH@a$lNnXSv=n)X>@}$??@ormUOQ5-H$OYGurQNYQOCwx2$7Xa!Mm1R# zy*`th^}Bdp?(xrSzsr>RcCQeR+xNYC-}BmipXYu5yQ9Ra_v^P+-|uBct(!c3y+ht7 z?b73VrR$#;-DJ``-dyIHw*BWJT@53#=I7j@HZpvQ5;Gi4jQVBPKKs=8i#s-(<;}L; z8W$E%J*2KVvoQ2(^@Qo^495$#JRcqrcde2A%`$hIcedrbGY6G({f(LUe$PED@?A84 z{i(HgV!3o5;e%b1*n>+ifsej+@d9Rm*Y}$S& z=J0P;eXlbI9z0kuYssByhSQE6IN-2MrFHk_C6mhiYu>3gUg`UpAW*#d-phV@nWNj% z@9qp?uDK_eb1L5N$2-}!vuUktGHv(v`1!p5 z6Frn3@F!$t9a?w(vA>Dg_Lb?g-YlGuYPMyA!14KQ-npqZ>xwoNhB^cW&pG+&wucIj zXH?Var@u@SL{4w&@laV(FlY7l)lXAb2U^d4c%eVRNKwpH*|5b~QMBJRSs-&lhWwJX zpSSBrY`ymR`m~f|a-2>4V%@A^t3_>X!)BbFD!{^6@-5(D%JUc3u6zA`|EXf%+@(u$ zb#>LJ|9&^0F_Ect`@df^!rp%TyZU!VVBEY+)>$g{rSFoSf3WBL)Bjf_fkCmv_gnPZ RYn-4`-_zC4Wt~$(69AzVa^e60 diff --git a/toxygen/smileys/default/D83DDC12.png b/toxygen/smileys/default/D83DDC12.png index a584b4a700d91213926c70379133ea6aa5724c13..30c9e4fe91117692e63c488a8808ed6860f56750 100644 GIT binary patch delta 1691 zcmZ3)dzg2EWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LKtdpAgso|Nr-fSoDTi%t*FeRd2hh z-ga8DWp9W@SCGY$D(fXx)?GmsZ2^`|epXF>R&_qslXHyg>wK(x(hYjj4ce1++mm%W zQuJmNo6jk;oKt4m5T^}6jqy4&i!EjrTTCl7n^tI+8XE*b6LU;m&D3mkrQ$<9dozt8 z$kj~E-BPn7)vzPg&`MjPIZ3ZMNiQ`v$V^SJK3=CjUZ*KiA z0L%4_p&PqHS2Tz3nVzt*Cvvfcmc#++MQ3$JV}SWxRQF~@Xehu^8CiPtvf z-r8EUq0g@`+iXIi&8fxVm)B=p-<-E)QoyQCuVYJ6=Ty6H>GQs{Cgt4f)U};_>wji#GoFdhpS+AHVW$asJp}zfJwB@yyQ+zuy`PuBcRe z=)d>Vk0Vx{?>^Q~W)@?6_?&@(ficP3-G!lpRn~)nfq}im)7O>#89N`BD(h*Bh(!zx zOm$Tu5hX#1c?!Fa@>RZFu*$dXnv>w2FCrT+9rI!S7!oJ3 zqNheNIwB&O_oi%+m+i$Pd+PVEz2LFgls9~a#nsCT+^bqN5*Di{m3j4gX({i0))ZLj zYIXVZ!n294(Qa=X;&vRbaCNHamt_#Nw0_zJ>nK(z43Sy%R!@nSKDU@7yQ-;KFEGzTVsh-(yECJFOKrGKaSkmTwGRv@x^9g z#iyr!Uq824SoY+>v^MMKXWDzXqYVGD>xXy#wO%Q^E#{$Jtlf`D)m7KNJ2SJFbBe!_ z%9T!?q&I2q-u$NYlb5%2r7aXcb7W4u)a#g|85W)w3JyDb=GrJ`BCgiKDl<=cr^7xb zzUQoRu40Cr|K1&%VJRZ%>-;*T%w@r(fchUnoy;yD?U<&!^tC<7@Z^5(Ut;?Ua`DUH=z9sqlneKV5(5v&1x_YuC+q|_^T2)2Ecba4s3eCg8BBdRKi zNP4GYS?ydF zHruh?QCwp|c=-JbH>TXVvxxtR1jEL}?C5Iu^zib2LFVR-oXwf*Bjf+KbWT?DpP6;; zKtkflgw$N)vt?EF_j_h;e)C3FX6~HEsF)bZJ9lht_U@^z`TK{N;r>E1vo&fJ(F_a> zswJ)wB`Jv|saDBFsfi`23`Pb9 enO2EQ1Dk=t1DQy%iHho63=E#GelF{r5}E)rjNVWH literal 1698 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@rOyCXQ~7CYH{Q z#)gKjMiyoU<`%9_Cawmi&IU%#1~9#zdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*K^x z`}F*^wAowlM15PH5f>sJcyFa-tVG5X&w39Dmj|uJ8a$V}xp=&~I$D^P-WHhDG}FNA zw2AKjXIJL@_B8)qdwbvfd-+QP&%{+!-2ZHMzOv%HrOc(GJug@o?!-=a%3EcAnSFyD z&;CWXA3wTh(4EA5ZThSDFLvz~j-Q!g?$3^&^UwCi^-Z3WHG}i-2q^#I-j_V_h0jdQ z~0!H73Dm z_Cv>h_AOSmJVCjqOq-7^eD*qu?Gj6`qS>8=VcMSM)utiWZU2-qzBPR?uhg-lw`0zN z4Qo>!TmoCB6<6=l5lfzFXZ>2gD65FCt^K(5`aAyvKE^jcn`L8st!#B^_NyCvJ0&I- zUu2lye(cBFF+S!3?i`9*T}o5*qM6kei!EE0mhkfU zqu?j2vhu%o74n8HTDxz9@fpXRZj)KVy7`1&uyhnCS;+hq;A-=mpZ~TxBs%nBYY_LM zmS5#dGD38&XBZz`5Z;_}u2AMgCRfnqYu+(ym&}xrYX1~!Oyhi#f*F6%q4my54dpVJx~-%JNW)< z({cF;Cg((L?=?^AZezVwmi0V4M&9Yh4G)R7eHI`0Xw7kAJohrvrj4PSk6WOQkT#}bEy3mO$R%5*WE=9ptVW7`rR&*^oq zQ)X~(_3f#Xil{x7Jync(*}P}z&Nm~bC&jjFIGhmVtLa(&%`rc-ukw53PNl$SQu4Kn fLR4e_X&zwE*0i5{+xCbesMhdw^>bP0l+XkKX`y{J diff --git a/toxygen/smileys/default/D83DDC13.png b/toxygen/smileys/default/D83DDC13.png index ed3c077a1bf3e537cffd89483e4b2ab1e6d1c4b7..db6531afea8df290bc34b3065e69c3c990ac12e8 100644 GIT binary patch delta 1556 zcmeywvzKRrWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817mrBPl)UP|Nq|@7$Bp&+S<3RtapOJ zHZV9^RRyNEZ3ct-^kzLNJ&E2CG}#R=2n;Zm)SIT;o2I)>i+`II z-)RPh(+mv9kkJ3^?EmTM|I^a`CnujbG<>G1`QOOszpd>hGqZ;eA6~wE`Olv}|NsAg zP~X~mQ&I7zvhu_B_7f*goIQJX&z?Qs-@QBF?tZ|>=lh2bXU?42xpU|7z`*eN^XK=^Z=P6DbYgkohZna$efqSm z*LTP4y!TJ8|NZsj-kA+M`U77cFMV;WWXFWy2WL0``SoMRtopp?n>Y_oGu_bce0{0% zzoQKQpA`LnocHH2!_}qA&o=SgTgmYM7{mX&QUC9R&Mr1uS*Lz@635%a-fs@M9iGU! zEJW=m2j>qC&SQR2+ZY%a^h$#Kf*CSj+`D7@@z*phmfnAVnHMh#3EIE+>zA)z_U}D^ z&FrhG@zpCvTzVhBJbxAN;z_+vWUtW`1_lPkByV>Yh7ML)4+aJX_7YEDSN3P@d|awb zp)qF}85o#qt3o15f)dLW3X1a6GILTDN-7Id6)JKI7#J*ir-nvPx?{lc=edYw6A#Cd z#}#hZ;#Yl|`FQz3bd)@_`*JhZeP_V$>v(n5xFcU=E*zX zu>7`WGo~|QdLMJkZ^t;fH8PlG8CRUx#U1zRsqI~sQNX)`VF@DYR?+haxmTy{h>!j&2 zk+$lyqKi*(l`ngKc(!x*_LMgcaZ8R@7$)vH&d1=U*Z0T8WzRufg_b|Y+yM@&CU9(< zH}O@6fXTd~Xb#5NeFxZ9+io}>aYXj4lH(Z$db$ze9s@0t4b3Da*FZpK+cp1KRk69=4dwa08PV7TFU%N+}vr^W+ zpU%eiPD<@a_LSTup1ZTczSVI|GtIxe%2ar7LfXVXt2Uks?P;6M(rLmYvVK#DQ(@wv zZAxwFYT_rt4;U;>^jNel(Ql%_jLAoQ+2+i7q$?%U+*$81IeBRf%QBa~rYRbp-3P9k zKl#^K!!S+R;IF)q!{S}qlNJ{iW?9(3)ob?CUAVyWY{;aTsW138^n0IL`>v8(Q%~{x zbyp@1S*GaEYLXv5G?q?Du?+c?RnN&-+r_xf%Ek6x!R5nO-DNC<8|0Q;Tx})P-1b?c zxv5tD!#l}ln}q;7ha zSNxxTL4Hdvsz zLvw*cNQqBQOpQ}gP?48UmPmkum9DL_v9`6kxxTV(Je!7sc6dNUL_`RW3fGe-j(mXu zI-0uL>sRRL1_rfR#kq#AT~+TD8_IgJTI#ClES?h%-qG&jhnN^Vl64n;=3@B5z`&qd z;u=wsl30>zm0XmXSdz+MWME{ZYhbQxXcA&*U}bD-Wn!sqU|?lnaH4q0a}*7^`6-!c bmAEzR)RT#3n5d-2#lYa{>gTe~DWM4fZ|Bnz literal 1650 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy#_8W&W0vVCgv`# zriO;DMoz9qMy|$=mKJWtPDU1PPB6WmdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{mIF%jvIKm(IWWwk+Q&erd^r6069K?v;}# z=UVzLU*#dN;QTo@6R91-oKJXp4?h05A#a}0yDL|(s_vX1njj%7Tl=zXcdF;4l(cMF z*-iIzru|*Iv}nWT$;Tu53OjBue^J4H`0!!#FI9g7!otM1ZQC|s{(S!Kn>{m+xiut( z{{8!bg^8p6@P(Hp0U+p@3)|aK(aoxg@RSruJCO9M}DoRO7?O3sTwRUMBD=TY(h0Gm!8JQ=tI{NzI zOW!TnzTMo*vCdB{Dd5|g8S9VCeEaU*sU<%Tzu3d9R3pZ6aN6wI+)qQ57UqAbfAP@2 z^4X`{CFi*dXQs9NlzfwXLvEul?;YnS*M+_vVF`QgW?)<(o;F!ZwI%aY^XFubpuR#o zzB}o&v{yQI9TV<0T|O_oruH-E!(3fI@%0Deb}~)fIM-@If$mSPe@k6^c6u#1ecSP8 zq5LwlRkbUbHb(5ZXb zYvb=7zpU!~qN^^67Gf<&tEQgQJLJCq;QIo@vPDuJCwcfk=54c?Iro0J!^Y!hpIUC2 zV`Kd@St&{L(xw}M`5LcQ8-`An&UpV*=(c)*+b4yz#;->v=0qw?*k352FWf$ZW6L?t kk9lV4Ez=HH)U!%32q{hObH2s<0aOcky85}Sb4q9e00S*}-T(jq diff --git a/toxygen/smileys/default/D83DDC14.png b/toxygen/smileys/default/D83DDC14.png index 2e92ba265ad71d08965d7958c3ee63167e555bc7..7af6dd19193d83d7a49993f7626c8c37b9a909c4 100644 GIT binary patch delta 1613 zcmZqS{l+svvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{w0z3^@_a9)@<8oq`0Q7v$t>BwrzWMuUWrr%{C`flX^c#hiyCdFI%(C)kh z-W`kU;6j&->+Z4e*F0H z<;$11Z{I$8^yuQH%ZCpi{r&s5zk_Xnv%}xNe?Ncz{PN|?J9q9JK63p2gNJ5nN*0=` z|NsC0^XJdEZ{P0UzklZJxx42N)s+-AmY072{{6v&2Ny10yt0(x>@(}@0-Mhby;mdu7cb6GX&+>Wp?AgtmH&32Cb-0b`{dI;P&l$cwX1F+?;qv9n`wtvB zJDuU$3Wiu;a?{k{JC`G$s%uKxn} z|7-s8^Kx5*(`z>u1_lPkByV>Yh7ML)4+aJX_7YEDSN3P@d|awbp)qF}85o%At3o15 zf)dLW3X1a6GILTDN-7Id6)JKI7#J*ir-sH}x^2Kw^IXKXiHGCKV~c5Dug$dk_9M37 z(T@7Q+jYI^N|K6V)8sFoe{cUH`p+!0Nuh1#`erk49A2d@p1k^**=@DCmD2sklMS5h zX16Wb74kM{OLbsl-sYKGem>d}aa8>7Dyc9Dk%OvflA8)AUY)ITk4Jnh`wZWhM|Z<+ z$2{s`V!XGa_k;7azYhZZ{qLnIR3G8B%_~^xTdjNKVf~#CBA!ouTde4wJNYo)W1;oy zdNaAHFF5$Dl+Wx;vB`1LIJ!lC&mGop?-!Is>^fXKv*y$0jFjAav)S0fc!eLCo-(_% zWOG*7w>r*grn@sQ&gz`EVUx$ct2dtO&1>^J)M;X)5tq8M<zyPw6DXE@F4cVpt1+p+eh zC!5Wl1bNj+zo~m}#A_@tX77-yS7u@?I>yJ%&Oe9q0MDrl=`DSYpLLoS9ObM#fAXN@ z&()dsSEXyWKG6I6ZO&7hxaO93>Ssf9=XbS#c^s@Dee~j4_LXjBMl$#ItA63$cc-h@ zdtN_30|UbsPZ!4!iOb0e49sb{vaw=eW^57(*DsqJm>4}{D3*|vwDk2kdq!7hZT*fN zicB_pu94+jK9gv1EV5EFfxC_kk>FiP#TLU>$Y;LL{)9?YC49UH2vf9AlE zB>}QM}|iijLg;l+3hB96GEF40h_tL{C&y=VD;+boFyt=akR{07zf-tpET3 literal 1668 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>6}su5K;{Cgv`# z7KVndMotEf7M5nt1{NlUCQcT{aJ`;+#U+V($*C}VGc!}Hpn8q*>a}t%N=+=uFAB-e z&w-_YfQ?6ghr=?oQ*)x6i$^ z(!0e!H7ej@oq{9(#mV|kK`H+#HngAOaMpOWaEegUb)jmFMG73sHQY@)y|0!ky;A(k zTs2X}^~BvA^Rl~h?-ss$bMEcEjgnLL9Dc}i=4ahG%kO6#rv%-2<@0t@GVdk96O(WP+sDux-ma)f_9`wyL5L2 zEBoToc@;12O*U+45~gwiZbIs zcxJs_4O5Td2bCrTi+RmVxBm!))F!g+c)@rsPU_6nYgfajpHB5qo939n&@!RiT;{;- zhYs)l*0D0xEj`S9<>3)I>&+}nf|?hr`&l{O^Zn19`$fWdM#HR+J{*pF!uzgzTrVt} zc6MrA%fpL?6I4!W&SWn>-+R9`drI}U`UfJy+e;=@gc>tkWUM=5*WmFuad}GKpB;On zr#+Qa6k|Rp)92#-u}kCho8;O}Z7DC;DnD?{J1M>VfV(G0<08ie2{YDzobPJ0>BGz; ze{NOn*Dh9K3D)Fjb&A_BbD&WsL4xf?tCF$taXWjb_~zG#N*K0Z4gO`9KTWaz8I#K| zHiyewd>Q7=>^6>^n^wAc_Fs?Sdm>sci52Vp+vYeuxV`&%$-=J^w+@#vbie*=9(lga zan6~_!#2l{&YtEe8mPVDDW@jak1ki2oXnD+;m<6jLz$U9Qn~I&p3jeXQLE-xty~rz y5xCSM`o2W{eD!Z9%IIF|%KbLh*2~7Z0fpU-l diff --git a/toxygen/smileys/default/D83DDC15.png b/toxygen/smileys/default/D83DDC15.png index d9fc6220d38787d426454ab769cd587b4a23bae1..ee2f83ff7f6d8ad69a70cd91af28303fc6328a6f 100644 GIT binary patch delta 1602 zcmaFJ^O|RZWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LN!fpAgq=RhI8|RqbzaKHB5+Vsr8P z-8JW@g`b)fxVOpa%;b=rb#}WO98OOPe!MpK^MQta&CVBQ#n->yUh#Be(UXnE57*{A z+unF(LE5uzHBUBIJm1~%a$D8QEhRS>CY+xV`e1$0t;LCt)`4_>+E@2k!L0b zoap!4R%Lm1O6Zk2F$Y@Ro^L7LR%Lm(+xPUO;B8fw5VWt!>B^kgD>EavRaxFzlyq@s z)UjUQQxk)pZ_Gbj-{En%%j@){;CH*LUT-UZyR-7c-WrHD2zh5!;kT0$zF(NXt;%w5 zlhfn%dEYNA_;j@Q^6a>?lY+mUo%!u(`{%=*ul9A_m>+*@amteoMIR2deL39v^=RjZ z!(E^DH-0e^S)nM z@%i|q$LsSyAMW^eZtlaC>2G#ad^t1g-J#w)OH)tu`o7uS@b2)0r<=>Z9PK{U=XZHl z^q1rPpU=#`GAH)K-kK|OV(u(Wd%LIM>fE@On+xu($auQ3;On8LYxCl+&5!^8|Nr`Z zhP4a~3~nVsexTT90E2ok$-wYH$?mJ&i6_ayFP_UBY54RcoL4k-!0Ruz5Meo$mz)iOmc>Y}1 ztYXpOFUquFC8Z{dqZt8{1RE z{f`~?JZA-BecoGVpN6knU{x#O<(-XNm-2w&~n2UEJY&FN6y z`7YVh=)KaN3I3B@8?;u4XLql7G9x+nuok~{@o|g)y*qffms+eVDD<1&KL4rVrUw7C z(5>E?K8GcqiiX>GhE2I#`=_G3;-j5WnSy@MT60I*VtCt7s)mJj#fLwePUZ%5m%8{Aj6BJ z9Sv^--ipM;6o0wdDzK~j-}46-JNq6zxU4+q^on+0j|L$JF>0Q3V{e-&x z>b9&>IVqMKCQcpCGj31tT>a|Bo1L5!OXn=B7k$(vB6_e%M`9tX++`leiz{{|ER&GD zEB?3Lv?==FmZT^BK6w+~H!x3X5ZkeU<1d%YS*t+iy=*KF6sov5Mq}4^5R@QmjfpWz{ciI49$iXqu<|r1MgNzn>i6&A_rI z4Mm=arn!mS&$L*Do12Y4iKp6sm#*FVQ19=zJ*Ridi*w$+ZxX(}QhQ&Ry&dbO;2C*# z9+p=mpMLl=?Sj7UtgbCPY$h&ZU|=}o>Eak7aXC35At9wc$${|rOwo)_GyQi*bXrLJ>t+HV9qj|HY&6_xLs_cWwhxvsro;`c=w7!Op zmflI7tIXP(+Uq}DsW&$;S+!=B8Qbmn;B2NvKCfR%pEVgh#BPu&gxAft?{mL-YJ$?*IQ77#LJbTq8qOw6?n46FRb#Ap00i_>zopr009g8H~;_u literal 1633 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0z0PLlZf-_KF0Ll7 zPKJiAMwVulmQIG2=9ZRbW+q0aPB6WmdBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{zAR`qcWWTs~UE$8-3A>*h~(DK?bu~7%?ERs&q(Y|D(%bvIBQtyZ1FjAA+g3zr*=Tu6 z2OhlKZmZdS=o44%uehboja4%ruc zOQr>y2d}<%T&iNfoqOIo^Nn}&>|cEU+2c??C(p2eMPS}MIZg4@7QL3~LAP(mGCn`_ zaM#DYFMlGB%{X~}|HlC9E9+O}%bnxjZLn=ceDM8cb!9RINwwk$40~;VzIimQ@BpaL N_jL7hS?83{1OT!UU%LPR diff --git a/toxygen/smileys/default/D83DDC16.png b/toxygen/smileys/default/D83DDC16.png index c3212776c54d869cf8748df8c081764842b39289..a1c3d5d86fb11aba4d80eb8b9814b6d768008dc1 100644 GIT binary patch delta 1513 zcmeyxJ(*{MWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817m7{Pl)UP|Nk#{CjNPF{>7G=cjs2W z-o5DaneE@N9Qt|l_^-QXe&0Lu?dqYo2UpykU-$jW!B;!y-<(oVe}8fFr^73MTsiRN z#D)(CmcHM=_``uE@Aof$zi-jIy$e86*N;3~)BWk#nqRk1emS|}&7Ot#=T%+qOuW{g zak(=Qj&4pXyxg69xij%=P7+r`~KZyx=9=k%Wk=YL#11hW3@mfyEd{CRNh&)rkMZy*1C z=j4wo2VU=*|LfL?U$>6_ytw1%)qUSjt@(ZJz^_|JzMWkC>-xdp_s{;mviHfF?vMKx zyx%_c+u@}@&TRO7$9|sO^zHDH-!~5ZIKTB;U)qoB^+&!OS^8{g!>6MweqP-E z@xa2z3#-4K-T(@N-SaMYCcfF&_iFRB_lH-#+Bos^;pOl4&j0`azf8i31^*;NT{HPZxyyUR=(dGlXdd10|TCF-M1w_h!LdFJq73fNoMzD*jN8pRvmhnr&-D3h>mlaUeR~8+2)5GUo)n0PyexP_gg*1#SRVT zS;~JV*4^3B~xE&KcmR{B=!9(j1@!<|W|c5{^f2u=}P!1d_pJtZyEYd3Z$ zPY*t4V{m@EVEy-y+d+lRUB#BXbuGs~Fj-9vz3R8naH+we>~#{ZW-Z1x^7mw;_AEcn z_r&#@gucJTdppJl6T6$|pXe}eVQraE|BXqnK}+D^wzOqoDI8M%J9S%Hc-@l@ubz2B zS$tx`+XcVQK4-aOpP^KvaPyj=U$eg|lS7r^;e`54Wd?fdOkdT%nIf=j)4#1BSGc$r zT#!sWr}{Wi*hzEQ-xH@p6aMpM9l9j??(!35``K*SulJZ36kIn@j9Z-3tQH%(dDC;n zh--U%rB8@vYBljj8Z>kBT;_5LT(K)*=?uxM;$Pcso1zXE9X*pjFGeArY5RwcM3(Qu z&xG7p`(1I!e>wY=65mtSfAt50PE0YJrfi^EB(lWCGfHLMKE?I&C)ghpu`K)V%$5;S z&T1(ozTEA@;dhr_UJ>G&b;U#VWN7ERfQe->_rlck6_2Vu`C2-W!KwYowLP9}HZ=*d zs*!wC_uPyRa+qH<;Ye1A@()hEi|Xle^R_5|X;KsFWfA4EbXGfP^>FFTX5~-y<(c+Z zq-(c7@ca7h#Z#Mky)Emk&qn5!9lieU_Iighu8T+cSF%}4@|~~e{L8p#b5{iq<3&RT z28IouE{-7*my;6|I3zAI1T=KcY@OOWw|R2+>~?XL1L@i-I!anr2;Dz9BoSGBN7b&s$ckiMs?C_a0KJRY(np zN#YPRFfwBdT$Z-9M}V=I+gl(tHJ4e`;Ul*d1H;d!3h`SxTfZ_eFsPQeMwFx^mZVxG zhZLnImZUNm85kMq8kp-EnuHh{SQ(pInV4!D7+4t?@LxVR8AV5KeoAIqC2kFMeP#L+ S71g;I7(8A5T-G@yGywpG$?RnS literal 1530 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%D~vd+04z*+1%OL&DF@z(9zYz(8<))%+S!;!pzdy2xbO0y%sJ;mTpcKhOQ>A zPKJiAMivIfCXVJ7j&3H-hOSOdjxfERdBr7(dC93Tdowdrte|=g@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{rVd3=B+Ko-U3d6}Q#|pU%7NAYz;BAG5tukc0ms zgRV+QtCCsHja2{04Na^eADB6&%5!*!-QAGT`a>W?)5%G5c9`y3r;LEh?s7*eE&XJU zfBUjascG6Ku5hbaGplRg%`x7U=ET7xl=FaXwf=mbD-GYxCx4&6Xwx>9#Z%m0JYu8eI65nC;$Fs9Ec_@j_GXP^s>jGK%0}fC8z<6^j&yB526)$*ZXgRkr+s?1lJMS~s z=Sp$5aGCq_pz|%E`iUAOfF5{ zdiLwhKhuw1J#>sqX48X7m$K3%Wpl;zb~6UAcRZcDV0rk-B>uF;&p4|u)-UBsJlDMD z*W|f#^SS@s5Y3qX{Q1_oYgwM^3v5$87vA>0=(3GKnH8CFfFw{+2;ImUTW2G z{{K>`{ZH=xnKi*8=(ONH!F58Djz@i%@-IsG_!YhX-}e{BH!w1khW(s#Bd2o#s5JI; L^>bP0l+XkKM}0Oi diff --git a/toxygen/smileys/default/D83DDC17.png b/toxygen/smileys/default/D83DDC17.png index 0043f3cd1b5cb4a95c4b6d235a9e00c4511f9769..42f14de3d6e9511657e8d3c6de65e64816f540ea 100644 GIT binary patch delta 1703 zcmX@cbDnpCWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LN-ipAc7PU3o`sIXf*`YYk~jH3?H? zF(U;LJvm`HAxBM6E2!V|82M*52}Uoka`l(raTq z11&Tib>u^Av^V#b@0(V?rN3fRZ`u5sv`GcgEy@0kiM}(+lfuHnc22HY)ta}cK4Vrz z(xklTp3Km;)PR+dgI3b)s*iu0aVlQz2|>E-KJrw;FnbvDimvM&j9&i1!S zbTf@|&=0oOGED8~>}(e0&FZ2ysV;JtU{bDLR>8W|Np<37B!23fx)mO$Pbj97~nwq zVEulf+wX1O=KU%6_;+1t$-`U6AI;O+f2rqa{hL3pkE~=|&wu0NLCqr!3=E7(-tI0d z0yxYjF)%Q&mw5WRvOiTix!vyEwi*H+WaE>h`Lu*VZh%kZUNOy;Fs6 z*R!PA(Ftvip)x`j*Ub*un|@g}`a!}PmTph2{g36MwNhI-5|S^Tbe`Zg-6dy!TX(H-&Xw%q z4hedddE&4wBln9PGa~22Og3sMS-&(fB=&*d`Hv^2U6_<>;HThkRlH{g$Gz8l3~nns z{}?FUd>|&E@&5#q$OJ7FrzE+Fvji10Y@Qm5vaC^O@(!y`=--*pkn6h2zEG~=wzV&- zJV)7@g5{0DOPM+J)9NK+=0{pfTsKLdJC`YF-J1H`Ia6m8@LA1$7SwJp6cx~Q*zR%B z(;NHe-by?z`tI@*WxH;}?9X+X2@mp27QWPpUUo8VbKuS2iV@oiXN!HF_=0ouia!14 zRckDy@`O}w83gQ6d&PRRmGgF+#Jrg?3h|7K7aY{cZJOblZ^mNNy(o)Q)OQuX zs_g1h?;do?1uXqJ&)ln!Gqo;OrD2M5LhYR;403iG(mby@tLlH8E-;1PFk#b6HS4~O zEDxj}A6WA))4k~7CmvmvLpK6sjq6LeR~>NU={s@X=l8~Yr^>nCh2;0g>HWRwajJB= z$F8QhUCj~<3=B^^T^vIsE+;26FvrSt~nkFYSF$o41b9-xNOS7eM2ncWAy1Bfdq$n+ck(pCG+`OFMU%x(I zo-IsZ!jw;6Kl2yx^KkJcCNML;ys%-zhmI2~Ud)JKD=U+X+_~dN3(NN-OKv=w62ex= zdUVT|o-=FS%(=7YPtzeKwxy3I9k{gVQ`f0guVzKf-(|)2^jF)lWzVKv+xCr5Vf*Ip z#>KmrZ(m=3=!j?OWb5S?zH^TqSZP%It7qoswDU9Tt@HmiwRVdc7cd?a3NdTA5gA8yHv_804|62NeVe4Y~O#nQ4{C8iY`F$VZC5pQxzL#lYa{ L>gTe~DWM4f&K-x) literal 1606 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy(T8+jxI(9CYH{Q z&W47rMvktgP8Jq!76!%!7RGKyaJ`;+#U+V($*C}VGc!}Hpn8q*>a}t%N=+=uFAB-e z&w-_YfQf8t zW!pC2?7q~rdI#keGq+YwN0<7A=A1o3N*x*#GQI@)e-m(d?|A;_EpJ-2Bd#vG@jO7!KQ$GrPl_x$(1`<9m$#>)Pf{`~5d=LWp}oGbsW`^MAD zbpEMV>Rz=BN|N_(1zhNuRTIt;gRoMLwukiueEa zcJJ=>>FiQ5Ts~=spMOqB%#EAund{OYoVMT*u6XPSA(q3HP6`qxs(EDuK$%I0-o6r6nec5;=V2>?N=c*|yOx12$ z)x2YRm%i@c{U?o1|Bm*h&Mi}w7P}eo|6<+pTMK-ii+3wWJD=@%Bky1~n>9x6%W}S? zjb|oGR(C|KmXDe#A~5q~@4C(pu8K2N{GN$VH~g6?w)vLu9GQJx(&2iCTDT{k(^I*1 zCF{NG6;a1cl1~%Dxu?0ZMTbn+_|Y>#Y_ibXRg?3VA8wrB@0cFVEn}j-dP=jaOS9W# zrl$!{oU*=N`+LhN@ty2L&4u$f%v~U~Pw<5NvlZ7QXXmCk3voShUyuzPa|eN-w7(&Jzf1=);T3K0RUBZVEX_7 diff --git a/toxygen/smileys/default/D83DDC18.png b/toxygen/smileys/default/D83DDC18.png index 8a93ce9b317f3205b42c0a41a995374304e7b08d..27f35b69599d6c1ec3c852d9582d2dae4368ca94 100644 GIT binary patch delta 1265 zcmZ3?GnZ?EWIYQ51H;x|=C2qS7+BIBeH|GXHuiJ>Nn~YUU{FZ*2=ZlMs8VBKXlP+z z_{G4$(C~tRq11qZ;Z*_ygVhWM2JwP9y8>+(7#Nt6yxm&pI& zosUbE@l5EP-3$y&wN)VzB|(Yh3I#>^X_+~x3MG{VsR|YKxdjXi7QItL1Gn8#;P_iE z8r#Ik66>dSZ2!E==k@lB&pEkd=9b#t=Pu1BTDd2E`TV>8%h~x_rXPa<!$kp*UNTqe6Q->V7}<^-v_06GOr$usZ?AZq^VCu=eanf~ z<~lAh8R^UT>W%byLfrOD(A9~l;l0VGdDQOW0h{}?Re3GB`ILIW*rrMRf~Ja6 z6=!cyPF2NK7Uj=f1#L&2cRO}}`*5RSPr>KH3a5(ioDBUvygv)w)+p&H)c^SRKJEbP z60S~#+=C&^&KD})zMkZ8!$9D5oNvd+xA)d%r*}XV>A{nKhp_ zccrgA>&?tOdk5#XN2#wfC#eZdwdFrpzv)PB)KZs4;%AP?#2>xB@l#;oKnU888 zQC+yhz)|PHhgTn;qk4NsGVoMh<&-aZggNE9_#oFL^vqPgilVXK%oxh}8FdtrwY% zt{T;Uezto@%ay)vmI)H5n*W~sc~cRA*2TDZ*;`Tb1yCg!`t{M0%?u2T<(@8%AsXjXPu1`Do>Iur@bUcbqByzoF6S$8 z6Fi(BPrdly!lMfpgj#}=|F?Kg^!8S`D)uFtb9vSlMcF)!7L}zQfdPWn9{0b$tEt@| zzkXr7^ZCs-hbPE>Ox4j%aN?NV1v-qCOEua-u|T|arK#_DpfKHpLH@ckkB@3J#)gzx?HB)g_WW8a*Y z%Z}`3mw#NV)}5G}dsvP8(cb2z741vji=V&n%KYLbpJjg(|3*h$b}oA(acS1K{{}~0 z(k?IS>8wBOzuSMa=3vB}fD+7Z(mi5699Z(Iq`6-!cmAEy?M~c6nsHo1xz~JfX=d#Wzp$P!3iA0|O literal 1571 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy~dU57yyYlaU?|q;0=;M!2rK$j}0?tZM**Z>!jOnmpv`= zoa*&->z=!L`As3KuVxs{oN)g67{d@R5I+O>Jy%$a61 zvu6Y2jSp+Xv|oL#TCOx_*D=w$Pg_Fo?2KAFY1OJ3?^bHF)SY2wO=#|(yzyTj)BGQA zLnohfd0Q4O5UVHtvc&3Fc)_Hioj!B@+TWD@-sr$){G;LVjI(K*Zst_<)-4PWaCQ=8 zFstckWY*&vK^u zUiU>zIPLGD_uu~~Jkou`X2rPSX1V%Ak0~=hzdBIc9`kcUpuhX#i{{tPf0X55nHG_{ zhl@daAMA9SbLO7;FrDrGJ+nknLZ`2T^21#{jkHu!G7`t|Yrnddz8rdRiD z&D|QswBe!k@xSXGEPCp-?;hTD?R6>RjSq9RKe#tAGMI~P(z?m1zz-_6Jzf1=);T3K F0RSW=Q}h4; diff --git a/toxygen/smileys/default/D83DDC19.png b/toxygen/smileys/default/D83DDC19.png index ac19c2dab3b6533e9b9324a25511782945b34019..da588a4a86b41c6ba66592449d41e2252761d81a 100644 GIT binary patch delta 1577 zcmaFLbBSkyWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817mrBPl)UP|Nob}_|3ETSmfY2+s1X0 ziG7ETWu1!QHt$e4+Uy><**$QbvmXT2ueA4CY45ez+HJA5+gww}X$H0vwJp0;jpx~W z%(M4sR?vqqCYm`+G;`=TwyTrY>NmEVXy(wQZQi79KFP$s+rYX^QLj?jpx@B8LR_s_ zNV!7UpxeM2Vtt2>B?Q%}7-B*7NTO%6ieAi^^I`d_m$T-b$u2&TnEASQ(z~AicfAwd zPnz~_#+);m1sk1w9^@50FDiXeQ2e5>@; z@X^SaM@9KhN{Ws~#y%*_e^Oe!!!P(|R_4Q^{71zF3oV_FMn?bdtZ(a9HD2xDo|08| zCMu>&*^q&OVXeJOpP_ZTj(NR|R-=qoud?Apb>qcG_8T32`VDMmn>rrx4&CJ%aKtNg zrm4e5NALgt|EIUG{byicurCSn1H~i*2=KPt`7V2#<;2OT$gP`S{C=_ZlS#D2$1Uen z&M9xT{{3Rn*+2jO{jC49-r@wq$C@8e#T=HZt!TSsh42((M?k)@+tg;>q z3=Hfgp1!W^&)E66RM}O{Ec+N3nChxRB1(c1%M}WW^3yVNQWZ)n3sMy-atjz3EPAJg z22Q$d!14FFh;0)O$CAeu6Te=cS@-Qn?E{t9rF-6*&FWNfu&Gd(Sy%u6kE{Ko>suz? zNv-;v)ZKY^&8{NDZCQDD%_Ps<-g7r^vgD^csY$PbNvx5k zo+;31az0~}`dFzve^ZsQ%of3fZ3Ufs@8%YB%Ca&o3Y;z2zGnBM)OizYb~L^bQ~y%h zVr9d?gzJ|B0>*^;##yH@Svntx??Vd}?L-ycyr?mE|(GEqll%{Jco& zk4L{CYmV0HRda)qy;7H4k1_08e85hL@J(`}EU zu0qSdVi69;rA~@Bs*J846wsMh70uD&%f356j;m)wYCtyE>&Bm)Us|*{aYKWixXvX(smS|thv#0q3fl6#ZIp`_dVZk zC~5s&^7BFZ)fG|q^?7CPn8tKm&$z8Ix$NqjH-80evhOVm%@(UVct&aG>W$|@d)j8R z^qTOPtlt#kRGiopaqyw#BXEn(W8xC4UZIZF7+`2!&VZKF2lDpHyd2ZYaM;|B# z-8tRYbFe-^q$|HW?J@>koBN1yxrbHatM-o<9O=3dkV`XJq-zc|fosNESj)vOS*6P;Q-hrNRGiN%wnwsnHU$9}t1_gWl@X*#) zN8ecI;MEb)?zdk#%FyJFF%RaI8A|pV2L5^Q<{<+EgKCLuL`h0wNvc(HQEFmIDua=Mk&&)}xvrr}h@pX%v8k1* vp|*j6m4Sh|*(*5|4Y~O#nQ4^>4I=0|+_HlfO;l9pVqoxe^>bP0l+XkK<9C>8 literal 1637 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 TO;l9pVqoxe^>bP0l+XkKegfV< literal 1720 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy^dy%7H-B)E-pr{ z28M>NMwVu#hOTBV#;(Rj#zu~gE-<~GdBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{)NKMjXMe+uB$)Ts`2WQtGE`|qia+1SR4gccdW>8jp5kZttGlT zW9uyw-^<@kX8JvQ*}LVzq9(pOm9^jJRKMFfeXn%Iy7cYOB^zh{^KJ1GQ7tXlNw_;G%Z{LHr#o)~b&?|;K| z_`|73Nlgd;i-kby9y`%)U$iD#yFFsV6+IZ?o3j%+=Xs$jIdReJ1<- z6K8)~PSLOsU(j|~uJYk5wR`olhwoew`Bqw{ zboWBTE*B=j$hOm!*QE}2C=`fztF}CqVtf!>x~tJ$WRF}$Uvu@hq~c(yuboycOBA*q zPP|c;)0<$R;JMlOGq-eVWRrJ7>$CH$j1x0>PF678T*hOvN=}VG>o?xm+eRUP6F7b5rb6Mw<&;$S! C7>%(2 diff --git a/toxygen/smileys/default/D83DDC1B.png b/toxygen/smileys/default/D83DDC1B.png index dccb76eae5d93b7861e6ca88e0822d21aa1efaac..412d5fec992587f342daf20f35c8cde0e8871eab 100644 GIT binary patch literal 1611 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 diff --git a/toxygen/smileys/default/D83DDC1C.png b/toxygen/smileys/default/D83DDC1C.png index 73d740e3024cd902a292a3fb56b4194cc8486154..fd285ed40db47b2bf3970fa09d7d00ce363307c9 100644 GIT binary patch literal 1738 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#ytT(A+GN7 zOzr8uEy*7Ban4ndHpPKvxn6pyPO5R%vf;*}0XjThYOJmb%>V!Ycb8`(4>?;nI$Jop zSUS2|Iwp8IwIqAET7yKK&8=O`t*at!%=L^QRzqxuI{@N_aAQ#j3ee$!BQG`9FmoAi zbv9ou&QLSyFmoB=k&%&kT1v{C=`)lu zMoY4+wpF?BZSdYw;j$pzY)iFQSB&Yrbc@AVmK#bOH5Fd)G1rw0l-^MA1^7oF8ASJ5~oE_Q^e{{8PC zpI_DW_DTJfTe$M$*N+R=NI!oir+ng!D$|Fr-=8njs4Z7|;d^=>v&{U`EFIHI)c^(t z2F4_BcNgAoTN1xAFfg!}c>21sKV#?PQsq7R_`(DR2Bx~Ikcg6?#Bzm#qWrYXoK%I9 z%7RpdirfMQ28-UQp|O*08*tP;7qM;P;aKw6V&d0pGwr_p_7{3@A18{&m#)sbu`qz8e@5!5W3KP+rF>1R$WT6z^H8XK_w8pwHe4(bi_H%6MV24) zw$c3=z9Bx-q1<7v6Cq)t);o_)Ia9EHXOd{-!*gH$e0(U5nI{6&FJRXz_;Yr z*=H@K^T+0d9;-`#g*s$v>1hWM(3>oN(7xG*b_}NlXZ+)rTo8Cy^yI~iIa25Rcb{Hx z*+WAj$hcL-Uv-%?tKF>aS8ekTx(XN7n+iF+5V$dGZpwkftj{%07Oi-4^`8*aFA-+X z=ev$9Sh4V-B>N6|&I3G`W`xS}B|oq^wV>gh$i)*9G86aD{Fc1;VmbG_ko^A0bDu3e zUQ}J0bM)KFAB*@IEF}EPJZxM zTtFm=fmtz;u}+YUEzq!#(^DWm$jK{eN@JrSM@q_53CX9TOB{39)YM#6Q$<5fO+!UX zd0o}i*wWI@9$4^T!i5VHHhgf28C%)nId}H_X*zUh(W5<&gp|wJ_#R){v}ez! zu2Y}(>{%5g%*W;zy!q9vTf2V!+SPU}tC-u5O;68z^|EKvu5J6)r zTzQTcMMG|WN@iLmZVj7M$~H1EFi3)I2+mI{DNig)WpGT%PfAtr%uP&B4N6T+sVqF1 RY6B{aJYD@<);T3K0RW$dsrdi^ literal 1695 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@tlFMsCJtCgv`# zj)sP=MwX_I7M3pNZYHLdriRWYmN31ZdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{T?pRG>gZy};TR~mDE3CPmGUg% zyC%i2wr!p}cXRQ!&6nTLF$?%~;Q62WKXv!tzyEXo|Di8eHfAy;9`f%I^yiecwl8~R zRX8t0=~QG@WCzw6F?W@J{_)wF6=^j~SARbfr5Ema zIQx;&(`VaXRq9SQYwj=ZGMv5JC-k|2@8Q6=Nz+!OiGANz5*fD0id}5-`{hRiy?RTw z-`M2V>pS@v!>(TztGvETM}M2n7c2G1dG@Nehb~sNHwxb0Q#|{T@DG}c-uN<@=Po7aNm95YgR3BnDaWb zR8j7JgG%0l-wmR%XH`?q%qrRCU9hUrG`sECg-OfrfB3ZW%6{GVwlUo2^LV|YTmu_L zi@aLTo|29iRPv<@QGD^vAvuH+Kacr{yb+eZ+)nYfY6B z>vTD%rEZuP@PPYIocehM#S1(suhiBHsMmfzxPJ3NjbGKjA4H2OsDvJI5#6lF$h=%r zyr-+#Ebh;>d(u`%4}%I|jlYi@bs zi-=rh-pRdNO{>M6PN!?`+kX0`jgVl=bCLM!pQ=l8ul~QTaD7QzTl*3I?7+iTD5j diff --git a/toxygen/smileys/default/D83DDC1D.png b/toxygen/smileys/default/D83DDC1D.png index 1b49267cc76bec96b5f8e26840924cd00f9f7391..c74c7a7fe0be61e2f2d014ef64e51cc529f2d673 100644 GIT binary patch delta 1860 zcmdnQ_nL2lWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CxJ%Pl)UP|Nj{n7^=$UJ>0l#Y;5dw z8A^j0+F}`26f>+SW!SZA*TI7a5AWHCjOzFASc8leQ+8 zr*$lz)w^Q;q@<*zu7>=X6B=euYGz^-9th`YHMp-T3YJs>-+lp>gwuZV`CE&6N7?+;^X5pGBUPo*>d^xz6aM%T{^Mn z)PYTpZ=bt#a`)`nvo9UpdgoI8k*lY6?pQzf;q{Zx?p=Cv=R#Fg)xxRuhd0k!F}wN5 z&LvmQ9EgmJJh^A-y~~H!E}jw)5D*+3TvHwX@880&pF6U%vm44%4sV@v>Glbz`)Si9CY{ZH5Y~bf9}}6e0=+2echAq z-|NdiTwrN=bNAbmpRqmL7~lO${WjrXqUpO!9>4#J)i3$*GW4pO&6_`AN+M@Zz5ey^ z@~og~hw4=v_B+Tf4C_xRn^CLFz`($mmG00|ItT<`O!h<#9u%Eve$OUUv}Gcs{X^XqiVv{5v5O)a<1MCX z|MABPp0pF`mnVcwUlrR`rmwXj*KD#`ybSy5AB&iV?K&R zPB{1B&&OvQ9_cEVDfk;5FLpWfX*b%~E{TN(s@-Cyj$p&`n3&`dqpSfc5u zcfN4rhKCAfi*9w=URUC{))!QNWKP5F-99bi6LMu=nj8sHV!Y5TTF>`;b(Z?`M^TH< z>$O~%``zBU->*c4eYW7c7$bQz#Vtv{_~YIQ{Vor(*17v|UyR+)NRw4&@29izh4G3% zGQDMXYRTrTvTt=9^Gx$EFEZt}O-!9wvwGwCSQ}n*PN~d_3wurlah`2uO=4}o?vXbk zzcEtkV0mkmn0x)6Me+e_NTXiW}B^XR$19Z*OKl$CSX9d1loD2Il8C*;tFz>yOx~ zv!_2c;=qD6K`T}*TemPWWKQHFR-p@ey4vek ztXZ{g<=V>^Svg-^H8(Lc-M(ep*4NSojVwIYR=amqSN;B_?lULl2#<)tJe$;4TV5Kc zottHw{?10&O^hvg^G|Luy{J7YFE>5a4x7cyDxs?rzBX=i+1+2f(&o9_HnDNtxN$4@ zrcvtaZEx$X%ii6}eShG?=Sl{nUYfGOv@+dUOasHwD!jF>*wc-W^gAv zC@4n<7*u;`u%|~zcrdUWQkIZZUc9JLhmB$41;4m^A!R}g3=FCzt`Q|Ei6yC4$wjG& zC8-QX21Z7@2IjhkCLx9fR>r1QCg$1(237_JGkosvVqnlm(vh2=l9^VCTgOYmbhe3# Q>Rb#Ap00i_>zopr0Ak8GZ2$lO literal 1842 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy-pTxrY@G|Cgv`# zW`>5YMvg8{E|w-HrY2@amX=PA7BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{_dK_JpU9yoAtPgx zwlN|kY_c9xMfV|RL)VS**SAX4ehaqo5?p@c_Ky&6hW{+d91?~G1`b}QXZqy!rthuo zpx!7xD>7~ zC@FpP(xs$>XRQ>AC?TQ2EfL$|q&Qje!j9m&vnhX*pZ%6U|IGdH#1OsN zqCPUsi`Gq>ynNB)h>$1S!%H80t?Iva^{Q-GsObHy7Y-!HvBaWOv^>$#ndfhwD$ zPAJcwYEbh}-9>4-U+}xrp-d*({o(oJyI!zPG2I{k`AHlCv_IdxEyW1|G-n++UXStT4|jd$LcL@v_a* zWv7nIFT4>eCg57=ul4@VqSqGxOF~1h?(uz4_%L?YCz%zB2h#2D$Sg{i+sUW1P-Di2 zJ10(bbWfXSQkH8`r*CSs?Da37-^q*eSLgqpdE!I<(JjnduUQBF{pBu}yLRoAx*Nf3 zd6t&5-?#X7@48#8f`G>MrVNu?LjA}2CEly?)yEqCEUv#+zP<1H10IoGa_?Wh{Q329 z_?B54KYp3^Ib6%%#IAZt;Zw6m^^4Z7%@uyTV)y?2`|tk9%gei$y#01LN0Y(D>({@( gTx;`K?Z55;hKLvKe>L(XQbDztr>mdKI;Vst0Amm2zyJUM diff --git a/toxygen/smileys/default/D83DDC1E.png b/toxygen/smileys/default/D83DDC1E.png index d66de864edc170c3749912cce006ce1cef8a067e..4a475984e68e37799956ac1f29af5d3ddfbfb959 100644 GIT binary patch delta 1506 zcmX@i)59}CvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{X<1 znV6Uu85#Nb_+pruGFg~2S(v?9Sn9o5ShCnzve{Xt^RP_kX5r)Go6pCxkPpP*2Y9U66fWe#LhB_lNn-o3^P+b8*>92f}70D)X&aRsK^OXz{khO!osqA#{qFE zDK!l(NhxVZR}U9Y?}Y*^Yo%B=sIZl4aIdMCVp$}>lEco-#>h~o&GEm@y31d(UWPGng3}`4|~1M44Hc7+d``{_n1=5Mlml#ri)_`v20X|4X9&=Su&yVB_QC zV`5bH)F682`@^|L?|P!Nzt{g5{e5%YS#4|Iu9E^jH`f z>KT3-Gdr>{J(pwY@R9ic|Nmse{pk!03inY@FSHgqeEc*&L2l;5pYc}}On*B~U|?WiO!9Vj$x=<4%E!RKz+U3% z>&pI&osUbETc@FPDFXvjeN{+ANl;?BLP1e}eOhKtszOO+L8?MUZUF;>Meo$m=uNi` zIR2)K#y0VAEO}}%@$2=Ob!9(oAE;cv{xLUy@yr*Dj}9;<{`&h_{BKA6Ww)Z~FVA-C zs+Dbib?WGiRXa;>rx@gaF5B&!HmCfu#Y(-?+Ne#y$PU?Ym~vCj=@Sklrd;cVZWJ+^d%-+a>4ReblxuuVAIG zyY7*PcfQz6IyIYv|3`3&-~z5kM{g==nO?Kl{dh|7IUB?G|J`hYa($1AYMpy(@Ax@`EGvZ`4z!{+HW0gl;xNrzX@y^*ZflpyQzb@qAA9kok3H!$8b6Yy*H zS7l<@J0r2cW^+*&6!o>{_v|;=WhDG<;{8Vj%D}enD?zWMO3g zfhi{fCkUx#I>yvGzmq?~o?yvz`M)Bw$*SE-mQwu7-9B7?XY}HV5ZA0LKC1O6Lp$FE zu-YytSY3V3smuMu->(xGoY<4H_o*<;nMJsJPIFdW|4p3hg)sZXHw!GS{8(-Z#(fZo zynDINVd5t~-D7%ESx0$dx#T7_D}OS-T(`#jSKh(7uijofU1=xH8CyPU^R0WL@yqk) zGu_m15tm;gb@OCF#s6s+O>uVcdZM&=QL;>O(qpBk3hz4nJ$$^%eEPgSJK8x; zXuB+1X1Q#so7So3pI)c5R=t#*Dfu!>>y?)RqZu!ouWqhvuC6az?1t}OzDOoG#P>Hc zGB8;wCwncAc*wxOpjzS@QIe8al4_M)l$uzQ%3$PcU}U6gV6JOu5@KjzWo&9?YNBmm rU}a#C%6D`pijLg;l+3hB1|wV=M1pHsCn~CQF)(<#`njxgN@xNA2cJEI literal 1603 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy~fUNP6p=1I*aSW-r)e?N)TO?5A-@7}zt>5j+ z^;)@G=tlF_g`!C;N4Z{l3ryNKCF+}q>q58pZcFUGF)!Nwk8yDvm*NN2JFTaL4*BS; zZf98;8+$71d%Lyu=IzDlhfPR7MCvw-J_z_a>9N_U(9-bw%N13zbj^M z>h;wMtK53{RIK6iw_b!++}x!wM~0-*=lF6R~Y zeK;DgCYHrobza^r_C)^Yq}~Ipr-J`#yv&WU zvF>$OJ4e3#uDkxPijSqp2JD@eT6%xmr+HT^9%k=TkXKVVcY!xXA(nUH;h$5^?cdFVdQ&MBb@0G4oLFaQ7m diff --git a/toxygen/smileys/default/D83DDC1F.png b/toxygen/smileys/default/D83DDC1F.png index 52f30a8112e09e8bd1da3e996959b7f1e42babf8..ca88daffb9f375adb853f2f8474105676d4a5fd4 100644 GIT binary patch literal 1672 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@I#uEWPA+G=b z|7UP0V{k5KaIerxYc|hraVcnHaH~K;epL)`NIa$5GaQ*`w;&^zf;Kb+a#-pThRih# z$qN{wCNQ|yGPqYWxK=WFH8KSEGQ`hjh@Zqzv&VSBy@2`Wyw<)@oOO$#W*F?#Y ze)w&A%h3CbA-etfhaV3<{hNO9=IU#2cRu;O_0g~O_kS+F`Mu%TC;tUk7@GDoWG!r6 zzU$?u@1KADd;R&xmh<=bJ^s1o&iBPPzRkV-ZPJ-9(UUe8PhYd^?9E4SKY#l9_tVdR zFTehM`swf5qgRuA7HOq5Cw0y1TCsi0@yl5g7XSbMfAy`yjWpP`Y4XU|@)Q zyFEil^XtcS)A!}cfBpr@-#7d7@16YpGwagdiyP1U7`p1foIjjv9q$Hjc>dRc%`|iI z!}}+TT%8yg7#Neh-CYC+sN> z-r}%NxyRaGRrM*6t-9Z0;c-sAtuLdv3oXoTwlDdlc1Zh=rV(cV>&K*gp{Y{YZ**@w zonp*?=J(}}>mjp)3){Mick`#_KBWdMz0>cgF(n>$%&FKaTz$?} zV2yUr-)F^4dGeV`dm3`01^t@+Rhby}&PXh%O?{gZ6O(=AW~;!i&HuK3T;bwga6vNh zoa*C5VJFRHe;+*6+fdKG_3$Oxcc-6Bo*${4U7If@5nSXCB)(;GPtrU3^_b=MFaCP9r z%kO&qy3S4QD@`c>W0@r%wQym2g4Yv`6C2DU1;X!7_|5)_GveHVFZCxFdS9*c=yQ8~ zg`@6JtE9l7>Kg;_3cL*FEwWf@Z=ezJq;A@4FPlQFq10RNW3cb5mDI0S9jbu};Q2udua3ymdKI;Vst0D1W9-T(jq literal 1571 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 diff --git a/toxygen/smileys/default/D83DDC21.png b/toxygen/smileys/default/D83DDC21.png index 279dc2e152232ca07f1a87d8e9c4efe4e6477cc6..475564a5effcdcbf102d16a6e0e7ea391c31e92d 100644 GIT binary patch delta 1428 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 U*yK=u2T&2~>FVdQ&MBb@04@M0CIA2c diff --git a/toxygen/smileys/default/D83DDC22.png b/toxygen/smileys/default/D83DDC22.png index 2314d9f9928a76c0dcf5735e8f420d5c56e44ab1..9db8e981e9fadc9ed6024da07b8cb2a4fa18c1f0 100644 GIT binary patch delta 1588 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 diff --git a/toxygen/smileys/default/D83DDC23.png b/toxygen/smileys/default/D83DDC23.png index 7a6f8d5b53b208dbcbde9d9d4d76118f66c9434c..12b00cd38ef82bf9f5eae4312e563977c1224bf5 100644 GIT binary patch delta 1657 zcmbQhyNGv!WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LLg#pAgso|Nk>EFx;OHMYm@$+@8g7 zZ7RdXeh@m{$#A-p;b=3%`N;@W59b{3W;ou>u&0q>$^7Yi8X4v36#i;frD^uL_2|9j2%|E;M14|4w3-%I;+-1*&pi(gYkZp~2pcg^qZUb71m z_}^AB%(Dql?*pmbKF}i z`saehn?2&EdKmWAFr1sj@aeG5kJC1{mh(N@%y@Y=!>(@0&qoY?T{OP8mUrdczT?yM zU+!dhvyb87+Is2rtL8l1?s#Yd!;vmpv9I51&P0%<!_{KcCD|S1^$*qwg|BBlW z#m{;cS1wQ9F8RjxQHz}Vjw{Qub&ou}^Wn~GCtTh|ACrl4M zXJhc*UpX!~*YBvL*14xfKP2T2bH4})3*FsvY|0^y^*ig6L}m(}`|#)EvkQ-OZOat= zFD1*&Gnn_9kD)(C<|n7y9z`96mOthC514{lT{e78S=FqVvFC}IAcr(x(&5!}Zxrh_ zCB#mA#Xn!Tpn7@d2c}KAt`@v@Q#lyEB()vEcc=&i$I->#l0m)O?Pk zSWl9Fwt$!6TXy~O`o_Q3D`cm=D~MN{|L2TUNUv>KYQvP|G`bq za%XK#TcmzQXHvfDwVhoT3nnW}Y-@ZJI?YQ%&*w&1LB-c6Y(M2JY`Cf$S4mtrs#SP- zcHe&$MW>^Oxg*>RIZdMW%x~13xLCp2C{iHYe!_3*pDa4*3BT-9B)aQgt@G$Rc66oS zpAD{IySB76shI|O7KJXd4sqnyxbte>_6d)KitP1-9A3EOoSmI=;4t^Kz)4R-PHz6& z%4AjCkZ?gs=|YdJ7@wSlaD&{Ei>s|77&y-xY1}Apx^HV~pFfh5PrHP59sj*okq+Y&y**qb+m3xD=58F!%;=SK*K`CL`TO)$wP`+|i z>A5+k)#qYNLuYtjH8t6~rPSn#V-CZc7_-~co}@69FfcHvmbgZgq$HN4S|t~yCYGc! z7#SED=^B{p8k&R{8dw>dTA3PX8yHv_7)*402r2{+8glbfGSez?Yj_|NDK=42or{6N M)78&qol`;+0G+!b#Q*>R literal 1680 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nujy)Fh$#;ztN<}R+L zhK8<2mPUrg&Q9iTCT?cNrWWRIFuk66#U+V($*C}VGeP!3^cv&UYvo*&npl!w6q28x z14{t`8Tlpo#Toep3eLf13L4>=c`5nj#hRe#0f)VnOKMSOS!#+~QGTuhIDD-#vDj~H z+a)FVdp`($rfu)I&xrLjNfup&pr4m$c3OQk>Z>M0R4@w+Ji3KJUK68J_F?#=dJ`49jQ}Ic=4R!W?Q<(j5!jCJx9r@nh_G)K6 zx-J=`ST>f67Hml@p4$EnXATyWqR@snwGblqS824_y-ZMSt_=qyy_6 zohQpF9aTQK;X~%KfWl?M>AM+~U#jo*f83idp!JpYN4-bFB8H3mf@10w)_(4aGF-D$ zztXndf}{3W)E~2z6}*a18Fq*qIMN_mCDGf)*TVPphW##{^<{l(x7vSZi?AHbF1C=3 zH#@eF@7IE2z6X0Y3s2mup=2$4LtNjfe4V^lH>dO)ffc7%icEy9>e!w1zMD-xX_a?p z59_q23EQJTrJ18nPYx0(%VkVli%NbapJ;U#|P>$dFD)w3__hOlTNB! zdRZdZmgsP~>iGFb26A*=Vw+`rA?W!JZ$w=pY_)lE?egIj{ng|i`>U3=E#G KelF{r5}E+iGju%w diff --git a/toxygen/smileys/default/D83DDC24.png b/toxygen/smileys/default/D83DDC24.png index 480fcf1fce22a85a3c6504e94f49557266d38662..37ff954d5117aff6c604583fc8ed5ebac874a127 100644 GIT binary patch delta 1768 zcmdnZGl6e{WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CwxoPl)U9RT@8+tAAUf`gx(!-OUVV zDj80eF&ryq__n_K*@{&1el^Wy?zy`ezyLMlFF-> z%FO3aGet+;+Wze4&u3=;wl9i$aGI&%rLy|jGfH5U8zML~RE!xI7#Neh-CZ^~r>r}| zz`(#>;_2(k{*0ZEOO;*K%(9PxfvK)4B%&lJv0R~`C_gPTCsm=OvLIEVBDa8n!J>C+ zXyB&X1{{B`YgVyH3V0kZRC-;%-uUmEkGr+iE9?5=e@mTrF;Wl{n{fI4{QZ~s|E;>Z zq+B*o`S+t&=K6_~V)s;3eD>Xa!Ypt5ZGpx;I|GB9x=YVqQ=WZjTa|ZMGOvt|)au-$ zGTJh$9!==ryQ}ircGbE-S@ylcXT(p|eBYhF@QSLEg4E}O7A1E@^*>9VY?nTfXH%cJ zUU-uC*0>@eHuI#eEhm;~$$m_U6!^n~Qn1-)t6Ee0l2k^>a5n8=oW?ztAgrro4`Gt;OHT(^nt) zH}?wP>~#h6_2mDoIkswRZN`HKc_j-2Z*0?LlfYStoKlQoXG@wewHBW&X*q#{I#U`V$4m-sx%d6&7$!man@$&oA8Rn5yZr ziRWUUy=CCxS$SpC_1-9+gY?KADTHyDH_Yk#<8cx#5l%9WiaK74&A?(6hwl^t&th{kvn1eOB_kZhrP= z&VJGOOWXY$^Ey{`^9LoCrde&O_r4^r|G4Yq>?@r`3=E9yo-U3d5|@(`9N2pt4jepr zH22`)qo)~Dj13G8KYomkimbnV4L#=O!j6oIHD4UqeTWPghe{d;N+j=?Q@=R;^pPc6ELRk48pTUSLWh!T_*kacgkP4q7x(QJsr{!PC{xWt~$(699wSMFs!> literal 1595 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>6Dy#wM1ICKhI9 zPKJiAMwaHL#%|_DCdSULmL|^TMlijedBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*uAi(^Q|ttG+U*}{Pmwx7L%Zh3lL zin#62l+e-L6t(sTL!iL-JQc2YF}unhF0*&uCD78Z{sSd$|0ts6iqgWTkG{*|ZOBB!7@MzL&u~3??YUeZO;v~t!Y718M%zARS#muyAs^^|qJvD>TiN_?#bV|n2i9JkiH`G_Nv6{?l z7Fl?)Y z`O?;-2U7agxeFpE-TwalvG*;ub0_j#bnJBGDw)n-kIJ08_Y;%LWX-KRHWxN8oUmZS zg0q>w${vOv=m%SlhdDeM$EE zCu`~Y<<)!vgY*VV%LN5O#ow3hxwU_P|Bsm`*1VBr6V3_U%@?w7R_fjjJ=&X=SeF~$ zI5+)6_TKI*Z-tC5|E_yGSK{=BnXz~ETaVk_zH0ly=*E?5zIicwB|`5U6Z5&Y^(U{# zU507%w$?Ca@8Ey3W5vPiYn>OUPA+_ty7%>iXO5fuVzta(ta!&{J55^l`4N+Pg96W# zdYOGWpX2m{7POrYIs0uwEnREC9tBBcYSsvCI|NfbMlooAEON~2q zeEE;(@e=(>_fq(q-^}+@bX;`x!RqP*iL&bdTlceU>u60AjpUrSUa&|eG->V$LG8nR e6|eglm>DiiwtM#C_K{9daqj8r=d#Wzp$P!~7goIh diff --git a/toxygen/smileys/default/D83DDC25.png b/toxygen/smileys/default/D83DDC25.png index 6e05fed6633d98a2859ce5b7d66236a53d19a2e0..f72eb69c0688f8710a74391cb1315bff9f8691f0 100644 GIT binary patch delta 1581 zcmcc1bB$+$WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817l}^Pl)UP|NpPfU|3biFfWy1aty=w zrD|~WX@MdHy_qHRW|qv;$zu0<_-?ha*WYaAI5&v_f_Bt0?5G9lf6y=VpikgV2lt&0 z?ki0!SDIMP)iA)(|5}Et93vlK)OJ{J+NV z|DoCcm%;zv#{IuvZ~XTt!|S!~g&P z1CPB|Wnf^?EeY}i#VrGa+S-q*8t>KDul@Xa$5yTSD+yn9{{6dGf4i!TZRH~7`YR8= zJ-A%&VBfI+R>J*13=9m6N#5=*3>~bp9t;c&>?NMQuI$g)`M6X$5A}(3GB7aJRfR;9 z1SOU$6cpvBW#*(RlvEa^DpceaFfds3P7U?FcH4mCPH~rQ-1xd|I1$69e>%aX!^^u-MVUJn_rzedSlhj(%UHp`Jc;n`=*`P zo$0a4`>LXAySV5Jv)4<`h95tuweMBfRfEF|IQR@tPwDNxv-jkyvjrF28g3p^z4tb6 zw)dMrg#*%CCF@S?%HMP4^5pH3C-MrN;?;LtS+-d0Q9)i+*OE_ahnW9~M6#@?Z>ms? zc3FB%YLoSnIls>cfBt9kXku2j*1CW~z2yh{KS;zmEZ?;1mZr(FJ3Sv}X}e7nedu#u zKA%tb-lb&rPfE7OP75C^e#7wL#LS1!+vfR{8nE;}->1uR zXj$`(4_WHpW^^k=um}BpSj?37-b66YVHF++pCj=D~+ICs{v<7D%Y` z`m`=wQU8A3qSN=}UNUl9iN9t&FZZ{nak2zso_m7V6Ah2fNm~@w)hT>8|HK&4ci>C? z35MQR>pc409$(?8yA{hecU?=9ch<5=PgWgy8^C&uYx}O}x@8wz?!@=92v`Y3Z=TFk zQE|}9Ym{>g)a zKf5#SuSwT#ec<=?+m6$fcKSlQ_GO;S`n~qYq`Dfel*Ko8{ZP1i!msV{@9iyhjH$=F zvNd9b7Bes~Jo0pL43W5;oWQ^sW>!{a#>Zyy;K`+{m(3qMYUBPOAtfn2d&Z|jtUu12 z(bYM-_QwGkH-!h=w{5K7x^?q&_KgRbnE3kq&h_avB`_)`3Wl4P^ZWbj=bMKMCMvSY z$J_sJII!Tsg9#T5{uji{vjuMKII-e|L~`=!h}}CX3L><;ImGCZ2_A zY-wpLzx255KC|Y{oI5s|X=!X?Vl)3V9a{7#>G-5eMv-D-3%s8^Ijya=x_;HF>?~0c zMZVBg!C`@+p~35en?9Jnl9n=+l-MFA{mOJf-OB6N5)&?5T*tnVQSg}dn;IVO7zPFg z)e_f;l9a@fRIB8o)Wnih1|tI_BV7Y?T|<))Ljx;gQ!7(rZ36=<1A_%Cf19Id$jwj5 eOsj-wkie$n;uq)ciHho63=E#GelF{r5}E+8Gvp5d literal 1629 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@rNP#x6#NCYH{Q z28M>NMowl=1I*tjt(3QNq`xP(kI$!6OG9+1cec?=Ex^W)89XF{+i@r>+ty?g2UHi4bmU|c0X^X2Z zS{f5LOX2GF_X>C2{k<3S|Bhxnmik3{%lprqkJN3Zr>XsWdfUrb%5B~9O@+;m1OmeN zEJQm31g7$~^>5t0^p7lO$=w$luIo6zdBw`bwqau9hwlgFI%aPSURvY+WaACCkQZ@$ ziLymnEI(f~ww}xoo)9qeKxfduKCVw~c0Z~w$s7~eVl&~~$LQe1B+I)M`xi6ab>RMM zd3jGN$8V;q5gmGmPCN^|vDed!k8!o3bXNA?<-$Mb7wNVe9@6&_mVf8S)<3WH{%-b} z6T?5qestdNCA%Cj5S42tYSj-+QB+HBu&?nLPES#8Vz{8+Pmv$B~u zOHbh>OOKrg+3r5$xLGqPq9?9(Z_{kARF%@>k}{lc34JO7~X?#WkY+vjS%-(r#VOyk9q4;y!6 z70%Cj`ETB*faMEB{&tB!U!BD;eLm|H@$lJC)xs^BT)To_sFyrFk-OPFY{OQ6Cbql| zkFK}pjvI+*qFIy0UBzR|XSi=aV|SP9*3{oc2eRJJ z%DB8qV#T+K%cH)$`(Rx8dH;VmH6upj8*V|>g4N#N`>wg>+}8asn7|-(UyikK>)BJF O0^ifs&t;ucLK6U@32Bi4 diff --git a/toxygen/smileys/default/D83DDC26.png b/toxygen/smileys/default/D83DDC26.png index e53f6433551abfc007b27a1592b9020512910cfe..033be848bc70933edb735b7131ba50df836b7667 100644 GIT binary patch delta 1684 zcmeC--NrjXvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{d%Pl)UP|Nj{n7&7bI;V8AH1%i?) z>ys+$MdjoZN~<8q(8fNxu(aMkF(o29zq7q+){O3;^o;J#npLaj_$4LxcGr5v#x^yT zx`kqb_yj(MB`l202uRVG8`pbJALu*YP%O~&O z>^y&K^U14=b{%e-Ik&7VyJpgyJI@|HeDx$WIw2@5?#!*btB+h> z>ekKmD=5L zbxKZ4eRytKVQuY<4LcX@Iy!&Hq1l`FOkT68WB%gAiagKQ@cMwH#Qs(5C#+o8vvg(K zyoGgBrIasHDxu`Bvj;wWX8Kj1(~@!{Qv)-*Uaz) z0|SFWNswPK1B1zer}L}-RumTG{QRNtz3bH#wKKn@9)I6)|HUeys!P8@Tn;nMJR=&o z{=w(>J5yLTC|m{Ut!GT~c6VXuV3qY?U|?V`@$_|Nf5y(orOJBRB4QB(15a<4`e^;C>w>nc{MXfe7E`-=bawmsef2N3Huc_qYv!Ub z|E@;n!n0v(x4D_N=6>H9(XYB;W2VkF(_)*40iv^AN?xdizQ0)7`R>LG?SnOwPL=Qf zE#?)g$h62%zQ=ib>_?;X9{2JTb{DqqE%V5|do8TnV27aQCAHbB)^sd?wZtLx3HQw< z%X+gbc6y^&4 zoUEIm8to?IFt6m<*DjZW_;Lmp5vdpa+Cs9KjE%qg0~J`daPuh4q3rg$Yl6aib&M*+!;|&)}4`N90fYdZy_vYKl{*l!&dXb((koM1I1dJ&mXT zD>6@clb6v~SRgf7zV78%n@Gvk|CCKPGpoc1hw z@Q^h-Xp`KGJwM{T0_?6VcwQ5}WqYc*b<{a4n_GObPo!5e_(%Az5kJ4I$#>qm8Y&`UQRy~ z62`bYuJqa)%i`x}<2MBO%D$>M=k?Wn>uR;D>Q}@cUWV1*ELjg%Oi5;7U{Eb_jVMV; zEJ?LWE=o--No6oHFf!6LFxNFS2{AOVGB&j`HPALNure^1==g9IiiX_$l+3hB+!`Lp cM2aymFi4iTMoiRD;9_9#boFyt=akR{0Jbb7m;e9( literal 1676 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy~bwduEyq$jxI(n z&W47rMwTwFrjAAi7UqVo7M2E<1~9#zdBr7(dC93Tdowdrte|==@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{KnJR5JY2DlV;kgR6wR&+|fERdcTCNW3(W;9+|qn&sqNP$(#`u4Z8TdF|@|Y2R%R zzncAa-622U&?>DxPYbMz1EB9e+mAR-6Kzq}xnz84;)JJ%H5+el*Q;I=_WJY7_x5jsgX04q z>pJMDC4@^(7P_$Cc@2x7fTE{iK@H0)HRmu@|5-=p6znJ$sa=tup<_DRr^5b^n4Z67 zhO^LF-_jNNYmUBI)Zq}}RVv#3@qv;g6gRd3_(j{C@B0ikWbAOh7`D%ag zOH-FVBWv(yt!az0x^%h%Kg3kHHbl3rI^h^4@VNTVzrvp1>KCt-vFNBTn4#J3=5u*| z#ATDc0!cNkGHK#LDSfG51??_IIccw6d$amp#**9W>h{wwMc(c?%G10#^QlN=$0WUoN6k9?L95FbzKh77tgHS#*7tC=z8$-#_Yte@AYz2>bMOU)-Ot&%K5@J3{*dOy85}S Ib4q9e0PbFRvH$=8 diff --git a/toxygen/smileys/default/D83DDC27.png b/toxygen/smileys/default/D83DDC27.png index 779766ce8f63158bbe73401e0946b13aadd0bd4e..f48cc2f1d9c4aa87e94cadbd03d444b2a6631ea0 100644 GIT binary patch delta 1684 zcmeC;-NHLTvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{6)C}+zV3~>O)yr+1Oaw*cPTTEX!ulkYr|IVPQrBj7&_J$k5Ef#nU@1 zCN4ZS!M8pz#LC_=Fgz-+ysD(OA-|$JIV&$DD%RfF)zd$)prWRwXF~Vnna$n(CDnC_ z894zVVVhTpnV4IpWaXVad+yh--yc4H+JE?1O>=ujVeylz47X1(IJmg4T(|!J|No~> zpUo;NjYvp3vybiGQ3f|}zlV<>&ziGn#;gVX(`MFJTVL5+&*19i^XvC-9YY%}UCT4) zFN8$JIJkKf#ck4E9RZvzDm5`|4-oUY^O>kE;U*E*ZH}2fOdh70mt2d6Fy$}SqS zlRtmO+SOZj^-Z15&dJHb!ZQ2cA~psF2J@02zhDLimFZ8O&Q}Zj`aQwr&p&_8hhKQo ztG3^L@#oL0FaCGW;_2(k{*0ZEOO<15r;0oS15<5PNJL3cV!1*=QGQxxPO3slWkIS!MQ#BD zgGKMu(CA5b3^@Kg7qM*O;dt`6!tGl8s!uZ?FLyNF9M^At^G4`4o{nc1Pkj0PT>qC~ z_+_`E=?~9#>#CJ)es${Tja56>Zu3sBzjyp?ne>?n>z666)C=7$@-{9c;dYK{?mkPi zu#djqSGAeTbR`IH=DE4^#NF9B_jsh&vd_?sd35{Tw%sj#OpF&-^maIJTUjwPT}3`m zVfRtK+P4paW`B$7P~P!SbJD5Z9OXxXQv?@qeQM1SoGO)l;@yOzS2Nk~ea<{$dv=@2 zorFjAF@EOumy%r=XP;V?wfxGAAjy}e;dflkmKfXU-;)j7lYWHniR-l;@e>W^)iNn4 ziM!V?Qo1)mOu*^CC09#B*h0aa?654?)`X8&ZaZn*C=fAQG^^G2n-bR=*|l}umW?ZNa$b8ZS6n8ojksK!3qajN* zALn)*Tb(giDwX$?litRx2MWPm7H&Ly?A(hQYdCq-MMC)Bf6zAIzUH-vt*U#WgUtsG z<9SUIpC>JN#W+K#-jYMp(X;!|Rr4obnSU~zaZmVVpVGj#TYJ`GLkrVM@@3QgmMbrO zFhMF)Wv5o5RmefffE`zFZoiPkQCZ6?~A(zkUIeP2$!CS#qVyIhWQP6#Th6*ZxEAKJOjE_vPx-E&HE4I$c*k zdoyQVm;TG!>mACtt{&xI=~iweJMX{HFXl=ca*HI0lc)l@@;R1~I8lbbnFQgY_h zxszwJ3r#qu-=U+^)uW@QsXLkRQ|i-G+74+hVOHUxr;QCXG_xpwvXbyE{F)9o6@ zE4FOgy1J~Std#xRmv3KJuQbl^nPK4>oc(Fa%FD~n&#?5CekCq@_h#|4bF=q5_{-Nn zvu1WyP8JR}R^VpXl5Lf2d0r-kfq_A_#5JNMC9x#cD!C{%u_Tqj$iT=**T7uY&?Lmr zz{=Rv%G5~Pz`)ADfbHIgW)uy%`6-!cmAEyOH+$GKFfd4hYzUsHCCA0U;OXk;vd$@? F2>`@C!4d!f literal 1674 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 diff --git a/toxygen/smileys/default/D83DDC2B.png b/toxygen/smileys/default/D83DDC2B.png index f09e1a33a50b047310b44075f90696a613bc0fcf..0f40d31579bdb43ff144c9dd5bd186044f1778e1 100644 GIT binary patch delta 1519 zcmX@hGm~e6WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817l2pPl)UCN{9U&_W%F?|9@lZ(P{B$ z=*ylm2)c3c^us659zA(>^YWScFMG%FGVYhH=xyb{l8xvtZ4T_&w?4 zzkdC`egE;w8rS_>R=jxqo`Hek%JsXiSNdPRa{c9-4+plb*|T}s&RzR@E0W3+y}HZd z_ikQsu*<0-#i_9{er89-xv4&>LH4;ZUZp8Pse$(ArunU_cb`_^5b9=;7Ur_9-fcy- z$K*WcXiw7%^OBBC4)02LJu<64_1@aN_B7XuB#&Sh%j#s$4}0tWo$UCqr}oUOgtt2@ zR@Zo3Tb#7I+T;KK{{lw@elsvI7?lM1fuf7SVCAEq5A~P)I<@uRzimq&JvBM?=kFQw zfB(LHJ+RN;cg7U9Z(qJVxW6qUon`))dCc>l>0M@EU|>x0c6VV)_%7Hgf$3d;aE0SzlfHs$R=ATwm)#?lsTZ=lhyNYp$(bmDuJeD3dfz zQCd85&&fAu3ocw^*xc#8=XTO;?-LpV4|;EL*r(iMZLg~Ol*m@yZ?W(=r{30=QQUwe`NfV6vJT zdR4D}q2W@4L)q&jT+>>NZR+<}ZmIC@mpw80i$uG6&+}c(2b9I#>zV}fIk-6#|G#04 zaac8hW7|BRQUjLW>37ta5)V7(RBRQlKIba1Mmy;5vtp(^`Anrf4LQ+*e$D=>ObmNx zBo@@BzDLzby_4p$zYm`3ZK!A8diav;yVFl5 z&yQ5juHA29upwiH!1};#6V=|XioE$-G2+@D-_6&2zO>AA{j~O&Rk)#4o{-8VgMdA! zf;f-1a^B|RTR-oP!hJ^OEfd2yW;3yV<&9mCwP2aKY&|oJiehs)qg0)QSJ3$bQ%(d< z5K_%_jHz{gCx4<|J;9Rs@_$8UlU2KwETzPkyL~t;7Wwj$5ZA0L9;zopJKqJc+Ab(q zU474~%lpLN9Ucq|g*Te!Ph#ZPk8t;#7OZ;yn>g1CVfG!b1in@%r9@a%Jn&(tn&7tn z)dN=E=aFozzx_V!R%cItlE3V4O#YX<76SvrSx*?d`5Er<_?u7A;CnN={z%SY#m+ zkJm@e&Q8vcr+5q)MXxS-@s*6C!08ib&IGv&T{v;>gI_Vckl{c(9_V-x#Y-U@bcBm*U}PAGZ`+=R;g`&^u3>ffkCyzHKHUc zC9x#cD!C{%u_Tqj$iT=**T7uY&?Lmrz{=Rv%G6lfz`)ADV8P1Y<}e*#4Y~O#nQ4`{ YHC+7S+&xiIor{6N)78&qol`;+0H5l$xBvhE literal 1613 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 IBWY= z+x&yB!bSE{oC+meIcNQh9z0}yu5q$x)svh5gqVJbFmJJnu$@;RB-Lk8pvGYAksS85 z*nok7;h3k3V~E7%Ci-`tXFTPW`$PDMg{CMYwlY&uP%LhqhiQ`6E|88URiVd4%4#3mrmU}w(a<> zK1RMb)v_{k=LpuYiHEa=Jb1>J%*decqHXtfM;;#o1A}UbYeY#(Vo9o1a#3nxNh*Vp zfsv7}fw``sNr<6=m9eRniKVuIft7*5iQ*;CQ8eV{r(~v8;?}TJPbQjyfk6^vLvVgt qNqJ&XDuZK6ep0G}XKrG8YEWuoN@d~6R2v2c1_n=8KbLh*2~7YYb-Qr@ literal 1570 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDz2*jP&W?_5Cgv`# zriO;DMixdEmX@xj2F_*{W+q0aW-z^;dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{2XZ*Z^CYFH)OrR6d8%4Y=vdTYDCOw5|JP#sR)^a%+S;61 zrX{(>5!=?@-F>@wn~`=pyWG6u_m%gb*PbsvC)k#luuf6Wn%nd2k-#MOg@Uy%{TK7z zhyU|@v1jMhE%R@d5%RhK3YQZX| z-lInYra19rS_#Z#U}J0WKbYT>ZFJ5e?$n;9mzS!Z31z*GO1RxDEG@;bx$EJQzJ>an z3RBs1xEpe#Z%n>==Ek%5OkwjVHjMWl#1^q`oVQ`q zq9)Th%YqJgFsz8{dn3lJG3kdFqwVdzNhPrnrmufm`+ZIN@-}fs*Nf93(tqyp>8uQ^ z)X{N|H_83tFZ^F_*0t6P$3+%BF1qLcMN#ZzsYs3ff7^4~3Vkx~4XW1dYhHF+u)F<4 zT~gw;6A#4?+=!TR|J)zh7q{FBKg{U)#o5|fNJM%T_Gj~c!RPW>;=YJ?3V0iZ8nVqfdRW(ql?dj_0vd$@?2>|zt BOUeKM diff --git a/toxygen/smileys/default/D83DDC2D.png b/toxygen/smileys/default/D83DDC2D.png index ff2e49a3af23f7878d16c57e06bed2d57b0007e3..d8b4f9085c6b486aaa6ff369efbfe6889f5b44a4 100644 GIT binary patch delta 1651 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 bH!w0h-Wh6hMM%m8RL6L_`njxgN@xNA*~^mQ diff --git a/toxygen/smileys/default/D83DDC2E.png b/toxygen/smileys/default/D83DDC2E.png index f95c3b91fe155a52c29193dffc896aa591636b2b..cd0554130613ed034b4558fa1fb1deea7f3c3b4c 100644 GIT binary patch delta 1558 zcmX@fv!7>zWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817lu*Pl)UP|No=?-J|{7&+J-<6{SW6 zXT*j@`@7c^q@CQp`sDW2^%>D1q1340gb=@$vYf`^tb`E1C||e45dXDvCa#{VrbPR><;MBs#`$bsIBnB{sStKkyrr{vdu#Tl zy5voD$!p6Z*Oo;t%kf{9*)XyIM`Q_lAdX>9(lsG?qu-o$L$0iGZ<(3i#1_s6? zZ+91l4pvzY1_lQ95>H=O_Gj#TT&g^qo;+YD9TUE%t=)!sVqoU zsK_l~V6f<&8X7rijsef#@0vv{i?7=8@&3>c{dwl&XO&%&ZE2G@Lms+rx_3D#uz9beRaAV=n!M50Cw7I@%vsUfd&3vy+Q{`gw*1W#vVOH@Q_<9`s|`wKe^ol0 ze%vv2erm8`kHLu^XBV4_#izQ>=k1l`yCs!i7TCL0cbjGV8WyG-9lZ|@e%7I;hjiaVonmpfUao;;-d`EH~e=eWytw|f1D<8?dKiKjiww%GB zsO*Wl(j(qHM#U%dBO924T3sf5?Fn&jS@3bv?GS+t4>@u+++wai=gJZhw&YiDF;kv= zrdEW)jcteKviX_H)H_rd9(wTW)UA^iyECrbR8@F-?$`BWi-ly*Ii|N*KL66*!|i4G z);;E(%XksE~&(-Xjj{_oSw8jQ|zpZzeJ~H zxYXx4M{gHQR(RMZepLHNR6vZx2HlQ_Q%~}Kk~KI}DRIewrMRi(h!<16sU%DLG27=8 zicZX0;S(V#F{i;>$=$JN$`aQHUwC)cE6i{B$rJOBUvfd<-L5AdF*3Z*+jaL$^Y&hm z5u`lTBHeUXvr(19(q+~6oVvU{{_gN#&|H2X>z)d;oY)L^&uhV|=f6!CnBs5vpk(cF z@w`Ka1ldoqEMWKivWoBe#e#XM0reZ-v+k)r_Mqp_*)8*5udUhsK=13fH&0FCdY9ZO zpSAkdJ<Eak7aXC4`fxX9v zhv$sWnw&Q>b7FXSe0q59*zBqKb53vFI=wumraexFc$D7BEt(g1&(7{%+`c5acaM04 z>L=9&{bRIq5D?IKtFC$QfP%8N_!DNfgP*vJczC+J6i+QoOtiC0Onj&%xGIb1)x=j) zffp|tZoGK0a8}gBTMpf&C69#zk`v-xtwINQ(=I2#JXaibzQ0{Ma|Ov_3PkvNE!@SM*Htwz6-0eBb)~cy2H- zd{}6(RijW*fPsNQwZt`|BqgyV)hf9tHL)a>!N|bKNY}tz*U%)y(7?*r)XLOE+rYre oz#x_H=uQ+3x%nxXX_dG&hy>R%vQAV~=VD;+boFyt=akR{0Ek4$kpKVy literal 1609 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy%y#!uBJv#Cgv`# zCWeNtMoz94maaxF=B6eVu8uBdE-<~GdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{G$p1h2_lqs5aIB^CiYrNejTU*KQN9C_SUdCxMdHdhCzBi}tavt1g$hQ6TK3>!Ez}?ebe%W4G{1ympsA5sHs%zewJ}n>Zbd*E?9Zd$#D=UF|=#w4}xJyW>- zYFZDhS@QdzxP!0ixms?Os^`z1-I?D1wrOR8088KR~+}xo{7|=gyEN6JiVIeBbN6R-$3u>-JYtFWWL4W{ADi rXqtAn@cGx}|LWOta`%7EXJlj8w9>Ne?A(9`P~qgTe~DWM4fdURi| diff --git a/toxygen/smileys/default/D83DDC2F.png b/toxygen/smileys/default/D83DDC2F.png index 35983295524d58a817aa4f90361f11f284e80539..086a5b6440a177f3ad17f9b9311d38689d55d8cc 100644 GIT binary patch delta 1713 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 h2ak8J{`jB6fI(T;Mp&2Y$3alV@w8$%_C0 diff --git a/toxygen/smileys/default/D83DDC30.png b/toxygen/smileys/default/D83DDC30.png index 3249366c3ad7592fca404fa2cd7daa4d6adf280f..e926a23df3760d58449155793e5a74ca377f5f88 100644 GIT binary patch delta 1593 zcmX@f^N44HWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817lu*Pl)UP|Nqxao3(QC^rih%7Iyc| zY3rQc*fO!YuDh(VwV=2@E3YawqbxCbepfF9&FSc_pVQF|N3&WxX0>)qZ*GI2$qh}D z8=5B7)-RhldFABk{Z+N|I=bg|bkAw)gcuDm9c}@{4u~}no5~WC;ppa>b4R!AID6pG z@y*+gZ{B)h$L@VI=IyUvzItQt&H%P znZNkZ=B;P_2P-E`JF#u2udnZ}b?YD9xOsB> zo*i=*9oo1>Q&V%tnze7AK0mT?%g(ur*Y-}?wP^X7-TUudxb*4yt3O}A-@SP0^qvFj z`|796u4*{5X7jznCtqE;`SIT4M{|~by8rm))tmQ@oIba9^PH-Np1hK|wasg~CmmU| z>Qr#tyxQj8ypk&?kN*GvU)+BBZ3YGglae4mPy{l-LGn}o13xv?_dj3!_2r8X-!J_7 z-5eb*a$We^>^bk>e_Xmm^f;q)>BJL)o9m~mGcYhPCV9KNFm$lWdN43Bu$OrHy0SlG z=i^ebN^y0$$H2hUP!$qU5|mi3P*9YgmYI{PP*Pcts!)+zz`$V9J2ljE(rp8tJz znOdh-_2qPJ4|zJ{`Etiwa=mLj)zYT!WN=~T63;Po!@uASJYhW(qR6|_V0vTuDDl+C-WOy?0(8yd-p+7=xotPCF_n` z2tAH%k^ZB(i6emZfWSElK32fM5T|$F>WO{{CRmu;;|A$4@yQ@AqY>5D_}zZzLp|$f)>q{@w;| z4^g3m`}mfH8L;#&za!3+XzP$uW!h=`Oo{VaTgd*OHVohUO$5ypZc86p`C(-W!vb+* zo^9#Z_>QSw*>tYnujRtjuj_l$RoR-F{rrm+mF3D5r##v)e@_hSxAzOwbYdUc`P%)s zX|j3SId^8}a$jMgX{oa_FNKzdeA~lv+A8kyGBcsQ3275IZjR{>?dzJ((iy@dvVN1q z!W{+=BMv^iIhp&3c7nv=#ilpn7i?IxFI;r)jKk8YZz6-Zsv{mhtEt!c5bSt}aRytF z*pd{_DxviJ6R+7nF-G(q_)>p@;p{6j75C2%BiQc$)$8@sUAVyW>q3>}&`s7e7O;z~ zGy6O($i^uyo}W{pMB&;o@1h5Z&85>)EJHp$t6$h)S2*FM->l7#Go~>(`N$cqHhwFa zb@V2a)~YmT-*XGRS1`%S$V}W{Kl5Ai-izhj??UqXG5LM6&o;wfiy#21W)?7sn8Z%ep5r!x;-1T0j0yKW638x+z^# z_uIlZ-dvJP7fNZlR^JM=t218yuDkl&dq#%2_Y~}Ss$~C}9hZ9E$f~^ihrCbDA)fjf z!fQN*6pu{06Q`6?sh%mQ$eOK{t9)ddU*uAS$tT~;TyywkNs4iq;Dpmp*Tw7$&=LD& zIh`Xw@j-7As=dM_Ib5DImO_f}4 zT-^P8_S;M@usP3!5=vv!{y-Z|aU{Eb_ zjVMV;EJ?LWE=o--No6oHFf!6LFxNFS2{AOVGB&j`G1E3Mure^Pv%bQIqCq7$KP5A* a61NWjw46;771g;I7(8A5T-G@yGywpb*z}wL literal 1609 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy~dWV7LG2KCYH{Q zhK7c&Mo!L7hAw92&TejIW`@Qt1~9#zdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*VJJ&di}xA z3)WqSZ5~Ngaj(;>=$?@~f6gq+ExgB{a~E%)ysodNXG>rCq1i7Z52WSv9Q$Zn&3s~g z)tAUiRyHrs{)oR<{ot*`rBYdz<7#zBzD|%AlVN)j*lT|Ivg7Qnx~o$iihY&!Dob|m zoW}fV#j@qh$0Euf8Em_q;S|fM?zO1v~_W!L=5+$cGK%Cubut*vc0drY`z($9cxq06h1%ww!rQnOZn*MvUEWOHj+Vn4=EPdIUEW+5)w-{0 zE1S~-e!2T|*;5@_BWgIBf@V|)voC*m_HufF-js!t{&M$bP2Ke@pQAtF;gPE7W1qDg z1*iN|==itxfeG&kyUL@CKN>uOe>)3scLr#QX>Rt2+~acZV5>cA?vh#mDipL%{|G;6 z&?3unZ|`$io#gZ=zvig5T%vdOUb$kwux8_Dz8|t8D=zT$MYc=_P~qgTe~DWM4fk7rnh diff --git a/toxygen/smileys/default/D83DDC31.png b/toxygen/smileys/default/D83DDC31.png index 5a410e3262b518d53bc9d4f6f0d94dd84a35d1c6..c250bafe57951fe33ba1abd79ed6f767203297db 100644 GIT binary patch delta 1486 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 diff --git a/toxygen/smileys/default/D83DDC33.png b/toxygen/smileys/default/D83DDC33.png index 6f025da42757d9c8b77f37721e8431740d636eb5..aacba602c88715e5f3ee1d86b6f42ab76575e543 100644 GIT binary patch delta 1684 zcmcc2vy*p%WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LL^>pAgso|Nk@0ImWQ&GQ)=J4D(Mg zth&T-`agDb;eY1c{|x8;*CV7af|brW1~LKep(+u;^GR!>2u=75{;pYr%uQ5zL3Q{)b7{j8I1}o1p)UUe2 zu>8z~oyQrr-Y(jAReQ?~hHZBl_B~-(d~VLJ@`0@(RPU^9(y5Fw8#5Fz*b*vP%q$&V=9kA94FX!}hAlVruYHes^snUE|N58z8(-G{uX^!6)915X%EzG1lVk>SvD zv1{Lr@BH$3@Hgbqzwk%@eINdFxcgh~#t(+m?-}+#Ww`VuEG*3M_Akv_KLxJ;V7UC1 zVcm6xZTA??zSq#uV7T&);p#VrD_O`2ZpmB7|y(B z*nWp$x0c6VXuV3qY?U|?V`@$_|Nf5y(orDDC+sPDDg1{`;ayW||%SS}ULIdSXjUcKr+d=~cW9&d|ZY~#&#^g<0^&t6JmiYH@3au(_B8UGU?v@ zdN-R(IlhNQ)y_RN`7u#Wnd?SKSg7^VV^dCftlya=5;^hQmp>n$ZFr=sT&AFZDS3}l zqRnx>hK?ASubggs6m=C^{(aXFVDf5p+VC}HRkLD*&DU)L9JBe7lvmBYk*wR4Agl4! z{S4=hy{BC&7;laqg8b*A;d-nekYR{r1o`Gt#n(T151V+ z6n4^H_W!|Ct&R2STMu6neRuh3^Zd}oS+({i1qIg&6yp}>F7~+eEi3=*HE)|-^EqsKF@BG9Vg?L8y<-t1y0v3IIueB>-tIZ-# zmx@aj#xHaoS?j-vO4%_5E4e#+ZCS!v_?3C*{)zktiddHYcV^2dDR(V2;q&W#vixr9 zg_m9$JC>wLd9L%i!}p=n`PABXrQEuFir=q0GI7XutgW2HQ&DlyDr%FARpr+G0S@YX zDwl81bl_fa=AiSE{nKTSx^9vUQ*e3ge|GRhJt%Bus)pwrS z%opd3y>F7f`EOW_r@bBProa_?b{?jyCcpgfPj_Mbp~$XDYGP~lF)%RP@^ob zQ3;8qqKlK09zPPEGDR{pr2gs^NfQ&{$&-SEE?+Xa@0&*6LNgk~4j>d?e3gy?*stT59&GS7b=J<1ToZl!mi4UJ)>gZB zRafQaNJ_qWBP%oa*RS2LZmC=Nmn1UoI(#+v(i6?#Wqz+$6n;-&v_5nB^zGx<>(AH! z>S5qjS-zQ#jkUdnNs@tUj!D}R@69I|7#LJbTq8qOiZ;646F)mp44$rjF6*2U FngC0nATIy_ literal 1619 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy+#H`Cg#S@CYH{Q z&W47rMiv%k=8mprCT32i#^$CLMlijedBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{bmM$Y~EtsfJRMO~r~2lh~bK2nB6_bAj*Y zCJ{4@Ioa=ys_r`bw({+Fn_Vj|{yE(K|M2JP|9}74FMDnE&rYYMxWrc=`bg*_g^Nx# z7yTa!Yw@iv+3@-Zr?wjVogEWG58its(9~tod2iC>@c$apeE-;wX20vbaD2|cCA0b% z5C70vHE(jPmB^svUQZ$w_km=$mO5V~di36He81s8=4WahM=HY|RV zF`;yai%B8d@>KPdPNNfA2O_4h3B*b+h)+J@saYa>ll>x>%C&ALmeT^?en&Pf@t)M= z>ghZ~XaCd&)?L%RxuRxEF$V6*6T5J&M*89V$P-ej-&P+`Ts=3ybcu-Q-Nn~^=ALNg z{Hzxw$r*Z_?c{^cB?@kdBAHxo8zYZg6I!u$=lO*%q_%0!(MhhWyJupu=u$(kJ5vUm z$l2)U3hezGH;OtfyigtUWa9cok68~td8B;mf!-h9hTEJ4Sq#Zf!?&G&S6(H1V+mvC zre9Z-<97*PUT!(((cXhnD}UE2zULQTT6M$K{EZo7*y71zGp@ObNn0vxUU~OR(LH=eLbi#J601!R`ZI^^yVq2 diff --git a/toxygen/smileys/default/D83DDC34.png b/toxygen/smileys/default/D83DDC34.png index 0be777d7b83280914d309b561e30a8a8f4620b12..a0b1b67b05af81d444d62542876b793db7206983 100644 GIT binary patch delta 1575 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 Oz~JfX=d#Wzp$PyTG6;bH literal 1696 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>4bECXR*%CYH{Q zMuvv2MvjK&rcSP|1|}v(hEC3|#xT8}dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*e0)uk0&t2CX?-sWJ_ zRhfP0U&GWUw#!dmc)OTPd-!DQ(~Zv_%$v&K^tWl<#@VI02bV-lQBaMZ;H7>-$06`k zKv$Q&!cWr+Bp6DK+^r+vY-F{pyGSpesvb6 z0$<^6tP{SAt~b7xpPBbF-K=|4Ow&Rm9@|RhnUO1|FS6T|_3+?_?1)uulVaTLrnj!N zOg%4caYNHcy3A$$irBQ)hqsdi-{$(xs4;h)p6aaToR`SBy!u;WVjbt|o9|CtN?Wz| zzVXpd+V9TYoO@4mcE|dc878NcIAw0^=l;mXDI(lN}W6N!sN+M z(Tx4tEnQn<7>&;_Pn2HfJZs;oYu~ThrRpDxxn8+u$E}^)ug5J23ik diff --git a/toxygen/smileys/default/D83DDC36.png b/toxygen/smileys/default/D83DDC36.png index 50ff6c0aa4aedce34cc999fa6f38d0a2d2a292c6..e4bb3d7c27ae6621e66ce40f9e6b9a90b885a5ac 100644 GIT binary patch delta 1712 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 diff --git a/toxygen/smileys/default/D83DDC37.png b/toxygen/smileys/default/D83DDC37.png index 78afd2c556ec2b15ef36a7e5f69e234f7c53aa43..4d6fad69116902e8dece9d32896d356baf926593 100644 GIT binary patch delta 1595 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 QSb!=DPgg&ebxsLQ02J$YkpKVy diff --git a/toxygen/smileys/default/D83DDC38.png b/toxygen/smileys/default/D83DDC38.png index 2141d1b62bb057a21c17694d907d51c66062472c..d6469483e7916b0d02009c922f54c271ece5739d 100644 GIT binary patch delta 1599 zcmbQn^MYrBWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LM>HpAc7uVhx5OO@`fp429|p1!@d= zY7B`A3<>fK@p2&XB25&sP@SPry&kF*qB;+(Cr6bbN0lK<1%xt{88Vd_(!nTAi6Kpi zAw>~{k`x#qC{Y1q2gI5Ld5~ogveTI1j1R+QUxtaM3|D*^j(RR!xRBvaAj4!!hTDM* z$2=L1c!h<9G1OlTW!N9Ua6O#im={Bx0mB+MU0q#8~6&BGt5q4Sfs+VkdTdC4Crf?(%(_Pp8Grp?T#}w~^~&$>H?C!7F|J<4T=7{bXVc^Ft}F8j7#J8Blf2zs z+IsF;$1*T5u$OrHy0SlG=i^f4J^J{<1O^7C>Z*{4lAy$Lg@U5|w9K4Tg_6pGRE3J% z0tN<)-l?Inmu?$y)I1llZQ|ir^4MbH*K0HFzWs+Ko~6h6p14-^OjqyuzmfTX zvbcL)lVCmvH%H;W-Qop|E1i^XR2f}8C}1-0`8ENL*?dWdSI@nXtk;wvTkr9;{XFN6 zy_a2XG~`HgEI+thh_NB=jDf|xNNWl4>r$mZC|$*O3=NxoT& zA7|80X^8qH5PA1hGE&o0PVCFLN6{qdC>9a>K6G|z5AjHmRD7ODVlfQ&FT8w zXKS{VAD#Z?aj=5)(ThjfSGtuM$=uto`i1+_nyy(EbEW(l7#Lo8x;TbNTux3<;Eg9F%l(v@k>Q&iU-XT*ZZ(O-z`s$Um)a+T& zQKna}-jKL)^QP(6Eu|%;rCYX|G|I5?nJ_S@zA;KVk*oQgfq_A_#5JNMC9x#cD!C{% zu_Tqj$iT=**T7uY&?Lmrz{=Rv%G5;Lz`)ADAeHavPN)Vgh>qO+l+3hB+&V;pYgs2M Rs&g?gc)I$ztaD0e0sy-sak~Hj literal 1558 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy)FjMj;4l=CYH{Q zhK7c&Mox}qmTnf7mWF1Q#x8Ca&M>{6dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*o6W>?O}u7*BhA=U(yo-aF?Tr&pi*Ui<#{-#f)JRkt-#TQv$>Kghhe(YP=5knTaz zX{~jNG7IcC>~!>rJm7e4(bP{}=AL0+FYe=9Bvku8PJQy)ueB>(_}_X)e0{WM_RGVu zanrwUd+2q5n`KWPpP7+C)g}8f*=ol1cjf+Hn3(e5abdRo7oN6hyVf?SGX@`69k^5_ z(@RQDZd=puLmPIVe8)ZU|2J(e-Af_`Hk>mQ4LoA^$ZS9G2=& zel;mKb;{4Fj&rSK_3~`}mA=g=VfJV~rX{^&nVIc=(^L_yQ)_F}IE%6;%xr%1iq*LJ z^pV9I^bY0!y_z5SS6Y3Bx59(^;vdZVR%X`<#6C}VdX;Xyv7@K&Y)V*!lYN+iVs$Um zbC_CCkNT zuIZlnYS|e9c>yh(Q!;%Qey22@I3ba}%xl4phr4Fod8+l{{5_8ER;B9uPR;%-XEn!r zW?Os4C%+B#jjMm^Sl?gqh=13T%V7sH#XdSr+{m{>es!B*sO4*$>9408zA56^lzoUd ziYqi{_TvmI|L9(sQ@bP0l+XkKj8jLg diff --git a/toxygen/smileys/default/D83DDC39.png b/toxygen/smileys/default/D83DDC39.png index 775d8571c4f2016e7f2011b0f2b47770bccb3258..cd2027c4e01d8cc273572af6ee43b362c98ac369 100644 GIT binary patch delta 1812 zcmZ3@w}o$lWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081CvsKPl)T%_DTQ$|KGTAq7PyHXhHcIi6W_Ho5Fa>kW0HUTT zyPzR6uRbFeB6Fj*_ep(m>xqdAo)ov;ZJYX`ang%|=KFEQM<*>f-ZkrfLdna*=8sL2 ze=J!0ZR(OYb^Q;M3h!rzzV3*AkP-Yax#(?O|M%%je=Jz{XUo1{D>i@VntZ>|>q~$5 z|24(`w>5t1kGx;t^`U#}ua#Tg%wO|o{jR%JiLbjO{x47Yzoqv7u8ueDzIUtZlm4vV z`E0_(b4?kqP8|HdvEl#5ivK%W|1U}WKg;h*q3iYaD;{0A^5oLBH|LK3IJE5l^0fc+ z!v4?neb(Z4asBdV*Ka+(cxB?miH|Q|`?+n>|Ji~6m!{pBT5@XTl2>;hytsAu@x`mY zzP^vnUHm^Q@c+!f|7(i=Z>+i2lXdUp>1Wq&KDlzEUQ<)^@r5h@7bO4hbNfF%;`j2h zw+n0T96$Bw{N;z|EuAesR=8+S3Pn~;s=E9>3 zmshq_Jil>g>E!7NFR!oO!spHlEtvKC z)$fD*bl<#6-1Gd~v%Si8wyg4PFP^sAEA{NSlEuKlz?kIi?!x$=N%tND0|R@Br>`sf zGj={MRd!V~%RUAMrrN5Ih?1bha)pAT{ItxRRE3htf>ecy+yVv$i{7cB_0f~=DDeEb zu35ygMqTE(@r?Z;PmPcJbDuv`WqU6rc@>YFlkQd(pZ)d!7Rmn%S*ubm8Myezqetf3 z1N6@2$=$1&d~Q$g?YCw#Hr|gZKJMhRTjXtE$b#s-6R$-uv->7pJ$uFQ@B$7#!_yOb zyYDPMC3Us%LUq&6L#q4#zKixar6KsBzW0}heac-{{jV=iwoAUb`>AE0U%|#zcex6! z%#-puKdK#){U;L1vZASCi=0EfMcIWSq=;e>NramZ=`oq!xkhP>I z^s4_tL*K-Pq#%%ItokvY!)wI zTv_j=bmMl)sz${Oo2UB(IHVbqF0Y<@BUx`!!ds88v(F0`{Pqxj$edE`ZXs$nm51R= zQrp3QXKo$b(Gqp-W~aceP5-ujTyw;s;DThPUdpko)DRCzBbGFVm)`7*v$B8uXmmbyy!W>_FW#~;_reur1K%e) z9w#S#Q8{;S!fp0Xj1heYzSN&!IQz;>#eH+3PP2U7bid`=3m;693if=q>dD)LChG+S zTW#|-U4@IPO@$olUpTBe>sR#PA@ggUlSM0@T>bCFSo=s}@?4Yjj9ID;29w**yqZ_W z(ly0A<6|@1VV+ng(Kj+O6Zg;jmb~|3IrqDe{Qli@j(=8K_pUSy=1Y*cliY7&zEi zS(q6ZIJvB>ZMblamrtQqy8Z8N|fXGcvQXb8@nC^YYWh z#TXbRBnt|Qic7Lf%gXC3B&8S`q-83rs%vU$>uPH18yaQh804FpTUy)N+B@3XI=i}i z6c`kH`}zwfOqe)n!i33FrcP60P@X~Q5v**m6H-Ev5>BulJ#Y4es1I=XtAObq%4hV@2F#wMm_ z=Eh7$h8C8oFG4?-{w_Sqz`&qd;u=wsl30>zm0XmXSdz+MWME{ZYhbQxXcA&*U}bD- zWooExU|?lnU~cwG4n;$5eoAIqC2kFF*+GjK7#Ji$HcZr%=VD;+boFyt=akR{0DgaH Af&c&j literal 1835 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy%tVJ=1zvrCYH{Q zj)sP=MvkT~E^ZdCZU#n{PKK_|E-<~GdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*efjQdK#WAGfR>&mZ9O+Pj(wx4csc3Xcq(Lfn&mYWFq#InVJKMD`d6p4lQWZ>IBZdxeSr_x@j} z_SX9&2H)5Z?Kv(61cU_+fD$BiAO#AWn zYH3cIm}12v0VcZ-dEzp<>#ux?=h|sg5qT^vQ7o%n{9OloKn%m=a)sN%g@=X3G}74i zs2sXD!R(-S@3EO@&R?!Fo9q-gF|l8UJtpwL6~GJy)A8SrdpU)v{`Kri%i@eQwXTz?5)BFngsw-P<7Kk#$9*(!q56K}s%tHre8IuNXK(H``6RMr>+BM>}-*`yXR-QnD=+FqK1N|*>mQ& ztc|JMJm-E@@2U5yHMe*6PAkY}sIIO~e8DzNEck>d_lK5SCVh{&_8eG#r|y5KYiaz# zjY@vUY9Hy`+%NbmDr@EL9TV$c|NGROJS}zRl#)4rraxk5H{Q~HM407km+gNJ0|rI@ V;xn(K0(n8zm#3?r%Q~loCIC+S&7J@N diff --git a/toxygen/smileys/default/D83DDC3A.png b/toxygen/smileys/default/D83DDC3A.png index a2bbc5b63d46b92834f8d23a6008e509ba81a348..7dcd9f7e4b7962d97e7720f207d9f1028738bc74 100644 GIT binary patch delta 1635 zcmdnR+rv9SvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{pf0gf&&2*2M2 z<=)uVais;szPY{g(V6v+&aB^h_)`7r>w8~c-?Qbwc?ep!>%@x92jFPQhP_KR>|MBa z=fbr+uiSYIM;C6~pSNo3l{=45U%m|y+lol=P~}@r1^iN*8jN--?|t+Y$@Ej z_xQV+CjX{0{9mK^e~tXVX$*TeF8V!*;s0`}|I5XnEe`s>MC|_(k!MRn_HURI9v;4T z+4><>OXY_lRjF^E0fwAVWePJGr?zn{l7)>AG2?HyffNcx$)?c zys+4v649pH^Jeor%e|Mk{UlGNIj?8wN~4u`ew122Z(_}k#y4WtU$!=T=UQbc z_B~j(+(Rp0$wt6`qHBZJ3Gq#?Az5n_cRQb2nLcmE_vM29CefD*T04s^dFz%O?_e^U z8hX`lrQuSeOWErrT*H zr&-9HUnM)IJGXcx{UIB8|drg%x}UrkuDq z;bHbhg?0NBzwX~)hTJb9r0 zz{!Jo$JoRk7;WG1eAGolZ1kJdU`GyJr$iAdev0) zsnMmL9tTD@x0|ZbuHCwJZLF{88m1GW z*Dt*3=y-AAQdl@E=ZhDw42@n%OGp_SNzI;N#Kh40##Fg+*4Iu31_sp<*NBpo#FA92 z{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@qZUZcgTICgv`# z=7xr@MwUhKv!CW;@)mfANZ}~OZaY;g=fGdk@V!aU4Wu-%lgx>f^1*rYy+ST}hW^PV4U+c-0Lql3k={71h(&Uc)Z z#r2(|>XMFu-=29-wzO+)*SO=UR~)a;Y29Jvzvya*;j}|vEj0Z!jJ_8Z>s6}m{r+d8 z!{q&mh3+%qhp)rc(5#2^ISH~TXlwJhU1wl-FyG*2&_JLcjlah zhu16?&Fl`JIAPwxHWxv^Uw49b>^rzB%Z2N*#aZiXwMySA(hN?ee5zRcBUw5~=50ec zAIA<2VJ>Bl;`Pici{Blo?N$@4egq1YiBL z=PW+L+8ZuyY<+R2T-+n!gzUDUXEO8lNj+RHd%yDj-?e{kU2wg(HzoD@q5iF175ny2 zSg_oZrSIvVHnmI3IOU#+vNku`rrp*#vUIzE%wC?GSCT*YNiT8LEpakr(LB4JL*{g= z+bqv&_A`g~+?o8GEpGbSRy9w~&W*Br777Lm?Gja;y5dT!cZ5`X{l(@9^Rx7Jv(5h} z@}^I9>r*dpRa6Wp2OlXgJvG!W<^MMgZmq{3*S?q~<@5GMoueA@K4S$!2E6I;_bYWK59FtomNk2;gG{1m7*@pScbS?83{ F1OQB}ks1I1 diff --git a/toxygen/smileys/default/D83DDC3B.png b/toxygen/smileys/default/D83DDC3B.png index 6a91df894e17acec732cbd86d9a931a6566c0556..f9d2a6b8eef19fe50e5fb5ecc658b9c95b168cb6 100644 GIT binary patch delta 1601 zcmbQr`-*3RWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LKSUpAgso|NqZSGOADZgP@9dul#Vg zyl}VtaJPzhuYyRI%s~4DZ_7w$<49-Yco)5R7rpv82hBJK%}5*NP&1hzBe5VOF@IeF ze_a7@O>Q{yP-BB5H$`S#s58xTPJ!3*QqP&WUe)pL)3aUMBlY9$lyZaZXB7u;Yl%6~ z6?3pVW=l)-jBMAI5Um(%x$OEtyH(Y}+Z%$`S9q>2aa~;#Tot6B;cK>}%=gCL6~DiK zy|8WJ>=ct($;KDAF1)#K<&sk0>LC3TFXPTcht*|ne}DaCU|{(3``64Qqd&iXwkJBI zc^S{jbX<^Wbz#eb-#@<1%ygKYZapX6dP=;>j6_3M9m!;WduJWVN%1Cap*mCI>h-Jq zG%S>bCdcWwhUk>ID|UrxPK`InwwFw?7VQkxEOJ*&uoSQLQTEs5C~%SK3D?T96Hm4j z=?T}V@li^#7OVA93OC?&lxHy$XG*pdjWH1{b(d=lP-zd=Xb)B|ag&QS5&ZxE|CJBo z&lngOv`T{ff*BaXuKWyn^y7Zmm7n!jLc{&R)Fwz^k7> z_E(C|SjC~jz`($m?$j;aY)N0dHI%DH+c?RIMNz2A>NzBn`C{4&Lr zdZFf;yD9@4b2rc2^7D~J#8L6PtEIv$L=GyZz1&nh@#<`ydpzv9+y=9E9NE2g+wPV= zCdP{^zIHfITWE2{c+z(16LN)4{lYO>m)D9t;#gN--L>SC-XZBnnns)ftUsO1I=y;r zQ?!@Nse0B|`bYZXv@~xiJ%d8M>Fx7h8ZK(^O%2`Zo#nH#=gTbZc}m=uUe2?>CmXgW z{RrO^&u0~V9uog=GdQ^D_5ET|zBfTk!0CUr2nXX*C&e4jQnJ{bH&ndLRT8)+d&DiI z`9`sBQ^MVV`d9rkgbV7YcYa{nlA=BN51sb7-FBN<5_a~S zT>FE_&t|$&n=c55iJjao_504z%LS7cOl)I6yIN=Jg_sD1u!4&E-Y0B7#UyfljP4v+ z8X&^*ZiAn))Z8l@X8B!^(6M>@Y=_W<^XUm*Pclwyn5)yVTK>dm>7Oh*=?TB=Q!KjQ zt<&f`c68;$n(d)xyLwuh6t7F=!`^M+nlF&;+tFEsh_=?<8##c*GJ6>bDc^*JFW_x>(hRGdrKYT5~i-^KZ2I085kJe zdAc};NL)@%NJvUcOiN;5(r6C#j&=|4@0~l>*+1Mpx_fe?_x%GGPTV+h<;+b7%G8?RqKZ?9mWVPTGkkiU)TdxYYGhJu)yf&!)!ag8WRNi0dVN-j!GEJ@urfBaGO^G$Ft9Q($YWU#D)CiI ibYMDi^HVa@Dsk(Oj}(7DQBj?Xfx*+&&t;ucLK6UV1ek#U literal 1685 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy-wyvrbfmFCgv`# zCWeNtMviW-W{&1=2Ij^FmIh|7mN31ZdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{ zb8S}6JpDt9x&^cZw3;{;eNozTLH?m@$JPBD`MUxy?c!((P}%&#t*Il{mG_Fx+FkF~ zoq2QSTw?nEefdX@&ffK+HT~Jsx!)`IrQiGhm_t!Qy6%I-0S(tlkHn95Z~XW@*h0Ae z^+Pwd_qJS}DON#;5588JV8RzYsdbUtHco-`6uuKb?!-KDa&S1{yI_V)k(Q(5^LZ8~ zhb6dwSfudteJl-kQ@Gah&PAkG(LcDidy;1KHP58=3lz6jvS{w^-Nt6`^w7e{NV4?B zteIzIQ#MZ0RFd#tuw9qSzI2gjalon6+xrebbeMd}@L#_QOA}bs9&Y@X%(yg&^KN-t!QYq0JLdR(_-Xn^TV{@pla(V%8&zXcRXDq*S6LPTgr-O=1(iq_^xVi_`UX) z$(j3#=IpL|N~YGF!NC*f&0LYEP`m6{)uB8G^E0}qY7cJ|X3HS5b6aaV`DT|c*8qoj(#iw7xJ9n|!d)irzme0aQS zUqI=BM^^**@16Z`<9|7kOYNGY;1*E^vkj_UkzF5Wel7Y|vLdwcf3H0g55vYi@eEp- R9Opq5g{P~Z%Q~loCIGLDdVBx? diff --git a/toxygen/smileys/default/D83DDC3C.png b/toxygen/smileys/default/D83DDC3C.png index 58ecebc7cb1104a682b04040701880e41911fef8..c18d7289d168f9b43b1cafb692bd1082af46f781 100644 GIT binary patch delta 1695 zcmeyudz^QIWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LL^>pAgso|Nk@K0HLudljg3Px?t_Z zIV*!B6X2@qAuJYF)|rde&0Mr@#^Uue7O$TLL$jBzpR;rW9L-(20g9GymIx%AYzD~?W1 zA|fJl=gwWQV1bN`jGcqSnl1ao!Xnhw)fE&Jf`dcOUA`qICZ?gGQC(dvARw^&@Yy%- zKZ%QrdwY9(`}+U-^~*1?K3GUd=+?a_U%!2yH*X#b3yXn~$;~_WZ{K@pVrtIH$~t@Y z>|6Jq-oF3r`;VW`Uw?f0?$iGL`yCt{oSdAVzxjCY>6?$AzHZ&VQ(0L>MMZ7P)*WXr z-P*M4$deauzJC2ab+d~}zAfx)08$S;_IZN|q3 zt{*G{=DF?rRatrim#h?f7^rrmxZ|{$tab*S2tGO4I5M6H{#e zF1&AFf3d$}mf0lNHgkQmnKurv(iTr%{p?!q&H8(nlGs0~*j`KI ze7yG^!-f+_C(iHaFyF!2@}PbjlbS-P2TSyMk5U&7A^m;6Ee*Wx35QqNZg?K!(opKU z>VA&jfw#)OE&MFE!**Ow2!%ei?Zp!$7oJo7 zn<(t0x$IxZ>AA`O%d*;+THl#oe=5But#{4Wy_pLVE(R#ZEzVK)y%oB7(|7L+Yi{~( zzUK3#Wv=U|)yFLTH%eJ?%49}p#HFrmd31KO<2gxZ>GZXYJ>l7 zQ>oQW%-jkO6epc+5`R&~w#=okX^Mtt_kpYCPyRL5FikTy_$zPZuy~jDq{SC4Oy|_g zZ#&^NePV#Y5;rc-d0sy387`BHR#)F^>Q;MFXCmbALgdD*xhV$@v!4r`^fct;=D(^e zUo_b=N;g#Pd1z!JQ~1D#A#K93HLpHMGCz-GV=Ym?ao3nF{Yn0^zcKk=?l$eda=Uoi z=L%k-UGq)CH`Z#&UD+Prm?N@gwtP_UnUe%v>LC z-@0jSVP$E(Ysapg+zhNNwl=bJ_BI^2dGNM7hlawN*U#kzUOW{Qk$Lo7n@2=|lh>Go z|J$c7j{c5%9?{eU0pb3xzE0DRpSrqxIXi^JlP7fedzkon`FVQ!8u@v5Oi!5LbNv$wx`@g^(p)wJ}0fHyDSzMgMT_^9Qd=ibRvMInC}Q!>*kv1#Cf=-8{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy=JbK#x5?-Cgv`# zCWeNtMovzy&K7Pi=Elw@rpC^$ZZN%`dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{n_1p`nZzVyf!l`Cg!Uq2mnM%rk4#?YHTB;`B{6HOZR(ev~R2t-d8{ZLhX)=aK&F`nz-d#%y)#eKw_u9KU}54vgFQ z^FGg%{8b_{-Ni~4 z`g+D9jWt`hh90Stotv_R_oR?%)FM~sttKWW3Ag*CGPVe@+3z`i@o;Zbepc3@%kQ3l zDQi2*Ev_)pr!%XyXWPjHjg}N6%~QSSjxC(Nwc|*dq2a@You3tC0$IgFA1k#T|Hs$n z5Y_Kv`|4Z8a_*u{njBk6p00i_>zopr04!m^RR910 diff --git a/toxygen/smileys/default/D83DDC3D.png b/toxygen/smileys/default/D83DDC3D.png index 3863e975e1f0c7b923ed60418c42aafdbff0464d..0044223ffcb45d4a66949806c701e42b4cec0ed7 100644 GIT binary patch delta 1502 zcmdnN)4?-AvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{;L^1{_j2azy835|2Lk#TC(N;@vHwfAAa0B zZ=GA@2VI z|Nq%{U+-jKV9+iJ@(X5QFrReh#rNl@CK`)wSuetM=@LhI$=TCizumb#ZJO){^Luwp zIU|={diOhmW6_5nEes3{j7i?^E({&4vK|Z!4D2PIzOL-g*!j3r8FmM)sn=y-V5+SO zi6{w5ELSKf%1_J8NmVGREJ#(T$Sq)Cu;`r{>OE6P@m5S_?`erk49A2d@p1k_mwcN>5E2aC7CmS^1 z^KA}_U9~Q#irx**&*m0(ELWA!a;QGSYnxZF()W5)$Kf3hJtv)-&9Q#N@)If!qCdEA zsc4y=TcEq3=+jL0Z+~--WOtp_v~WJMGe*q*ZgK?U%u}nf#IMhoB>B!Xe2tRv9m#q8 z`SpC7_bw%|e^Rl%mdN>d?>mMKCyq{>-_c>dgSF*B{Wc~wg-{Qc=<^<>E*wJo`+Qp( zc-<2Yud?0nJjSJ=)OFSU9KQo^m3>?IS#F2zxSSBo$u1E0CSb?DO=UrP>r7w05fa#S z`QO%0D_q)(CrBfrg zqwIStbn~X~-WS%~^xb^T=S$06*H5dDS^963vf`AArVCtHF0tV1!B-#KPG2ZE(rC;$!%=GUDrG|ni}OYr3#<>mn`daD zTyOmLdDY_gVt1C_o40w+@$3l~{w~?s^)~q9f_;CvQWhuN{WBpeMZNy+!@u!P^$ee@ zx|Xi3Im^z#z;Mdb#W6(Ua&m$Kn}Olx4VyM@EG{T&+i#kQt7~kmchEYaAJOD!q++6Dqhw-aWTIrFW#ZE1Xl12lre~&R zXQyVSXr>m}<`|}@X{l;ys;g^it85$WH?7ezPEp%h-CSSaez}5oVCTff#=uC|P~TW* z=UCs+;O>bNH~2FO?boV|-?WI4fq_A_#5JNMC9x#cD!C{%u_Tqj$iT=**VDjU*U%)y z(7?*r)XKzM+rYrez+i^Y{aq+Ja`RI%(<&i4uxWTHn9ep)QJsr{!PC{xWt~$(699d^ BzqbGY literal 1592 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy+*EvZjLSnCgv`# zmWGC|MwVtyE-p?6MwW(_7Ut&WZZN%`dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{pe0lYxOL!_&nvq~g|*$=?3Lff8-^jm_t1F1-+u zmFvB7m%@>@Uayq&MTrYt#8lcIy^rTFbBNiQ_|Ws-nKPB=exI!_+ILvy_`yQ9_GG??@B6Q=y#Du#gfj1hq$LL! zwI-W+%=ZvJ8Ew8_u4=|^R-?8Te^;LP@p|rDL65hx4R@G6)D%z6WuC>hIOgEDGR9@e z%QL?*irz}S8J>C6XwCVH0V+%9cry7h8~7~9Gwpp>z`eDj!KG2M{l0djNZEF=3eO!X zCOXx7nx_YQ88JTD-stxD{_JO#KVG;u23dw~?QQ>a_jLHYIj>)LTkK>q6W{;%@n`W5 zzeRp9=Qd=@olKpXF8o3Jh(@b^mDFl)Z^wgoR)2FaPgp5sWW0IC+}Vfc1szFmnGtWhc_ zXLIk++5O7!?{lk|^$T8C#c5AHS0u95dftJsjU5FVPjrn!wpLy{B6QKB%i5Zw-P(4U zpz?b)`KZ4-Rc||n+jJ%E>yT~^R}B1Bl`GG?mv7xeg^Qxjiqnr@cwpK;^?UY> z0PSF%&o2sNVsFR1;GM_ivZCS6v-YsH3-^5Ho)(s`wqRZc|6H4-)~_oH=p;sJ;L!TcK_jsv(F;WiTrR4PRLK0KS@qf@@jw7&;9p~*)#Dl Xn8hoVmc~`tfXZ@DS3j3^P6QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@IMvDNS5ZC|J z)&I-O{}&hk&&m0pmi9kB{(o55e}Dh~Zf^hW?EagY{@2z0ud4Q6UjDy?#D5_nkoxLs zC|O<((hDJri$R9K$($UJfyiW98pwQ1GCm$;DKZ%r26cnKKgcBz(#;LzBsghj2XY>S zG&Kde6GH0hf*g!Vs;Yr}flSKFgFFQ%B_yC86cPe?6HXQvgPa!@2C~l76r@E$;{X5u z(=R>EU|?WKC<*cd#RCH}2)19n>gyll@9RrfFFBFyohSY6>AQb+(H{#89N`BDz{EU=~4y;riQAJh?1bha)pAT{ItxR zRE3htf>ecy+yVv$i{7cB(Ua~NaQwNhS;QhKaN_vKo;CYJo*Ezb-)>_3`fJ7Hvn#T` zFkHN#FsJVSpQ&{p!q=+p&F+`FDIUJRl5_H#M@J8b-{0E#=5N{VQo%Xv^K3L19-Wnz zJ<)ezbgge#GIxxQ)azxQ+KHua@FZlP7}n4sgE^F*LSsRm6|$C-@~hyEZg9{7lz>z!cQ#(y_jIr6`Z%W6kfXjT__?rp5Ut_3uzN1N!io1GI%QvYT@UUS5u;KEB2 zajQpy*IR;QzMVWBn(&`5>(DaoJIhb9+s`)4eqCc?P+&gmf`(4?vXfz(18;s7(z#vX zJA3NH7p$9C^u<1`T4SLVC!`W(7_dhzN~nulxQ|z6p6yPDeN1Vd>JM}ToaMjnoptL& zY85lbyhp0i-$jM&`nU?7{^FR!X2cy7Imz$QDd|tY8fzG?DHr^*Pife+i+fUXjYaFh z^8MM~%co7axa3#BrH!ZF@VQ=O-gL$2vzw;e!ZqvLStgVyZ0ni+}e@>A)lVr zFI1>~)NnGb;ElyGA%|HfnKxTq?t5ZXlEGP)v!Yd`K{=$6m#^={eV^YO?_DbAeixG8 zAE)>CrpKky<(8|@uKQtC`&;D2JdIsH6hza~Ta-UXPx{Aj+oo&I+R1hr3=9lwJzX3_ zBrdxie4cbLfPw9TzEjhrD?Z+9`MMUJUbT0t){0w;P4E8~d~*D3DPKWm>{ViPxiek${JoyOh2+Y??G%3Kucusix_w(p}z_lC!D+q%O07|!qC zs~(&g$7(zEWZjcbRT3+lHg3_DPFSLK_13<;BdixQw%wW^KR16y$GSVO`RktOojLNM zyu_kM-I-(6%Rob6g;meGpJx44pU2?4OXg5S-IBKq3=FCzt`Q|Ei6yC4$wjG&C8-QX z21Z7@2IjhkCLx9fR>r1QrpDR^237_J3s(L%N70a*pOTqYiCe?PFV5Wz3=EPW8-nxG pO3D+9QW+dm@{>{(JaZG%Q-e|yQz{EjrrLnY5>Hn@mvv4FO#p}Xwi*Bc literal 1289 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@n=6jxJ{ACYH{Q zmWGC|Mo#7?rf!a|7M4Z^7B0?~MlijedBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*xNBBX zSO1~|& z5^c9|mpguuE9PUHh?}yznVsl~b^a%kt_ah4 TKS%l?sBrRh^>bP0l+XkK>~X$! diff --git a/toxygen/smileys/default/D83DDC40.png b/toxygen/smileys/default/D83DDC40.png index 3a434193c6d8723e8445da83cfde7aa774f7b624..4c4ede39bc78b9dc7934ff5bb86821ad2cb41966 100644 GIT binary patch delta 1306 zcmcc4b(Cv@WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081HB?CPmrb9)Wa^xSlV{DJIAiYr|NmES-T^`ND>rU~pylhgEMLE6!t~io z)^6;XI1!=;qHXG&g%Hh?XU(5HYyRrZJD$FH`Tzg_C(mEpx_ke|?R$aVJ{f80(P3ea zA3gf{>zA>Cj;4yrnG2VFJiWY~?H8?DuPn{N#loZ{EzQTytSrGif5jSCdmTk_#@PB$ z$GJ;a#)r9RE3qd;x;=gR=*N#AC(m8{^Y`zuv**uUzV__R`?Hs>O`bKM{Zp3~0|SFn zNsu2Xuow`)e)&R;ugY!@Ty9Q%X~4k1z?kIi?!x#qu9;S8^%f!r z)zTz46;7@$iCkoD_G?yxUE%5S{lCQ~S$VOzEVeV^i!3{OdXG}&?#4IMtiNn+o?RNd zRxrIl*-PcvY>xFE%TKB}h(6)IsiHM4`^38mMK5QvKl}UgNcK$K)Ex&@cFbk7zm)94 zD4iVoH8{&>>6tH88g7$WU-qn!&u7%Wr}e0w;gd>nU${!o^IgmeizYbKPMY|_gPUXJ ze_O7WhMp&fZK`#(k~Q4w z!|)}k?eN#gt!_zMEIcoG z9C!Gv)h#%STlfeU-}#9#3h|7KS6eYIUF>*i?lrDiE^9NMy{eiMH$|(z)gy)FM|7XE zfoPH0k`(VMp>+8ZpQV2?=(s2RvQKG9E0^wCTv?zqS-x(1{Zzkjr^BkIr#!N*ekwCK zD0^YY?YQk%9tnKY z;YwK?5?eDtXLaz)5C3%Q7sj7e=$gM|?%56o28M1=7sn8Z%dIDl3Nls76u+YggBUP4Ghg*4px6gGbG2i49EG?dN#9`lcc^-Gx z$g~8TndhEu2@qkMw)$(Dw{>W0!u!g5jeWC@IeaYH7n83Vo4%mt`^owcp8Dp-RiA72 z&YhKMlyLQK|LqM;P6}e2A3f|JbITP=b80bh-C$r~P%UwdC`m~yNwrEYN=+{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy#^Mp7G@?+CKhI9 z&W47rMwW&qW=`f#2FB*b2F9kQrZBypdBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*`X_h)xwkn)F0((f z4(KjemB=I&kiB9t=QIY!OwK~KT3uef1r@Ra#tw#lraJoZHs7`88#g<~M9q5mx&1qj ztgS6i*C!*MbiLSbtv^4XT^(+5)j)Ti<;3^L&PwxWMq2X7+t(+a@aRcRNKQ@t@%{UJ z27%@bwwkO6yFL33e)#j$zRe(?zs)>9&Y*+KDd+F+_xd(VKCVm*47!-`xl_zNlaF+8PZ3g#;m5JXzeck`-&ChNXv$C=< zwzRM|>+f;~b1bX>NgGUbVfa{`p>${^+ms1;47!{)Rezr7DCqJZ;cv8H?Rc-WG-6Jc v16za08>S>yrjv53rQ9Y6vzwSRu}CnqXj*^lXq~ePRMdI8`njxgN@xNA4anIG diff --git a/toxygen/smileys/default/D83DDC42.png b/toxygen/smileys/default/D83DDC42.png index baeb7b17059969fe8bde64b961876c49ae3df602..990bff9d474bfd52a3e751929e45d4f9a69cfa04 100644 GIT binary patch delta 1516 zcmbQsGmU40WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817k*jPl)UP|NsBq+czs%>vT)Z#lEz= z3#uM2ZMe6v=E|g;(=9QlTVkH9YB}2;4`DxD-FmturvA#Ltn1SX?k%dhzqk%WE~>pc zIrr-1T!^C6EipG|7GD8#A1rNz2;QDsaj_@$^2DsuEiqT86VyF6S6PE zY`Qcd`})k13%zNlTVg)#pY#31vR|iH{y4e()4}=AH*|eJv-;JZIZw7ueNn$_*3VNb zUTp0Bba39cQ>)(Wo%{LN(yu3$y+5?@>DH-V4ljJYd-l(BYyRBW{^#b-KUcSWKDO-n zj_F@cEcgDFXm)oY?Tiy2i^2VPR)?b)fFe_N=@rK@CXIKBeRKM}l{<%-rb*#&Ay)w7r_vKAL zPA>a)Y{{FQ(?9H=dv9sur$h7K@0;`e)QZPzI^XS{`R&-!*E^>D|Nq}VZlWLq1A|dX zkRK@OELT7K{c2}e$-l*nuK!Z^oc#3l^OxOw!vmR4AM*KQ!2D?OWTV~ZUS_?x%jtgO z&k3!mx%^cO3=E7(-u3P-3>~bp9t;c&>?NMQuI$g)`M6YhHa&U3#=yW-TNM&f5|mi3 zP*9YgmYI{PP*Pcts!)+zz`$V9J2lkz+H3=!JFi7-n&pU3%92I6*YNWPR3*!~mA|8B0%}a}CSiRADT$MfX6?Bcb=(ZoLyS z;$qnl`1+vpG)0RmlTY5}`6gHB)Gr*9b$PAWBZcd>+n0ROJ0$%>WG2fBruvQMonF1B z-@@J$otkO=?4!ve-K*0=Vir7#(W`Hs|IRSNVfm(2w~m@F6KShHE4uguQ~9pv{pIaq z?@!)nv^gpBIN?x59y3EvjLgr@j*8}J2accbbsQL1Iw{?F)^nwY!|}1|_D+Ef?>KTc zNHy9%S7KTt9rX90CFA$g-A))M6NTX4Gd@qcfxG%U5gGyP(_`lAG)d2>~TP(uv0zl$BIA>sh?tsEBa>HDc766U0$_# zz1W?l_vV$?TYkUp>h#_GY-sMCuH)~HFLx;8x_Fj-rCZr0S-JZ{zgYJ;boFJiJr`hL zVA${J;us=vIXNLAAtfa(F*W)5gOrp81~)83&JK$3R-clfkDbr)Ai^q52h&9rKz{9JeYP>{bFkiFk@(&p)8s7 zT~n5UfkCyzHKHUXu_QdzD!C{%u_Tqj$iT=**T7uY&?Lmrz{=Rv%G5;Lz`)ADAeHav kP9zk literal 1565 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy-seXrsl4e2Brok z=7xr@Mvi8tCQhaXrp6{lu4ZOV<}kgUdBr7(dC93TdowdrtRQ+V-0G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6-6^Gc_}HH8C+ZHE=aDG<0+`b#!wxF?Y0ZF>y6EHdcb_O(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI(pIW$X^fq}`_)5S5Q;#N=a`D|fFiQ3=BH;?4_ z<}3-U(&z{i;8$v@_@wZ|Tx4UwmsgG~{PXsFQ))6_EvKXP%F#cL#ZjM2RZz7nVC{?< zx0anu+jevAUA5h>&835qeGc-s$)@dpf3`aRzE_}#W5|wAT6JRbcWhTK51L;7^qBw3 zS@ZaJ#hy-GE3`ejyzlH8+iA=Fk~b`IWRThyWMN^O|4d!wtcPQuNK0ae?)m2AtAW2` z;&_@L{P~#ueX(7`t@{@~%&cwaJO5m5xylMDvuK{^eg4tB$vbt5+88~9&V|}diwpk! z_TQX4_a&B9nr_~%;JS&c{iZ<>+otMG9xB2a>((kS2%DsOt<{n9Hc!y*V)ZXe8hwu? zTv{;6YqEo6XCAwjVbWQ)q9ZCZ4f}go1nQ3X7-f2x2%I!Zaqad0x`glS)fc*+?`NCo zG#Y=~_PE-`($iQ_>7CN8Pl9E;4U4MI^qgIy|0z$U;q)5TsElh_D{6kayh&|gI`Vk0 zo6+TsvlhL-=E1N^eeJFDORr8%-BYurBR+n5_3|ST9-lHR9v}a1Uh*kNg`rht%j@jx zpIbS5#J@G0zM5bnJ87Nr13tsmY;z)~vl$!YELtJH?&_*Jb7#&C5$>GFGwH%IpAA=E zIq!P%H{gG~P=J;w^ZHzSamKF8o`(;)R432nd17v`Xd9!;F?~DTZhv!K?fQb9j}PRU zdAaPh_#^-4@Pg;t>OxrDLj+hdf*UNd^%B~pzA?z2Q|`|b%`C`i3tRiCx9|O5^M?Cr xo3DOqpFJy4=ELo8LG8PXUFN@M`4HW}$dF>exn%yNk9$A`wx_F~%Q~loCIDb?KY{=N diff --git a/toxygen/smileys/default/D83DDC43.png b/toxygen/smileys/default/D83DDC43.png index 71af1e29f0953ed4164a91d4c6ad65ee3321f6b2..72b0103ec6ce90a51d12121ea1d9706aa6f41d8a 100644 GIT binary patch delta 1363 zcmZ3@{f}#cWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H+-~`TXV|lZ_O!tu(aX9lKRsv zF>rKmdE@=%O{ZI8ZqKW@J-_ni%;M86G1sOR++9?6ZCb(UmYDNh$v0+~o$pFM-4X+` zzpromb#3dj_1%AO z?RvFq#-BTTUhkUmvVO<3k4G1OJ-PDhsTD7GPXB&se!{BU^jmy^q1ZJ+x6(xyK*cmBA%`PKGmw-;3Z|Nr0Ue@P1i1A}@=kY6wZL$UUs z-&!1(FE-Vub*x%>?(F;bLW0cI&z`ltdGjXm)2mOsyZ&tFF;zP5#lXP8nB?tV@50c* zD(k_(z`$PO>Fdh=jGd25#qzI@XbJ-ZQ(aX^L`hI$xk5ovep+TuszOO+L8?MUZUF;> zMeo#5?@ey)vQ0!^l+E|7)@kfj+xQ~w zl|*6y%j_AcCyx2PyO`Sf?#7Gkrk_W2_y2tt?XfOU>45atiFGG7>F>F6dh&M36L~wG z4)O>hpme`{!2ZZe1!OzJg`@ z<+J_uA0*-&jz@(46_Eni&iy#sIOsvTlK z@lEt$$&V{W3>Ufu*{-Kw<2$B$Wz#vomJ3tA+Do7J;;Cn0pF8oHla^eW;*_MD{&DM= zewPPW>%=~^^R@dCX|mbuy)QF!xv#L$wA9&|mm;@nRo`bh9d&ohiqJ*sJvu@0(>~97 zdiSDd#Kb=7qpNk?H1v1`v@L9^iyCV;9lG)pPFP3y#tzLa~%|Y=- zk-ZJsR!qT)>5g9Y)0U_gervSZKQaD5!E+9u|Lr`P5?a9xp9t;baH-^o9a^Nugv&czr15S4TZ)LJ7YIyL;U?sb; z3%lL{=c$fr30`rZdIb%$#G2CfxpJ(k_LZNwf7UnWy%)o|--YD&Z?2zn{IkNs*OQ;E z**3pR|K;)Z4rZs8cFRvnEz^>@_h0o3_wxH)ZJPu%*D^3LEc0}643W5;oRE-^kP^hs zsOH9|k&u#hG2nVasx!xi;*3?S;qhoV z?{Ig5e5AtxkGl~9kq#$Z?uJOzcj_mlt;@?(<9giH=^YWVDmu!utJ{%f$(r1pM!l_@ zH%@Qj@UXV9TDdyg!g|*U2bL7x*t-pN#os?nY2`R^QoEw^_m5wi8mA8Sga(Cu3=a%t z-puHEPpHE1xUK*L1A}UbYeY#(Vo9o1a#3nxNh*Vpfsv7}fw``sNr<6=m9eRvm5G_Q sfq|8Qft~dgJ`^3f`6-!cl^8nU8vN68HceDi=VD;+boFyt=akR{0O=Zy(*OVf literal 1451 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy)I@>riRAm2Brok z=7xr@Mvl%FmTs=j76um1j*ccSjxfERdBr7(dC93TdowdrtRQ;L-0G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6-6^Gc_}HH8C+ZHE=aDG<0+`b#!wxF?Y0ZF>y6EHdcb_O(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI&OJUe$50|Vo2PZ!6Kid$2Hef@7c2*|##Dwnn9 zQwwbQu}Z}J53}E$7xgbDYK!;j3SCH!?qrs!dcnV&^3Z|h}({EN<*I9o~(`ilQ=wxMIT~PVwuj~C2FN8`YAL$Fmt1UgrZt>FZ z$ddeh&wox3SbTg(^N(;3!%0nVx4+(+d%pCM!l$*fwzeM6TF+u0mioNAIPrL6^7VtO z-qiJdh~qq9w?^*w+OU|q<#*e<7dHo29C^Z?v!7MzOS7j1`vjEGUmWJjFo1XqxdLX&=%W|eKF(+r(3g_&bdn)-P+rsis z0W%Z5Bbh(`Ghx!a>Eg5C#WODd<7t~(8xQV@Wa|{*Xl8hm#X4*2=F6Y|?)^Svv&Fwx zi|3oQUiUwy{!BEMJ@T0a!^?N9Usff$9O7H`^U>_Zy{5bmTlYs9w7D%Sc0TMd!{K3s z&7mV2j}7m&e?Ru&Mae5UmU--UdcF^K2zY5SUe?S4$X z`jT(UhzlG%kdR%l8Z0Szqu`2 lTlRWo-y_rA%L}v*FkHH~^5~<}ll?%&r>Co*%Q~loCIF2e8D#(f diff --git a/toxygen/smileys/default/D83DDC44.png b/toxygen/smileys/default/D83DDC44.png index fd7a6e25cfcdaaff7acb2b0f980bdeee4a8c3d8c..627f20400f39a1e68a213e9a731319c236caefe0 100644 GIT binary patch delta 1488 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 literal 1609 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 Iol`;+04d8!rvLx| literal 1388 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 literal 1374 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy^aPZj)n%#2Brok zW`>5YMvhK~POfgQ&c+6oF0ST|1~9#zdBr7(dC93TdowdrtRQ-w-0G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6-6?Gc_}HH8C+ZHE=aDG<0+`b#!wxF?Y0ZF>y6EHdcb_O(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI(J3_mu3fq}8l)5S5Q;?|a+Snop#B6h!vjH1uw zU)bcfV#$S2`^B#pbk})`pY2v?TJZluY>4^Y@GHJ90xd3zQ!~@zW~3G8ozC4mS!ns} zzzfXn%;zibKd7Nk z6kl#gv`FH;aC=jvSU``MgU9BD90D@z5}sc@WYrrMtcmxS{jSqt&i%rlObvm0-fIKb zHmzFq`>%|h#({!=cMFQlj3{OHpZ>EbLX3!#4f&Fci3Wu0;f4n75%Ne z>-bLJNgo}aT77q^ywUmYswwZ5?N%C#IOMx{f;RJQedIiCyYPhBz0)r}trYIvA>y;- zvs(DMSjL5#tyL>47Hv7A>>{fyEUKeC!%wE?d9$wbZhI51YU#Gvz4isx;xnsfFYr*G z-}~WU{zrSJt}lF9-fy1Sb6$D(>E`jxm-GI4J_v4LWVqt9IsRVR6Dd%M=jrO_vd$@? F2>`CX=ZXLT diff --git a/toxygen/smileys/default/D83DDC48.png b/toxygen/smileys/default/D83DDC48.png index 7a68d8c878a4a6b02c46e21185528e338e716e5a..c961655f6d8bf959a4eb5015ca2fe4260d62b3cd 100644 GIT binary patch delta 1274 zcmX@dwTx?mWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;t-pAgs6EiwQ9|3BRlb9rLc>6V!D zJt>zbW}a?|xzLw(e^vX{DS4+`VotZj+*?!&VVrJ>seint?P7lhgnxHo&Fy)WZ}!f9 zv3bIeD_cGvU;1og-@WCHPdD~G+dSdv=7~?XPJOa<+TXi-f8W~m<;1cZvrE5U-t_n0 zzCU;Od_1xI&5kMe7uP;m+x1|5@9*2Y|J>a1^W=)V^Q(TGS@q}6o?jQ&KUvlC@xa{Q z*S7w?wDHZ(X}>S5`&)l&*Y8Uk9<6TuadP>e8#{hn-}Z9voS#=V-&<7sXhqY5C3XM* z|4)A`a+QIBLA)f$FPMR0{|%oLtCUopT-2<8Y$@1qe}25em36KB`^p#?7#Neh-CY>} zGwI%AU|?V`@$_|Nf5y(orOFm$$SJ|Vz*JKe5>XPASgue|l%JNFld4csUs;fA)sm}c>#_`Tme!wy z>w7AkBOEw>rt3do3Tkzk@HJ&svtq`cCuX7oW->?G!mT$xk6}5uN$G07o%(}s6PGn{ zp4cY((BwymGQ)*#IkxNJ*Zhv%bX|N-uVsq$>v-vYuM!saxq{E;7|R*yX&l`mzvm9? zxAzOmZWOiuHv0J_ecFkr`~18-IznnUOi!6zQi-is(XPJFa(dG8OtG^r{x3Q$)1@BE zxMo{;PAG78{>ZgaOhnvDhudOLRZ(*dx6d8!38L%MZ)(SNHgBkAZgprgKlCz|y~^Vd z|8JfcAxo}Fi&bN?PfoP3kUGd8U7sBz1iK*VnH%auZ}x1c~}aruAIbEF(W}nHPU~o&Fy#%jdZ>p-Z5%FlUghU zr7BJoC^Me)uDPh}*6B3ign4n}tGLVQPu?&48@_Y8PRom`& z9e;IueZzL4MYH86xs@5o-1@Kjl>bp+*E~7ryblZv40WC^jv@6Dmy;6|gbWNfFW}nH z<-K8}GIv3NmzQU8Veuq;SmZjGHm7Im^rhVRVRVz(6q2?3XIY-8amd_?cCeESojVP8)JH~c%Wgp zGow$u!6HT;Muz@Q{@edkJ*z-Pk7|i)L`h0wNvc(HQEFmIDua=Mk&&)}xvrr}h@pX% zv8k1*p|*j6m4Sh|*(*5)1_p$N-29Zxv`X9>+_HlfO;l9pVqoxe^>bP0l+XkKMEF6u literal 1358 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy-t>vrk3VL2Brok zW`>5YMowl%rmoJeMwSMSt`^2-aJ`;+#U+V($*C}VGc!}HAbJhm@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*ZYQ5|$6Q+cw9&03wVB(y>GIL9;>?UJEe8Xtrzp(wVUXr& zoZi1=es}BRT`KP`XK&l1_Uy+Bhn0tx+%~?Y)6>Y?s>qRXO#0dRC)2f9#aVU2wRfJn z>bh-yW1hs8LyQ|kW~m)#H$Kv!eunwu@5aym&8ah*bRAbrf7fzeR{5mp#hTn>6ZJ1l zHhI85kKu^b!Ix#c<{Lk%JiR)N$0c~bxuoIZ*^y$b7uM-s*3lBpyncl@ pW6Dw)sqe;$PAC5VJ66uf#vpUAS470$Y8I%3^K|udS?83{1ORF=-8cXM diff --git a/toxygen/smileys/default/D83DDC49.png b/toxygen/smileys/default/D83DDC49.png index db52a0d8750176aecec7205d5f04eb25f0f9145b..06ce893aad8419a5d9d2e124ac6f91ff780ee277 100644 GIT binary patch delta 1275 zcmdnZwVZ2$WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;t-pAgs6EiwQ9|3BRlb9rLc>6REU zBlCPu%ITJvt5fpsuWG;0mj)5Px2P7vsDHes?NWaRgmZgd<=urfFE&qjvv>BhjeVbw zFa2?4%a;?&e&5>l_wL>&TcW!TR0@YrCGTYWa0>{hvE~ewx0c6VX?&!l^gfq{X& z#M9T6{TVwSmn!RNi-<)G3{16EArU1(iRB6fMfqu&IjIUI^_2yw3Kh8p3=9^%Q$qu% z-8SI(^IXKXiHGCKV~dSnuYdn_^KrSG-=?_ZxAPW@d%JZsGN%0e`+54G2kR?^cSoO} zrn+py4p-mnLQ>nktSY^|<%aES-J4A8^~tIcK2bm9zTddCL*1ppTyAdNjZOJ`uC%Mmrz!0I*s|X* zW98ZFQ7$iI&MR(OHGAf|56f?)IG7&czL}CYHT%T739oL1zn$?ty6rZ4_;*>1sb2foP>ipjsEG=JIHh#%c|2BhL=#6&J-)Em0?$}*YsbIXh zQE3j_d@g2&y)zOEYSZ2(+_|Lv`i)CROyvJt7TSh_G9^}Lm-bee_ylwvwyWI9_2$0g z+YLpnzfFEVNH0vwy*HbU?VX?Sw5h45P3nKXvD2!yXE|-REAyk3kZoev=8s`J<+S_S z{0{Y*@`$Wo9qL$^*mUFI!<&=2pLi!UaK66P85EfE$6ET~!?Qe{KV1x-$2|0I-6;D< zcmq$0*piJZ%}1`9Kl#c0la;5u|G=mE6DG&l+>7S)220lT?_OK9ONdKvrQMXAHC*}Z z3l@0RZ@p@pf5%n$)t@Yth7*l9tgJK}+QrvIPI?+}()&*<8c~vxSdwa$T$GwvlFDFYU}U6gV6JOu5@KjzWo&9? tYM^akU}a!1(edFb6b-rgDVb@NxHUYGi4>ctsLsW};OXk;vd$@?2><}(Tjc-% literal 1339 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDz0R(NCXR-t2Brok zW`>5YMwX_=jxJ8lj;G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6-6^Gc_}HH8C+ZHE=aDG<0+`b#!wxF?Y0ZF>y6EHdcb_O(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI&OcIta30|R5Wr;B4q#jPWg&-xv55IFw-c3x(y z$SHTjh6OV^1bU7gIsQSgLfre&VF^9C8iwBR{49<(2~MvG%!`8Twq3Yp<|dpgm1JRY zGNs(uc>mm+Hxe6&I@-doHoJ4uJJ@A<{tJB}Q!#;?M5dL|s+ zw&uNqncSlK`&VKP1SZ+8Ke?)Su~7SxRSHiwZwguY;ykM@PHrFv6~qILzat=CX+ zI`BASrkd6E>+jf@mf9OheA^YO)z+jiiNlH2f5YreKg}5OH(e`UJn#G4L*b|78V(*P zk!?Qy?P-(Qt+>-Q??g@+BnNMp`0h8qhIDVD@3IXRa_jfXDZMmVF?r%qwLYVs`|RuP zx9UGwdU@Thxer*oW*S;wogc;O$TP*Vuh}s5#di6a>eKuV(`40>Zh!ml_qd&bnIS5= V_9gd+v^r36=IQF^vd$@?2>{>N))xQ( diff --git a/toxygen/smileys/default/D83DDC4A.png b/toxygen/smileys/default/D83DDC4A.png index 0026ab1284fb35af4651f92d44c386126959e26b..a4f5a838de725436abb048d5a1d12de5798a25bd 100644 GIT binary patch delta 1584 zcmeC;xyds@vYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{Uism`+~3!?z286Q$JH%A&aeG*bH|?>+u!e=@oL-TUzaw1KECAl_3dAdF8*_U z+wW^zpRMnHxpnf_lgodcUGwYmrgwX1-CJ1u_tvf#8|(Yt@1Og2&#dPgdVXEq^6T=( z-&Zz2S=;_>W8bT7Q(kSK`u+UcPlp!VUQ~ByVeN;*3%+02@OIaXYcqTA=AuFWdFG$Hfe%J!E#X1v`q=fS$(TZn3e?j1v4<1 z-+fabTyD=;x@+^hf2mAe^;os(;-0*+sAC zuV#pEd-zO`fq{WB$=lt9@jsL9Jq88__7YEDSN3P@d|WCPUEZpn7#Nu9t3o15f)dLW z3X1a6GILTDN-7Id6)JKI7#J*ir-ph@n`6Lp=eLMu)5YsD?Z+ynf8TWGV{pCDT^?uS zlPvZh+&6h?om}?k-_O;5I+lBC@A>rM*;TdV>KVHNOpQ#p-!yyYa=XGtu9@TYX3sS% zHhVqSy1Uw-WcFFtS^9mwr}mv#mh>WVg5>OuiVtp6!}Ir480Tz}OE3%U-MMy~W&0Wy zragkv583s1KM3&mw>y*YR?zy%4ad;gCG~4t5AA%o^N5$Uap{lX%A^HGkB;6ta!4w9 z&Dw~iQ-pcH{JzfhH6%Kyu(2z6uSr##`v)c~FQugE7gj7WIFzlIF;%?9*hc=AOw^X8 z$N8RgKF?@ZZ^^&U_+a8@W&492<}Iu(6Y9S)=_!PIu-u&Eb2foPN`I#>YYQ*?#)nt^ z-dKv)J8mdh^6Tt#mOFNrLJSyhUK8|d_BUl>*!v=J!~RWWM(6aTO3M_N+=={ut0H8P zvc-!YgXeQTPAGKLT=w_DQ@suK>{|~{v%WL^q`G~#%e7~JFB~|amDm)!I7ivns5>R~ z{p^IuPbRvHX1?IGHGJ-qJZEtRuR5ntUWP`T;bNq$?A{&ekyk0wd2JYu=3&|2d$--;_G=c7FeIWq0|gd9$FMx33Ta^Nt_ zV~vwltDYSF=j8bFNc};b!kJIH1z$K638uBlbIxd*{$r7Qk5H?^#QbMWSNASEf8xH+ z@16Hfm2Q>KWc>8<e$#A9Tn>nwX>z8t*hCs ztK9_y*$maw#ly|5tE;Qa`Kzt{3)wjJ^R27pfaj* zs(*86vb{X9BIEari2Ul`J1VMwvq-YtJhDXK_lzeZ)w{2lRR1opWc&H0$LF_BYfg3l zoEY}sF0O2CmG3N0TlYnJ(CU#nfc$meNz&TB}!OXO%w8yS{pLen!qE z_ljc&Htall;zmnjqaber2LnUrTiv^=1@BfdFfgc=xJHzuB$lLFB^RY8mZUNm85kMq z8kp-EnuHh{SQ(pInV4!D7+4t?@LxVR8AU^GeoAIqC4-THG!7kgeP#L+71g;I7(8A5 KT-G@yGywp?avMwl literal 1546 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy#|)fjs`}~2Brok z7KVndMwSNV#*U5#7N!OU2F9)~7BIb@dBr7(dC93TdowdrtRQ-w-0G8yO;GfJ!`{jzwJ5VJHN~wcKUV=9zE+u7 z?6-6^Gc_}HH8C+ZHE=aDG<0+`b#!wxF?Y0ZF>y6EHdcb_O(7@D^z9UE^g)RODY3wW zf?V7{OmMmerExn2MCM7&OR-fdQnI(ZW4-4a0|S$lr;B4q#jPd5*7?GY0`uM zt=(-40b%V$3mj}_X$UEZxCmy|F-)2GgZaV*2S-Lz-k_Ej8WVqTmTgtJmev{MszRsVVex$;EwX`{w4SO|Pk_VeexuJM#1CrOu_{cM5GjuKw(kc2oS0 zb@w;Ew{Q1zaNM%p9kn}i_th)5S9d*|mv^0ava^{`f66Qd4c4Unt9QlQ-*>&A>+5iF zo<-c>rpH%5y*l-3ZO~puk!GI0V;5Ql5~j5>&pb2tnb67hgTeogzw`FpaC6s|vi;kt z*my3p9MfHJ=D_TPM>g)y-ZOvCa=6eQZ5A?1{k6viKi}mgx1%$gV+$LtRe90^JUSU` zXKXsdp~SdK;(UwHgQuJ3U|(8Vz2%<|{1j3*y3FpF_21yt5W)ZS)W{b=37 zkbOA;%qmt4Y*`vI$%1}dX6^Hy6@E$2Es9RBotWnN==JQ)D*wETg;*S(u~_)kEOKV* z+G?=#>Y_&r_6wI;PiSKldA!oibecf>!5t?qy_CvUO64jvn3H0@-g(~XRU1qH{waU> z-8@x~dDVf%s~R?3ZcI^WW9c~T!=_o5aN%|15uLA6-xII=<@|d$fBuO>!FdVX_djf^ z*6Tg;yKt6Gg5S%Xp=Ikera1UYi@0`1X>yz4n)U efQ4Eew*kXD3zlmqLeu7eN@q`3KbLh*2~7YNk2d@O diff --git a/toxygen/smileys/default/D83DDC4B.png b/toxygen/smileys/default/D83DDC4B.png index 87d5bfe457f42676c749f459852db600f87632fa..66590dde584e74d636f8c005af4fe5758d8d9f82 100644 GIT binary patch delta 1724 zcmZ3=`-FFbWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LKJKi`#np)U=>hp4zVH6Nn?&f>ZY-N~n0V(zbKx-q-# zbW6gFM7Fc%H>HpSEl4WT-^rI_;79K_1R@$2Y`svEisSRcVC{A3*vws z_i$AUvPF057gS%KlnW6$-4b(cTG7?11rWQ>w8dSSoPTv{;kk~4(=9RAXO(_Fz3ScJ zg&&VEd$D8sy%jA#uWh?Ezxv0OEiblBy}zpU$Mx-R56=H`dgZgt6W$(NaC=eRvn`Vz ztZM&$dDGkdb6@YD^XJBndyDJ8oL~3mz`Waw>z{1s{c^H?#qS$CJ{(*6=jM(#`{sPT zu;KOIId_*d{<^&B&&{2`Z*2d0Vg2jfvp*hR{`K72Psf+tUQqMx{Q9paSA0LS`tz|R z@AuF9d}`I-Tf6Qqto?Rr(}$x=Zq2Xxadyq0J9~az+W6_{;`fKz~EUDnjuNJiguHvb1~oR83;)laC9ezVM#7!N9=4nB?v5viSI|_fiZD4D2PIzOL-g z*!j3r8FmM)(Pdy@s;vr%C<#g|S12gTPs_|nRVb+}NL8rFEnr}<=$#tsIPJCp$KB#C zIY%~@ONDbz-1@p#uli42!K9BC^U9?5Mi@EO{7~@OUtj-kk^IN(TORL>_Ev5@dL%C_ zcBe$N>Gr(YJkN6Prp??` zIB~amTO0<_JC5wWn_Jvz%f(W$*z7QW{+d~FLp-r3&jaHB*(I_~Wexne<<*AA=h*Z3WHtL)n%J|VZB`Ju^=5G95S z-I8p_jkDPwKZ;s>&adUd)UWG%-Bqocn$PhR>q+v@7Vt8B%dQ{Z_}6-c?6h?S^VQ`4 ztdWX*{d;L+z>^k+n>+|OE5EV}riTB5rYTUU6b z*1{bIj%yx#cyp5Vld8d)NqW0Fp4JC1)z;HKa{3)_iqb?i#*jB{`ie(;7QJ#$@Oq-* z(K%_0!n%Em-}#>~Z;)wx`F}#9&Z^x?mQv!&-9B7?XY}HV5ZA0LKB^}}JKqJc+Ab(q zU41V}eENjnuay{E_>NrLie|fj~zq;Mm-4VRkw#xE+y7I!;e#K$8cR%v}^-Xg^Z_?{W z%^}Lxl6>#)3;klg&)zln&*Qc63=9mPJY5_^BrYc>G%%{Av9YZPOt;VqJ-n%!iw%)gV`+EEOCO+lnb@TQ$CjLI+IeEEH z?mV-*9TOYtBk$ihawX?i&rIWV-`NJYWmX(LG^z36sj72#dMZDQ-?-5<=UnFT0}1a1 z_7z;raaZricRXRb#A Mp00i_>zopr07KMV{r~^~ literal 1701 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 literal 1607 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 Jvd$@?2>_u%>a_p> literal 1590 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy@r-Xt`;sP2Brok z=7xr@MwX6dCN8e7jz)$iZpLO7aJ`;+#U+V($*C}VGc!}HAbO46@ana4E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*{{JTC0(r`@unRg!~xy?k);@_ymChA^EbRT-8@ zw$82u35V#&W9#LMGhX^Q`;{KpsI#Vawr=;@lYbc*1t*_$bnI$;YO&?pmjv}aZnC?U z9W0m45#w5Mk-g?VzsHxXJUge*87&_sRs{UvmuVMUlbBp8d9qP)7u!L>c|sKdMcsFI zo_T%RT;0SaRCkz_-CS^{y02 zCP;pnZ`JG(bB`~&l-K+H{d-G}v$y;@_4@Tu#?zY^qeSxa*(a*7v^1#%Noz+`eCE>0 z{bXhznwItIdW!^mrpDCj?#nYhrzyL(C8rz)*IG|6p!YISWx_U@j*)vekV>_;8#Zs#!dEsS6Jq_`_pqT`tA zj%Ag6yBBCOrs~hk`nqvl$jUEzF45oKdO6IB{T9EYv48vVeHL39KlYg{+H^h2%Q~loCIBTRP(lCz diff --git a/toxygen/smileys/default/D83DDC4F.png b/toxygen/smileys/default/D83DDC4F.png index 45a633a8e97d93905e17d59c76fdf00e9ebfe042..f51185767d4eac8a86c21f906ca1c6f3d17359b8 100644 GIT binary patch delta 1690 zcmZ3=dx&>}WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LM&EpAgsoO$`74{|Avx47kYCEir$a z)!#H*oo`A@9xc17VtVb&vPq)N8THg3*Mbqh)nA0sW50=zl0ULOG zZu#|T1z^)spKqOXx*jZjx+Uh`qS|Mh`%kyTobOCJ(;5q6^rYNdRP%hxq(>{79xQFV zIyvvfj;S~1lz%$B@a_J&cNf;a+co{lw8D=^7JWXw>gK$vt22tWV%dbx_dVgT<>)kW|+}-o}^y=5UXMH=j_WqLk`aAQhAFOJ* zF}LE=u_fOwZuom|-_NUCKOdNTYfjmtHEkbHEcJYL;;XMWYI?Nh&>U;p>kuGd>8emXes?!ubqn)D^N^Kq$I{`C<}VPIgYuL_AM2}&$iC@9KL%gjktD5)$+Rj9}5c)%p3hx2j%+NSifyKqdWZH^)=Aks{#v!?WLik_NufmxGf#f`{apW-;QnR3 zDN`Sw?eLVJiE;^{ob+1A0^JrnD3jsY*)zJpe^;)fsMJFXKwlV=tjg* z@w=;}sw_kfs-;P8Dx7$Aw$42s>9y=Ld}AKn4Z9ta)XT(paYb*3^R(KB2IoEO?j*bu zwElAA;8L@$YUv-ftur z=di7G$z_4yP8J9KDvLMkSIwQ%oy|KpmSdsy`*>@9RjWzOXF1CCX7bM#STdvjU-R_c zNB+&d!slJSLw~w|omlUxv%eQMHisT=t>C@sHnSwmH}(DO2a%smbfY$35UvtCxn1h9 zjBB=qXT-!dwvSrgsvX?2%S2`3ly^GrV>Q!U;@}<9dP3`;mfQ+S-XqHcR2HZmlw_-l z2tOl!7)ySwcTplsc_~K z`@M%`3O;zwYYv-Tds; zoc*Hx^*6WsH|B_Vo69eex_Yvt;{UV@^7%qtdD3zUHyIchetNn%hDcmaPDn^dN=ryg zWnfA(V=Loh^JCM?lZ&&Ht6R{~ut31Nc|*sF8LM}+%w8fA;1cCFt&7{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>14MrpB%&2Brok z=7xr@MwXV&MoxxiPR53&rj`Z=1I*h52Bxc?E{-7;w@N16%oYw5Ikx|d%H%UC zD^L1dHp>jUtQwoM^;&?Yh)Dcar6z@-z>k3+9BTx{13t=MX!0+0jCbi;^dsuGLdUjs zS8m&!zXV$FRNs{N6gal7#zJKQ7z3RH>^NQE`?QJUE?f6QWcf!2M?PucrJrK6+ z*C=Q-{OA2yw?rt^(qkUau}NEO-!{y7ix&>Ut%? z$hL^^{;4}7cf6Xd@Vtm)U+vV>sh6g=3jLnxWAjZ({d7BLMF)ep1lKd(JpvPqjdGUy ztNDCd@j2*eC|KD-_O3C)0=$uThAjop#^;l&wHMHYMuno0EDdLnS}c24b?Us8^2V(H7uTGfy5>&oNbw2{L}S$<+{T+=MCpya3b z6|}kq&zP8=3)5J5V};Dc1E066_+PDC%YO2z1 mQJC%*zv#j7TmSqS*%+>#(O4$6xM∈_!6!b6Mw<&;$Va+J+JU diff --git a/toxygen/smileys/default/D83DDC50.png b/toxygen/smileys/default/D83DDC50.png index da64391506240b4a0dc3747899bfb01eb9b1c4b2..8809893542cdad2564b98e6fa139b8a164bb6a3b 100644 GIT binary patch delta 1556 zcmbQkvz=#xWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817mi8Pl)UP|Nr+?dhe?XJlr0AW@f?B z?t~+qac5=~9cqi(Ul(|8Zt31?zrEGI=jN6j?TY{Z^6dY9llns~5m#5Wo}Qj}d3odI z<&CGO=UrXZdZ;z({JipWvx{!6?>alP;OeT@V?9YndlIj%ZaX)-`276xLP*H^b*Th;t%&x||un|d!Tth~JeBzI+bhid#^68`Ehsu-HqKRC#1bPzVzPa{{R2~v)dS6U|?W~ zD+%%g#U29+kZ@fT6|%xf+VaoLSKE0wcm1*7yXeo%S5beG6T)B3{Im2x{?A2!7X8^$ zzx3DApG&`$K3~21<-?8N);+KIy!y|&4~?JSKKb09pV^i3L+U_N`ZyO!8d1wwzoR8g?W2s_2Rryqi3ECrSR*DfeLA%xTSD}YuwkJ$Kg(7mbI!zQsR(46;uvuyvd`?YCh$o+Y z$>XK28SW1zo$&EU^|I>B&Oh;){S#wE|AD9fC#>udmpNz{H^=RH`~6bg<&y#em$>C! zk_ioKUlu7|F!#2c<+{gG`}BkyPBq?`W`44E|Cj$aphl#*{a2CQX|4?A5b(2OqqAI(NpT zc@y7Fo;h{yr1QrY713237_JseDIwqG-s?PsvQH#H~RjxE55tOP07sOw>@|Vqoxe L^>bP0l+XkK#qZ`8 literal 1564 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`Gtf;oFf&vz zGto0NF|ahT)KM@pFf`CNG}1RP*EKY-GBvj{FjRm7B|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9ucp-l$oBHmzd*{pIn-onpfiKVyje< zTcDScnPTN?XldwbWbA5a>T2X_XlQ8RW@Kz(VQk=J>f&T-Wa0`l1DjrFM^__9b0-s5 z3kw%RLsvr!3r7PdCsQX2Q!`TwH#0MsUeCPZlEl2^RG7V)nJHFKy~cR;S~(Y`CYIzE zh2-bwz*0a!Mt(_taYlZDf^)E`o}of`W?o8uc`+z@z+rFYl3J8mmYU*Ll%J~r4qvNG zEcUxv7&#eRx|&)Tx>|tzZRuia>SpF_?qX`;>T2X@s07uULQa_J+bP)SgAxZ)Vu1+- zxwwIt;B*a2<8}&&%#)gzVyjf7WN(+e*WoM!1Cx)Zi(^Q|ts}wS*}{P$cE&R^jVjY8 zsTr;}^WAc5P0SDWD5j=7rdAI}f#2*$LZgo)Z4B5wp(7|fPfMh`+bKZnsNP-8)~>D< z8+~V$S52NdBi-0|9%o3*&Xb3j+pFI_thsOb{oebau0;nF+zvM!d!rX4+tZNN-thFx zfscnT2xebUdRos^5@!7(hc6?HOJQrYgRygzx%{jStxbFKzOkl5wr(?2rr<-L_G@=A7W%jkW!>vayxlF7mfGq0$wyq$S_ zk8*>{8YOLGk9j#V)03>a-}>rSS40=TT{PR(@a~a|amG5Hn(T5nh;j7Sx3xhy6ocT3*t^GMzrN^W=v9aU-dG? zk#%*8ZexS3KvVqEm=FuAik2HkgH_jExwKOKCI9;af-=WH?w;^_3;WUazrB+lW?26I zSZlxh`1BL?H`$9myj{ulj{Bv4@0sWJ?^7>6H+Xj8z0cjKAnru-!tK%eY2mu*e{2=! wS2p}?pYghR#rDtP|30VBDiUK?`k{M(fpIId2h)lb*FojAr>mdKI;Vst0P|-_T>t<8 diff --git a/toxygen/smileys/default/D83DDC51.png b/toxygen/smileys/default/D83DDC51.png index 0eeaeec65ac979442538dbff019264244776bb0a..d05d576c9f95ea8bd686b21eba058a2802361d76 100644 GIT binary patch literal 1832 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4rT@h1`S>QUNSs54@ z6p}rHd>I(3)EF2VS{N99F)%PRykKA`HDF+PmB7GYHG_dcykO3*KpO@ICiwuL5Z5y^ z)J{)VJvCMN#AL<)XD*zcrUFMNrzoDBq6m>aK2Z*iPEA#Ys6WyxeWX|V#AL-2lNI5N z;}hi$b%`J95{IZqW*_a7InXY0v`+?^ai~k;K)dLH4$(th5{J7b4|a-u+_P+jpY_MX zt~=|vwpVjN(6&leWVEH689#b`GU4=e)ii?fV`s+&T`|GoAZp}K>E%WM3%?dy3H^)MMUpfBg+WF59 zH@(^K@pA2)qd_sxSImBQ#Q*c7&3~?+|9$!R^Zh;_54miOh}jqr@o25~S|_g)UZE$v z!&ck6y*m={WRvmEI<6%iYIAKB=b5SRaq!;j=rhMiZHc?ulieQY=W2aB>GAcr%hPp! zQ`HnFDJni*>GSod^QR*&C;J7jFHn58-Qnx`g#T9~{$CCIayItCMw|1~<@PsoZz*T~ z|NsA~ZQI^3Ffe$O1o?rI8v{e%_s))sC+h$8w{-Wheh|8J4 zxNv6OWnf@nFY)wsWq-!b$EC`w6#aKP0|Qf4RY*ihP-3}4K~a8MW=^U?No7H*LPc%? z1A|5H)KJe&a}0Ry{uZ%px|q@D%zSUD)@d1g{~~^qv;K7jqGp@cEPog|<-UFWRQZo4 znXTdP?(Mmil4Bk$zRGRcGqc<0q%Ypf-yW^rRAa{3X|*~hCiSxx&)u}*iO;$pFW98N zX|+U}M9MKmrFVORpI(*LzsJL#%WW`w#j)*cw^<*L<7BB&zIK>D?%1(lTieQ+jc>%% zpHw!jGP}7&jO+QXn$1n{jw*7&l}F0CvfjJLxcOdzF#hG zdlYpPj{Ms#Uck82N%6$9Ggo?e93OAY4|Y7XOL^nNs~&GQicL(o8SsjKzHmYP)RhmI zPj2IVX!0XOnPG{zF;8CqD%qaR-E8OmI5oqs?KhM2DPdusEB9=Uv7C|Kf}~shdtz9> zyvFAp7P{6$+fw{-ro}aw*Tf`7kYPOd$s=Ze;F!IcU_)y)h)cn%f60v z&-5F+ekf*!s2^VVQD3v3Vb74L#=T4sW_C0<6tfq#JQG$k^rmnW``W0)|tXj8n?Mma675N!CS$Ubc+1c-3ym|G` zFzH3^i`Vln7!_W6q8YsW>(}oR#y!S9iz1{Uy+AMF=w#3AQ#En}wmlxc5^W@E=SI^EJVq(ZP_AA&m zL4G3x1A}UbYeY#(Vo9o1a#3nxNh*Vpfsv7}fw``sNr<6=m9eRniKVuIft7*5iQ*;C zQ8eV{r(~v8;?}TJPbQjyfk6^vLvVgtNqJ&XDuZK6ep0G}XKrG8YEWuoN@d~6R2v2c O1_n=8KbLh*2~7af^ju&7 literal 1711 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7BuiW)N`mv#O3D+9QW+dm z@{>{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy%vrJCQh!7CdQ`b zW`>5YMvi6{u9hw?F2>HLMrO{&aJ`;+#U+V($*C}VGc!}Hpn8q*>a}t%N=+=uFAB-e z&w-_YfQCWi3aPNUc-kij7z^!SQ=Gx@Elj|HL!otU^M;zbv&s&Ns?7 zs-0V#y@#`LhJMDp^DXYJwVv6n7jmYYUolx`-RG#QFIjHB^_k>a-mFu#-)WNSt(!k| zWJGQ+vXVJ;ZK9e&joIY~ht`?xh)WFjJoEKcNf5u*s#{;RjRF<5xk?XI8?Ouzd}*d# zre-3#Oi?_Vl_!rQ`fRD}%OfV6{#FK+ z9Z8Sl$rse>55?b8(A7T`U$ZYkgP}gMStaSE3(uw;rSh|VsY=r`-)=wClh1MSqk)>e zozTe>`s-Shjk%`2-@=tLh408yo0fKVgP$)IMYCJ3&u0$Re*7lyvDoHysg^eHgiG$} zsKhFJ@W!+EzdL;4E#GQ~7Y@8v9wckDxOg zr|*<=bHTGU4$mt&StIp!@wH8q@x=(+26o$4~R*L4aaSy8+9O}H0*{K}0TR~@y| zjyF$p{J^te`&yZGOf#({TCY#%%`+;ouhzZyU#Ge%s_?=St->SI_q!BTZ+`f3eT#Yk z%ktX3J8X}NwUide3rhdy(=}>MQL^=Doc#6d`#6Km^#`)vp4_`CG=)#-_L1`{n>fuE sb_xp~c9H)$p<;LX|LWI1`&lIzWD;ko$@@H?52`{uUHx3vIVCg!0C1Ctr2qf` diff --git a/toxygen/smileys/default/D83DDC52.png b/toxygen/smileys/default/D83DDC52.png index 897a3309962a61bf035e3b4eed167ff046e25a7f..4f4cc0ff7a35ac4726b52c9a71ca747c41547886 100644 GIT binary patch delta 1903 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 diff --git a/toxygen/smileys/default/D83DDC53.png b/toxygen/smileys/default/D83DDC53.png index 5b9b401716e36598a949dba814c0391608b2fcdd..3a691f0426e9190cd5849fae391ba4ae7f9b66ff 100644 GIT binary patch literal 1297 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 E0N{<$vj6}9 diff --git a/toxygen/smileys/default/D83DDC54.png b/toxygen/smileys/default/D83DDC54.png index fa76d908e2b9b737e4102fbf95a0dffe2ad688b3..b095f9cbca1e8ee1c689f6857ec9e1be68c6ec3b 100644 GIT binary patch delta 1564 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 Ng{P~Z%Q~loCIEF5el7q2 diff --git a/toxygen/smileys/default/D83DDC55.png b/toxygen/smileys/default/D83DDC55.png index 23d1ebce769f39d8ca47684cc0bc2450c4d0822e..84a5d628f012618f46db6078ee9c4e0192aabfe1 100644 GIT binary patch delta 1632 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 diff --git a/toxygen/smileys/default/D83DDC56.png b/toxygen/smileys/default/D83DDC56.png index 3d3656b8a41c906daed9e1795a482a0c6cf1b2db..6e6cdf4d577bb6a2ca9cc17d3e7232e6a3101c48 100644 GIT binary patch delta 1313 zcmcb{b%tw#WIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081H;1rpAgso|Nk4icN#b~>N(Wt+Lr6s zRTwxm7+^c=y>vE;aV*w+f!Sd-rYw z*G^;iUYqd6Cf>bfelv_bCs>Cpun3xK=-y}QGsVPvlBrjZfpfEwXTQ1sOf%o%qXlz+U3%>&pI&osUbEXVa4hYzz!cbyXn|B|(Yh3I#>^Y4w>osR|{P1*r-ZxdjXi z7QItLqbJ=l;P~@g#IlKpW69$Ro!9!IKhJ!;>|q?Y{`l;iH(IY57gd@V{r&f|yY5Kv zmI*nR_Eu~lns*&>#(t)Odb?zY`6Jxq-ER($>7d`;8hO1rvz+J^5P z!c}t~OzGY>r{nO>cgfyH@0IR6@So(`pw%H>-M!+;jO6^odi<8f$1V1|-D$gJB@({E zV)^OQ=RZiqIjC=5bxYM`nPSg}F710N+J|1+$=#EU+QZ*3d!qYF1;4+HO9XmTc#h_iN-De!I`) z;8wXUbx8ArrYnPozMDkO`c-p#y0iJ_#R@rETVY!?5iqjuHlYh@y(XtImY#4mBGmf_N)bu7w9#&2_)*A zFBQAU`o@m&vIC!!SHLBu!k@f5>lEfUJmvBE-_CO(q1>&|L`vQL!(Fq?lAS_AdMoXw z)Tf*Yy2H1jUG3?l^t(=7YEP<6gdAQ>xnW~K!BF42OM#6=^vtjS_9+_Ezb4)P6+!y>``ZmS4+Oi&X9E>*d#lyY+A^6 zQuy#&!w=i6W$RDK+49*n9ysRAah!k7zr&H0wru;V6BR_(neVXgI<(t{(Rihpu{(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%E-{r%*DdV%+1l#+11F<(8<}r%+1Kz!qmvwz{t(q6lMlCy%v^k7G?$(E-pq+ z7KVndMwTv)hL$GIh88Z4hNi}jZZN%`dBr7(dC93Tdowdrte|?G@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{e^qI~%#u{| zc27g6_qBNkgDV)rkF|E@?P>7Ne;{eoAocj%i&Hxc@&p{8U+mwMp~5J`sQuDsSN5;d z@%5p?#>q0)yW3^d->$p*b8+#n#zQ4w z`g(8atIXBKd-k_BZ;ocsa=CTOVV%+$H;cWgQ|8wi2zM~a%(ZWRGN~)+NwZ$ZUHyhx zcV_(Ic1!PgB;xvwak+P*(V-;|KQDdqoau(;71yk_H{Ar(E*w>Vw54&yG^KUh14V-w zF1|he!QfHir`F6TGlLEaG1MFFdQhV0u&;2J#O3MC8%hg~9h$mxO3J$>OOu*EXKONa zrbMR|oI7#px)Jw<%4g@rnNFNmsh@eX$+z(BekMiNsT1PW*cvDC9obh~9 zhk%#Ot0uWR{Q%m{rj7y=RCGgP7^w6ldYANYQNOE__>|hVs`PHzfaHG vabsJW$@gnK0bkLp1!#upOKBBhiya0pZ-i9P)XgTe~DWM4fp0eud diff --git a/toxygen/smileys/default/D83DDC57.png b/toxygen/smileys/default/D83DDC57.png index 14a977455de53e98e2d2f9b2757fb1829313c572..a795c98923d4cd3578ce1256faac0a9ccf937504 100644 GIT binary patch delta 1492 zcmdnZ{f~QsWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+0817lc#Pl)UP|Nj|^(?O`C8br2NF)Wo+gicUQV!wPGqhJRv{gbl3|+Mh-L+75Ujvj;mc>w>4P{s6g3PKf zX6UG9=xtzVC}F58WT?mi$@VoeOlV@5(ao@L1H;T7hW#5D&g^A4x1V9lyZKWSBRR zp}(17*BXY+OBi;pVc52kVci0T(|bT3+OmRS>q?NfX7w^mXl2;8f??l!hN&G4TURjb zSi>;Ai(&UVhV3gECbcq5>tZ;$n_<-)hAHg~`!_MHn$0k^lVMUD!ZhIx}1`WqRRPG{J-n4ztTVg985|NmQmb#Vd3Q%R6tFav|` z3XT<*FYiC#;KX?1L?H9ks|oB69^~`Btf~9(Q6YZH?mHho+}S;48c(P-0|NtNlDE4H zLkFv@2Ll5Gdx@v7EBiBcJ}y^X_+~x3MG{VsR|Xj z1q=)py;DPDugx*ws7)7*bz)&EwCpp?w_V;JTc>aFIO6!W_^&dP86JKr7Jm8syZjg3 zdCPiJranB|?W^`}!>dzAZ>-vRcAIDVyY>vfw%o%K2vk_lz#z230!9+1h=@XwKVVHZ8MZQ z_xsDAuotH!X9!txXgYd!AG*r_7>H(j|omcm^Uw9<&sYXVn;lj!drv8%{`MJ+DPkMUf$=)&Kd3?Qt8Q0ab{43qcjb!irSN&4YeRf0Fq}gAlu`w_(9P@N>43W5;oRGl6#H;4! zrp7DK-N>l3;N+P@hgP+!ELeN^*t*5tDhC!W&tGu7Q{_NvMoP+=R+R&}Nm*HkdIcUl zG&MHg{@`&V_lM}Hm(jOxd^*g+!t(Zwtjydwtt<X{e1a& z`+EI)L%|IV6%Gjj1s)j@6)sCGJRF>iw5-&w+3H!?SvdwVX(&vcG3CvCf$586r_B=6 z<`tRXS#Es!)QK~v&Ye7adO9oDiIdtITAJ44D^{#p$$i?8fr0&-g4ENmw+=8cFsPQe zMwFx^mZVxG7o{eaq%s&87#ZmrnClvvgcurF8QYs$nOJHY7+4t?oG4!M97RWNeoAIq cCAtoThMjsc(GwNbxfmEcUHx3vIVCg!0ISGz*#H0l literal 1467 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 diff --git a/toxygen/smileys/default/D83DDC59.png b/toxygen/smileys/default/D83DDC59.png index 4d2cfdecae183203309e7c644a59aed03888fa00..8ebca9a1bf123ec014cd99902b0fcf85f9a65f03 100644 GIT binary patch delta 1462 zcmZqUdB8nEvYwfNfk8u;KbV1mfhFD1*O7r?V?XzwL{w|-SOXd`&X?srv#?|KF#pocgIVE&8I}BKagK=MP&AUg+(WY zr(aW9_`z=D7rTug&DVW2-|)qD^Jlw_H#8UBQC|GtV$*-C&HwE-{dd~%NOQ?OsfFLv zH~ur&_*ik_HIW6|*xUa9{~umpdXj;G!M-HOuRfT8p__@L#V(*iwA|)(z-y!nZs-U;;R*HoyU%nhBdgF%jyLU<{?FA>p6`qnxqUo(G(T@)%$&P6dw#0Tt(4w=JJ;axo}JG+TW6GM-deoEVSAPG z+Kp^_vyQBqy)wDYQ7|THnxeG$PWP#%s~#`<&hoj{``zDn_0b-uGz1^?{_?O-xy!2m z_2tRiJm>N%o#V}SL|n~lwU{ey*ngx#_#t0?mkv{i;~tA>m!n0!o2~sSPt9b1_V<40 z?~v%_kGQ5jD3bca(f^ROq$l*M|3X9G#I|r}!>%Yw-B;Py z%xk!9?c2h8;#*&VR*jY!#! zufkjYaqF0Vmj_tu#6Gn1wfhliy7}5ScV^~tUtytXsk1XLEwj?D-p_Kn-YWj`Dl_4| z3275|ZjL!0+SfImr89&_Wc?67|Awjd$*yU{A1Qy8K`9a75U4Max+Y_+Y=*F38Ho!$RhbX7Q7l`K8xvRynIy1H*1l7sn8Z z%k`l>u0n?b1X$BIJb33nC}Qxr4P aDsk($_{F({(JaZG%Q-e|yQz{EjrrIztFsEgPM3hAM`dB6B=jtVb)aX^@7BGN-jeSKyVsdtB zi9%9pdS;%j()-=}l@u~lY?Z=IeGPmIoKrJ0J*tXQgRA^PlB=?lEmM^2?G$V(tSWK~ za#KqZ6)JLb@`|l0Y?Z*~TICg6frRyy6u?SKvTcwn`GuBNuFf>#! zGt)CPF*P$Y)KM@pFf`IPFw!?L(={})GBvX@GFN~CB|8P1qLehNAQv~NT}3Hrwn`Z# zB?VUc`sL;2dgaD?`9X%D*TxJu@#c$0a|xG&eP`#M8xAsUo*P zFC{a@%GJ=)(ACJ;)zH+{$kou$(8A5g*uuitz{%9b$<)Zi6=nuDy>5=CPG$zqCYH{Q zE{2A#Mvksd1{Ovx7EUfM<`!md7BIb@dBr7(dC93Tdowdrte|?0@#?j5E=o--$uA1Y z&(DFSfPjqrlKkR~`~n5%U^4}c@XWlF{PJQ=Q1pPq-pVDlD6=dz#jPkmR{=1I*N78y_+B^7 zzO>~^@a<-~7=Nn^elvpno2oRbg!GO6H)x(Yy@XjX@WN($hdrwvw8+XhC*JCQoyoCj zA)nRph}BoNMdnOi_Hl2Ln`f7}yv?V5^Upv3{c!Jl^Y{(9(sy=B>?kd7)c-Zd^lA(5 z z%gG9}rAL%c31!+U8}NMgu~X>2kn0@6b5coNM=>WjNVj{TMuA$-(IpC*b8F`Wzi|8g zcKZ3*)2{>wY_S!d)pjl7jOgYgy#=>!rA_T_DUn^ZcJIDx$rqS^zv>T*U7daQ^t9Zy z(%UR+mz8Y3wEtF&VSMIHe#^PGSGKNMzGCjp_jh7i|8p&GKJ@9yTk9tK4QGt@^}pEN zwmAF)L)Hmai3W`%&m)^|+`YIh^T}uVyfaGQn|MUdaJS{gr00GMv=N-|r?zz8=Bpcj zJwNkF=Y|jewrt)XaW~g4{>CLF{_;}>i`Kowu)=FSFT~@Q3U?l!8PQVb{YLQjgQ^W% z7au%!?VS4A3+Er-ta07jW%<;S*`%|z@?&jQi0upxZv_^{ZPNP_7DvVA&-}%CCr0{; z@s~w+K3W;fmwf6T_PEK$?e*RJ_OE?|t(*E^&5pLd(vYNR5dT2(fAOl1Ufj>EiuK!H c{^KxU@U-CFb&%U`AE;#ZboFyt=akR{0A_1U4FCWD diff --git a/toxygen/smileys/default/D83DDC5A.png b/toxygen/smileys/default/D83DDC5A.png index f72b865b1a16f85c94438d162a5da659fe5dbd43..b065c3b01f5edb2b9affe71b7a22292f69688ab0 100644 GIT binary patch delta 1615 zcmcb`^MhxCWIZzj1A~Sxe=q|B153K2uOkD)#(wTUiL49^3<}8}LB0$ORcZ_j4J`}| zzZe)88eT9klo~KFyh>nTu$sZZAYL$MSD+081LMvBpAgso|Nr+I`?MKH&b3IKYmwM) z9JSe}dUsgc&V=qA@tvD}s^MsZd-(?Uvduo#^_zXFcZRgUCD0h#0_wK~)b9#y-5%Ju zEwFL3PxUtcx&u*N#}g(VkDs{Nr@G(7zt`Ajf{FiRv-o9Bxu*-}oyeWN#3AQW<-M3Rgroy_tXJMCSD8vrayrapL}j{oVC8i8~W|j%G~hvQE6$ zzwhOoQzx@#T&r6Buxt03%B2rGcU`YpbGvcF%Q>fC&pUInVAidw<*(+Qc`@_k_3G98 z(k4Ecap>KWbLVQ8y*}jdXC2v_GT}gC|LK}#XBt-QOX)k3+JCBU*{RxP2a+Zn zi0`}Gx&7s=lV_`!-EP~uKd$#cLVe%e?j2_e=iI7U`D*UzTdkW8B~G~Cz2ohIvlr@C zzF&0y?SeCR+BP3fo_Mcg>$|z9&KAzPQ@#A{oRfE2Hy=%%a;I_q>k0eMl`VWd@xZJ8 z{dXGHA4#2ZEPd+js#Q;$ww_F#bS-n{)8?%=D_0&)o^+*f(Syphr?O@~uHNvVV(pcJ zg@=aQ6yzx;l_{!6p})>$Q2I)<^4+}k#Ig%(?bKIqWZ@I*J-RKaPmnFN(8FbS-lWprRo)@T z9rIZ}dm3He_e<>5-g+07isXAQ+x31QTWDwV(}4MAv-%qg#hoG9s_F}iZQDG$b5>n? z;D61taaD)-CQo1P=M&aWICVq(rNsC3>Y2{Dl3m;(PM@AmO|M|mUl4rc%pajyOE#YT zkR@?%4fmm!hH^KVqBimNGoP4zu7=;A<^DG21B)*>)Xtc=f`gmm&i^;8^)U`B12{fb zrewLbE%>9=kUG+gfO3C?Hc}b+)yy;)rQu*I~QQMdCO1H{VLUEc@>C6Jz`Cjai@X znHX%4^34#rG%a=0%^ZVjdzRB?yE^N0m}IY>-7+ho#QCA`+Beo}Zl|2q%?T|CxFosoreSMjULZa#B+p~kxgovm+5AM4Eu zsDHM2BEuxVPTj?mWhx>P{JfqCPZt00&2?$HjDkXF8&_*nQF7zWk|}dl9;`LlY%=F| zfw;flnZqXzO8(rPYX4EUc6x#MKHK+2FV}ag+`VrSzV&aI&6526Om~I0=hb<%UX^@! z;-9`}J;To{T`LZs|6azxz;Mgc#W6(Ua&kgKLPAP?Qdn^K^@POK1_rY-K40Bj*;rq{ zGBbhs-v0LwTsU##$cZaH@%`Ky2XgZgGgEVuv(ximJ~+WF^5AK4(Y7t+8@FsPDSa%? z@KR8u==+B+pT2$kDlF7CL+g>efP{#QkdzwJRyPwHB_k~>H8VdREd{yx6J|U~oH$2` zPjrHZ?`O~R^(W4FO>E%eNPqt1*;8?mWnNCKOS-zey-uIf?pm^d>C)8DtEO8^zw#Od zFy1`2?Af$y+rGI)u4ssHPY*Bm&yU}46XM_)=vjHlMO4)_RDqqr>XW`qfX$Pi3=9ma zC9V-ADTyViR>?)Fi6yBFMg~Skx(4RDh9)6~23E$VR;DJ}Rt5%E1_r5oM|Yy=$jwj5 dOsmAI1FS(LxR!OIqB<7?gQu&X%Q~loCID_T0FVFx literal 1626 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 diff --git a/toxygen/smileys/default/D83DDC5C.png b/toxygen/smileys/default/D83DDC5C.png index 4fad011bad1753b2f6bb25e1d4775b89cf01fc43..2dc62a91cb329067ac2f2341dbcf79b1131d654c 100644 GIT binary patch literal 1842 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