Implement reactions and image sync
This commit is contained in:
@@ -28,7 +28,7 @@ async def stop_typing(uuid):
|
||||
return await response.text() # Optional: Return response content
|
||||
|
||||
|
||||
async def download_and_encode_base64(file_url, filename, content_type):
|
||||
async def download_and_encode_base64(file_url, filename, content_type, session=None):
|
||||
"""
|
||||
Downloads a file from a given URL asynchronously, converts it to Base64,
|
||||
and returns it in Signal's expected format.
|
||||
@@ -42,10 +42,17 @@ async def download_and_encode_base64(file_url, filename, content_type):
|
||||
str | None: The Base64 encoded attachment string in Signal's expected format, or None on failure.
|
||||
"""
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
if session is not None:
|
||||
async with session.get(file_url, timeout=10) as response:
|
||||
if response.status != 200:
|
||||
# log.error(f"Failed to download file: {file_url}, status: {response.status}")
|
||||
return None
|
||||
file_data = await response.read()
|
||||
base64_encoded = base64.b64encode(file_data).decode("utf-8")
|
||||
return f"data:{content_type};filename={filename};base64,{base64_encoded}"
|
||||
|
||||
async with aiohttp.ClientSession() as local_session:
|
||||
async with local_session.get(file_url, timeout=10) as response:
|
||||
if response.status != 200:
|
||||
return None
|
||||
|
||||
file_data = await response.read()
|
||||
@@ -82,19 +89,39 @@ async def send_message_raw(recipient_uuid, text=None, attachments=None):
|
||||
"base64_attachments": [],
|
||||
}
|
||||
|
||||
# Asynchronously download and encode all attachments
|
||||
async def _attachment_to_base64(attachment, session):
|
||||
row = dict(attachment or {})
|
||||
filename = row.get("filename") or "attachment.bin"
|
||||
content_type = row.get("content_type") or "application/octet-stream"
|
||||
content = row.get("content")
|
||||
if isinstance(content, memoryview):
|
||||
content = content.tobytes()
|
||||
elif isinstance(content, bytearray):
|
||||
content = bytes(content)
|
||||
if isinstance(content, bytes):
|
||||
encoded = base64.b64encode(content).decode("utf-8")
|
||||
return f"data:{content_type};filename={filename};base64,{encoded}"
|
||||
file_url = row.get("url")
|
||||
if not file_url:
|
||||
return None
|
||||
return await download_and_encode_base64(file_url, filename, content_type, session)
|
||||
|
||||
# Asynchronously resolve and encode all attachments
|
||||
attachments = attachments or []
|
||||
tasks = [
|
||||
download_and_encode_base64(att["url"], att["filename"], att["content_type"])
|
||||
for att in attachments
|
||||
]
|
||||
encoded_attachments = await asyncio.gather(*tasks)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
tasks = [_attachment_to_base64(att, session) for att in attachments]
|
||||
encoded_attachments = await asyncio.gather(*tasks)
|
||||
|
||||
# Filter out failed downloads (None values)
|
||||
data["base64_attachments"] = [att for att in encoded_attachments if att]
|
||||
|
||||
# Remove the message body if it only contains an attachment link
|
||||
if text and (text.strip() in [att["url"] for att in attachments]):
|
||||
attachment_urls = {
|
||||
str((att or {}).get("url") or "").strip()
|
||||
for att in attachments
|
||||
if str((att or {}).get("url") or "").strip()
|
||||
}
|
||||
if text and text.strip() in attachment_urls:
|
||||
# log.info("Removing message body since it only contains an attachment link.")
|
||||
text = None # Don't send the link as text
|
||||
|
||||
@@ -112,6 +139,42 @@ async def send_message_raw(recipient_uuid, text=None, attachments=None):
|
||||
return False
|
||||
|
||||
|
||||
async def send_reaction(
|
||||
recipient_uuid,
|
||||
emoji,
|
||||
target_timestamp=None,
|
||||
target_author=None,
|
||||
remove=False,
|
||||
):
|
||||
base = getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080").rstrip("/")
|
||||
sender_number = settings.SIGNAL_NUMBER
|
||||
if not recipient_uuid or not target_timestamp:
|
||||
return False
|
||||
|
||||
payload = {
|
||||
"recipient": recipient_uuid,
|
||||
"reaction": str(emoji or ""),
|
||||
"target_author": str(target_author or recipient_uuid),
|
||||
"timestamp": int(target_timestamp),
|
||||
"remove": bool(remove),
|
||||
}
|
||||
|
||||
candidate_urls = [f"{base}/v1/reactions/{sender_number}"]
|
||||
|
||||
timeout = aiohttp.ClientTimeout(total=20)
|
||||
async with aiohttp.ClientSession(timeout=timeout) as session:
|
||||
for url in candidate_urls:
|
||||
for method in ("post",):
|
||||
try:
|
||||
request = getattr(session, method)
|
||||
async with request(url, json=payload) as response:
|
||||
if 200 <= response.status < 300:
|
||||
return True
|
||||
except Exception:
|
||||
continue
|
||||
return False
|
||||
|
||||
|
||||
async def fetch_signal_attachment(attachment_id):
|
||||
"""
|
||||
Asynchronously fetches an attachment from Signal.
|
||||
|
||||
Reference in New Issue
Block a user