Added contact based dest

This commit is contained in:
Alex Wolden
2025-04-13 22:19:08 -07:00
parent 1a9f6d1024
commit a1fb931200
5 changed files with 94 additions and 26 deletions

View File

@@ -26,8 +26,11 @@ async def main():
# Send a message to the first contact # Send a message to the first contact
if contacts: if contacts:
contact_key = next(iter(contacts.items()))[1]['public_key'] # Get the first contact
await meshcore.commands.send_msg(bytes.fromhex(contact_key), "Hello from Python!") contact = next(iter(contacts.items()))[1]
# Pass the contact object directly to send_msg
await meshcore.commands.send_msg(contact, "Hello from Python!")
await meshcore.disconnect() await meshcore.disconnect()
@@ -247,15 +250,31 @@ This logs detailed information about commands sent and events received.
### Sending Messages to Contacts ### Sending Messages to Contacts
Commands that require a destination (`send_msg`, `send_login`, `send_statusreq`, etc.) now accept either:
- A string with the hex representation of a public key
- A contact object with a "public_key" field
- Bytes object (for backward compatibility)
```python ```python
# Get contacts and send to a specific one # Get contacts and send to a specific one
contacts = await meshcore.commands.get_contacts() contacts = await meshcore.commands.get_contacts()
for key, contact in contacts.items(): for key, contact in contacts.items():
if contact["adv_name"] == "Alice": if contact["adv_name"] == "Alice":
# Convert the hex key to bytes # Option 1: Pass the contact object directly
await meshcore.commands.send_msg(contact, "Hello Alice!")
# Option 2: Use the public key string
await meshcore.commands.send_msg(contact["public_key"], "Hello again Alice!")
# Option 3 (backward compatible): Convert the hex key to bytes
dst_key = bytes.fromhex(contact["public_key"]) dst_key = bytes.fromhex(contact["public_key"])
await meshcore.commands.send_msg(dst_key, "Hello Alice!") await meshcore.commands.send_msg(dst_key, "Hello once more Alice!")
break break
# You can also directly use a contact found by name
contact = meshcore.get_contact_by_name("Bob")
if contact:
await meshcore.commands.send_msg(contact, "Hello Bob!")
``` ```
### Monitoring Channel Messages ### Monitoring Channel Messages

View File

@@ -38,7 +38,7 @@ async def main():
# Send the message and get the MSG_SENT event # Send the message and get the MSG_SENT event
print(f"Sending message: '{args.message}'") print(f"Sending message: '{args.message}'")
send_result = await mc.commands.send_msg( send_result = await mc.commands.send_msg(
bytes.fromhex(contact["public_key"])[0:6], contact,
args.message args.message
) )

View File

@@ -16,7 +16,10 @@ async def main () :
await mc.commands.get_contacts() await mc.commands.get_contacts()
repeater = mc.get_contact_by_name(REPEATER) repeater = mc.get_contact_by_name(REPEATER)
await mc.commands.send_login(bytes.fromhex(repeater["public_key"]), PASSWORD) if repeater is None:
print(f"Repeater '{REPEATER}' not found in contacts.")
return
await mc.commands.send_login(repeater, PASSWORD)
print("Login sent ... awaiting") print("Login sent ... awaiting")

View File

@@ -17,6 +17,10 @@ async def main () :
await mc.connect() await mc.connect()
await mc.ensure_contacts() await mc.ensure_contacts()
await mc.commands.send_msg(bytes.fromhex(mc.get_contact_by_name(DEST)["public_key"])[0:6],MSG) contact = mc.get_contact_by_name(DEST)
if contact is None:
print(f"Contact '{DEST}' not found in contacts.")
return
await mc.commands.send_msg(contact ,MSG)
asyncio.run(main()) asyncio.run(main())

View File

