private messages support

This commit is contained in:
ingvar1995 2018-07-22 12:59:52 +03:00
parent e15620c3ad
commit 5521b768bc
16 changed files with 384 additions and 65 deletions

View file

@ -32,6 +32,8 @@ 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
import styles.style # TODO: dynamic loading
@ -42,7 +44,8 @@ class App:
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 = self._tox_dns = None
self._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = None
self._group_peer_factory = self._tox_dns = None
self._group_factory = self._groups_service = self._profile = None
if uri is not None and uri.startswith('tox:'):
self._uri = uri[4:]
@ -336,7 +339,9 @@ class App:
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._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_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)
@ -357,7 +362,10 @@ class App:
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)
self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms)
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)

View file

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

View file

@ -3,10 +3,11 @@ import common.tox_save as tox_save
class ContactProvider(tox_save.ToxSave):
def __init__(self, tox, friend_factory, group_factory):
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
# -----------------------------------------------------------------------------------------------------------------
@ -34,7 +35,7 @@ class ContactProvider(tox_save.ToxSave):
return list(friends)
# -----------------------------------------------------------------------------------------------------------------
# GC
# Groups
# -----------------------------------------------------------------------------------------------------------------
def get_all_groups(self):
@ -57,12 +58,29 @@ class ContactProvider(tox_save.ToxSave):
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()
return self.get_all_friends() + self.get_all_groups() + self.get_all_group_peers()
# -----------------------------------------------------------------------------------------------------------------
# Caching
@ -84,3 +102,6 @@ class ContactProvider(tox_save.ToxSave):
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)

View file

@ -2,6 +2,7 @@ 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):
@ -223,6 +224,17 @@ class ContactsManager(ToxSave):
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]
@ -340,6 +352,16 @@ class ContactsManager(ToxSave):
num = self._contacts.index(group)
self._delete_contact(num)
# -----------------------------------------------------------------------------------------------------------------
# Groups private messaging
# -----------------------------------------------------------------------------------------------------------------
def add_group_peer(self, group, peer):
contact = self._contact_provider.get_group_peer_by_id(group, peer.id)
self._contacts.append(contact)
contact.reset_avatar(self._settings['identicons'])
self._save_profile()
# -----------------------------------------------------------------------------------------------------------------
# Friend requests
# -----------------------------------------------------------------------------------------------------------------

View file

@ -11,8 +11,6 @@ class GroupChat(contact.Contact, ToxSave):
def __init__(self, tox, profile_manager, message_getter, number, name, status_message, widget, tox_id):
super().__init__(profile_manager, message_getter, number, name, status_message, widget, tox_id)
ToxSave.__init__(self, tox)
status = self._tox.group_is_connected(number)
self.set_status(constants.TOX_USER_STATUS['NONE'] if status else None)
self._peers = []
self._add_self_to_gc()
@ -57,6 +55,11 @@ class GroupChat(contact.Contact, ToxSave):
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]

View file

@ -3,11 +3,17 @@ import contacts.contact
class GroupPeerContact(contacts.contact.Contact):
def __init__(self, profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id, group_pk):
super().__init__(profile_manager, message_getter, peer_number, name, status_messsage, widget, tox_id)
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 None

View file

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

View file

@ -2,13 +2,14 @@
class GroupChatPeer:
def __init__(self, peer_id, name, status, role, public_key, is_current_user):
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
def get_id(self):
return self._peer_id
@ -48,3 +49,8 @@ class GroupChatPeer:
return self._is_current_user
is_current_user = property(get_is_current_user)
def get_is_muted(self):
return self._is_muted
is_muted = property(get_is_muted)

View file

@ -6,11 +6,13 @@ import wrapper.toxcore_enums_and_consts as constants
class GroupsService(tox_save.ToxSave):
def __init__(self, tox, contacts_manager, contacts_provider, main_screen):
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._peers_list_widget = main_screen.peers_list
self._widgets_factory_provider = widgets_factory_provider
self._peer_screen = None
def set_tox(self, tox):
super().set_tox(tox)
@ -23,8 +25,12 @@ class GroupsService(tox_save.ToxSave):
def create_new_gc(self, name, privacy_state):
group_number = self._tox.group_new(privacy_state, name.encode('utf-8'))
if group_number != -1:
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']
def join_gc_by_id(self, chat_id, password):
group_number = self._tox.group_join(chat_id, password)
@ -45,13 +51,13 @@ class GroupsService(tox_save.ToxSave):
def disconnect_from_group(self, group_number):
self._tox.group_disconnect(group_number)
group = self._get_group(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(group_number)
group = self._get_group_by_number(group_number)
group.status = constants.TOX_USER_STATUS['NONE']
self._clear_peers_list(group)
@ -63,7 +69,7 @@ class GroupsService(tox_save.ToxSave):
self._tox.group_invite_friend(group_number, friend_number)
def process_group_invite(self, friend_number, group_name, invite_data):
friend = self._get_friend(friend_number)
friend = self._get_friend_by_number(friend_number)
text = util_ui.tr('Friend {} invites you to group "{}". Accept?')
if util_ui.question(text.format(friend.name, group_name), util_ui.tr('Group invite')):
self.join_gc_via_invite(invite_data, friend_number, None)
@ -98,7 +104,10 @@ class GroupsService(tox_save.ToxSave):
PeersListGenerator().generate(group.peers, self, self._peers_list_widget, group.tox_id)
def peer_selected(self, chat_id, peer_id):
pass
widgets_factory = self._widgets_factory_provider.get_item()
group = self._get_group_by_public_key(chat_id)
self._peer_screen = widgets_factory.create_peer_screen_window(group, peer_id)
self._peer_screen.show()
# -----------------------------------------------------------------------------------------------------------------
# Private methods
@ -107,15 +116,18 @@ class GroupsService(tox_save.ToxSave):
def _add_new_group_by_number(self, group_number):
self._contacts_manager.add_group(group_number)
def _get_group(self, group_number):
def _get_group_by_number(self, group_number):
return self._contacts_provider.get_group_by_number(group_number)
def _get_friend(self, friend_number):
return self._contacts_provider.get_friend_by_number(friend_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 _clear_peers_list(self, group):
group.remove_all_peers_except_self()
self.generate_peers_list()

View file

@ -46,8 +46,10 @@ class Messenger(tox_save.ToxSave):
text = self._screen.messageEdit.toPlainText()
if self._contacts_manager.is_active_a_friend():
self.send_message_to_friend(text)
else:
elif self._contacts_manager.is_active_a_group():
self.send_message_to_group(text)
else:
self.send_message_to_group_peer(text)
def send_message_to_friend(self, text, friend_number=None):
"""
@ -57,10 +59,15 @@ class Messenger(tox_save.ToxSave):
"""
if friend_number is None:
friend_number = self._contacts_manager.get_active_number()
if text.startswith('/plugin '):
self._plugin_loader.command(text[8:])
self._screen.messageEdit.clear()
elif text and friend_number >= 0:
return
if not text or friend_number < 0:
return
if text.startswith('/me '):
message_type = TOX_MESSAGE_TYPE['ACTION']
text = text[4:]
@ -77,7 +84,8 @@ class Messenger(tox_save.ToxSave):
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 self._contacts_manager.is_friend_active(friend_number):
if not self._contacts_manager.is_friend_active(friend_number):
return
self._create_message_item(message)
self._screen.messageEdit.clear()
self._screen.messages.scrollToBottom()
@ -103,10 +111,15 @@ class Messenger(tox_save.ToxSave):
def send_message_to_group(self, text, group_number=None):
if group_number is None:
group_number = self._contacts_manager.get_active_number()
if text.startswith('/plugin '):
self._plugin_loader.command(text[8:])
self._screen.messageEdit.clear()
elif text and group_number >= 0:
return
if not text or group_number < 0:
return
if text.startswith('/me '):
message_type = TOX_MESSAGE_TYPE['ACTION']
text = text[4:]
@ -120,7 +133,8 @@ class Messenger(tox_save.ToxSave):
message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
message = OutgoingTextMessage(text, message_author, t, message_type)
group.append_message(message)
if self._contacts_manager.is_group_active(group_number):
if not self._contacts_manager.is_group_active(group_number):
return
self._create_message_item(message)
self._screen.messageEdit.clear()
self._screen.messages.scrollToBottom()
@ -137,6 +151,53 @@ class Messenger(tox_save.ToxSave):
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
self._add_message(text_message, group)
# -----------------------------------------------------------------------------------------------------------------
# Messaging - group peers
# -----------------------------------------------------------------------------------------------------------------
def send_message_to_group_peer(self, text, 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 text.startswith('/plugin '):
self._plugin_loader.command(text[8:])
self._screen.messageEdit.clear()
return
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)
message_author = MessageAuthor(group.get_self_name(), MESSAGE_AUTHOR['GC_PEER'])
message = OutgoingTextMessage(text, message_author, t, MESSAGE_TYPE['TEXT'])
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, 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['TEXT'])
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
# -----------------------------------------------------------------------------------------------------------------
@ -211,6 +272,9 @@ class Messenger(tox_save.ToxSave):
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

View file

@ -375,6 +375,26 @@ def group_message(window, tray, tox, messenger, settings, profile):
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, length, user_data):
message = str(message[:length], 'utf-8')
invoke_in_main_thread(messenger.new_group_private_message, group_number, message, peer_id)
if not window.isActiveWindow():
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 = os.path.join(util.get_images_directory(), 'icon_new_messages.png')
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
return wrapped
def group_invite(groups_service):
def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data):
group_name = bytes(group_name[:group_name_length])
@ -499,6 +519,7 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
# 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(groups_service), 0)
tox.callback_group_self_join(group_self_join(contacts_provider, groups_service), 0)
tox.callback_group_peer_join(group_peer_join(contacts_provider, groups_service), 0)

View file

@ -1,10 +1,7 @@
from wrapper.toxcore_enums_and_consts import *
from PyQt5 import QtCore, QtGui, QtWidgets
from contacts import profile
from file_transfers.file_transfers import FILE_TRANSFER_STATE, PAUSED_FILE_TRANSFERS, DO_NOT_SHOW_ACCEPT_BUTTON, ACTIVE_FILE_TRANSFERS, SHOW_PROGRESS_BAR
from utils.util import *
from ui.widgets import DataLabel, create_menu
from user_data import settings
from ui.widgets import DataLabel
class ContactItem(QtWidgets.QWidget):

36
toxygen/ui/peer_screen.py Normal file
View file

@ -0,0 +1,36 @@
from ui.widgets import CenteredWidget
from PyQt5 import QtCore, QtWidgets, uic
import utils.util as util
import utils.ui as util_ui
from ui.contact_items import *
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)
uic.loadUi(util.get_views_path('peer_screen'), self)
self._update_ui()
def _update_ui(self):
self.statusCircle = StatusCircle(self)
self.statusCircle.setGeometry(50, 20, 20, 20)
self.statusCircle.update(self._peer.status)
self.peerNameLabel.setText(self._peer.name)
self.ignorePeerCheckBox.setChecked(self._peer.is_muted)
self.sendPrivateMessagePushButton.clicked.connect(self._send_private_message)
self._retranslate_ui()
def _retranslate_ui(self):
self.setWindowTitle(util_ui.tr('Peer details'))
self.ignorePeerCheckBox.setText(util_ui.tr('Ignore peer'))
self.sendPrivateMessagePushButton.setText(util_ui.tr('Send private message'))
def _send_private_message(self):
self._contacts_manager.add_group_peer(self._group, self._peer)
self.close()

View file

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>400</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>400</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>600</width>
<height>400</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<widget class="QLabel" name="peerNameLabel">
<property name="geometry">
<rect>
<x>110</x>
<y>10</y>
<width>431</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
<widget class="QPushButton" name="sendPrivateMessagePushButton">
<property name="geometry">
<rect>
<x>50</x>
<y>120</y>
<width>500</width>
<height>50</height>
</rect>
</property>
<property name="text">
<string>PushButton</string>
</property>
</widget>
<widget class="QCheckBox" name="ignorePeerCheckBox">
<property name="geometry">
<rect>
<x>50</x>
<y>70</y>
<width>500</width>
<height>23</height>
</rect>
</property>
<property name="text">
<string>CheckBox</string>
</property>
</widget>
<widget class="QGroupBox" name="banGroupBox">
<property name="geometry">
<rect>
<x>50</x>
<y>200</y>
<width>521</width>
<height>161</height>
</rect>
</property>
<property name="title">
<string>GroupBox</string>
</property>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -1,6 +1,7 @@
from ui.main_screen_widgets import *
from ui.menu import *
from ui.groups_widgets import *
from ui.peer_screen import *
class WidgetsFactory:
@ -69,3 +70,6 @@ class WidgetsFactory:
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)

View file

@ -1804,7 +1804,7 @@ class Tox:
"""
Write the group public key with the designated peer_id for the designated group number to public_key.
This key will be parmanently tied to a particular peer until they explicitly leave the group or
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.