From c767ebe530bf5db4c3c9d4d24cf8091813c528b0 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 24 Jun 2016 00:20:02 +0300 Subject: [PATCH 01/22] enums and consts --- toxygen/toxcore_enums_and_consts.py | 735 ++++++++++++++++++++++++++++ 1 file changed, 735 insertions(+) diff --git a/toxygen/toxcore_enums_and_consts.py b/toxygen/toxcore_enums_and_consts.py index 4d52837..6942d3a 100644 --- a/toxygen/toxcore_enums_and_consts.py +++ b/toxygen/toxcore_enums_and_consts.py @@ -188,6 +188,729 @@ TOX_ERR_GET_PORT = { '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. + # + 'TOX_GROUP_PRIVACY_STATE_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. + # + 'TOX_GROUP_PRIVACY_STATE_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. + # + 'TOX_GROUP_ROLE_FOUNDER': 0, + + # + # May kick, ban and set the user and observer roles for peers below this role. + # May also set the group topic. + # + 'TOX_GROUP_ROLE_MODERATOR': 1, + + # + # May communicate with other peers normally. + # + 'TOX_GROUP_ROLE_USER': 2, + + # + # May observe the group and ignore peers; may not communicate with other peers or with the group. + # + 'TOX_GROUP_ROLE_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. + # + 'TOX_GROUP_MOD_EVENT_KICK': 0, + + # + # A peer has been banned from the group. + # + 'TOX_GROUP_MOD_EVENT_BAN': 1, + + # + # A peer as been given the observer role. + # + 'TOX_GROUP_MOD_EVENT_OBSERVER': 2, + + # + # A peer has been given the user role. + # + 'TOX_GROUP_MOD_EVENT_USER': 3, + + # + # A peer has been given the moderator role. + # + 'TOX_GROUP_MOD_EVENT_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_PUBLIC_KEY_SIZE = 32 TOX_ADDRESS_SIZE = TOX_PUBLIC_KEY_SIZE + 6 @@ -196,6 +919,18 @@ 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 From c0601444d95cc77478112b78a0b4075f3439e73e Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 24 Jun 2016 14:24:28 +0300 Subject: [PATCH 02/22] generated wrapper without callbacks docs and errors check --- toxygen/tox.py | 913 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 913 insertions(+) diff --git a/toxygen/tox.py b/toxygen/tox.py index 862badd..a83f94a 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -92,6 +92,22 @@ class Tox: 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_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) @@ -1510,3 +1526,900 @@ 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 chat instance management + # ----------------------------------------------------------------------------------------------------------------- + + def group_new(self, privacy_state, group_name): + """ + 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 groupnumber on success, UINT32_MAX on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, + len(group_name), byref(error)) + return result + + def group_join(self, chat_id, password): + """ + 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 groupnumber on success, UINT32_MAX on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_join(self._tox_pointer, chat_id, password, len(password), byref(error)) + return result + + def group_reconnect(self, groupnumber): + """ + 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 groupnumber: 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, groupnumber, byref(error)) + return result + + def group_leave(self, groupnumber, 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 groupnumber: 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() + result = Tox.libtoxcore.tox_group_leave(self._tox_pointer, groupnumber, message, len(message), byref(error)) + return result + + # ----------------------------------------------------------------------------------------------------------------- + # Group user-visible client information (nickname/status/role/public key) + # ----------------------------------------------------------------------------------------------------------------- + + def group_self_set_name(self, groupnumber, 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() + result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, groupnumber, name, len(name), byref(error)) + return result + + def group_self_get_name_size(self, groupnumber): + """ + Return the length of the client's current nickname for the group instance designated + by groupnumber 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, groupnumber, byref(error)) + return result + + def group_self_get_name(self, groupnumber): + """ + 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(groupnumber) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_self_get_name(self._tox_pointer, groupnumber, name, byref(error)) + return str(name[:size], 'utf-8') + + def group_self_set_status(self, groupnumber, 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, groupnumber, status, byref(error)) + return result + + def group_self_get_status(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_self_get_role(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_self_get_peer_id(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_self_get_public_key(self, groupnumber): + """ + 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, groupnumber, + 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, groupnumber, 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, groupnumber, peer_id, byref(error)) + return result + + def group_peer_get_name(self, groupnumber, 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 groupnumber: 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(groupnumber, peer_id) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_peer_get_name(self._tox_pointer, groupnumber, peer_id, name, byref(error)) + return str(name[:], 'utf-8') + + def group_peer_get_status(self, groupnumber, 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, groupnumber, peer_id, byref(error)) + return result + + def group_peer_get_role(self, groupnumber, 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, groupnumber, peer_id, byref(error)) + return result + + def group_peer_get_public_key(self, groupnumber, peer_id): + """ + 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 + 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, groupnumber, 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, groupnumber, 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() + result = Tox.libtoxcore.tox_group_set_topic(self._tox_pointer, groupnumber, topic, len(topic), byref(error)) + return result + + def group_get_topic_size(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_get_topic(self, groupnumber): + """ + 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(groupnumber) + topic = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_topic(self._tox_pointer, groupnumber, topic, byref(error)) + return str(topic[:size], 'utf-8') + + def group_get_name_size(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_get_name(self, groupnumber): + """ + 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(groupnumber) + name = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_name(self._tox_pointer, groupnumber, + name, byref(error)) + return str(name[:size], 'utf-8') + + def group_get_chat_id(self, groupnumber): + """ + 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 true on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_get_chat_id(self._tox_pointer, groupnumber, + create_string_buffer(TOX_GROUP_CHAT_ID_SIZE), byref(error)) + return result + + 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 group_get_privacy_state(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_get_peer_limit(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_get_password_size(self, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_get_password(self, groupnumber): + """ + 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 true on success. + """ + + error = c_int() + size = self.group_get_password_size(groupnumber) + password = create_string_buffer(size) + result = Tox.libtoxcore.tox_group_get_password(self._tox_pointer, groupnumber, + 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, groupnumber, 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 groupnumber: 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, groupnumber, lossless, data, + len(data), byref(error)) + return result + + def group_send_private_message(self, groupnumber, peer_id, 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 groupnumber: 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, groupnumber, peer_id, message, + len(message), byref(error)) + return result + + def group_send_message(self, groupnumber, 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 groupnumber: 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, groupnumber, 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. + """ + + 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_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, groupnumber, friend_number): + """ + Invite a friend to a group. + + This function creates an invite request packet and pushes it to the send queue. + + :param groupnumber: 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, groupnumber, friend_number, byref(error)) + return result + + def group_invite_accept(self, invite_data, password): + """ + 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 groupnumber on success, UINT32_MAX on failure. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_invite_accept(self._tox_pointer, invite_data, len(invite_data), password, + len(password), byref(error)) + 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. + """ + + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, 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. + """ + + 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. + """ + + 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, groupnumber, 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 groupnumber: 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, groupnumber, password, + len(password), byref(error)) + return result + + def group_founder_set_privacy_state(self, groupnumber, 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 groupnumber: 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, groupnumber, privacy_state, + byref(error)) + return result + + def group_founder_set_peer_limit(self, groupnumber, 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 groupnumber: 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, groupnumber, + max_peers, byref(error)) + return result + + # ----------------------------------------------------------------------------------------------------------------- + # Group chat moderation + # ----------------------------------------------------------------------------------------------------------------- + + def group_toggle_ignore(self, groupnumber, peer_id, ignore): + """ + Ignore or unignore a peer. + + :param groupnumber: 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, groupnumber, peer_id, ignore, byref(error)) + return result + + def group_mod_set_role(self, groupnumber, 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 groupnumber: 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, groupnumber, peer_id, role, byref(error)) + return result + + def group_mod_remove_peer(self, groupnumber, peer_id, set_ban): + """ + 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 groupnumber: 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. + :param set_ban: Set to true if a ban shall be set on the peer's IP address. + + :return True on success. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_mod_remove_peer(self._tox_pointer, groupnumber, peer_id, + set_ban, byref(error)) + return result + + def group_mod_remove_ban(self, groupnumber, 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 groupnumber: 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, groupnumber, 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, groupnumber): + """ + 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, groupnumber, byref(error)) + return result + + def group_ban_get_list(self, groupnumber): + """ + 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() + result = Tox.libtoxcore.tox_group_ban_get_list(self._tox_pointer, groupnumber, POINTER(c_uint32)( + create_string_buffer(sizeof(c_uint32) * self.group_ban_get_list_size(groupnumber)), byref(error))) + return result + + def group_ban_get_name_size(self, groupnumber, 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 groupnumber or ban_id is invalid, + the return value is unspecified. + """ + + error = c_int() + result = Tox.libtoxcore.tox_group_ban_get_name_size(self._tox_pointer, groupnumber, ban_id, byref(error)) + return result + + def group_ban_get_name(self, groupnumber, 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_name_size(groupnumber, ban_id) + name = create_string_buffer() + + result = Tox.libtoxcore.tox_group_ban_get_name(self._tox_pointer, groupnumber, ban_id, + name, byref(error)) + return str(name[:size], 'utf-8') + + def group_ban_get_time_set(self, groupnumber, 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 groupnumber 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, groupnumber, ban_id, byref(error)) + return result From 40d0b03227bf175888a1dc5045071608fa757dc2 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 24 Jun 2016 15:20:05 +0300 Subject: [PATCH 03/22] icon and gc.py --- src/groupchat.py | 16 ++++++++++++++++ src/images/group.png | Bin 0 -> 4142 bytes 2 files changed, 16 insertions(+) create mode 100644 src/groupchat.py create mode 100755 src/images/group.png diff --git a/src/groupchat.py b/src/groupchat.py new file mode 100644 index 0000000..a165813 --- /dev/null +++ b/src/groupchat.py @@ -0,0 +1,16 @@ +import contact + + +class Groupchat(contact.Contact): + + def __init__(self, *args): + super().__init__(args) + + def load_avatar(self): + avatar_path = curr_directory() + '/images/group.png' + os.chdir(curr_directory() + '/images/') + pixmap = QtGui.QPixmap(QtCore.QSize(64, 64)) + pixmap.load(avatar_path) + self._widget.avatar_label.setScaledContents(False) + self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio)) + self._widget.avatar_label.repaint() diff --git a/src/images/group.png b/src/images/group.png new file mode 100755 index 0000000000000000000000000000000000000000..22adab0a66b1b939ae0c9b6fee5d0781c41b91c0 GIT binary patch literal 4142 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 Date: Fri, 24 Jun 2016 21:10:06 +0300 Subject: [PATCH 04/22] basecontact created --- src/basecontact.py | 113 ++++++++++++++++++++++++++++++++++ src/groupchat.py | 11 +--- toxygen/contact.py | 150 ++++++++++++++++++++++++++++++++------------- toxygen/friend.py | 56 +---------------- toxygen/profile.py | 3 +- 5 files changed, 227 insertions(+), 106 deletions(-) create mode 100644 src/basecontact.py diff --git a/src/basecontact.py b/src/basecontact.py new file mode 100644 index 0000000..1ae1a2b --- /dev/null +++ b/src/basecontact.py @@ -0,0 +1,113 @@ +import os +from settings import * +try: + from PySide import QtCore, QtGui +except ImportError: + from PyQt4 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 + """ + + 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._widget.name.setText(name) + self._widget.status_message.setText(status_message) + self._tox_id = tox_id + self.load_avatar() + + # ----------------------------------------------------------------------------------------------------------------- + # 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, default_path='avatar.png'): + """ + Tries to load avatar of contact or uses default avatar + """ + avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + os.chdir(ProfileHelper.get_path() + 'avatars/') + if not os.path.isfile(avatar_path): # load default image + avatar_path = default_path + os.chdir(curr_directory() + '/images/') + pixmap = QtGui.QPixmap(QtCore.QSize(64, 64)) + pixmap.load(avatar_path) + self._widget.avatar_label.setScaledContents(False) + self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio)) + 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() diff --git a/src/groupchat.py b/src/groupchat.py index a165813..89c2eb5 100644 --- a/src/groupchat.py +++ b/src/groupchat.py @@ -3,14 +3,9 @@ import contact class Groupchat(contact.Contact): - def __init__(self, *args): + def __init__(self, group_id, *args): super().__init__(args) + self._id = group_id def load_avatar(self): - avatar_path = curr_directory() + '/images/group.png' - os.chdir(curr_directory() + '/images/') - pixmap = QtGui.QPixmap(QtCore.QSize(64, 64)) - pixmap.load(avatar_path) - self._widget.avatar_label.setScaledContents(False) - self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio)) - self._widget.avatar_label.repaint() + super().load_avatar('group.png') diff --git a/toxygen/contact.py b/toxygen/contact.py index f4b3f9a..2df8c1c 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -5,78 +5,139 @@ try: except ImportError: from PyQt4 import QtCore, QtGui from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE +import basecontact -class Contact: +class Contact(basecontact.BaseContact): """ Class encapsulating TOX contact Properties: name (alias of contact or name), status_message, status (connection status) widget - widget for update """ - def __init__(self, name, status_message, widget, tox_id): + def __init__(self, message_getter, 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._widget.name.setText(name) - self._widget.status_message.setText(status_message) - self._tox_id = tox_id - self.load_avatar() + super().__init__(name, status_message, widget, tox_id) + self._message_getter = message_getter + self._new_messages = False + self._visible = True + self._corr = [] + self._unsaved_messages = 0 + self._history_loaded = self._new_actions = False + self._curr_text = '' + + def __del__(self): + self.set_visibility(False) + del self._widget + if hasattr(self, '_message_getter'): + del self._message_getter + + 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 + 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 get_corr_for_saving(self): + """ + Get data to save in db + :return: list of unsaved messages or [] + """ + if hasattr(self, '_message_getter'): + del self._message_getter + messages = list(filter(lambda x: x.get_type() <= 1, self._corr)) + return list( + map(lambda x: x.get_data(), list(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 '' + + def clear_corr(self): + """ + Clear messages list + """ + if hasattr(self, '_message_getter'): + del self._message_getter + # don't delete data about active file transfer + self._corr = list(filter(lambda x: x.get_type() in (2, 3) and x.get_status() >= 2, self._corr)) + self._unsaved_messages = 0 + + 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) # ----------------------------------------------------------------------------------------------------------------- - # name - current name or alias of user + # Visibility in friends' list # ----------------------------------------------------------------------------------------------------------------- - def get_name(self): - return self._name + def get_visibility(self): + return self._visible - def set_name(self, value): - self._name = str(value, 'utf-8') - self._widget.name.setText(self._name) - self._widget.name.repaint() + def set_visibility(self, value): + self._visible = value - name = property(get_name, set_name) + visibility = property(get_visibility, set_visibility) # ----------------------------------------------------------------------------------------------------------------- - # Status message + # Unread messages and actions # ----------------------------------------------------------------------------------------------------------------- - def get_status_message(self): - return self._status_message + def get_actions(self): + return self._new_actions - 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() + def set_actions(self, value): + self._new_actions = value + self._widget.connection_status.update(self.status, value) - status_message = property(get_status_message, set_status_message) + actions = property(get_actions, set_actions) # unread messages, incoming files, av calls - # ----------------------------------------------------------------------------------------------------------------- - # Status - # ----------------------------------------------------------------------------------------------------------------- + def get_messages(self): + return self._new_messages - def get_status(self): - return self._status + 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 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) + 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) # ----------------------------------------------------------------------------------------------------------------- # Avatars @@ -112,3 +173,6 @@ class Contact: def get_pixmap(self): return self._widget.avatar_label.pixmap() + + messages = property(get_messages) + diff --git a/toxygen/friend.py b/toxygen/friend.py index 3dd5b0f..6f2dcf4 100644 --- a/toxygen/friend.py +++ b/toxygen/friend.py @@ -15,23 +15,13 @@ class Friend(contact.Contact): :param message_getter: gets messages from db :param number: number of friend. """ - super(Friend, self).__init__(*args) + super(Friend, self).__init__(message_getter, *args) 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._receipts = 0 - self._curr_text = '' def __del__(self): - self.set_visibility(False) - del self._widget - if hasattr(self, '_message_getter'): - del self._message_getter + super().__del__() # ----------------------------------------------------------------------------------------------------------------- # History support @@ -188,48 +178,6 @@ class Friend(contact.Contact): 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) - - # ----------------------------------------------------------------------------------------------------------------- - # Unread messages 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) # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/profile.py b/toxygen/profile.py index 9d5f1ab..816f78d 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -15,9 +15,10 @@ import time import calls import avwidgets import plugin_support +import basecontact -class Profile(contact.Contact, Singleton): +class Profile(basecontact.BaseContact, Singleton): """ Profile of current toxygen user. Contains friends list, tox instance """ From 9b0c6e63ce63e422d9d0667561f475c8052ebc63 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sat, 2 Jul 2016 18:01:11 +0300 Subject: [PATCH 05/22] bug fixes --- src/groupchat.py | 6 +++--- toxygen/contact.py | 5 ++--- toxygen/messages.py | 12 ++++++++++++ toxygen/profile.py | 10 +++++----- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/groupchat.py b/src/groupchat.py index 89c2eb5..2064de7 100644 --- a/src/groupchat.py +++ b/src/groupchat.py @@ -4,8 +4,8 @@ import contact class Groupchat(contact.Contact): def __init__(self, group_id, *args): - super().__init__(args) + super().__init__(*args) self._id = group_id - def load_avatar(self): - super().load_avatar('group.png') + def load_avatar(self, default_path='group.png'): + super().load_avatar(default_path) diff --git a/toxygen/contact.py b/toxygen/contact.py index 2df8c1c..451ea5f 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -1,11 +1,10 @@ -import os -from settings import * try: from PySide import QtCore, QtGui except ImportError: from PyQt4 import QtCore, QtGui -from toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE import basecontact +from messages import * +from history import * class Contact(basecontact.BaseContact): diff --git a/toxygen/messages.py b/toxygen/messages.py index 87a1cc2..e4fe693 100644 --- a/toxygen/messages.py +++ b/toxygen/messages.py @@ -39,6 +39,18 @@ class TextMessage(Message): return self._message, self._owner, self._time, self._type +class GroupChatTextMessage(TextMessage): + + def __init__(self, friend_name, *args): + super().__init__(*args) + self._name = friend_name + + def get_data(self): + data = list(super().get_data()) + data.append(self._name) + return tuple(data) + + class TransferMessage(Message): """ Message with info about file transfer diff --git a/toxygen/profile.py b/toxygen/profile.py index 816f78d..ede3979 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -27,11 +27,11 @@ class Profile(basecontact.BaseContact, Singleton): :param tox: tox instance :param screen: ref to main screen """ - contact.Contact.__init__(self, - tox.self_get_name(), - tox.self_get_status_message(), - screen.user_info, - tox.self_get_address()) + 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 From 3aba7dffd203b867d8fa1a06db8f8232d38a4fe3 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Sun, 3 Jul 2016 22:42:32 +0300 Subject: [PATCH 06/22] ui: chat creation window --- toxygen/mainscreen.py | 18 +++++- toxygen/menu.py | 32 +++++++++++ toxygen/profile.py | 127 ++++++++++++++++++++++++------------------ 3 files changed, 121 insertions(+), 56 deletions(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 3260614..047ddc9 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -29,6 +29,8 @@ class MainWindow(QtGui.QMainWindow): self.menuProfile = QtGui.QMenu(self.menubar) self.menuProfile.setObjectName("menuProfile") + self.menuGroupChats = QtGui.QMenu(self.menubar) + self.menuGroupChats.setObjectName("menuGroupChats") self.menuSettings = QtGui.QMenu(self.menubar) self.menuSettings.setObjectName("menuSettings") self.menuPlugins = QtGui.QMenu(self.menubar) @@ -56,6 +58,10 @@ class MainWindow(QtGui.QMainWindow): self.pluginData = QtGui.QAction(MainWindow) self.importPlugin = QtGui.QAction(MainWindow) self.lockApp = QtGui.QAction(MainWindow) + self.createGC = QtGui.QAction(MainWindow) + self.gcRequests = QtGui.QAction(MainWindow) + self.menuGroupChats.addAction(self.createGC) + self.menuGroupChats.addAction(self.gcRequests) self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.lockApp) @@ -68,6 +74,7 @@ class MainWindow(QtGui.QMainWindow): self.menuPlugins.addAction(self.importPlugin) self.menuAbout.addAction(self.actionAbout_program) self.menubar.addAction(self.menuProfile.menuAction()) + self.menubar.addAction(self.menuGroupChats.menuAction()) self.menubar.addAction(self.menuSettings.menuAction()) self.menubar.addAction(self.menuPlugins.menuAction()) self.menubar.addAction(self.menuAbout.menuAction()) @@ -81,8 +88,10 @@ class MainWindow(QtGui.QMainWindow): self.actionNotifications.triggered.connect(self.notification_settings) self.audioSettings.triggered.connect(self.audio_settings) self.pluginData.triggered.connect(self.plugins_menu) + self.lockApp.triggered.connect(self.lock_app) self.importPlugin.triggered.connect(self.import_plugin) + self.createGC.triggered.connect(self.create_groupchat) QtCore.QMetaObject.connectSlotsByName(MainWindow) def languageChange(self, *args, **kwargs): @@ -95,6 +104,9 @@ class MainWindow(QtGui.QMainWindow): def retranslateUi(self): self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8)) + self.menuGroupChats.setTitle(QtGui.QApplication.translate("MainWindow", "Groupchats", None, QtGui.QApplication.UnicodeUTF8)) + self.createGC.setText(QtGui.QApplication.translate("MainWindow", "Create groupchat", None, QtGui.QApplication.UnicodeUTF8)) + self.gcRequests.setText(QtGui.QApplication.translate("MainWindow", "Groupchat requests", None, QtGui.QApplication.UnicodeUTF8)) self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8)) self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8)) self.menuProfile.setTitle(QtGui.QApplication.translate("MainWindow", "Profile", None, QtGui.QApplication.UnicodeUTF8)) @@ -439,6 +451,10 @@ class MainWindow(QtGui.QMainWindow): 120)) self.menu.show() + def create_groupchat(self): + self.gc = AddGroupchat() + self.gc.show() + # ----------------------------------------------------------------------------------------------------------------- # Messages, calls and file transfers # ----------------------------------------------------------------------------------------------------------------- @@ -511,7 +527,7 @@ class MainWindow(QtGui.QMainWindow): 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) + friend = Profile.get_instance().get_friend_or_gc(num) 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) diff --git a/toxygen/menu.py b/toxygen/menu.py index a1c39ed..8ebf179 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -11,6 +11,38 @@ import toxencryptsave import plugin_support +class AddGroupchat(QtGui.QWidget): + + def __init__(self, parent=None): + super().__init__(parent) + self.initUI() + self.retranslateUi() + + def initUI(self): + self.setObjectName('AddGC') + self.resize(570, 320) + self.label = QtGui.QLabel(self) + self.label.setGeometry(QtCore.QRect(50, 20, 470, 20)) + self.createGCButton = QtGui.QPushButton(self) + self.createGCButton.setGeometry(QtCore.QRect(50, 280, 470, 30)) + self.name = LineEdit(self) + self.name.setGeometry(QtCore.QRect(50, 40, 470, 27)) + self.privacy_type = QtGui.QLabel(self) + self.privacy_type.setGeometry(QtCore.QRect(50, 70, 470, 20)) + self.privacy_combobox = QtGui.QComboBox(self) + self.privacy_combobox.setGeometry(QtCore.QRect(50, 100, 470, 30)) + + QtCore.QMetaObject.connectSlotsByName(self) + + def retranslateUi(self): + self.setWindowTitle(QtGui.QApplication.translate('AddGC', "Create groupchat", None, QtGui.QApplication.UnicodeUTF8)) + self.createGCButton.setText(QtGui.QApplication.translate("AddGC", "Create", None, QtGui.QApplication.UnicodeUTF8)) + self.label.setText(QtGui.QApplication.translate('AddGC', "Name:", None, QtGui.QApplication.UnicodeUTF8)) + self.privacy_type.setText(QtGui.QApplication.translate('AddGC', "Privacy type:", None, QtGui.QApplication.UnicodeUTF8)) + self.privacy_combobox.addItem(QtGui.QApplication.translate('AddGC', "Public", None, QtGui.QApplication.UnicodeUTF8)) + self.privacy_combobox.addItem(QtGui.QApplication.translate('AddGC', "Private", None, QtGui.QApplication.UnicodeUTF8)) + + class AddContact(CenteredWidget): """Add contact form""" diff --git a/toxygen/profile.py b/toxygen/profile.py index ede3979..97638ee 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -46,7 +46,7 @@ class Profile(basecontact.BaseContact, Singleton): aliases = settings['friends_aliases'] data = tox.self_get_friend_list() self._history = History(tox.self_get_public_key()) # connection to db - self._friends, self._active_friend = [], -1 + self._friends_and_gc, self._active_friend_or_gc = [], -1 for i in data: # creates list of friends tox_id = tox.friend_get_public_key(i) try: @@ -61,7 +61,7 @@ class Profile(basecontact.BaseContact, Singleton): message_getter = self._history.messages_getter(tox_id) friend = Friend(message_getter, i, name, status_message, item, tox_id) friend.set_alias(alias) - self._friends.append(friend) + self._friends_and_gc.append(friend) self.filtration(self._show_online) # ----------------------------------------------------------------------------------------------------------------- @@ -88,11 +88,11 @@ class Profile(basecontact.BaseContact, Singleton): 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 = message.format(tmp, value) - for friend in self._friends: + message = message.format(tmp, str(value, 'utf-8')) + for friend in self._friends_and_gc: friend.append_message(InfoMessage(message, time.time())) - if self._active_friend + 1: - self.create_message_item(message, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) + if self._active_friend_or_gc + 1: + self.create_message_item(message, curr_time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self._messages.scrollToBottom() def set_status_message(self, value): @@ -118,7 +118,7 @@ class Profile(basecontact.BaseContact, Singleton): """ filter_str = filter_str.lower() settings = Settings.get_instance() - for index, friend in enumerate(self._friends): + for index, friend in enumerate(self._friends_and_gc): friend.visibility = (friend.status is not None or not show_online) and (filter_str in friend.name.lower()) friend.visibility = friend.visibility or friend.messages or friend.actions if friend.visibility: @@ -137,29 +137,29 @@ class Profile(basecontact.BaseContact, Singleton): self.filtration(self._show_online, self._filter_string) def get_friend_by_number(self, num): - return list(filter(lambda x: x.number == num, self._friends))[0] + return list(filter(lambda x: x.number == num, self._friends_and_gc))[0] - def get_friend(self, num): - return self._friends[num] + def get_friend_or_gc(self, num): + return self._friends_and_gc[num] # ----------------------------------------------------------------------------------------------------------------- # Work with active friend # ----------------------------------------------------------------------------------------------------------------- def get_active(self): - return self._active_friend + return self._active_friend_or_gc 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 + if value is None and self._active_friend_or_gc == -1: # nothing to update return if value == -1: # all friends were deleted self._screen.account_name.setText('') self._screen.account_status.setText('') - self._active_friend = -1 + self._active_friend_or_gc = -1 self._screen.account_avatar.setHidden(True) self._messages.clear() self._screen.messageEdit.clear() @@ -168,26 +168,32 @@ class Profile(basecontact.BaseContact, Singleton): self.send_typing(False) self._screen.typing.setVisible(False) if value is not None: - if self._active_friend + 1: + if self._active_friend_or_gc + 1: try: - self._friends[self._active_friend].curr_text = self._screen.messageEdit.toPlainText() + self._friends_and_gc[self._active_friend_or_gc].curr_text = self._screen.messageEdit.toPlainText() except: pass - self._active_friend = value - friend = self._friends[value] - self._friends[value].reset_messages() - self._screen.messageEdit.setPlainText(friend.curr_text) + self._active_friend_or_gc = value + friend_or_gc = self._friends_and_gc[value] + self._friends_and_gc[value].reset_messages() + self._screen.messageEdit.setPlainText(friend_or_gc.curr_text) self._messages.clear() friend.load_corr() messages = friend.get_corr()[-PAGE_SIZE:] self._load_history = False + friend_or_gc.load_corr() + messages = friend_or_gc.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]) + data[3], + True, + data[4] if len(data) == 5 else None) elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: if message.get_status() is None: self.create_unsent_file_item(message) @@ -217,11 +223,11 @@ class Profile(basecontact.BaseContact, Singleton): else: self._screen.call_finished() else: - friend = self._friends[self._active_friend] + friend_or_gc = self._friends_and_gc[self._active_friend_or_gc] - self._screen.account_name.setText(friend.name) - self._screen.account_status.setText(friend.status_message) - avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) + self._screen.account_name.setText(friend_or_gc.name) + self._screen.account_status.setText(friend_or_gc.status_message) + avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend_or_gc.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)) @@ -238,17 +244,20 @@ class Profile(basecontact.BaseContact, Singleton): active_friend = property(get_active, set_active) + def is_active_a_friend(self): + return type(self._friends_and_gc[self._active_friend_or_gc]) is Friend + def get_last_message(self): - return self._friends[self._active_friend].get_last_message_text() + return self._friends_and_gc[self._active_friend_or_gc].get_last_message_text() def get_active_number(self): - return self._friends[self._active_friend].number if self._active_friend + 1 else -1 + return self._friends_and_gc[self._active_friend_or_gc].number if self._active_friend_or_gc + 1 else -1 def get_active_name(self): - return self._friends[self._active_friend].name if self._active_friend + 1 else '' + return self._friends_and_gc[self._active_friend_or_gc].name if self._active_friend_or_gc + 1 else '' def is_active_online(self): - return self._active_friend + 1 and self._friends[self._active_friend].status is not None + return self._active_friend_or_gc + 1 and self._friends_and_gc[self._active_friend_or_gc].status is not None def new_name(self, number, name): friend = self.get_friend_by_number(number) @@ -266,8 +275,8 @@ class Profile(basecontact.BaseContact, Singleton): self.set_active(None) def update(self): - if self._active_friend + 1: - self.set_active(self._active_friend) + if self._active_friend_or_gc + 1: + self.set_active(self._active_friend_or_gc) # ----------------------------------------------------------------------------------------------------------------- # Friend connection status callbacks @@ -307,8 +316,8 @@ class Profile(basecontact.BaseContact, Singleton): """ Send typing notification to a friend """ - if Settings.get_instance()['typing_notifications'] and self._active_friend + 1: - friend = self._friends[self._active_friend] + if Settings.get_instance()['typing_notifications'] and self._active_friend_or_gc + 1: + friend = self._friends_and_gc[self._active_friend_or_gc] if friend.status is not None: self._tox.self_set_typing(friend.number, typing) @@ -376,7 +385,7 @@ class Profile(basecontact.BaseContact, Singleton): t = time.time() self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) self._messages.scrollToBottom() - self._friends[self._active_friend].append_message( + self._friends_and_gc[self._active_friend_or_gc].append_message( TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type)) else: friend = self.get_friend_by_number(friend_num) @@ -399,12 +408,17 @@ class Profile(basecontact.BaseContact, Singleton): self._screen.messageEdit.clear() elif text and friend_num + 1: text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text) + 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) + # TODO: send to gc + # friend = self._friends_and_gc[self._active_friend_or_gc] + friend.inc_receipts() if friend.status is not None: self.split_and_send(friend.number, message_type, text.encode('utf-8')) @@ -432,7 +446,7 @@ class Profile(basecontact.BaseContact, Singleton): s = Settings.get_instance() if hasattr(self, '_history'): if s['save_history']: - for friend in self._friends: + for friend in self._friends_and_gc 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']: @@ -452,14 +466,15 @@ class Profile(basecontact.BaseContact, Singleton): Clear chat history """ if num is not None: - friend = self._friends[num] + friend = self._friends_and_gc[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._friends)): + for number in range(len(self._friends_and_gc))): self.clear_history(number, save_unsent) + if num is None or num == self.get_active_number(): self.update() @@ -470,9 +485,10 @@ class Profile(basecontact.BaseContact, Singleton): if not self._load_history: return self._load_history = False - friend = self._friends[self._active_friend] - friend.load_corr(False) - data = friend.get_corr() + friend_or_gc = self._friends_and_gc[self._active_friend_or_gc] + + friend_or_gc.load_corr(False) + data = friend_or_gc.get_corr() if not data: return data.reverse() @@ -526,11 +542,11 @@ class Profile(basecontact.BaseContact, Singleton): self._screen.friends_list.setItemWidget(elem, item) return item - def create_message_item(self, text, time, owner, message_type, append=True): + def create_message_item(self, text, time, owner, message_type, append=True, friend_name=None): if message_type == MESSAGE_TYPE['INFO_MESSAGE']: name = '' elif owner == MESSAGE_OWNER['FRIEND']: - name = self.get_active_name() + name = friend_name or self.get_active_name() else: name = self._name item = MessageItem(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'], message_type, self._messages) @@ -589,7 +605,7 @@ class Profile(basecontact.BaseContact, Singleton): """ Set new alias for friend """ - friend = self._friends[num] + friend = self._friends_and_gc[num] name = friend.name dialog = QtGui.QApplication.translate('MainWindow', "Enter new alias for friend {} or leave empty to use friend's name:", @@ -627,14 +643,14 @@ class Profile(basecontact.BaseContact, Singleton): self.update() def friend_public_key(self, num): - return self._friends[num].tox_id + return self._friends_and_gc[num].tox_id def delete_friend(self, num): """ Removes friend from contact list :param num: number of friend in list """ - friend = self._friends[num] + friend = self._friends_and_gc[num] settings = Settings.get_instance() try: index = list(map(lambda x: x[0], settings['friends_aliases'])).index(friend.tox_id) @@ -648,10 +664,10 @@ class Profile(basecontact.BaseContact, Singleton): 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._friends[num] + del self._friends_and_gc[num] self._screen.friends_list.takeItem(num) - if num == self._active_friend: # active friend was deleted - if not len(self._friends): # last friend was deleted + if num == self._active_friend_or_gc: # active friend was deleted + if not len(self._friends_and_gc): # last friend was deleted self.set_active(-1) else: self.set_active(0) @@ -672,7 +688,7 @@ class Profile(basecontact.BaseContact, Singleton): log('Accept friend request failed! ' + str(ex)) message_getter = None friend = Friend(message_getter, num, tox_id, '', item, tox_id) - self._friends.append(friend) + self._friends_and_gc.append(friend) def block_user(self, tox_id): """ @@ -739,7 +755,7 @@ class Profile(basecontact.BaseContact, Singleton): 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._friends.append(friend) + self._friends_and_gc.append(friend) data = self._tox.get_savedata() ProfileHelper.get_instance().save_profile(data) return True @@ -782,7 +798,7 @@ class Profile(basecontact.BaseContact, Singleton): del self._tox self._tox = restart() self.status = None - for friend in self._friends: + for friend in self._friends_and_gc: friend.status = None self.update_filtration() @@ -875,7 +891,7 @@ class Profile(basecontact.BaseContact, Singleton): 0, -1) def cancel_not_started_transfer(self, time): - self._friends[self._active_friend].delete_one_unsent_file(time) + self._friends_and_gc[self._active_friend_or_gc].delete_one_unsent_file(time) self.update() def pause_transfer(self, friend_number, file_number, by_friend=False): @@ -998,7 +1014,7 @@ class Profile(basecontact.BaseContact, Singleton): st.get_file_number()) item = self.create_file_transfer_item(tm) st.set_state_changed_handler(item.update) - self._friends[self._active_friend].append_message(tm) + self._friends_and_gc[self._active_friend_or_gc].append_message(tm) self._messages.scrollToBottom() def incoming_chunk(self, friend_number, file_number, position, data): @@ -1091,12 +1107,12 @@ class Profile(basecontact.BaseContact, Singleton): def reset_avatar(self): super(Profile, self).reset_avatar() - for friend in filter(lambda x: x.status is not None, self._friends): + for friend in filter(lambda x: x.status is not None, self._friends_and_gc): 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._friends): + for friend in filter(lambda x: x.status is not None, self._friends_and_gc): self.send_avatar(friend.number) # ----------------------------------------------------------------------------------------------------------------- @@ -1120,7 +1136,8 @@ class Profile(basecontact.BaseContact, Singleton): else: text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None, QtGui.QApplication.UnicodeUTF8) - self._friends[self._active_friend].append_message(InfoMessage(text, time.time())) + + self._friends_and_gc[self._active_friend_or_gc].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 From 3ad7d2082728b2623fe9c9f8566f8852695fa3f5 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 4 Jul 2016 13:49:46 +0300 Subject: [PATCH 07/22] history improvements, ui for creation finished, some updates --- src/groupchat.py | 5 +++-- toxygen/contact.py | 11 ++++++++++- toxygen/history.py | 2 ++ toxygen/menu.py | 32 +++++++++++++++++++++++++------- toxygen/messages.py | 1 + toxygen/profile.py | 15 ++++++++++++++- toxygen/tox.py | 8 +++++--- 7 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/groupchat.py b/src/groupchat.py index 2064de7..fbc6633 100644 --- a/src/groupchat.py +++ b/src/groupchat.py @@ -1,11 +1,12 @@ import contact -class Groupchat(contact.Contact): +class GroupChat(contact.Contact): - def __init__(self, group_id, *args): + def __init__(self, group_id, tox, *args): super().__init__(*args) self._id = group_id + self._tox = tox def load_avatar(self, default_path='group.png'): super().load_avatar(default_path) diff --git a/toxygen/contact.py b/toxygen/contact.py index 451ea5f..6fc90ed 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -87,9 +87,18 @@ class Contact(basecontact.BaseContact): if hasattr(self, '_message_getter'): del self._message_getter # don't delete data about active file transfer - self._corr = list(filter(lambda x: x.get_type() in (2, 3) and x.get_status() >= 2, self._corr)) + self._corr = list(filter(lambda x: x.get_type() in (2, 3) and (x.get_status() >= 2 or x.get_status() is None), + self._corr)) self._unsaved_messages = 0 + def delete_old_messages(self): + old = filter(lambda x: x.get_type() in (2, 3) and (x.get_status() >= 2 or x.get_status() is None), + self._corr[:-SAVE_MESSAGES]) + old = list(old) + l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old) + self._unsaved_messages -= l + self._corr = old + self._corr[-SAVE_MESSAGES:] + def get_curr_text(self): return self._curr_text diff --git a/toxygen/history.py b/toxygen/history.py index ad18ee5..581d8a0 100644 --- a/toxygen/history.py +++ b/toxygen/history.py @@ -8,6 +8,8 @@ from toxencryptsave import ToxEncryptSave PAGE_SIZE = 42 +SAVE_MESSAGES = 150 + MESSAGE_OWNER = { 'ME': 0, 'FRIEND': 1, diff --git a/toxygen/menu.py b/toxygen/menu.py index 8ebf179..c4b53a9 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -11,36 +11,54 @@ import toxencryptsave import plugin_support -class AddGroupchat(QtGui.QWidget): +class AddGroupchat(CenteredWidget): - def __init__(self, parent=None): - super().__init__(parent) + def __init__(self): + super().__init__() self.initUI() self.retranslateUi() + self.center() def initUI(self): self.setObjectName('AddGC') - self.resize(570, 320) + self.resize(570, 240) + self.setMaximumSize(QtCore.QSize(570, 240)) + self.setMinimumSize(QtCore.QSize(570, 240)) self.label = QtGui.QLabel(self) self.label.setGeometry(QtCore.QRect(50, 20, 470, 20)) self.createGCButton = QtGui.QPushButton(self) - self.createGCButton.setGeometry(QtCore.QRect(50, 280, 470, 30)) + self.createGCButton.setGeometry(QtCore.QRect(50, 190, 470, 30)) self.name = LineEdit(self) self.name.setGeometry(QtCore.QRect(50, 40, 470, 27)) self.privacy_type = QtGui.QLabel(self) self.privacy_type.setGeometry(QtCore.QRect(50, 70, 470, 20)) self.privacy_combobox = QtGui.QComboBox(self) - self.privacy_combobox.setGeometry(QtCore.QRect(50, 100, 470, 30)) + self.privacy_combobox.setGeometry(QtCore.QRect(50, 90, 470, 30)) + self.pass_label = QtGui.QLabel(self) + self.pass_label.setGeometry(QtCore.QRect(50, 130, 470, 20)) + self.password = LineEdit(self) + self.password.setGeometry(QtCore.QRect(50, 150, 470, 27)) + self.createGCButton.clicked.connect(self.button_click) QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): - self.setWindowTitle(QtGui.QApplication.translate('AddGC', "Create groupchat", None, QtGui.QApplication.UnicodeUTF8)) + self.setWindowTitle(QtGui.QApplication.translate('AddGC', "Create group chat", None, QtGui.QApplication.UnicodeUTF8)) self.createGCButton.setText(QtGui.QApplication.translate("AddGC", "Create", None, QtGui.QApplication.UnicodeUTF8)) self.label.setText(QtGui.QApplication.translate('AddGC', "Name:", None, QtGui.QApplication.UnicodeUTF8)) self.privacy_type.setText(QtGui.QApplication.translate('AddGC', "Privacy type:", None, QtGui.QApplication.UnicodeUTF8)) self.privacy_combobox.addItem(QtGui.QApplication.translate('AddGC', "Public", None, QtGui.QApplication.UnicodeUTF8)) self.privacy_combobox.addItem(QtGui.QApplication.translate('AddGC', "Private", None, QtGui.QApplication.UnicodeUTF8)) + self.name.setPlaceholderText(QtGui.QApplication.translate('AddGC', "Not empty group name", None, QtGui.QApplication.UnicodeUTF8)) + self.password.setPlaceholderText(QtGui.QApplication.translate('AddGC', "Optional password", None, QtGui.QApplication.UnicodeUTF8)) + self.pass_label.setText(QtGui.QApplication.translate('AddGC', "Password:", None, QtGui.QApplication.UnicodeUTF8)) + + def button_click(self): + if self.name.text(): + Profile.get_instance().create_gc(self.name.text(), + self.privacy_combobox.currentIndex() == 0, + self.password.text()) + self.close() class AddContact(CenteredWidget): diff --git a/toxygen/messages.py b/toxygen/messages.py index e4fe693..6c1c9e6 100644 --- a/toxygen/messages.py +++ b/toxygen/messages.py @@ -83,6 +83,7 @@ class TransferMessage(Message): 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 diff --git a/toxygen/profile.py b/toxygen/profile.py index 97638ee..cf527d3 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -16,6 +16,7 @@ import calls import avwidgets import plugin_support import basecontact +from groupchat import * class Profile(basecontact.BaseContact, Singleton): @@ -175,7 +176,8 @@ class Profile(basecontact.BaseContact, Singleton): pass self._active_friend_or_gc = value friend_or_gc = self._friends_and_gc[value] - self._friends_and_gc[value].reset_messages() + friend_or_gc.reset_messages() + friend_or_gc.delete_old_messages() self._screen.messageEdit.setPlainText(friend_or_gc.curr_text) self._messages.clear() friend.load_corr() @@ -1195,6 +1197,17 @@ class Profile(basecontact.BaseContact, Singleton): self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE']) self._messages.scrollToBottom() + # ----------------------------------------------------------------------------------------------------------------- + # Group chats support + # ----------------------------------------------------------------------------------------------------------------- + + def create_gc(self, name, is_public, password): + privacy_state = TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PUBLIC'] if is_public else TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PRIVATE'] + num = self._tox.group_new(privacy_state, bytes(name, 'utf-8')) + if password: + self._tox.group_founder_set_password(num, password) + # self._friends_and_gc.append(Groupchat(num, self._tox, )) + def tox_factory(data=None, settings=None): """ diff --git a/toxygen/tox.py b/toxygen/tox.py index a83f94a..90b3ddf 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1545,12 +1545,14 @@ class Tox: 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 groupnumber on success, UINT32_MAX on failure. + :return group number on success, UINT32_MAX on failure. """ error = c_int() - result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, - len(group_name), byref(error)) + func = Tox.libtoxcore.tox_group_new + func.restype = c_uint32 + result = func(self._tox_pointer, privacy_state, group_name, + len(group_name), byref(error)) return result def group_join(self, chat_id, password): From c8bdb32e86ab119434b8319fd679ef15900bb43c Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 11 Jul 2016 17:52:03 +0300 Subject: [PATCH 08/22] rebase --- src/images/group.png | Bin 4142 -> 0 bytes {src => toxygen}/basecontact.py | 0 toxygen/contact.py | 3 +++ {src => toxygen}/groupchat.py | 0 toxygen/profile.py | 29 +++++++++++++---------------- 5 files changed, 16 insertions(+), 16 deletions(-) delete mode 100755 src/images/group.png rename {src => toxygen}/basecontact.py (100%) rename {src => toxygen}/groupchat.py (100%) diff --git a/src/images/group.png b/src/images/group.png deleted file mode 100755 index 22adab0a66b1b939ae0c9b6fee5d0781c41b91c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4142 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 Date: Mon, 11 Jul 2016 22:19:35 +0300 Subject: [PATCH 09/22] group creation, invites and message sending (untested) --- toxygen/basecontact.py | 5 +- toxygen/callbacks.py | 14 ++++++ toxygen/contact.py | 41 +--------------- toxygen/friend.py | 6 +-- toxygen/groupchat.py | 5 +- toxygen/mainscreen.py | 65 +++++++++++++++----------- toxygen/profile.py | 103 ++++++++++++++++++++++++++++++----------- toxygen/tox.py | 31 ++++++++++--- 8 files changed, 165 insertions(+), 105 deletions(-) diff --git a/toxygen/basecontact.py b/toxygen/basecontact.py index 1ae1a2b..a484173 100644 --- a/toxygen/basecontact.py +++ b/toxygen/basecontact.py @@ -91,10 +91,11 @@ class BaseContact: if not os.path.isfile(avatar_path): # load default image avatar_path = default_path os.chdir(curr_directory() + '/images/') - pixmap = QtGui.QPixmap(QtCore.QSize(64, 64)) + width = self._widget.avatar_label.width() + pixmap = QtGui.QPixmap(QtCore.QSize(width, width)) pixmap.load(avatar_path) self._widget.avatar_label.setScaledContents(False) - self._widget.avatar_label.setPixmap(pixmap.scaled(64, 64, QtCore.Qt.KeepAspectRatio)) + self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio)) self._widget.avatar_label.repaint() def reset_avatar(self): diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index fc13b99..0ce0fd6 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -287,6 +287,20 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud rate) +# ----------------------------------------------------------------------------------------------------------------- +# Callbacks - group chats +# ----------------------------------------------------------------------------------------------------------------- + +def group_message(tox, group_number, peer_id, message, length, user_data): + pass + + +def group_invite(tox, friend_number, invite_data, length, user_data): + invoke_in_main_thread(Profile.get_instance().process_group_invite, + friend_number, + invite_data[:length]) + + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/contact.py b/toxygen/contact.py index 0743e70..ac41dbe 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -5,9 +5,6 @@ except ImportError: import basecontact from messages import * from history import * -from settings import ProfileHelper -from toxcore_enums_and_consts import * -from util import curr_directory class Contact(basecontact.BaseContact): @@ -17,7 +14,7 @@ class Contact(basecontact.BaseContact): widget - widget for update """ - def __init__(self, message_getter, name, status_message, widget, tox_id): + def __init__(self, number, message_getter, name, status_message, widget, tox_id): """ :param name: name, example: 'Toxygen user' :param status_message: status message, example: 'Toxing on Toxygen' @@ -28,6 +25,7 @@ class Contact(basecontact.BaseContact): self._message_getter = message_getter self._new_messages = False self._visible = True + self._number = number self._corr = [] self._unsaved_messages = 0 self._history_loaded = self._new_actions = False @@ -150,40 +148,5 @@ class Contact(basecontact.BaseContact): self._widget.messages.update(self._new_messages) self._widget.connection_status.update(self.status, False) - # ----------------------------------------------------------------------------------------------------------------- - # Avatars - # ----------------------------------------------------------------------------------------------------------------- - - def load_avatar(self): - """ - Tries to load avatar of contact or uses default avatar - """ - avatar_path = '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) - os.chdir(ProfileHelper.get_path() + 'avatars/') - if not os.path.isfile(avatar_path): # load default image - avatar_path = 'avatar.png' - os.chdir(curr_directory() + '/images/') - width = self._widget.avatar_label.width() - pixmap = QtGui.QPixmap(QtCore.QSize(width, width)) - pixmap.load(avatar_path) - self._widget.avatar_label.setScaledContents(False) - self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio)) - 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() - messages = property(get_messages) diff --git a/toxygen/friend.py b/toxygen/friend.py index 6f2dcf4..7347671 100644 --- a/toxygen/friend.py +++ b/toxygen/friend.py @@ -10,13 +10,11 @@ class Friend(contact.Contact): Friend in list of friends. Can be hidden, properties 'has unread messages' and 'has alias' added """ - def __init__(self, message_getter, number, *args): + def __init__(self, *args): """ - :param message_getter: gets messages from db :param number: number of friend. """ - super(Friend, self).__init__(message_getter, *args) - self._number = number + super(Friend, self).__init__(*args) self._alias = False self._receipts = 0 diff --git a/toxygen/groupchat.py b/toxygen/groupchat.py index fbc6633..1038407 100644 --- a/toxygen/groupchat.py +++ b/toxygen/groupchat.py @@ -3,10 +3,11 @@ import contact class GroupChat(contact.Contact): - def __init__(self, group_id, tox, *args): + def __init__(self, tox, *args): super().__init__(*args) - self._id = group_id self._tox = tox def load_avatar(self, default_path='group.png'): super().load_avatar(default_path) + +# TODO: get peers list and add other methods diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 047ddc9..cc4f648 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -82,7 +82,7 @@ class MainWindow(QtGui.QMainWindow): self.actionAbout_program.triggered.connect(self.about_program) self.actionNetwork.triggered.connect(self.network_settings) self.actionAdd_friend.triggered.connect(self.add_contact) - self.actionSettings.triggered.connect(self.profilesettings) + 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) @@ -205,9 +205,9 @@ class MainWindow(QtGui.QMainWindow): Form.status_message.setObjectName("status_message") self.connection_status = Form.connection_status = StatusCircle(Form) Form.connection_status.setGeometry(QtCore.QRect(230, 35, 32, 32)) - self.avatar_label.mouseReleaseEvent = self.profilesettings - self.status_message.mouseReleaseEvent = self.profilesettings - self.name.mouseReleaseEvent = self.profilesettings + 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") @@ -232,6 +232,11 @@ class MainWindow(QtGui.QMainWindow): font.setBold(False) self.account_status.setFont(font) self.account_status.setObjectName("account_status") + + self.account_status.mouseReleaseEvent = self.show_chat_menu + self.account_name.mouseReleaseEvent = self.show_chat_menu + self.account_avatar.mouseReleaseEvent = self.show_chat_menu + self.callButton = QtGui.QPushButton(Form) self.callButton.setGeometry(QtCore.QRect(550, 30, 50, 50)) self.callButton.setObjectName("callButton") @@ -389,7 +394,7 @@ class MainWindow(QtGui.QMainWindow): self.a_c = AddContact(link) self.a_c.show() - def profilesettings(self, *args): + def profile_settings(self, *args): self.p_s = ProfileSettings() self.p_s.show() @@ -455,6 +460,11 @@ class MainWindow(QtGui.QMainWindow): self.gc = AddGroupchat() self.gc.show() + def show_chat_menu(self): + pr = Profile.get_instance() + if not pr.is_active_a_friend(): + pass # TODO: show list of users in chat + # ----------------------------------------------------------------------------------------------------------------- # Messages, calls and file transfers # ----------------------------------------------------------------------------------------------------------------- @@ -533,29 +543,32 @@ class MainWindow(QtGui.QMainWindow): 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) 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)) - clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) - 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)) + if type(friend) is Friend: # TODO: add `invite to gc` submenu + set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) + clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) + 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)) - auto_accept_item = self.listMenu.addAction(auto) - remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8)) - notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8)) + auto_accept_item = self.listMenu.addAction(auto) + remove_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Remove friend', None, QtGui.QApplication.UnicodeUTF8)) + notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8)) - submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num) - if len(submenu): - plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8)) - 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(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)) + submenu = plugin_support.PluginLoader.get_instance().get_menu(self.listMenu, num) + if len(submenu): + plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8)) + 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(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)) + else: + pass # TODO: add menu for gc parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position + pos) self.listMenu.show() diff --git a/toxygen/profile.py b/toxygen/profile.py index fae590f..0403c0b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -60,7 +60,7 @@ class Profile(basecontact.BaseContact, Singleton): 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 = Friend(i, message_getter, name, status_message, item, tox_id) friend.set_alias(alias) self._friends_and_gc.append(friend) self.filtration(self._show_online) @@ -138,7 +138,10 @@ class Profile(basecontact.BaseContact, Singleton): self.filtration(self._show_online, self._filter_string) def get_friend_by_number(self, num): - return list(filter(lambda x: x.number == num, self._friends_and_gc))[0] + return list(filter(lambda x: x.number == num and type(x) is Friend, self._friends_and_gc))[0] + + def get_gc_by_number(self, num): + return list(filter(lambda x: x.number == num and type(x) is not Friend, self._friends_and_gc))[0] def get_friend_or_gc(self, num): return self._friends_and_gc[num] @@ -350,12 +353,13 @@ class Profile(basecontact.BaseContact, Singleton): except: pass - def split_and_send(self, number, message_type, message): + def split_and_send(self, number, message_type, message, is_group=False): """ Message splitting - :param number: friend's number + :param number: friend or gc number :param message_type: type of message :param message: message text + :param is_group: send to group """ while len(message) > TOX_MAX_MESSAGE_LENGTH: size = TOX_MAX_MESSAGE_LENGTH * 4 / 5 @@ -369,9 +373,15 @@ class Profile(basecontact.BaseContact, Singleton): else: index = TOX_MAX_MESSAGE_LENGTH - size - 1 index += size + 1 - self._tox.friend_send_message(number, message_type, message[:index]) + if not is_group: + self._tox.friend_send_message(number, message_type, message[:index]) + else: + self._tox.group_send_message(number, message_type, message[:index]) message = message[index:] - self._tox.friend_send_message(number, message_type, message) + if not is_group: + self._tox.friend_send_message(number, message_type, message) + else: + self._tox.group_send_message(number, message_type, message) def new_message(self, friend_num, message_type, message): """ @@ -394,18 +404,20 @@ class Profile(basecontact.BaseContact, Singleton): if not friend.visibility: self.update_filtration() - def send_message(self, text, friend_num=None): + def send_message(self, text, number=None, is_gc=False): """ Send message :param text: message text - :param friend_num: num of friend + :param number: num of friend or gc + :param is_gc: is group chat """ - if friend_num is None: - friend_num = self.get_active_number() + if number is None: + number = self.get_active_number() + is_gc = not self.is_active_a_friend() if text.startswith('/plugin '): plugin_support.PluginLoader.get_instance().command(text[8:]) self._screen.messageEdit.clear() - elif text and friend_num + 1: + elif text and number + 1: text = ''.join(c if c <= '\u10FFFF' else '\u25AF' for c in text) if text.startswith('/me '): @@ -414,22 +426,30 @@ class Profile(basecontact.BaseContact, Singleton): else: message_type = TOX_MESSAGE_TYPE['NORMAL'] - friend = self.get_friend_by_number(friend_num) - # TODO: send to gc - # friend = self._friends_and_gc[self._active_friend_or_gc] - - friend.inc_receipts() - if friend.status is not None: - self.split_and_send(friend.number, message_type, text.encode('utf-8')) + if not is_gc: + friend_or_gc = self.get_friend_by_number(number) + else: + friend_or_gc = self.get_gc_by_number(number) t = time.time() - if friend.number == self.get_active_number(): - 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)) + + if not is_gc: + friend_or_gc.inc_receipts() + if friend_or_gc.status is not None: + self.split_and_send(friend_or_gc.number, message_type, text.encode('utf-8')) + if friend_or_gc.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() + else: + self.split_and_send(friend_or_gc.number, message_type, text.encode('utf-8'), True) + if friend_or_gc.number == self.get_active_number() and not self.is_active_a_friend(): + self.create_message_item(text, t, MESSAGE_OWNER['ME'], message_type) + self._screen.messageEdit.clear() + self._messages.scrollToBottom() + friend_or_gc.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) def delete_message(self, time): - friend = self._friends[self._active_friend] + friend = self._friends_and_gc[self._active_friend_or_gc] friend.delete_message(time) self._history.delete_message(friend.tox_id, time) self.update() @@ -1198,12 +1218,43 @@ class Profile(basecontact.BaseContact, Singleton): # Group chats support # ----------------------------------------------------------------------------------------------------------------- + def add_gc(self, num): + tox_id = self._tox.group_get_chat_id(num) + name = self._tox.group_get_name(num) + topic = self._tox.group_get_topic(num) + 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 + gc = GroupChat(self._tox, message_getter, num, name, topic, item, tox_id) + self._friends_and_gc.append(gc) + def create_gc(self, name, is_public, password): privacy_state = TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PUBLIC'] if is_public else TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PRIVATE'] num = self._tox.group_new(privacy_state, bytes(name, 'utf-8')) if password: - self._tox.group_founder_set_password(num, password) - # self._friends_and_gc.append(Groupchat(num, self._tox, )) + self._tox.group_founder_set_password(num, bytes(password, 'utf-8')) + self.add_gc(num) + + def process_group_invite(self, friend_num, data): + # TODO: add info to list and support password + try: + text = QtGui.QApplication.translate('MainWindow', 'User {} invites you to group', + None, QtGui.QApplication.UnicodeUTF8) + info = text.format(self.get_friend_by_number(friend_num).name) + fr_req = QtGui.QApplication.translate('MainWindow', 'Group chat invite', None, QtGui.QApplication.UnicodeUTF8) + reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) + if reply == QtGui.QMessageBox.Yes: # accepted + num = self._tox.group_invite_accept(data) + data = self._tox.get_savedata() + ProfileHelper.get_instance().save_profile(data) + self.add_gc(num) + except Exception as ex: # something is wrong + log('Accept group chat invite failed! ' + str(ex)) def tox_factory(data=None, settings=None): diff --git a/toxygen/tox.py b/toxygen/tox.py index 90b3ddf..41fce73 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1893,13 +1893,14 @@ class Tox: """ 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 true on success. + :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, groupnumber, - create_string_buffer(TOX_GROUP_CHAT_ID_SIZE), byref(error)) - return result + buff, byref(error)) + return bin_to_string(buff[:TOX_GROUP_CHAT_ID_SIZE], TOX_GROUP_CHAT_ID_SIZE) def group_get_number_groups(self): """ @@ -2093,6 +2094,15 @@ class Tox: """ 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 + groupnumber 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) @@ -2140,7 +2150,7 @@ class Tox: result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error)) return result - def group_invite_accept(self, invite_data, password): + def group_invite_accept(self, invite_data, 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. @@ -2151,8 +2161,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_invite_accept(self._tox_pointer, invite_data, len(invite_data), password, - len(password), byref(error)) + result = Tox.libtoxcore.tox_group_invite_accept(self._tox_pointer, invite_data, len(invite_data), + password, + len(password) if password is not None else 0, + byref(error)) return result def callback_group_invite(self, callback, user_data): @@ -2161,6 +2173,13 @@ class Tox: 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, c_char_p, c_size_t, c_void_p) From 4f42098d56c78535bc37dea081a29f4950534f49 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Mon, 11 Jul 2016 22:47:39 +0300 Subject: [PATCH 10/22] incoming messages --- toxygen/callbacks.py | 22 ++++++++++++++++++++-- toxygen/menu.py | 11 ++++++++--- toxygen/profile.py | 27 ++++++++++++++++++--------- toxygen/settings.py | 3 ++- 4 files changed, 48 insertions(+), 15 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 0ce0fd6..d961ea2 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -291,8 +291,23 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud # Callbacks - group chats # ----------------------------------------------------------------------------------------------------------------- -def group_message(tox, group_number, peer_id, message, length, user_data): - pass +def group_message(window, tray): + """ + New message from friend + """ + def wrapped(tox, group_number, peer_id, message_type, message, length, user_data): + profile = Profile.get_instance() + settings = Settings.get_instance() + message = str(message, 'utf-8') + invoke_in_main_thread(profile.new_message, group_number, message_type, message, True) + if not window.isActiveWindow(): + bl = settings['notify_all_gc'] or profile.name in message + if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked and bl: + invoke_in_main_thread(tray_notification, '', message, tray, window) # TODO: friend name + if (settings['sound_notifications'] or bl) 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 group_invite(tox, friend_number, invite_data, length, user_data): @@ -337,3 +352,6 @@ 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_message(group_message(window, tray), 0) + tox.callback_group_invite(group_invite, 0) + diff --git a/toxygen/menu.py b/toxygen/menu.py index c4b53a9..6989fae 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -560,28 +560,33 @@ 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, 200) + self.setMinimumSize(QtCore.QSize(350, 200)) + self.setMaximumSize(QtCore.QSize(350, 200)) self.enableNotifications = QtGui.QCheckBox(self) self.enableNotifications.setGeometry(QtCore.QRect(10, 20, 340, 18)) self.callsSound = QtGui.QCheckBox(self) self.callsSound.setGeometry(QtCore.QRect(10, 120, 340, 18)) self.soundNotifications = QtGui.QCheckBox(self) self.soundNotifications.setGeometry(QtCore.QRect(10, 70, 340, 18)) + self.gcNotifications = QtGui.QCheckBox(self) + self.gcNotifications.setGeometry(QtCore.QRect(10, 170, 340, 18)) font = QtGui.QFont() font.setPointSize(12) self.callsSound.setFont(font) self.soundNotifications.setFont(font) self.enableNotifications.setFont(font) + self.gcNotifications.setFont(font) s = Settings.get_instance() self.enableNotifications.setChecked(s['notifications']) self.soundNotifications.setChecked(s['sound_notifications']) self.callsSound.setChecked(s['calls_sound']) + self.gcNotifications.setChecked(s['notify_all_gc']) self.retranslateUi() QtCore.QMetaObject.connectSlotsByName(self) def retranslateUi(self): + self.gcNotifications.setText(QtGui.QApplication.translate("notificationsForm", "Enable group chat notifications", None, QtGui.QApplication.UnicodeUTF8)) 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)) diff --git a/toxygen/profile.py b/toxygen/profile.py index 0403c0b..fd6ed7b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -41,6 +41,7 @@ class Profile(basecontact.BaseContact, Singleton): self._call = calls.AV(tox.AV) # object with data about calls self._incoming_calls = set() self._load_history = True + self._gc_invites = {} # dict of gc invites. key - friend number, value - list of gc data settings = Settings.get_instance() self._show_online = settings['show_online_friends'] screen.online_contacts.setCurrentIndex(int(self._show_online)) @@ -383,25 +384,28 @@ class Profile(basecontact.BaseContact, Singleton): else: self._tox.group_send_message(number, message_type, message) - def new_message(self, friend_num, message_type, message): + def new_message(self, num, message_type, message, is_group=False): """ Current user gets new message - :param friend_num: friend_num of friend who sent message + :param num: num of friend or gc who sent message :param message_type: message type - plain text or action message (/me) :param message: text of message + :param is_group: is group chat message or not """ - if friend_num == self.get_active_number(): # add message to list + if num == self.get_active_number() and is_group != 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._friends_and_gc[self._active_friend_or_gc].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: + if is_group: + friend_or_gc = self.get_gc_by_number(num) + else: + friend_or_gc = self.get_friend_by_number(num) + friend_or_gc.inc_messages() + friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) + if not friend_or_gc.visibility: self.update_filtration() def send_message(self, text, number=None, is_gc=False): @@ -1241,7 +1245,7 @@ class Profile(basecontact.BaseContact, Singleton): self.add_gc(num) def process_group_invite(self, friend_num, data): - # TODO: add info to list and support password + # TODO: support password try: text = QtGui.QApplication.translate('MainWindow', 'User {} invites you to group', None, QtGui.QApplication.UnicodeUTF8) @@ -1253,6 +1257,11 @@ class Profile(basecontact.BaseContact, Singleton): data = self._tox.get_savedata() ProfileHelper.get_instance().save_profile(data) self.add_gc(num) + elif reply != QtGui.QMessageBox.No: + if friend_num in self._gc_invites: + self._gc_invites[friend_num].append(data) + else: + self._gc_invites[friend_num] = data except Exception as ex: # something is wrong log('Accept group chat invite failed! ' + str(ex)) diff --git a/toxygen/settings.py b/toxygen/settings.py index f4c7746..627a4ed 100644 --- a/toxygen/settings.py +++ b/toxygen/settings.py @@ -126,7 +126,8 @@ class Settings(dict, Singleton): 'unread_color': 'red', 'save_unsent_only': False, 'compact_mode': False, - 'show_welcome_screen': True + 'show_welcome_screen': True, + 'notify_all_gc': False } @staticmethod From bae87c8d724ac67ed0ef89f473b0eed281ba7ff8 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 12 Jul 2016 13:48:59 +0300 Subject: [PATCH 11/22] some fixes and image --- toxygen/callbacks.py | 2 +- toxygen/contact.py | 11 +++++++++++ toxygen/friend.py | 12 ------------ toxygen/images/group.png | Bin 0 -> 4142 bytes toxygen/mainscreen.py | 6 ++++++ toxygen/profile.py | 6 ++++-- toxygen/tox.py | 2 +- 7 files changed, 23 insertions(+), 16 deletions(-) create mode 100755 toxygen/images/group.png diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index d961ea2..3420161 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -298,7 +298,7 @@ def group_message(window, tray): def wrapped(tox, group_number, peer_id, message_type, message, length, user_data): profile = Profile.get_instance() settings = Settings.get_instance() - message = str(message, 'utf-8') + message = str(message[:length], 'utf-8') invoke_in_main_thread(profile.new_message, group_number, message_type, message, True) if not window.isActiveWindow(): bl = settings['notify_all_gc'] or profile.name in message diff --git a/toxygen/contact.py b/toxygen/contact.py index ac41dbe..5d78c63 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -150,3 +150,14 @@ class Contact(basecontact.BaseContact): messages = property(get_messages) + # ----------------------------------------------------------------------------------------------------------------- + # 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/friend.py b/toxygen/friend.py index 7347671..0045dcc 100644 --- a/toxygen/friend.py +++ b/toxygen/friend.py @@ -175,15 +175,3 @@ class Friend(contact.Contact): def set_alias(self, alias): self._alias = bool(alias) - - # ----------------------------------------------------------------------------------------------------------------- - # 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/images/group.png b/toxygen/images/group.png new file mode 100755 index 0000000000000000000000000000000000000000..22adab0a66b1b939ae0c9b6fee5d0781c41b91c0 GIT binary patch literal 4142 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 Date: Tue, 12 Jul 2016 15:47:17 +0300 Subject: [PATCH 12/22] updates and fixes --- toxygen/callbacks.py | 11 ++++++----- toxygen/mainscreen.py | 13 +++++++++++-- toxygen/menu.py | 41 +++++++++++++++++++++++++++++++++++++++++ toxygen/profile.py | 15 +++++++++++++-- toxygen/tox.py | 5 ++++- 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 3420161..12e72d1 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -291,19 +291,20 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud # Callbacks - group chats # ----------------------------------------------------------------------------------------------------------------- -def group_message(window, tray): +def group_message(window, tray, tox): """ New message from friend """ - def wrapped(tox, group_number, peer_id, message_type, message, length, user_data): + def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data): profile = Profile.get_instance() settings = Settings.get_instance() message = str(message[:length], 'utf-8') - invoke_in_main_thread(profile.new_message, group_number, message_type, message, True) + invoke_in_main_thread(profile.new_message, group_number, message_type, message, True, 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, '', message, tray, window) # TODO: friend name + invoke_in_main_thread(tray_notification, name, message, tray, window) if (settings['sound_notifications'] or bl) 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')) @@ -352,6 +353,6 @@ 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_message(group_message(window, tray), 0) + tox.callback_group_message(group_message(window, tray, tox), 0) tox.callback_group_invite(group_invite, 0) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 72b4c05..5d00ba7 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -59,8 +59,10 @@ class MainWindow(QtGui.QMainWindow): self.importPlugin = QtGui.QAction(MainWindow) self.lockApp = QtGui.QAction(MainWindow) self.createGC = QtGui.QAction(MainWindow) + self.joinGC = QtGui.QAction(MainWindow) self.gcRequests = QtGui.QAction(MainWindow) self.menuGroupChats.addAction(self.createGC) + self.menuGroupChats.addAction(self.joinGC) self.menuGroupChats.addAction(self.gcRequests) self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionSettings) @@ -92,6 +94,8 @@ class MainWindow(QtGui.QMainWindow): self.lockApp.triggered.connect(self.lock_app) self.importPlugin.triggered.connect(self.import_plugin) self.createGC.triggered.connect(self.create_groupchat) + self.joinGC.triggered.connect(self.join_groupchat) + QtCore.QMetaObject.connectSlotsByName(MainWindow) def languageChange(self, *args, **kwargs): @@ -103,9 +107,10 @@ class MainWindow(QtGui.QMainWindow): return super(MainWindow, self).event(event) def retranslateUi(self): + self.joinGC.setText(QtGui.QApplication.translate("MainWindow", "Join group chat", None, QtGui.QApplication.UnicodeUTF8)) self.lockApp.setText(QtGui.QApplication.translate("MainWindow", "Lock", None, QtGui.QApplication.UnicodeUTF8)) - self.menuGroupChats.setTitle(QtGui.QApplication.translate("MainWindow", "Groupchats", None, QtGui.QApplication.UnicodeUTF8)) - self.createGC.setText(QtGui.QApplication.translate("MainWindow", "Create groupchat", None, QtGui.QApplication.UnicodeUTF8)) + self.menuGroupChats.setTitle(QtGui.QApplication.translate("MainWindow", "Group chats", None, QtGui.QApplication.UnicodeUTF8)) + self.createGC.setText(QtGui.QApplication.translate("MainWindow", "Create group chat", None, QtGui.QApplication.UnicodeUTF8)) self.gcRequests.setText(QtGui.QApplication.translate("MainWindow", "Groupchat requests", None, QtGui.QApplication.UnicodeUTF8)) self.menuPlugins.setTitle(QtGui.QApplication.translate("MainWindow", "Plugins", None, QtGui.QApplication.UnicodeUTF8)) self.pluginData.setText(QtGui.QApplication.translate("MainWindow", "List of plugins", None, QtGui.QApplication.UnicodeUTF8)) @@ -460,6 +465,10 @@ class MainWindow(QtGui.QMainWindow): self.gc = AddGroupchat() self.gc.show() + def join_groupchat(self): + self.gc = JoinGroupchat() + self.gc.show() + def show_chat_menu(self): pr = Profile.get_instance() if not pr.is_active_a_friend(): diff --git a/toxygen/menu.py b/toxygen/menu.py index 6989fae..725ee60 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -38,6 +38,7 @@ class AddGroupchat(CenteredWidget): self.pass_label.setGeometry(QtCore.QRect(50, 130, 470, 20)) self.password = LineEdit(self) self.password.setGeometry(QtCore.QRect(50, 150, 470, 27)) + self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password) self.createGCButton.clicked.connect(self.button_click) QtCore.QMetaObject.connectSlotsByName(self) @@ -61,6 +62,46 @@ class AddGroupchat(CenteredWidget): self.close() +class JoinGroupchat(CenteredWidget): + + def __init__(self): + super().__init__() + self.initUI() + self.retranslateUi() + self.center() + + def initUI(self): + self.setObjectName('AddGC') + self.resize(570, 150) + self.setMaximumSize(QtCore.QSize(570, 150)) + self.setMinimumSize(QtCore.QSize(570, 150)) + self.joinGCButton = QtGui.QPushButton(self) + self.joinGCButton.setGeometry(QtCore.QRect(50, 110, 470, 30)) + self.id = LineEdit(self) + self.id.setGeometry(QtCore.QRect(50, 10, 470, 30)) + self.password = LineEdit(self) + self.password.setGeometry(QtCore.QRect(50, 50, 470, 30)) + self.password.setEchoMode(QtGui.QLineEdit.EchoMode.Password) + + self.joinGCButton.clicked.connect(self.button_click) + QtCore.QMetaObject.connectSlotsByName(self) + + def retranslateUi(self): + self.setWindowTitle( + QtGui.QApplication.translate('JoinGC', "Join group chat", None, QtGui.QApplication.UnicodeUTF8)) + self.joinGCButton.setText( + QtGui.QApplication.translate("JoinGC", "Join", None, QtGui.QApplication.UnicodeUTF8)) + self.id.setPlaceholderText( + QtGui.QApplication.translate('JoinGC', "Group ID", None, QtGui.QApplication.UnicodeUTF8)) + self.password.setPlaceholderText( + QtGui.QApplication.translate('JoinGC', "Optional password", None, QtGui.QApplication.UnicodeUTF8)) + + def button_click(self): + if self.id.text(): + Profile.get_instance().join_gc(self.id.text().strip(), self.password.text()) + self.close() + + class AddContact(CenteredWidget): """Add contact form""" diff --git a/toxygen/profile.py b/toxygen/profile.py index ac577dd..d2d03a3 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -385,13 +385,14 @@ class Profile(basecontact.BaseContact, Singleton): else: self._tox.group_send_message(number, message_type, message) - def new_message(self, num, message_type, message, is_group=False): + def new_message(self, num, message_type, message, is_group=False, peer_id=-1): """ Current user gets new message :param num: num of friend or gc who sent message :param message_type: message type - plain text or action message (/me) :param message: text of message :param is_group: is group chat message or not + :param peer_id: if gc - peer id """ if num == self.get_active_number() and is_group != self.is_active_a_friend(): # add message to list t = time.time() @@ -402,10 +403,13 @@ class Profile(basecontact.BaseContact, Singleton): else: if is_group: friend_or_gc = self.get_gc_by_number(num) + friend_or_gc.append_message(GroupChatTextMessage(self._tox.group_peer_get_name(num, peer_id), + message, MESSAGE_OWNER['FRIEND'], + time.time(), message_type)) else: friend_or_gc = self.get_friend_by_number(num) friend_or_gc.inc_messages() - friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) + friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) if not friend_or_gc.visibility: self.update_filtration() @@ -1239,6 +1243,13 @@ class Profile(basecontact.BaseContact, Singleton): gc = GroupChat(self._tox, num, message_getter, name, topic, item, tox_id) self._friends_and_gc.append(gc) + def join_gc(self, chat_id, password): + num = self._tox.group_join(chat_id, password if password else None) + if num != 2 ** 32 - 1: + self.add_gc(num) + else: + pass # TODO: join failed, show error + def create_gc(self, name, is_public, password): privacy_state = TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PUBLIC'] if is_public else TOX_GROUP_PRIVACY_STATE['TOX_GROUP_PRIVACY_STATE_PRIVATE'] num = self._tox.group_new(privacy_state, bytes(name, 'utf-8')) diff --git a/toxygen/tox.py b/toxygen/tox.py index 6f4c703..5f55628 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1570,7 +1570,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_join(self._tox_pointer, chat_id, password, len(password), byref(error)) + result = Tox.libtoxcore.tox_group_join(self._tox_pointer, chat_id, + password, + len(password) if password is not None else 0, + byref(error)) return result def group_reconnect(self, groupnumber): From 3602b3433e145d133dff5f0dfe42ef44508e4656 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 12 Jul 2016 17:53:38 +0300 Subject: [PATCH 13/22] contact.py fixes and right click menu update --- toxygen/contact.py | 73 +++++++++++++++++++++------ toxygen/friend.py | 113 ------------------------------------------ toxygen/mainscreen.py | 28 ++++++++++- toxygen/profile.py | 19 +++++-- toxygen/tox.py | 3 +- 5 files changed, 100 insertions(+), 136 deletions(-) diff --git a/toxygen/contact.py b/toxygen/contact.py index 5d78c63..8673bab 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -5,6 +5,8 @@ except ImportError: import basecontact from messages import * from history import * +import file_transfers as ft +import util class Contact(basecontact.BaseContact): @@ -25,6 +27,7 @@ class Contact(basecontact.BaseContact): self._message_getter = message_getter self._new_messages = False self._visible = True + self._alias = False self._number = number self._corr = [] self._unsaved_messages = 0 @@ -57,11 +60,8 @@ class Contact(basecontact.BaseContact): Get data to save in db :return: list of unsaved messages or [] """ - if hasattr(self, '_message_getter'): - del self._message_getter messages = list(filter(lambda x: x.get_type() <= 1, self._corr)) - return list( - map(lambda x: x.get_data(), list(messages[-self._unsaved_messages:]))) if self._unsaved_messages else [] + return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else [] def get_corr(self): return self._corr[:] @@ -81,24 +81,50 @@ class Contact(basecontact.BaseContact): else: return '' - def clear_corr(self): + 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 delete_message(self, time): + elem = list(filter(lambda x: type(x) is TextMessage 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:]: + self._unsaved_messages -= 1 + self._corr.remove(elem) + + 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)) + + def clear_corr(self, save_unsent=False): """ Clear messages list """ if hasattr(self, '_message_getter'): del self._message_getter # don't delete data about active file transfer - self._corr = list(filter(lambda x: x.get_type() in (2, 3) and (x.get_status() >= 2 or x.get_status() is None), - self._corr)) - self._unsaved_messages = 0 - - def delete_old_messages(self): - old = filter(lambda x: x.get_type() in (2, 3) and (x.get_status() >= 2 or x.get_status() is None), - self._corr[:-SAVE_MESSAGES]) - old = list(old) - l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old) - self._unsaved_messages -= l - self._corr = old + self._corr[-SAVE_MESSAGES:] + if not save_unsent: + self._corr = list(filter(lambda x: x.get_type() in (2, 3) 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() in (2, 3) 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()) def get_curr_text(self): return self._curr_text @@ -161,3 +187,18 @@ class Contact(basecontact.BaseContact): self._number = value number = property(get_number, set_number) + + # ----------------------------------------------------------------------------------------------------------------- + # Alias support + # ----------------------------------------------------------------------------------------------------------------- + + def set_name(self, value): + """ + Set new name or ignore if alias exists + :param value: new name + """ + if not self._alias: + super(Contact, self).set_name(value) + + def set_alias(self, alias): + self._alias = bool(alias) diff --git a/toxygen/friend.py b/toxygen/friend.py index 0045dcc..e9a5657 100644 --- a/toxygen/friend.py +++ b/toxygen/friend.py @@ -1,8 +1,5 @@ import contact from messages import * -from history import * -import util -import file_transfers as ft class Friend(contact.Contact): @@ -15,7 +12,6 @@ class Friend(contact.Contact): :param number: number of friend. """ super(Friend, self).__init__(*args) - self._alias = False self._receipts = 0 def __del__(self): @@ -38,100 +34,6 @@ class Friend(contact.Contact): self._receipts -= 1 self.mark_as_sent() - 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 - 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 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 '' - - 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 delete_message(self, time): - elem = list(filter(lambda x: type(x) is TextMessage 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:]: - self._unsaved_messages -= 1 - self._corr.remove(elem) - - 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)) - - def clear_corr(self, save_unsent=False): - """ - Clear messages list - """ - if hasattr(self, '_message_getter'): - del self._message_getter - # don't delete data about active file transfer - if not save_unsent: - self._corr = list(filter(lambda x: x.get_type() in (2, 3) 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() in (2, 3) 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()) - - 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) - # ----------------------------------------------------------------------------------------------------------------- # File transfers support # ----------------------------------------------------------------------------------------------------------------- @@ -160,18 +62,3 @@ class Friend(contact.Contact): 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)) - - # ----------------------------------------------------------------------------------------------------------------- - # Alias support - # ----------------------------------------------------------------------------------------------------------------- - - def set_name(self, value): - """ - Set new name or ignore if alias exists - :param value: new name - """ - if not self._alias: - super(Friend, self).set_name(value) - - def set_alias(self, alias): - self._alias = bool(alias) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 5d00ba7..98ea01c 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -559,6 +559,14 @@ class MainWindow(QtGui.QMainWindow): if item is not None: self.listMenu = QtGui.QMenu() if type(friend) is Friend: # TODO: add `invite to gc` submenu + arr = Profile.get_instance().get_all_gc() + if arr: + gc_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Invite to group chat', None, QtGui.QApplication.UnicodeUTF8)) + for gc in arr: + item = gc.menu.addAction(gc_menu.name) + self.connect(item, QtCore.SIGNAL("triggered()"), + lambda: Profile.get_instance().invite_friend(gc.number), friend.number) + set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) copy_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8)) @@ -583,7 +591,23 @@ class MainWindow(QtGui.QMainWindow): 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)) else: - pass # TODO: add menu for gc + 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", 'Topic', None, QtGui.QApplication.UnicodeUTF8)) + copy_key_item = copy_menu.addAction( + QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8)) + leave_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Leave group', None, QtGui.QApplication.UnicodeUTF8)) + set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) + notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8)) + 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(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num)) + self.connect(leave_item, QtCore.SIGNAL("triggered()"), lambda: Profile.get_instance().leave_group(num)) + self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num)) parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position + pos) self.listMenu.show() @@ -607,7 +631,7 @@ class MainWindow(QtGui.QMainWindow): self.profile.set_alias(num) def remove_friend(self, num): - self.profile.delete_friend(num) + self.profile.delete_friend_or_gc(num) def copy_friend_key(self, num): tox_id = self.profile.friend_public_key(num) diff --git a/toxygen/profile.py b/toxygen/profile.py index d2d03a3..4842811 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -674,10 +674,10 @@ class Profile(basecontact.BaseContact, Singleton): def friend_public_key(self, num): return self._friends_and_gc[num].tox_id - def delete_friend(self, num): + def delete_friend_or_gc(self, num): """ - Removes friend from contact list - :param num: number of friend in list + Removes friend or gc from contact list + :param num: number of friend or gc in list """ friend = self._friends_and_gc[num] settings = Settings.get_instance() @@ -732,7 +732,7 @@ class Profile(basecontact.BaseContact, Singleton): settings.save() try: num = self._tox.friend_by_public_key(tox_id) - self.delete_friend(num) + self.delete_friend_or_gc(num) data = self._tox.get_savedata() ProfileHelper.get_instance().save_profile(data) except: # not in friend list @@ -1228,6 +1228,9 @@ class Profile(basecontact.BaseContact, Singleton): # Group chats support # ----------------------------------------------------------------------------------------------------------------- + def get_all_gc(self): + return list(filter(lambda x: type(x) is GroupChat, self._friends_and_gc)) + def add_gc(self, num): tox_id = self._tox.group_get_chat_id(num) name = self._tox.group_get_name(num) @@ -1278,6 +1281,14 @@ class Profile(basecontact.BaseContact, Singleton): except Exception as ex: # something is wrong log('Accept group chat invite failed! ' + str(ex)) + def invite_friend(self, group_number, friend_number): + self._tox.group_invite_friend(group_number, friend_number) + + def leave_group(self, num, message=None): + number = self._friends_and_gc[num].number + self._tox.group_leave(number, message) + self.delete_friend_or_gc(num) + def tox_factory(data=None, settings=None): """ diff --git a/toxygen/tox.py b/toxygen/tox.py index 5f55628..f33b7df 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1607,7 +1607,8 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_leave(self._tox_pointer, groupnumber, message, len(message), byref(error)) + result = Tox.libtoxcore.tox_group_leave(self._tox_pointer, groupnumber, message, + len(message) if message is not None else 0, byref(error)) return result # ----------------------------------------------------------------------------------------------------------------- From bb4e80ca095ec146a587846ea204e2bc39668141 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 12 Jul 2016 22:53:02 +0300 Subject: [PATCH 14/22] fixes, gc loading on start --- toxygen/contact.py | 8 ++++++ toxygen/mainscreen.py | 6 ++-- toxygen/profile.py | 64 +++++++++++++++++++++++++++++-------------- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/toxygen/contact.py b/toxygen/contact.py index 8673bab..03fad92 100644 --- a/toxygen/contact.py +++ b/toxygen/contact.py @@ -102,6 +102,14 @@ class Contact(basecontact.BaseContact): self._unsaved_messages -= 1 self._corr.remove(elem) + def delete_old_messages(self): + old = filter(lambda x: x.get_type() in (2, 3) and (x.get_status() >= 2 or x.get_status() is None), + self._corr[:-SAVE_MESSAGES]) + old = list(old) + l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old) + self._unsaved_messages -= l + self._corr = old + self._corr[-SAVE_MESSAGES:] + def mark_as_sent(self): try: message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0] diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index 98ea01c..c6383f4 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -558,14 +558,14 @@ class MainWindow(QtGui.QMainWindow): 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) if item is not None: self.listMenu = QtGui.QMenu() - if type(friend) is Friend: # TODO: add `invite to gc` submenu + if type(friend) is Friend: arr = Profile.get_instance().get_all_gc() if arr: gc_menu = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Invite to group chat', None, QtGui.QApplication.UnicodeUTF8)) for gc in arr: - item = gc.menu.addAction(gc_menu.name) + item = gc_menu.addAction(gc.name) self.connect(item, QtCore.SIGNAL("triggered()"), - lambda: Profile.get_instance().invite_friend(gc.number), friend.number) + lambda: Profile.get_instance().invite_friend(gc.number, friend.number)) set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) diff --git a/toxygen/profile.py b/toxygen/profile.py index 4842811..00431e2 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -64,7 +64,24 @@ class Profile(basecontact.BaseContact, Singleton): friend = Friend(i, message_getter, name, status_message, item, tox_id) friend.set_alias(alias) self._friends_and_gc.append(friend) - # TODO: list of gc + + l = self._tox.group_get_number_groups() + for i in range(l): # creates list of group chats + tox_id = tox.group_get_chat_id(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.group_get_name(i) or tox_id + status_message = tox.group_get_topic(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) + gc = GroupChat(self._tox, i, message_getter, name, status_message, item, tox_id) + gc.set_alias(alias) + self._friends_and_gc.append(gc) + self.filtration(self._show_online) # ----------------------------------------------------------------------------------------------------------------- @@ -394,24 +411,27 @@ class Profile(basecontact.BaseContact, Singleton): :param is_group: is group chat message or not :param peer_id: if gc - peer id """ + t = time.time() + if num == self.get_active_number() and is_group != 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._friends_and_gc[self._active_friend_or_gc].append_message( - TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type)) - else: - if is_group: - friend_or_gc = self.get_gc_by_number(num) - friend_or_gc.append_message(GroupChatTextMessage(self._tox.group_peer_get_name(num, peer_id), - message, MESSAGE_OWNER['FRIEND'], - time.time(), message_type)) + if not is_group: + self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) else: - friend_or_gc = self.get_friend_by_number(num) - friend_or_gc.inc_messages() - friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) - if not friend_or_gc.visibility: - self.update_filtration() + self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type, True, + self._tox.group_peer_get_name(num, peer_id)) + self._messages.scrollToBottom() + if is_group: + friend_or_gc = self.get_gc_by_number(num) + friend_or_gc.append_message(GroupChatTextMessage(self._tox.group_peer_get_name(num, peer_id), + message, MESSAGE_OWNER['FRIEND'], + time.time(), message_type)) + else: + friend_or_gc = self.get_friend_by_number(num) + friend_or_gc.inc_messages() + friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) + + if not friend_or_gc.visibility: + self.update_filtration() def send_message(self, text, number=None, is_gc=False): """ @@ -674,10 +694,11 @@ class Profile(basecontact.BaseContact, Singleton): def friend_public_key(self, num): return self._friends_and_gc[num].tox_id - def delete_friend_or_gc(self, num): + def delete_friend_or_gc(self, num, is_gc=False): """ Removes friend or gc from contact list :param num: number of friend or gc in list + :param is_gc: is a group chat """ friend = self._friends_and_gc[num] settings = Settings.get_instance() @@ -692,7 +713,8 @@ class Profile(basecontact.BaseContact, Singleton): 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) + if not is_gc: + self._tox.friend_delete(friend.number) del self._friends_and_gc[num] self._screen.friends_list.takeItem(num) if num == self._active_friend_or_gc: # active friend was deleted @@ -1155,6 +1177,8 @@ class Profile(basecontact.BaseContact, Singleton): def call_click(self, audio=True, video=False): """User clicked audio button in main window""" + if not self.is_active_a_friend(): + return num = self.get_active_number() if num not in self._call and self.is_active_online(): # start call self._call(num, audio, video) @@ -1287,7 +1311,7 @@ class Profile(basecontact.BaseContact, Singleton): def leave_group(self, num, message=None): number = self._friends_and_gc[num].number self._tox.group_leave(number, message) - self.delete_friend_or_gc(num) + self.delete_friend_or_gc(num, False) def tox_factory(data=None, settings=None): From 8a53fc87279ff3d19e0df9b614fd3055261d0825 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Tue, 12 Jul 2016 23:31:32 +0300 Subject: [PATCH 15/22] menu update + group leaving --- toxygen/mainscreen.py | 20 +++++++++----------- toxygen/profile.py | 13 +++++++------ 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/toxygen/mainscreen.py b/toxygen/mainscreen.py index c6383f4..b828907 100644 --- a/toxygen/mainscreen.py +++ b/toxygen/mainscreen.py @@ -582,14 +582,8 @@ class MainWindow(QtGui.QMainWindow): if len(submenu): plug = self.listMenu.addMenu(QtGui.QApplication.translate("MainWindow", 'Plugins', None, QtGui.QApplication.UnicodeUTF8)) 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(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)) else: copy_menu = self.listMenu.addMenu( QtGui.QApplication.translate("MainWindow", 'Copy', None, QtGui.QApplication.UnicodeUTF8)) @@ -601,13 +595,17 @@ class MainWindow(QtGui.QMainWindow): QtGui.QApplication.translate("MainWindow", 'Public key', None, QtGui.QApplication.UnicodeUTF8)) leave_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Leave group', None, QtGui.QApplication.UnicodeUTF8)) set_alias_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Set alias', None, QtGui.QApplication.UnicodeUTF8)) + clear_history_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Clear history', None, QtGui.QApplication.UnicodeUTF8)) notes_item = self.listMenu.addAction(QtGui.QApplication.translate("MainWindow", 'Notes', None, QtGui.QApplication.UnicodeUTF8)) - 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(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num)) self.connect(leave_item, QtCore.SIGNAL("triggered()"), lambda: Profile.get_instance().leave_group(num)) - self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num)) + + self.connect(notes_item, QtCore.SIGNAL("triggered()"), lambda: self.show_note(friend)) + self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num)) + self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num)) + 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(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num)) + parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0)) self.listMenu.move(parent_position + pos) self.listMenu.show() diff --git a/toxygen/profile.py b/toxygen/profile.py index 00431e2..b33b38b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -694,11 +694,12 @@ class Profile(basecontact.BaseContact, Singleton): def friend_public_key(self, num): return self._friends_and_gc[num].tox_id - def delete_friend_or_gc(self, num, is_gc=False): + def delete_friend_or_gc(self, num, is_gc=False, message=None): """ Removes friend or gc from contact list :param num: number of friend or gc in list :param is_gc: is a group chat + :param message: message in gc """ friend = self._friends_and_gc[num] settings = Settings.get_instance() @@ -715,10 +716,12 @@ class Profile(basecontact.BaseContact, Singleton): self._history.delete_friend_from_db(friend.tox_id) if not is_gc: self._tox.friend_delete(friend.number) + else: + self._tox.group_leave(num, message) del self._friends_and_gc[num] self._screen.friends_list.takeItem(num) - if num == self._active_friend_or_gc: # active friend was deleted - if not len(self._friends_and_gc): # last friend was deleted + if num == self._active_friend_or_gc: # active friend or gc was deleted + if not len(self._friends_and_gc): # last contact was deleted self.set_active(-1) else: self.set_active(0) @@ -1309,9 +1312,7 @@ class Profile(basecontact.BaseContact, Singleton): self._tox.group_invite_friend(group_number, friend_number) def leave_group(self, num, message=None): - number = self._friends_and_gc[num].number - self._tox.group_leave(number, message) - self.delete_friend_or_gc(num, False) + self.delete_friend_or_gc(num, True, message) def tox_factory(data=None, settings=None): From f775203f4cbfd840b3d8333bc3f57b3f01f49938 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Wed, 13 Jul 2016 16:12:15 +0300 Subject: [PATCH 16/22] some fixes --- toxygen/profile.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index b33b38b..446bc8b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -250,7 +250,10 @@ class Profile(basecontact.BaseContact, Singleton): self._screen.account_status.setText(friend_or_gc.status_message) avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(friend_or_gc.tox_id[:TOX_PUBLIC_KEY_SIZE * 2]) if not os.path.isfile(avatar_path): # load default image - avatar_path = curr_directory() + '/images/avatar.png' + if type(friend_or_gc) is Friend: + avatar_path = curr_directory() + '/images/avatar.png' + else: + avatar_path = curr_directory() + '/images/group.png' os.chdir(os.path.dirname(avatar_path)) pixmap = QtGui.QPixmap(QtCore.QSize(64, 64)) pixmap.load(avatar_path) @@ -427,9 +430,8 @@ class Profile(basecontact.BaseContact, Singleton): time.time(), message_type)) else: friend_or_gc = self.get_friend_by_number(num) - friend_or_gc.inc_messages() friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) - + friend_or_gc.inc_messages() if not friend_or_gc.visibility: self.update_filtration() @@ -493,6 +495,7 @@ class Profile(basecontact.BaseContact, Singleton): Save history to db """ s = Settings.get_instance() + # TODO: different saving for friends and gc if hasattr(self, '_history'): if s['save_history']: for friend_or_gc in self._friends_and_gc: From 4abd72e27883001d92228a0f6d5a8d493864f296 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Thu, 14 Jul 2016 22:23:56 +0300 Subject: [PATCH 17/22] more callbacks, fixes, peers --- toxygen/basecontact.py | 4 ++-- toxygen/callbacks.py | 15 +++++++++++++++ toxygen/groupchat.py | 24 +++++++++++++++++++++++- toxygen/profile.py | 2 +- toxygen/tox.py | 27 +++++++++++++++++++-------- 5 files changed, 60 insertions(+), 12 deletions(-) diff --git a/toxygen/basecontact.py b/toxygen/basecontact.py index a484173..b0accb3 100644 --- a/toxygen/basecontact.py +++ b/toxygen/basecontact.py @@ -29,7 +29,7 @@ class BaseContact: self.load_avatar() # ----------------------------------------------------------------------------------------------------------------- - # name - current name or alias of user + # Name - current name or alias of user # ----------------------------------------------------------------------------------------------------------------- def get_name(self): @@ -43,7 +43,7 @@ class BaseContact: name = property(get_name, set_name) # ----------------------------------------------------------------------------------------------------------------- - # Status message + # Status message or group topic # ----------------------------------------------------------------------------------------------------------------- def get_status_message(self): diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 12e72d1..f763604 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -317,6 +317,19 @@ def group_invite(tox, friend_number, invite_data, length, user_data): invite_data[:length]) +def group_self_join(tox, group_number, user_data): + pr = Profile.get_instance() + gc = pr.get_gc_by_number(group_number) + invoke_in_main_thread(gc.set_status, TOX_USER_STATUS['NONE']) + if not pr.is_active_a_friend() and pr.get_active_number() == group_number: + invoke_in_main_thread(pr.set_active) + + +def group_peer_join(tox, group_number, peer_id, user_data): + gc = Profile.get_instance().get_gc_by_number(group_number) + gc.add_peer(peer_id) + + # ----------------------------------------------------------------------------------------------------------------- # Callbacks - initialization # ----------------------------------------------------------------------------------------------------------------- @@ -355,4 +368,6 @@ def init_callbacks(tox, window, tray): tox.callback_group_message(group_message(window, tray, tox), 0) tox.callback_group_invite(group_invite, 0) + tox.callback_group_self_join(group_self_join, 0) + tox.callback_group_peer_join(group_peer_join, 0) diff --git a/toxygen/groupchat.py b/toxygen/groupchat.py index 1038407..3437696 100644 --- a/toxygen/groupchat.py +++ b/toxygen/groupchat.py @@ -10,4 +10,26 @@ class GroupChat(contact.Contact): def load_avatar(self, default_path='group.png'): super().load_avatar(default_path) -# TODO: get peers list and add other methods + def set_status(self, value): + print('In gc set_status') + self.name = self._tox.group_get_name(self._number) + self._tox_id = self._tox.group_get_chat_id(self._number) + self.status_message = self._tox.group_get_topic(self._number) + + def add_peer(self, peer_id): + print(peer_id) + print(self._tox.group_peer_get_name(self._number, peer_id)) + + # TODO: get peers list and add other methods + + def get_peers_list(self): + return [] + + +class Peer: + + def __init__(self, peer_id, name, status, role): + self._data = (peer_id, name, status, role) + + def get_data(self): + return self._data diff --git a/toxygen/profile.py b/toxygen/profile.py index 446bc8b..a27a9e7 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -720,7 +720,7 @@ class Profile(basecontact.BaseContact, Singleton): if not is_gc: self._tox.friend_delete(friend.number) else: - self._tox.group_leave(num, message) + self._tox.group_leave(num, message.encode('utf-8') if message is not None else None) del self._friends_and_gc[num] self._screen.friends_list.takeItem(num) if num == self._active_friend_or_gc: # active friend or gc was deleted diff --git a/toxygen/tox.py b/toxygen/tox.py index f33b7df..66b7210 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1607,9 +1607,11 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_leave(self._tox_pointer, groupnumber, message, - len(message) if message is not None else 0, byref(error)) - return result + f = Tox.libtoxcore.tox_group_leave + f.restype = c_bool + result = f(self._tox_pointer, groupnumber, message, + len(message) if message is not None else 0, byref(error)) + return result.value # ----------------------------------------------------------------------------------------------------------------- # Group user-visible client information (nickname/status/role/public key) @@ -1965,7 +1967,7 @@ class Tox: see the `Group chat founder controls` section for the respective set function. - :return true on success. + :return password """ error = c_int() @@ -2165,10 +2167,10 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_invite_accept(self._tox_pointer, invite_data, len(invite_data), - password, - len(password) if password is not None else 0, - byref(error)) + f = Tox.libtoxcore.tox_group_invite_accept + f.restype = c_uint32 + result = f(self._tox_pointer, invite_data, len(invite_data), password, + len(password) if password is not None else 0, byref(error)) return result def callback_group_invite(self, callback, user_data): @@ -2195,6 +2197,11 @@ class Tox: 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) @@ -2218,6 +2225,10 @@ class Tox: 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) From 2745caa5316acee436dcf3abdc2c7f3f20243393 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 15 Jul 2016 12:12:06 +0300 Subject: [PATCH 18/22] invite fix --- toxygen/callbacks.py | 2 +- toxygen/groupchat.py | 5 +++-- toxygen/profile.py | 11 ++++++++--- toxygen/tox.py | 10 +++++----- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index f763604..72eeb66 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -314,7 +314,7 @@ def group_message(window, tray, tox): def group_invite(tox, friend_number, invite_data, length, user_data): invoke_in_main_thread(Profile.get_instance().process_group_invite, friend_number, - invite_data[:length]) + bytes(invite_data[:length])) def group_self_join(tox, group_number, user_data): diff --git a/toxygen/groupchat.py b/toxygen/groupchat.py index 3437696..b692761 100644 --- a/toxygen/groupchat.py +++ b/toxygen/groupchat.py @@ -12,9 +12,10 @@ class GroupChat(contact.Contact): def set_status(self, value): print('In gc set_status') - self.name = self._tox.group_get_name(self._number) + super().set_status(value) + self.name = bytes(self._tox.group_get_name(self._number), 'utf-8') self._tox_id = self._tox.group_get_chat_id(self._number) - self.status_message = self._tox.group_get_topic(self._number) + self.status_message = bytes(self._tox.group_get_topic(self._number), 'utf-8') def add_peer(self, peer_id): print(peer_id) diff --git a/toxygen/profile.py b/toxygen/profile.py index a27a9e7..f1ed62b 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1262,9 +1262,12 @@ class Profile(basecontact.BaseContact, Singleton): return list(filter(lambda x: type(x) is GroupChat, self._friends_and_gc)) def add_gc(self, num): - tox_id = self._tox.group_get_chat_id(num) - name = self._tox.group_get_name(num) - topic = self._tox.group_get_topic(num) + try: + tox_id = self._tox.group_get_chat_id(num) + name = self._tox.group_get_name(num) + topic = self._tox.group_get_topic(num) + except: + tox_id = name = topic = '' item = self.create_friend_item() try: if not self._history.friend_exists_in_db(tox_id): @@ -1289,6 +1292,7 @@ class Profile(basecontact.BaseContact, Singleton): if password: self._tox.group_founder_set_password(num, bytes(password, 'utf-8')) self.add_gc(num) + self.get_gc_by_number(num).set_status(TOX_USER_STATUS['NONE']) def process_group_invite(self, friend_num, data): # TODO: support password @@ -1302,6 +1306,7 @@ class Profile(basecontact.BaseContact, Singleton): num = self._tox.group_invite_accept(data) data = self._tox.get_savedata() ProfileHelper.get_instance().save_profile(data) + print('In gc invite', num) self.add_gc(num) elif reply != QtGui.QMessageBox.No: if friend_num in self._gc_invites: diff --git a/toxygen/tox.py b/toxygen/tox.py index 66b7210..a80900b 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1549,9 +1549,7 @@ class Tox: """ error = c_int() - func = Tox.libtoxcore.tox_group_new - func.restype = c_uint32 - result = func(self._tox_pointer, privacy_state, group_name, + result = Tox.libtoxcore.tox_group_new(self._tox_pointer, privacy_state, group_name, len(group_name), byref(error)) return result @@ -1611,7 +1609,8 @@ class Tox: f.restype = c_bool result = f(self._tox_pointer, groupnumber, message, len(message) if message is not None else 0, byref(error)) - return result.value + print('In group leave. Result:', result, 'Error:', error.value) + return result # ----------------------------------------------------------------------------------------------------------------- # Group user-visible client information (nickname/status/role/public key) @@ -2171,6 +2170,7 @@ class Tox: f.restype = c_uint32 result = f(self._tox_pointer, invite_data, len(invite_data), password, len(password) if password is not None else 0, byref(error)) + print('Invite accept. Result:', result, 'Error:', error.value) return result def callback_group_invite(self, callback, user_data): @@ -2188,7 +2188,7 @@ class Tox: user_data - user data """ - c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_char_p, c_size_t, c_void_p) + c_callback = CFUNCTYPE(None, c_void_p, c_uint32, 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) From 735c88d5bf0d7d2a07df7d6236b0d27cc67c27af Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 15 Jul 2016 03:10:41 -0700 Subject: [PATCH 19/22] notifications fix, reconnect added --- toxygen/callbacks.py | 6 +++--- toxygen/menu.py | 1 + toxygen/profile.py | 3 +++ 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/toxygen/callbacks.py b/toxygen/callbacks.py index 72eeb66..450df1e 100644 --- a/toxygen/callbacks.py +++ b/toxygen/callbacks.py @@ -293,7 +293,7 @@ def callback_audio(toxav, friend_number, samples, audio_samples_per_channel, aud def group_message(window, tray, tox): """ - New message from friend + New message in group chat """ def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data): profile = Profile.get_instance() @@ -303,9 +303,9 @@ def group_message(window, tray, tox): 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: + 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'] or bl) and profile.status != TOX_USER_STATUS['BUSY']: + if settings['sound_notifications'] and bl 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 diff --git a/toxygen/menu.py b/toxygen/menu.py index 725ee60..8dc9621 100644 --- a/toxygen/menu.py +++ b/toxygen/menu.py @@ -638,6 +638,7 @@ class NotificationsSettings(CenteredWidget): settings['notifications'] = self.enableNotifications.isChecked() settings['sound_notifications'] = self.soundNotifications.isChecked() settings['calls_sound'] = self.callsSound.isChecked() + settings['notify_all_gc'] = self.gcNotifications.isChecked() settings.save() diff --git a/toxygen/profile.py b/toxygen/profile.py index f1ed62b..edd4255 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -96,6 +96,9 @@ class Profile(basecontact.BaseContact, Singleton): self.set_status((self._status + 1) % 3) def set_status(self, status): + if self.status is None: + for gc in filter(lambda x: type(x) is GroupChat, self._friends_and_gc): + self._tox.group_reconnect(gc.number) super(Profile, self).set_status(status) if status is not None: self._tox.self_set_status(status) From 1ab59ee42256ead8c39d834dfc64306d6519ea30 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 15 Jul 2016 13:03:44 -0700 Subject: [PATCH 20/22] group leave fix --- toxygen/profile.py | 8 +++++--- toxygen/tox.py | 1 - 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index edd4255..2cb8e29 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -418,7 +418,7 @@ class Profile(basecontact.BaseContact, Singleton): :param peer_id: if gc - peer id """ t = time.time() - + active = False if num == self.get_active_number() and is_group != self.is_active_a_friend(): # add message to list if not is_group: self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type) @@ -426,6 +426,7 @@ class Profile(basecontact.BaseContact, Singleton): self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type, True, self._tox.group_peer_get_name(num, peer_id)) self._messages.scrollToBottom() + active = True if is_group: friend_or_gc = self.get_gc_by_number(num) friend_or_gc.append_message(GroupChatTextMessage(self._tox.group_peer_get_name(num, peer_id), @@ -434,7 +435,8 @@ class Profile(basecontact.BaseContact, Singleton): else: friend_or_gc = self.get_friend_by_number(num) friend_or_gc.append_message(TextMessage(message, MESSAGE_OWNER['FRIEND'], time.time(), message_type)) - friend_or_gc.inc_messages() + if not active: + friend_or_gc.inc_messages() if not friend_or_gc.visibility: self.update_filtration() @@ -723,7 +725,7 @@ class Profile(basecontact.BaseContact, Singleton): if not is_gc: self._tox.friend_delete(friend.number) else: - self._tox.group_leave(num, message.encode('utf-8') if message is not None else None) + self._tox.group_leave(friend.number, message.encode('utf-8') if message is not None else None) del self._friends_and_gc[num] self._screen.friends_list.takeItem(num) if num == self._active_friend_or_gc: # active friend or gc was deleted diff --git a/toxygen/tox.py b/toxygen/tox.py index a80900b..6cfca5a 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1609,7 +1609,6 @@ class Tox: f.restype = c_bool result = f(self._tox_pointer, groupnumber, message, len(message) if message is not None else 0, byref(error)) - print('In group leave. Result:', result, 'Error:', error.value) return result # ----------------------------------------------------------------------------------------------------------------- From 2fc90880b88923ec4aabc56aef47a29fb5d8c072 Mon Sep 17 00:00:00 2001 From: ingvar1995 Date: Fri, 27 Jan 2017 21:09:15 +0300 Subject: [PATCH 21/22] api update --- toxygen/profile.py | 2 +- toxygen/tox.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/toxygen/profile.py b/toxygen/profile.py index 2cb8e29..fc54a31 100644 --- a/toxygen/profile.py +++ b/toxygen/profile.py @@ -1308,7 +1308,7 @@ class Profile(basecontact.BaseContact, Singleton): fr_req = QtGui.QApplication.translate('MainWindow', 'Group chat invite', None, QtGui.QApplication.UnicodeUTF8) reply = QtGui.QMessageBox.question(None, fr_req, info, QtGui.QMessageBox.Yes, QtGui.QMessageBox.No) if reply == QtGui.QMessageBox.Yes: # accepted - num = self._tox.group_invite_accept(data) + num = self._tox.group_invite_accept(data, friend_num) data = self._tox.get_savedata() ProfileHelper.get_instance().save_profile(data) print('In gc invite', num) diff --git a/toxygen/tox.py b/toxygen/tox.py index 6cfca5a..2e88366 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -2154,7 +2154,7 @@ class Tox: result = Tox.libtoxcore.tox_group_invite_friend(self._tox_pointer, groupnumber, friend_number, byref(error)) return result - def group_invite_accept(self, invite_data, password=None): + def group_invite_accept(self, invite_data, friend_number, 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. @@ -2167,7 +2167,7 @@ class Tox: error = c_int() f = Tox.libtoxcore.tox_group_invite_accept f.restype = c_uint32 - result = f(self._tox_pointer, invite_data, len(invite_data), password, + result = f(self._tox_pointer, friend_number, invite_data, len(invite_data), password, len(password) if password is not None else 0, byref(error)) print('Invite accept. Result:', result, 'Error:', error.value) return result From 098f295cb0306647635af5cdaa4e9889f4802bd3 Mon Sep 17 00:00:00 2001 From: Ingvar Date: Mon, 8 Jan 2018 22:40:52 +0300 Subject: [PATCH 22/22] Fix group join by chat id --- toxygen/tox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toxygen/tox.py b/toxygen/tox.py index 2e88366..a783e5a 100644 --- a/toxygen/tox.py +++ b/toxygen/tox.py @@ -1568,7 +1568,7 @@ class Tox: """ error = c_int() - result = Tox.libtoxcore.tox_group_join(self._tox_pointer, chat_id, + result = Tox.libtoxcore.tox_group_join(self._tox_pointer, string_to_bin(chat_id), password, len(password) if password is not None else 0, byref(error))