@@ -1,11 +1,50 @@
import asyncio import asyncio
import logging import logging
from typing import Any, Dict from typing import Any, Dict, List, Optional, Union
from .events import EventType from .events import EventType
import random import random
# Define types for destination parameters
DestinationType = Union[bytes, str, Dict[str, Any]]
logger = logging.getLogger("meshcore") logger = logging.getLogger("meshcore")
def _validate_destination(dst: DestinationType, prefix_length: int = 6) -> bytes:
"""
Validates and converts a destination to a bytes object.
Args:
dst: The destination, which can be:
- str: Hex string representation of a public key
- dict: Contact object with a "public_key" field
prefix_length: The length of the prefix to use (default: 6 bytes)
Returns:
bytes: The destination public key as a bytes object
Raises:
ValueError: If dst is invalid or doesn't contain required fields
"""
if isinstance(dst, bytes):
# Already bytes, use directly
return dst[:prefix_length]
elif isinstance(dst, str):
# Hex string, convert to bytes
try:
return bytes.fromhex(dst)[:prefix_length]
except ValueError:
raise ValueError(f"Invalid public key hex string: {dst}")
elif isinstance(dst, dict):
# Contact object, extract public_key
if "public_key" not in dst:
raise ValueError("Contact object must have a 'public_key' field")
try:
return bytes.fromhex(dst["public_key"])[:prefix_length]
except ValueError:
raise ValueError(f"Invalid public_key in contact: {dst['public_key']}")
else:
raise ValueError(f"Destination must be a public key string or contact object, got: {type(dst)}")
class CommandHandler: class CommandHandler:
DEFAULT_TIMEOUT = 5.0 DEFAULT_TIMEOUT = 5.0
@@ -166,41 +205,44 @@ class CommandHandler:
logger.debug("Requesting pending messages") logger.debug("Requesting pending messages")
return await self.send(b"\x0A", [EventType.CONTACT_MSG_RECV, EventType.CHANNEL_MSG_RECV, EventType.ERROR], timeout) return await self.send(b"\x0A", [EventType.CONTACT_MSG_RECV, EventType.CHANNEL_MSG_RECV, EventType.ERROR], timeout)
async def send_login(self, dst, pwd): async def send_login(self, dst: DestinationType, pwd: str) -> Dict[str, Any]:
logger.debug(f"Sending login request to: {dst.hex() if isinstance(dst, bytes) else dst}") dst_bytes = _validate_destination(dst)
data = b"\x1a" + dst + pwd.encode("ascii") logger.debug(f"Sending login request to: {dst_bytes.hex()}")
data = b"\x1a" + dst_bytes + pwd.encode("ascii")
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR]) return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
async def send_logout(self, dst): async def send_logout(self, dst: DestinationType) -> Dict[str, Any]:
dst_bytes = _validate_destination(dst)
self.login_resp = asyncio.Future() self.login_resp = asyncio.Future()
data = b"\x1d" + dst data = b"\x1d" + dst_bytes
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR]) return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
async def send_statusreq(self, dst): async def send_statusreq(self, dst: DestinationType) -> Dict[str, Any]:
logger.debug(f"Sending status request to: {dst.hex() if isinstance(dst, bytes) else dst}") dst_bytes = _validate_destination(dst)
data = b"\x1b" + dst logger.debug(f"Sending status request to: {dst_bytes.hex()}")
data = b"\x1b" + dst_bytes
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR]) return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
async def send_cmd(self, dst, cmd, timestamp=None): async def send_cmd(self, dst: DestinationType, cmd: str, timestamp: Optional[int] = None) -> Dict[str, Any]:
logger.debug(f"Sending command to {dst.hex() if isinstance(dst, bytes) else dst}: {cmd}") dst_bytes = _validate_destination(dst)
logger.debug(f"Sending command to {dst_bytes.hex()}: {cmd}")
# Default to current time if timestamp not provided
if timestamp is None: if timestamp is None:
import time import time
timestamp = int(time.time()).to_bytes(4, 'little') timestamp = int(time.time())
data = b"\x02\x01\x00" + timestamp + dst + cmd.encode("ascii") data = b"\x02\x01\x00" + timestamp.to_bytes(4, 'little') + dst_bytes + cmd.encode("ascii")
return await self.send(data, [EventType.OK, EventType.ERROR]) return await self.send(data, [EventType.OK, EventType.ERROR])
async def send_msg(self, dst, msg, timestamp=None): async def send_msg(self, dst: DestinationType, msg: str, timestamp: Optional[int] = None) -> Dict[str, Any]:
logger.debug(f"Sending message to {dst.hex() if isinstance(dst, bytes) else dst}: {msg}") dst_bytes = _validate_destination(dst)
logger.debug(f"Sending message to {dst_bytes.hex()}: {msg}")
# Default to current time if timestamp not provided
if timestamp is None: if timestamp is None:
import time import time
timestamp = int(time.time()).to_bytes(4, 'little') timestamp = int(time.time())
data = b"\x02\x00\x00" + timestamp + dst + msg.encode("ascii") data = b"\x02\x00\x00" + timestamp.to_bytes(4, 'little') + dst_bytes + msg.encode("ascii")
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR]) return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
async def send_chan_msg(self, chan, msg, timestamp=None): async def send_chan_msg(self, chan, msg, timestamp=None):