Last active
January 12, 2026 06:34
-
-
Save WeslenPy/410fbdedf51165d7ebebb70f7c7fa8e4 to your computer and use it in GitHub Desktop.
fix session bad mac
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class AxolotlReceivelayer(AxolotlBaseLayer): | |
| def __init__(self): | |
| super(AxolotlReceivelayer, self).__init__() | |
| self.v2Jids = [] #people we're going to send v2 enc messages | |
| self.sessionCiphers = {} | |
| self.groupCiphers = {} | |
| self.pendingIncomingMessages = {} #(jid, participantJid?) => message | |
| self._retries = {} | |
| def receive(self, protocolTreeNode): | |
| """ | |
| :type protocolTreeNode: ProtocolTreeNode | |
| """ | |
| logger.info(f"receive: {protocolTreeNode}") | |
| if not self.processIqRegistry(protocolTreeNode): | |
| if protocolTreeNode.tag == "message": | |
| logger.info(f"receive: message") | |
| self.onMessage(protocolTreeNode) | |
| elif protocolTreeNode.tag == "receipt": | |
| logger.info(f"receive: receipt - from={protocolTreeNode.getAttributeValue('from')}, type={protocolTreeNode.getAttributeValue('type')}, participant={protocolTreeNode.getAttributeValue('participant')}") | |
| #receipts will be handled by send layer | |
| self.toUpper(protocolTreeNode) | |
| else: | |
| # Outros tipos de nós passam para cima | |
| self.toUpper(protocolTreeNode) | |
| else: | |
| logger.info(f"receive: processIqRegistry") | |
| def processPendingIncomingMessages(self, jid, participantJid = None): | |
| conversationIdentifier = (jid, participantJid) | |
| if conversationIdentifier in self.pendingIncomingMessages: | |
| for messageNode in self.pendingIncomingMessages[conversationIdentifier]: | |
| self.onMessage(messageNode) | |
| del self.pendingIncomingMessages[conversationIdentifier] | |
| def onMessage(self, protocolTreeNode): | |
| logger.debug(f"[AxolotlReceive] onMessage chamado - tag: {protocolTreeNode.tag}, from: {protocolTreeNode.getAttributeValue('from')}") | |
| encNode = protocolTreeNode.getChild("enc") | |
| if encNode: | |
| logger.debug(f"[AxolotlReceive] Mensagem criptografada encontrada, chamando handleEncMessage") | |
| self.handleEncMessage(protocolTreeNode) | |
| else: | |
| logger.debug(f"[AxolotlReceive] Mensagem não criptografada, enviando para layer superior") | |
| self.toUpper(protocolTreeNode) | |
| def handleEncMessage(self, node): | |
| encMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node) | |
| isGroup = node["participant"] is not None | |
| senderJid = node["participant"] if isGroup else node["from"] | |
| logger.debug(f"[AxolotlReceive] handleEncMessage: processando mensagem criptografada de {node['from'] or 'unknown'}") | |
| logger.debug(f"[AxolotlReceive] handleEncMessage: node: {node}") | |
| logger.debug(f"[AxolotlReceive] handleEncMessage: encMessageProtocolEntity: {encMessageProtocolEntity}") | |
| if node.getChild("enc")["v"] == "2" and node["from"] not in self.v2Jids: | |
| self.v2Jids.append(node["from"]) | |
| try: | |
| handled = False | |
| if encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_SKMSG): | |
| logger.debug(f"[AxolotlReceive] handleEncMessage: mensagem TYPE_SKMSG (grupo)") | |
| handled = self.handleSenderKeyMessage(node) | |
| if not handled: | |
| if encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_PKMSG): | |
| logger.debug(f"[AxolotlReceive] handleEncMessage: mensagem TYPE_PKMSG (PreKey)") | |
| self.handlePreKeyWhisperMessage(node) | |
| elif encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_MSG): | |
| logger.debug(f"[AxolotlReceive] handleEncMessage: mensagem TYPE_MSG (Whisper)") | |
| self.handleWhisperMessage(node) | |
| else: | |
| logger.warning(f"[AxolotlReceive] handleEncMessage: tipo de mensagem não reconhecido, enviando receipt") | |
| self.toLower(OutgoingReceiptProtocolEntity(node["id"], node["from"], participant=node["participant"]).toProtocolTreeNode()) | |
| self.reset_retries(node["id"]) | |
| except exceptions.InvalidKeyIdException: | |
| logger.warning(f"Invalid KeyId for {encMessageProtocolEntity.getAuthor(False)}, going to send the receipt to ignore subsequence push") | |
| self.toLower(OutgoingReceiptProtocolEntity(node["id"], node["from"], participant=node["participant"]).toProtocolTreeNode()) | |
| except exceptions.InvalidMessageException as e: | |
| try: | |
| author = encMessageProtocolEntity.getAuthor(False) if hasattr(encMessageProtocolEntity, 'getAuthor') else "unknown" | |
| except: | |
| author = node.get("from", "unknown") | |
| error_msg = str(e) if str(e) else "Invalid message (Bad MAC ou sessão desincronizada)" | |
| logger.warning(f"[AxolotlReceive] InvalidMessage para {author}: {error_msg}") | |
| retry_count = self._retries.get(node["id"], 0) | |
| if retry_count >= 2: | |
| # Tentou 3 vezes, provavelmente é um problema do remetente ou sessão muito desincronizada | |
| logger.warning(f"[AxolotlReceive] InvalidMessage após 2 tentativas para mensagem {node['id']}, enviando mensagem PKMSG para sincronização") | |
| self.send_pkmsg_for_invalid_message(node["from"], node["id"], node["participant"]) | |
| else: | |
| # Envia retry para tentar sincronizar a sessão novamente | |
| logger.debug(f"[AxolotlReceive] Enviando retry para mensagem {node['id']} (tentativa {retry_count + 1}/2)") | |
| self.send_retry(node, self.manager.registration_id) | |
| except exceptions.NoSessionException: | |
| logger.warning(f"No session for {encMessageProtocolEntity.getAuthor(False)}, getting their keys now") | |
| self.toLower(OutgoingReceiptProtocolEntity(node["id"], node["from"], participant=node["participant"]).toProtocolTreeNode()) | |
| conversationIdentifier = (node["from"], node["participant"]) | |
| if conversationIdentifier not in self.pendingIncomingMessages: | |
| self.pendingIncomingMessages[conversationIdentifier] = [] | |
| self.pendingIncomingMessages[conversationIdentifier].append(node) | |
| successFn = lambda successJids, b: self.processPendingIncomingMessages(*conversationIdentifier) if len(successJids) else None | |
| self.getKeysFor([senderJid], successFn) | |
| except exceptions.DuplicateMessageException: | |
| logger.warning("Received a message that we've previously decrypted, " | |
| "going to send the delivery receipt myself") | |
| self.toLower(OutgoingReceiptProtocolEntity(node["id"], node["from"], participant=node["participant"]).toProtocolTreeNode()) | |
| except UntrustedIdentityException as e: | |
| if self.getProp(PROP_IDENTITY_AUTOTRUST, False): | |
| logger.warning(f"Autotrusting identity for {e.getName()}") | |
| self.manager.trust_identity(e.getName(), e.getIdentityKey()) | |
| return self.handleEncMessage(node) | |
| else: | |
| logger.error("Ignoring message with untrusted identity") | |
| self.toLower(OutgoingReceiptProtocolEntity(node["id"], node["from"], participant=node["participant"]).toProtocolTreeNode()) | |
| def handleMsMessage(self,node): | |
| logger.info(f"handleMsMessage: {node}") | |
| pass | |
| ''' | |
| msMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node) | |
| enc = msMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_MSMSG) | |
| plaintext = self.manager.decrypt_msg(msMessageProtocolEntity.getAuthor(False), enc.getData(), | |
| enc.getVersion() == 2) | |
| if enc.getVersion() == 2: | |
| self.parseAndHandleMessageProto(msMessageProtocolEntity, plaintext) | |
| node = msMessageProtocolEntity.toProtocolTreeNode() | |
| print(node) | |
| node.addChild((ProtoProtocolEntity(plaintext, enc.getMediaType())).toProtocolTreeNode()) | |
| ''' | |
| def handlePreKeyWhisperMessage(self, node): | |
| pkMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node) | |
| enc = pkMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_PKMSG) | |
| plaintext = self.manager.decrypt_pkmsg(pkMessageProtocolEntity.getAuthor(True), enc.getData(), | |
| enc.getVersion() == 2) | |
| logger.info(f"[AxolotlReceive] handlePreKeyWhisperMessage: plaintext descriptografado (bytes): {len(plaintext) if plaintext else 0} bytes") | |
| if enc.getVersion() == 2: | |
| logger.debug(f"[AxolotlReceive] handlePreKeyWhisperMessage: fazendo parse do protobuf") | |
| self.parseAndHandleMessageProto(pkMessageProtocolEntity, plaintext) | |
| node = pkMessageProtocolEntity.toProtocolTreeNode() | |
| node.addChild((ProtoProtocolEntity(plaintext, enc.getMediaType())).toProtocolTreeNode()) | |
| logger.debug(f"[AxolotlReceive] handlePreKeyWhisperMessage: enviando mensagem descriptografada para layer superior") | |
| self.toUpper(node) | |
| def handleWhisperMessage(self, node): | |
| encMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node) | |
| enc = encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_MSG) | |
| logger.debug(f"[AxolotlReceive] handleWhisperMessage: tentando descriptografar mensagem TYPE_MSG") | |
| try: | |
| plaintext = self.manager.decrypt_msg(encMessageProtocolEntity.getAuthor(False), enc.getData(), | |
| enc.getVersion() == 2) | |
| except exceptions.InvalidMessageException: | |
| raise | |
| except Exception as e: | |
| logger.error(f"[AxolotlReceive] handleWhisperMessage: erro inesperado ao descriptografar: {e}", exc_info=True) | |
| raise | |
| logger.debug(f"[AxolotlReceive] handleWhisperMessage: plaintext descriptografado (bytes): {len(plaintext) if plaintext else 0} bytes") | |
| if enc.getVersion() == 2: | |
| logger.debug(f"[AxolotlReceive] handleWhisperMessage: fazendo parse do protobuf") | |
| self.parseAndHandleMessageProto(encMessageProtocolEntity, plaintext) | |
| node = encMessageProtocolEntity.toProtocolTreeNode() | |
| node.addChild((ProtoProtocolEntity(plaintext, enc.getMediaType())).toProtocolTreeNode()) | |
| logger.debug(f"[AxolotlReceive] handleWhisperMessage: enviando mensagem descriptografada para layer superior") | |
| self.toUpper(node) | |
| def handleSenderKeyMessage(self, node): | |
| encMessageProtocolEntity = EncryptedMessageProtocolEntity.fromProtocolTreeNode(node) | |
| enc = encMessageProtocolEntity.getEnc(EncProtocolEntity.TYPE_SKMSG) | |
| try: | |
| plaintext = self.manager.group_decrypt( | |
| groupid=encMessageProtocolEntity.getFrom(True), | |
| participantid=encMessageProtocolEntity.getParticipant(False), | |
| data=enc.getData() | |
| ) | |
| self.parseAndHandleMessageProto(encMessageProtocolEntity, plaintext) | |
| node = encMessageProtocolEntity.toProtocolTreeNode() | |
| node.addChild((ProtoProtocolEntity(plaintext, enc.getMediaType())).toProtocolTreeNode()) | |
| logger.debug(f"[AxolotlReceive] handleSenderKeyMessage: enviando mensagem descriptografada para layer superior") | |
| self.toUpper(node) | |
| return True | |
| except exceptions.NoSessionException: | |
| # Não há sessão de grupo: envia retry e marca como handled para evitar receipt incorreto | |
| logger.warning(f"Got retry to {encMessageProtocolEntity.getAuthor(False)}, going to send a retry") | |
| try: | |
| self.send_retry(node, self.manager.registration_id) | |
| except Exception as e: | |
| logger.error(f"[AxolotlReceive] Falha ao enviar retry para mensagem de grupo: {e}") | |
| return True | |
| def parseAndHandleMessageProto(self, encMessageProtocolEntity, serializedData): | |
| m = Message() | |
| try: | |
| m.ParseFromString(serializedData) | |
| logger.debug(f"[AxolotlReceive] parseAndHandleMessageProto: protobuf parseado com sucesso") | |
| except Exception as e: | |
| logger.error(f"[AxolotlReceive] parseAndHandleMessageProto: erro ao fazer parse do protobuf: {e}") | |
| print("DUMP:") | |
| print(serializedData) | |
| print([s for s in serializedData]) | |
| raise | |
| if not m or not serializedData: | |
| raise exceptions.InvalidMessageException() | |
| if m.HasField("sender_key_distribution_message"): | |
| logger.info(f"[AxolotlReceive] parseAndHandleMessageProto: HasField sender_key_distribution_message - criando sessão de grupo") | |
| self.handleSenderKeyDistributionMessage( | |
| m.sender_key_distribution_message, | |
| encMessageProtocolEntity.getParticipant(False) | |
| ) | |
| return m | |
| def handleSenderKeyDistributionMessage(self, senderKeyDistributionMessage, participantId): | |
| groupId = senderKeyDistributionMessage.group_id | |
| self.manager.group_create_session( | |
| groupid=groupId, | |
| participantid=participantId, | |
| skmsgdata=senderKeyDistributionMessage.axolotl_sender_key_distribution_message | |
| ) | |
| def send_retry(self, message_node, registration_id): | |
| message_id = message_node["id"] | |
| if message_id in self._retries: | |
| count = self._retries[message_id] | |
| count += 1 | |
| else: | |
| count = 1 | |
| self._retries[message_id] = count | |
| retry = RetryOutgoingReceiptProtocolEntity.fromMessageNode(message_node, registration_id) | |
| retry.count = count | |
| self.toLower(retry.toProtocolTreeNode()) | |
| def send_pkmsg_for_invalid_message(self, sender_jid, message_id, participant=None): | |
| try: | |
| logger.info(f"[AxolotlReceive] Enviando mensagem PKMSG vazia para {sender_jid} devido a InvalidMessageException") | |
| send_layer = self.getLayerInterface(AxolotlSendLayer) | |
| if send_layer is None: | |
| logger.error("[AxolotlReceive] AxolotlSendLayer não encontrado no stack") | |
| return | |
| normalized_sender_jid = Jid.normalize(sender_jid) | |
| if normalized_sender_jid is None: | |
| logger.error(f"[AxolotlReceive] JID inválido: {sender_jid}") | |
| return | |
| message_attrs = MessageMetaAttributes( | |
| id=f"sync_{message_id}_{int(time.time())}", | |
| recipient=normalized_sender_jid, | |
| timestamp=int(time.time()) | |
| ) | |
| message_entity = TextMessageProtocolEntity("", message_attrs) | |
| message_node = message_entity.toProtocolTreeNode() | |
| message_node.setAttribute("to", normalized_sender_jid) | |
| message_node.setAttribute("type", "text") | |
| if participant: | |
| message_node.setAttribute("participant", participant) | |
| send_layer.sendToContactAsPkmsg(message_node) | |
| logger.debug(f"[AxolotlReceive] Mensagem PKMSG enviada com sucesso para {normalized_sender_jid}") | |
| except Exception as e: | |
| logger.error(f"[AxolotlReceive] Erro ao enviar mensagem PKMSG para {normalized_sender_jid if 'normalized_sender_jid' in locals() else sender_jid}: {e}") | |
| def reset_retries(self, message_id): | |
| if message_id in self._retries: | |
| del self._retries[message_id] | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class AxolotlSendLayer(AxolotlBaseLayer): | |
| MAX_SENT_QUEUE = 256 | |
| def __init__(self): | |
| super(AxolotlSendLayer, self).__init__() | |
| self.sessionCiphers = {} | |
| self.groupCiphers = {} | |
| self.sentQueue = [] | |
| def __str__(self): | |
| return "Axolotl Layer" | |
| def send(self, node): | |
| if node.tag == "message" and node["to"] not in self.skipEncJids: | |
| self.processPlaintextNodeAndSend(node) | |
| else: | |
| self.toLower(node) | |
| def receive(self, protocolTreeNode): | |
| def on_get_keys_success(node, retry_entity, success_jids, errors): | |
| if len(errors): | |
| self.on_get_keys_process_errors(errors) | |
| elif len(success_jids) == 1: | |
| self.processPlaintextNodeAndSend(node, retry_entity) | |
| #self.sendToContactsWithSessions(node, success_jids) | |
| #self.sendToGroupWithSessions(node,None) | |
| else: | |
| raise NotImplementedError() | |
| if not self.processIqRegistry(protocolTreeNode): | |
| if protocolTreeNode.tag == "receipt": | |
| ''' | |
| Going to keep all group message enqueued, as we get receipts from each participant | |
| So can't just remove it on first receipt. Therefore, the MAX queue length mechanism should better be working | |
| ''' | |
| messageNode = self.getEnqueuedMessageNode(protocolTreeNode["id"], protocolTreeNode["participant"] is not None) | |
| if protocolTreeNode["type"] == "retry": | |
| retryReceiptEntity = RetryIncomingReceiptProtocolEntity.fromProtocolTreeNode(protocolTreeNode) | |
| self.toLower(retryReceiptEntity.ack().toProtocolTreeNode()) #对重试那个包的ack,遵循协议 | |
| if messageNode : | |
| logger.info(f"Got retry to from {protocolTreeNode['from']} for message {protocolTreeNode['id']}, and Axolotl layer has the message") | |
| self.getKeysFor( | |
| [protocolTreeNode["participant"] or protocolTreeNode["from"]], | |
| lambda successJids, errors: on_get_keys_success(messageNode, retryReceiptEntity, successJids, errors) | |
| ) | |
| else: | |
| logger.debug("ignore retry receipt, because message not found in Axolotl") | |
| else: | |
| logger.debug("bubbling non-retry-receipt upwards") | |
| self.toUpper(protocolTreeNode) | |
| def on_get_keys_process_errors(self, errors): | |
| # type: (dict) -> None | |
| for jid, error in errors.items(): | |
| if isinstance(error, MissingParametersException): | |
| logger.error(f"Failed to create prekeybundle for {jid}, user had missing parameters: {error.parameters}, " | |
| "is that a valid user?") | |
| elif isinstance(error, exceptions.UntrustedIdentityException): | |
| logger.error(f"Failed to create session for {jid} as user's identity is not trusted. ") | |
| else: | |
| logger.error(f"Failed to process keys for {error[0]}, is that a valid user? Exception: {error[1]}") | |
| def processPlaintextNodeAndSend(self, node, retryReceiptEntity = None): | |
| if "," in node["to"]: | |
| #多个目标,直接发 | |
| jids = node["to"].split(",") | |
| logger.info("multi target send detect") | |
| #目标是第一个人 | |
| node["to"] = jids[0] | |
| self.ensureSessionsAndSendToContacts(node, jids) | |
| else: | |
| #单个目标,找到这个目标的所有设备来发 | |
| # | |
| account = node["to"].split('@')[0] | |
| isGroup = YowConstants.WHATSAPP_GROUP_SERVER in node["to"] or "broadcast" in node["to"] | |
| #isGroup= False | |
| def on_iq_success(result, original_iq_entity): | |
| entity = DevicesResultSyncIqProtocolEntity.fromProtocolTreeNode(result) | |
| jids = entity.collectAllResultJids() | |
| self.ensureSessionsAndSendToContacts(node, jids) | |
| def on_iq_error(entity, original_iq_entity): | |
| logger.error("Failed to sync user devices") | |
| if isGroup: | |
| self.sendToGroup(node, retryReceiptEntity) | |
| else: | |
| if ":" in account: | |
| #指定设备 | |
| accounts = [account] | |
| #获取account相关的所有accounts | |
| jids = ["%s@%s" % (r,YowConstants.WHATSAPP_SERVER) for r in accounts] | |
| self.ensureSessionsAndSendToContacts(node, jids) | |
| elif "lid" in node["to"]: | |
| self.ensureSessionsAndSendToContacts(node, [node["to"]]) | |
| else : | |
| #不指定设备,需要查客户所有的终端 | |
| entity = DevicesGetSyncIqProtocolEntity([node["to"]]) | |
| self._sendIq(entity, on_iq_success, on_iq_error) | |
| def enqueueSent(self, node): | |
| logger.debug("enqueueSent(node=[omitted])") | |
| if len(self.sentQueue) >= self.__class__.MAX_SENT_QUEUE: | |
| logger.warning("Discarding queued node without receipt") | |
| self.sentQueue.pop(0) | |
| self.sentQueue.append(node) | |
| def getEnqueuedMessageNode(self, messageId, keepEnqueued = False): | |
| for i in range(0, len(self.sentQueue)): | |
| if self.sentQueue[i]["id"] == messageId: | |
| if keepEnqueued: | |
| return self.sentQueue[i] | |
| return self.sentQueue.pop(i) | |
| def sendEncEntities(self, node, encEntities, participant=None,tctoken=None): | |
| logger.debug(f"sendEncEntities(node=[omitted], encEntities=[omitted], participant={participant})") | |
| message_attrs = MessageMetaAttributes.from_message_protocoltreenode(node) | |
| message_attrs.participant = participant | |
| messageEntity = EncryptedMessageProtocolEntity( | |
| encEntities, | |
| node["type"], | |
| message_attrs | |
| ) | |
| if participant is None: | |
| self.enqueueSent(node) | |
| nodeSend = messageEntity.toProtocolTreeNode() | |
| #把biz节点复制转发 | |
| if participant is None: | |
| self.enqueueSent(node) | |
| nodeSend = messageEntity.toProtocolTreeNode() | |
| #把biz节点复制转发 | |
| if node.getAttributeValue("category") == "peer": | |
| pass | |
| else: | |
| reporting = ProtocolTreeNode("reporting") | |
| reporting_token = ProtocolTreeNode("reporting_token",{"v":"2"}) | |
| reporting_token.setData(os.urandom(16)) | |
| reporting.addChild(reporting_token) | |
| nodeSend.addChild(reporting) | |
| if tctoken: | |
| tctoken = ProtocolTreeNode("tctoken",{},None,tctoken) | |
| nodeSend.addChild(tctoken) | |
| biz = node.getChild("biz") | |
| if biz is not None: | |
| nodeSend.addChild(biz) | |
| profile = self.getProp("profile") | |
| if profile.config.device_identity is not None: | |
| diddata = base64.b64decode(profile.config.device_identity) | |
| did = ProtocolTreeNode("device-identity", {},None,diddata) | |
| nodeSend.addChild(did) | |
| self.toLower(nodeSend) | |
| def sendToContact(self, node): | |
| recipient_id = node["to"].split('@')[0] | |
| protoNode = node.getChild("proto") | |
| messageData = protoNode.getData() | |
| ciphertext = self.manager.encrypt( | |
| recipient_id, | |
| messageData | |
| ) | |
| mediaType = protoNode["mediatype"] | |
| return self.sendEncEntities(node, [EncProtocolEntity(EncProtocolEntity.TYPE_MSG if ciphertext.__class__ == WhisperMessage else EncProtocolEntity.TYPE_PKMSG, 2, ciphertext.serialize(), mediaType)]) | |
| def sendToContactAsPkmsg(self, node): | |
| recipient_id = node["to"].split('@')[0] | |
| protoNode = node.getChild("proto") | |
| messageData = protoNode.getData() | |
| mediaType = protoNode["mediatype"] | |
| to_jid = node["to"] | |
| if "@" not in to_jid: | |
| to_jid = f"{to_jid}@{YowConstants.WHATSAPP_SERVER}" | |
| recipientId, a, deviceid = WATools.jidDecode(to_jid) | |
| session_backup = None | |
| had_session = False | |
| if self.manager.session_exists(to_jid): | |
| try: | |
| session_record = self.manager._store.loadSession(recipientId, deviceid) | |
| session_backup = session_record | |
| had_session = True | |
| logger.debug(f"Sessão existente encontrada para {to_jid}, será deletada temporariamente") | |
| except Exception as e: | |
| logger.warning(f"Erro ao carregar sessão para backup: {e}") | |
| if had_session: | |
| try: | |
| self.manager._store.deleteSession(recipientId, deviceid) | |
| logger.debug(f"Sessão deletada temporariamente para {to_jid}") | |
| except Exception as e: | |
| logger.warning(f"Erro ao deletar sessão: {e}") | |
| def on_get_keys_success(node, success_jids, errors): | |
| try: | |
| if len(errors): | |
| self.on_get_keys_process_errors(errors) | |
| if had_session and session_backup: | |
| try: | |
| self.manager._store.storeSession(recipientId, deviceid, session_backup) | |
| logger.debug(f"Sessão restaurada para {to_jid}") | |
| except Exception as e: | |
| logger.warning(f"Erro ao restaurar sessão: {e}") | |
| return | |
| if len(success_jids) == 0: | |
| logger.error(f"Nenhuma PreKey obtida para {to_jid}, não é possível enviar como PKMSG") | |
| if had_session and session_backup: | |
| try: | |
| self.manager._store.storeSession(recipientId, deviceid, session_backup) | |
| logger.debug(f"Sessão restaurada para {to_jid}") | |
| except Exception as e: | |
| logger.warning(f"Erro ao restaurar sessão: {e}") | |
| return | |
| jid = success_jids[0] | |
| recipient_id = jid.split('@')[0] | |
| ciphertext = self.manager.encrypt(recipient_id, messageData) | |
| if ciphertext.__class__ != PreKeyWhisperMessage: | |
| logger.warning(f"Esperado PreKeyWhisperMessage, mas obteve {ciphertext.__class__.__name__}") | |
| enc_entity = EncProtocolEntity( | |
| EncProtocolEntity.TYPE_PKMSG, | |
| 2, | |
| ciphertext.serialize(), | |
| mediaType | |
| ) | |
| self.sendEncEntities(node, [enc_entity]) | |
| except Exception as e: | |
| logger.error(f"Erro ao enviar mensagem como PKMSG: {e}") | |
| if had_session and session_backup: | |
| try: | |
| self.manager._store.storeSession(recipientId, deviceid, session_backup) | |
| logger.debug(f"Sessão restaurada após erro para {to_jid}") | |
| except Exception as restore_error: | |
| logger.warning(f"Erro ao restaurar sessão após erro: {restore_error}") | |
| self.getKeysFor( | |
| [to_jid], | |
| lambda success_jids, errors: on_get_keys_success(node, success_jids, errors) | |
| ) | |
| def ensureSessionsAndSendToContacts(self, node, jids): | |
| logger.debug(f"ensureSessionsAndSendToContacts(node=[omitted], jids={jids})") | |
| allJids = [] | |
| jidsNoSession = [] | |
| for jid in jids: | |
| if not self.manager.session_exists(jid.split('@')[0]): | |
| jidsNoSession.append(jid) | |
| else: | |
| allJids.append(jid) | |
| def on_get_keys_success(node, success_jids, errors): | |
| if len(errors): | |
| self.on_get_keys_process_errors(errors) | |
| return | |
| allJids.extend(success_jids) | |
| #所有能成功建立session的,都发 | |
| if len(success_jids)>0: | |
| if node.getAttributeValue("category")=="peer": | |
| self.sendToPeerWithSessions(node,allJids[0]) | |
| else: | |
| self.sendToContactsWithSessions(node, allJids) | |
| if len(jidsNoSession): | |
| self.getKeysFor(jidsNoSession, lambda successJids, errors: on_get_keys_success(node, successJids, errors)) | |
| else: | |
| if node.getAttributeValue("category")=="peer": | |
| self.sendToPeerWithSessions(node,allJids[0]) | |
| else: | |
| self.sendToContactsWithSessions(node, allJids) | |
| def sendToPeerWithSessions(self,node,jid): | |
| protoNode = node.getChild("proto") | |
| messageData = protoNode.getData() | |
| ciphertext = self.manager.encrypt( | |
| jid.split('@')[0], | |
| messageData | |
| ) | |
| encEntities = [ | |
| EncProtocolEntity( | |
| EncProtocolEntity.TYPE_MSG if ciphertext.__class__ == WhisperMessage else EncProtocolEntity.TYPE_PKMSG | |
| , 2, ciphertext.serialize(), protoNode["mediatype"], jid=None | |
| ) | |
| ] | |
| self.sendEncEntities(node, encEntities) | |
| def sendToContactsWithSessions(self, node, jids, retryCount=0): | |
| jids = jids or [] | |
| targetJid = node["to"] | |
| db = self.getStack().getProp("profile").axolotl_manager | |
| tctoken = db._store.getTcToken(targetJid) | |
| protoNode = node.getChild("proto") | |
| encEntities = [] | |
| messageData = protoNode.getData() | |
| participant = jids[0] if len(jids) == 1 and retryCount > 0 else None | |
| for jid in jids: | |
| messageData = protoNode.getData() | |
| ciphertext = self.manager.encrypt( | |
| jid.split('@')[0], | |
| messageData | |
| ) | |
| encEntities.append( | |
| EncProtocolEntity( | |
| EncProtocolEntity.TYPE_MSG if ciphertext.__class__ == WhisperMessage else EncProtocolEntity.TYPE_PKMSG | |
| , 2, ciphertext.serialize(), protoNode["mediatype"], jid=None if participant else jid | |
| ) | |
| ) | |
| self.sendEncEntities(node, encEntities, participant,tctoken) | |
| def sendToGroupWithSessions(self, node, jidsNeedSenderKey = None, retryCount=0): | |
| logger.debug( | |
| "sendToGroupWithSessions(node=[omitted], jidsNeedSenderKey=%s, retryCount=%d)" % (jidsNeedSenderKey, retryCount) | |
| ) | |
| jidsNeedSenderKey = jidsNeedSenderKey or [] | |
| groupJid = node["to"] | |
| protoNode = node.getChild("proto") | |
| encEntities = [] | |
| participant = jidsNeedSenderKey[0] if len(jidsNeedSenderKey) == 1 and retryCount > 0 else None | |
| if len(jidsNeedSenderKey): | |
| senderKeyDistributionMessage = self.manager.group_create_skmsg(groupJid) | |
| for jid in jidsNeedSenderKey: | |
| message = self.serializeSenderKeyDistributionMessageToProtobuf(node["to"], senderKeyDistributionMessage) | |
| if retryCount > 0: | |
| message.MergeFromString(protoNode.getData()) | |
| ciphertext = self.manager.encrypt(jid.split('@')[0], message.SerializeToString()) | |
| encEntities.append( | |
| EncProtocolEntity( | |
| EncProtocolEntity.TYPE_MSG if ciphertext.__class__ == WhisperMessage else EncProtocolEntity.TYPE_PKMSG | |
| , 2, ciphertext.serialize(), protoNode["mediatype"], jid=None if participant else jid,count=str(retryCount) | |
| ) | |
| ) | |
| if not retryCount: | |
| messageData = protoNode.getData() | |
| try: | |
| ciphertext = self.manager.group_encrypt(groupJid, messageData) | |
| except exceptions.NoSessionException as e: | |
| # Sender key não existe, criar antes de criptografar | |
| logger.warning(f"Sender key não encontrado para grupo {groupJid}, criando... (erro: {e})") | |
| self.manager.group_create_skmsg(groupJid) | |
| # Tentar criptografar novamente | |
| try: | |
| ciphertext = self.manager.group_encrypt(groupJid, messageData) | |
| except exceptions.NoSessionException as e2: | |
| logger.error(f"Falha ao criptografar mensagem para grupo {groupJid} mesmo após criar sender key: {e2}") | |
| raise | |
| mediaType = protoNode["mediatype"] | |
| encEntities.append(EncProtocolEntity(EncProtocolEntity.TYPE_SKMSG, 2, ciphertext, mediaType)) | |
| self.sendEncEntities(node, encEntities, participant) | |
| def ensureSessionsAndSendToGroup(self, node, jids): | |
| logger.debug(f"ensureSessionsAndSendToGroup(node=[omitted], jids={jids})") | |
| allJids = [] | |
| jidsNoSession = [] | |
| standardJids = [] | |
| for jid in jids: | |
| standardJids.append(jid.replace(".0:0","").replace(".1:",":")) | |
| for jid in standardJids: | |
| if not self.manager.session_exists(jid.split('@')[0]): | |
| jidsNoSession.append(jid) #如果是xxxx.0:0, 规范是不加任何后缀,否则解码后就会对应不上) | |
| else: | |
| allJids.append(jid) | |
| def on_get_keys_success(node, success_jids, errors): | |
| if len(errors): | |
| self.on_get_keys_process_errors(errors) | |
| allJids.extend(success_jids) | |
| self.sendToGroupWithSessions(node, allJids) | |
| if len(jidsNoSession): | |
| self.getKeysFor(jidsNoSession, lambda successJids, errors: on_get_keys_success(node, successJids, errors)) | |
| else: | |
| self.sendToGroupWithSessions(node, standardJids) | |
| def sendToGroup(self, node, retryReceiptEntity = None): | |
| logger.debug(f"sendToGroup(node={node}, retryReceiptEntity={retryReceiptEntity})") | |
| retry_info = f"[retry_count={retryReceiptEntity.getRetryCount()}, retry_jid={retryReceiptEntity.getRetryJid()}]" if retryReceiptEntity is not None else "[None]" | |
| logger.debug(f"sendToGroup(node=[omitted], retryReceiptEntity={retry_info})") | |
| groupJid = node["to"] | |
| logger.debug(f"groupJid={groupJid}") | |
| ownJid = self.getLayerInterface(YowAuthenticationProtocolLayer).getUsername(True) | |
| senderKeyRecord = self.manager.load_senderkey(node["to"]) | |
| def sendToGroup(resultNode, requestEntity): | |
| groupInfo = InfoGroupsResultIqProtocolEntity.fromProtocolTreeNode(resultNode) | |
| jids = list(groupInfo.getParticipants().keys()) #keys in py3 returns dict_keys | |
| if ownJid in jids: | |
| jids.remove(ownJid) | |
| return self.ensureSessionsAndSendToGroup(node, jids) | |
| if groupJid=="status@broadcast": | |
| # Para status@broadcast, obtém todos os contatos conhecidos | |
| profile: YowProfile = self.getProp("profile") | |
| logger.debug("Tentando obter identidades como fallback para status@broadcast") | |
| identity_store = profile.axolotl_manager._store | |
| logger.debug(f"identity_store: {identity_store}") | |
| logger.info(f"profile: {profile}") | |
| jids = identity_store.getAllContact() | |
| logger.debug(f"jids: {jids}") | |
| if jids: | |
| logger.info(f"Enviando status para {len(jids)} contatos via status@broadcast") | |
| self.ensureSessionsAndSendToGroup(node, jids) | |
| else: | |
| logger.warning("Nenhum contato encontrado para status@broadcast, enviando sem destinatários específicos") | |
| # Envia mesmo assim - o WhatsApp pode lidar com isso (pode ser que não tenha contatos ainda) | |
| self.ensureSessionsAndSendToGroup(node, []) | |
| elif groupJid.endswith("@broadcast"): | |
| jids = self._manager._store.findParticipantsByBcid(groupJid) | |
| self.ensureSessionsAndSendToGroup(node, jids) | |
| elif senderKeyRecord.isEmpty(): | |
| logger.debug("senderKeyRecord is empty, requesting group info") | |
| groupInfoIq = InfoGroupsIqProtocolEntity(groupJid) | |
| self._sendIq(groupInfoIq, sendToGroup) | |
| else: | |
| logger.debug("We have a senderKeyRecord") | |
| retryCount = 0 | |
| jidsNeedSenderKey = [] | |
| if retryReceiptEntity is not None: | |
| retryCount = retryReceiptEntity.getRetryCount() | |
| jidsNeedSenderKey.append(retryReceiptEntity.getRetryJid()) | |
| self.sendToGroupWithSessions(node, jidsNeedSenderKey, retryCount) | |
| def serializeSenderKeyDistributionMessageToProtobuf(self, groupId, senderKeyDistributionMessage, message = None): | |
| m = message or Message() | |
| m.sender_key_distribution_message.group_id = groupId | |
| m.sender_key_distribution_message.axolotl_sender_key_distribution_message = senderKeyDistributionMessage.serialize() | |
| m.sender_key_distribution_message.axolotl_sender_key_distribution_message = senderKeyDistributionMessage.serialize() | |
| # m.conversation = text | |
| return m | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment