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
if contacts:
contact_key = next(iter(contacts.items()))[1]['public_key']
await meshcore.commands.send_msg(bytes.fromhex(contact_key), "Hello from Python!")
# Get the first contact
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()
@ -247,15 +250,31 @@ This logs detailed information about commands sent and events received.
### 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
# Get contacts and send to a specific one
contacts = await meshcore.commands.get_contacts()
for key, contact in contacts.items():
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"])
await meshcore.commands.send_msg(dst_key, "Hello Alice!")
await meshcore.commands.send_msg(dst_key, "Hello once more Alice!")
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

View file

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

View file

@ -16,7 +16,10 @@ async def main () :
await mc.commands.get_contacts()
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")

View file

@ -17,6 +17,10 @@ async def main () :
await mc.connect()
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())

View file

@ -1,11 +1,50 @@
import asyncio
import logging
from typing import Any, Dict
from typing import Any, Dict, List, Optional, Union
from .events import EventType
import random
# Define types for destination parameters
DestinationType = Union[bytes, str, Dict[str, Any]]
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:
DEFAULT_TIMEOUT = 5.0
@ -166,41 +205,44 @@ class CommandHandler:
logger.debug("Requesting pending messages")
return await self.send(b"\x0A", [EventType.CONTACT_MSG_RECV, EventType.CHANNEL_MSG_RECV, EventType.ERROR], timeout)
async def send_login(self, dst, pwd):
logger.debug(f"Sending login request to: {dst.hex() if isinstance(dst, bytes) else dst}")
data = b"\x1a" + dst + pwd.encode("ascii")
async def send_login(self, dst: DestinationType, pwd: str) -> Dict[str, Any]:
dst_bytes = _validate_destination(dst)
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])
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()
data = b"\x1d" + dst
data = b"\x1d" + dst_bytes
return await self.send(data, [EventType.MSG_SENT, EventType.ERROR])
async def send_statusreq(self, dst):
logger.debug(f"Sending status request to: {dst.hex() if isinstance(dst, bytes) else dst}")
data = b"\x1b" + dst
async def send_statusreq(self, dst: DestinationType) -> Dict[str, Any]:
dst_bytes = _validate_destination(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])
async def send_cmd(self, dst, cmd, timestamp=None):
logger.debug(f"Sending command to {dst.hex() if isinstance(dst, bytes) else dst}: {cmd}")
async def send_cmd(self, dst: DestinationType, cmd: str, timestamp: Optional[int] = None) -> Dict[str, Any]:
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:
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])
async def send_msg(self, dst, msg, timestamp=None):
logger.debug(f"Sending message to {dst.hex() if isinstance(dst, bytes) else dst}: {msg}")
async def send_msg(self, dst: DestinationType, msg: str, timestamp: Optional[int] = None) -> Dict[str, Any]:
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:
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])
async def send_chan_msg(self, chan, msg, timestamp=None):