mirror of
https://github.com/liamcottle/reticulum-meshchat.git
synced 2026-04-27 16:10:32 +00:00
implement sending messages to group
This commit is contained in:
parent
2a5152ea67
commit
d084bbc73d
4 changed files with 99 additions and 4 deletions
16
database.py
16
database.py
|
|
@ -158,3 +158,19 @@ class GroupMember(BaseModel):
|
|||
# only allow a single row per group_destination_hash/member_identity_hash pair
|
||||
SQL('UNIQUE (group_destination_hash, member_identity_hash)'),
|
||||
]
|
||||
|
||||
|
||||
class GroupMessage(BaseModel):
|
||||
|
||||
id = BigAutoField()
|
||||
hash = CharField(unique=True)
|
||||
group_destination_hash = CharField(index=True)
|
||||
member_identity_hash = CharField()
|
||||
content = TextField()
|
||||
|
||||
created_at = DateTimeField(default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = DateTimeField(default=lambda: datetime.now(timezone.utc))
|
||||
|
||||
# define table name
|
||||
class Meta:
|
||||
table_name = "group_messages"
|
||||
|
|
|
|||
34
meshchat.py
34
meshchat.py
|
|
@ -139,6 +139,39 @@ class GroupChatDataProvider(GroupDataProviderInterface):
|
|||
|
||||
return members
|
||||
|
||||
# save a message sent to the group by the provided identity
|
||||
def on_message_received(self, group_destination_hash: bytes, identity_hash: bytes, data: dict):
|
||||
|
||||
# find group
|
||||
group = self.find_group(group_destination_hash)
|
||||
if group is None:
|
||||
raise Exception("Group not found")
|
||||
|
||||
# generate a message hash similar to lxmf
|
||||
message_data_to_hash = b""
|
||||
message_data_to_hash += group_destination_hash
|
||||
message_data_to_hash += identity_hash
|
||||
message_data_to_hash += msgpack.packb(data)
|
||||
message_hash = RNS.Identity.full_hash(message_data_to_hash).hex()
|
||||
|
||||
# get content as string
|
||||
content = None
|
||||
if "content" in data:
|
||||
content = str(data["content"])
|
||||
|
||||
# prepare data to insert or update
|
||||
data = {
|
||||
"hash": message_hash,
|
||||
"group_destination_hash": group.destination_hash,
|
||||
"member_identity_hash": identity_hash.hex(),
|
||||
"content": content,
|
||||
"updated_at": datetime.now(timezone.utc),
|
||||
}
|
||||
|
||||
# upsert message to database
|
||||
database.GroupMessage.insert(data).on_conflict(conflict_target=[database.GroupMessage.hash], update=data).execute()
|
||||
|
||||
|
||||
class ReticulumMeshChat:
|
||||
|
||||
def __init__(self, identity: RNS.Identity, storage_dir, reticulum_config_dir):
|
||||
|
|
@ -175,6 +208,7 @@ class ReticulumMeshChat:
|
|||
database.CustomDestinationDisplayName,
|
||||
database.Group,
|
||||
database.GroupMember,
|
||||
database.GroupMessage,
|
||||
database.LxmfMessage,
|
||||
database.LxmfConversationReadState,
|
||||
])
|
||||
|
|
|
|||
|
|
@ -103,6 +103,13 @@ class GroupChatClient:
|
|||
"limit": limit,
|
||||
}).encode("utf-8"))
|
||||
|
||||
# send message
|
||||
async def send_message(self, content: str):
|
||||
return await self.request("/api/v1/messages/send", data=json.dumps({
|
||||
"timestamp": time.time(),
|
||||
"content": content,
|
||||
}).encode("utf-8"))
|
||||
|
||||
|
||||
# python3 group_chat_client.py
|
||||
# used for testing group chat client
|
||||
|
|
@ -120,6 +127,7 @@ async def main():
|
|||
print(await group_chat_client.join("Test Display Name"))
|
||||
print(await group_chat_client.get_info())
|
||||
print(await group_chat_client.get_members(page=1, limit=10))
|
||||
print(await group_chat_client.send_message("hello world!"))
|
||||
print(await group_chat_client.leave())
|
||||
print(await group_chat_client.get_info())
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,11 @@ class GroupDataProviderInterface:
|
|||
def get_members(self, group_destination_hash: bytes, page: int | None, limit: int | None):
|
||||
raise Exception("Not Implemented")
|
||||
|
||||
# save a message sent to the group by the provided identity
|
||||
def on_message_received(self, group_destination_hash: bytes, identity_hash: bytes, data: dict):
|
||||
raise Exception("Not Implemented")
|
||||
|
||||
|
||||
# a group chat server than can handle membership management
|
||||
class GroupChatServer:
|
||||
|
||||
|
|
@ -53,6 +58,7 @@ class GroupChatServer:
|
|||
self.group_destination.register_request_handler(path="/api/v1/join", response_generator=self.on_received_api_v1_join_request, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.group_destination.register_request_handler(path="/api/v1/leave", response_generator=self.on_received_api_v1_leave_request, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.group_destination.register_request_handler(path="/api/v1/members", response_generator=self.on_received_api_v1_members_request, allow=RNS.Destination.ALLOW_ALL)
|
||||
self.group_destination.register_request_handler(path="/api/v1/messages/send", response_generator=self.on_received_api_v1_messages_send_request, allow=RNS.Destination.ALLOW_ALL)
|
||||
|
||||
# announce group destination
|
||||
def announce(self):
|
||||
|
|
@ -79,6 +85,10 @@ class GroupChatServer:
|
|||
def identity_not_provided_error_response(self):
|
||||
return self.error_response("You must identity to to access this endpoint.")
|
||||
|
||||
# error response for failing to parse request data as json
|
||||
def request_json_parsing_error_response(self):
|
||||
return self.error_response("Failed to parse request data as JSON.")
|
||||
|
||||
# /api/v1/info
|
||||
def on_received_api_v1_info_request(self, path, data, request_id, remote_identity, requested_at):
|
||||
return json.dumps({
|
||||
|
|
@ -101,8 +111,7 @@ class GroupChatServer:
|
|||
json_data = json.loads(data.decode("utf-8"))
|
||||
display_name = json_data["display_name"] or "Anonymous Peer"
|
||||
except:
|
||||
print("failed to parse request data as json")
|
||||
pass
|
||||
return self.request_json_parsing_error_response()
|
||||
|
||||
# ensure user is not already a member
|
||||
if self.data_provider.is_member(self.group_destination.hash, remote_identity.hash):
|
||||
|
|
@ -141,8 +150,7 @@ class GroupChatServer:
|
|||
page = json_data["page"]
|
||||
limit = json_data["limit"]
|
||||
except:
|
||||
print("failed to parse request data as json")
|
||||
pass
|
||||
return self.request_json_parsing_error_response()
|
||||
|
||||
# ensure user is a member
|
||||
if not self.data_provider.is_member(self.group_destination.hash, remote_identity.hash):
|
||||
|
|
@ -162,3 +170,32 @@ class GroupChatServer:
|
|||
return json.dumps({
|
||||
"members": group_members,
|
||||
}).encode("utf-8")
|
||||
|
||||
# /api/v1/messages/send
|
||||
def on_received_api_v1_messages_send_request(self, path, data: bytes | None, request_id, remote_identity: RNS.Identity | None, requested_at):
|
||||
|
||||
# ensure user has identified
|
||||
if remote_identity is None:
|
||||
return self.identity_not_provided_error_response()
|
||||
|
||||
# ensure user is a member
|
||||
if not self.data_provider.is_member(self.group_destination.hash, remote_identity.hash):
|
||||
return self.error_response("You are not a member of this group")
|
||||
|
||||
# attempt to parse data as json
|
||||
json_data = None
|
||||
if data is not None:
|
||||
try:
|
||||
json_data = json.loads(data.decode("utf-8"))
|
||||
except:
|
||||
return self.request_json_parsing_error_response()
|
||||
|
||||
# todo ensure only expected content is received
|
||||
# todo ensure timestamp
|
||||
|
||||
# handle received message
|
||||
self.data_provider.on_message_received(self.group_destination.hash, remote_identity.hash, json_data)
|
||||
|
||||
return json.dumps({
|
||||
"success": "Message received",
|
||||
}).encode("utf-8")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue