Improve insights and continue WhatsApp implementation

This commit is contained in:
2026-02-15 23:02:51 +00:00
parent b23af9bc7f
commit 88224d972c
13 changed files with 628 additions and 81 deletions

View File

@@ -5,13 +5,19 @@ import json
import re
import time
from datetime import datetime, timezone as dt_timezone
from urllib.parse import urlencode, urlparse
from urllib.parse import quote_plus, urlencode, urlparse
from asgiref.sync import async_to_sync
from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core import signing
from django.core.cache import cache
from django.http import HttpResponseBadRequest, JsonResponse
from django.http import (
HttpResponse,
HttpResponseBadRequest,
HttpResponseNotFound,
JsonResponse,
)
from django.shortcuts import get_object_or_404, render
from django.urls import NoReverseMatch, reverse
from django.utils import timezone as dj_timezone
@@ -19,6 +25,7 @@ from django.views import View
from core.clients import transport
from core.messaging import ai as ai_runner
from core.messaging import media_bridge
from core.messaging.utils import messages_to_string
from core.models import (
AI,
@@ -127,9 +134,31 @@ def _looks_like_image_url(url_value: str) -> bool:
return False
parsed = urlparse(url_value)
path = str(parsed.path or "").lower()
if path.endswith("/compose/media/blob/"):
return True
return path.endswith(IMAGE_EXTENSIONS)
def _is_xmpp_share_url(url_value: str) -> bool:
if not url_value:
return False
parsed = urlparse(url_value)
host = str(parsed.netloc or "").strip().lower()
configured = str(
getattr(settings, "XMPP_UPLOAD_SERVICE", "")
or getattr(settings, "XMPP_UPLOAD_JID", "")
).strip().lower()
if not configured:
return False
configured_host = configured
if "://" in configured:
configured_host = (urlparse(configured).netloc or configured_host).lower()
if "@" in configured_host:
configured_host = configured_host.split("@", 1)[-1]
configured_host = configured_host.split("/", 1)[0]
return host == configured_host
def _image_url_from_text(text_value: str) -> str:
urls = _image_urls_from_text(text_value)
return urls[0] if urls else ""
@@ -175,12 +204,23 @@ def _extract_attachment_image_urls(blob) -> list[str]:
filename = str(blob.get("filename") or blob.get("fileName") or "").strip()
image_hint = content_type.startswith("image/") or _looks_like_image_name(filename)
direct_urls = []
for key in ("url", "source_url", "download_url", "proxy_url", "href", "uri"):
normalized = _clean_url(blob.get(key))
if not normalized:
continue
if image_hint or _looks_like_image_url(normalized):
urls.append(normalized)
if (
image_hint
or _looks_like_image_url(normalized)
or _is_xmpp_share_url(normalized)
):
direct_urls.append(normalized)
urls.extend(direct_urls)
blob_key = str(blob.get("blob_key") or "").strip()
# Prefer source-hosted URLs (for example share.zm.is) and use blob fallback only
# when no usable direct URL exists.
if blob_key and image_hint and not direct_urls:
urls.append(f"/compose/media/blob/?key={quote_plus(blob_key)}")
nested = blob.get("attachments")
if isinstance(nested, list):
@@ -1632,6 +1672,29 @@ class ComposeThread(LoginRequiredMixin, View):
return JsonResponse(payload)
class ComposeMediaBlob(LoginRequiredMixin, View):
"""
Serve cached media blobs for authenticated compose image previews.
"""
def get(self, request):
blob_key = str(request.GET.get("key") or "").strip()
if not blob_key:
return HttpResponseBadRequest("Missing blob key.")
row = media_bridge.get_blob(blob_key)
if not row:
return HttpResponseNotFound("Blob not found.")
content = row.get("content") or b""
content_type = str(row.get("content_type") or "application/octet-stream")
filename = str(row.get("filename") or "attachment.bin")
response = HttpResponse(content, content_type=content_type)
response["Content-Length"] = str(len(content))
response["Content-Disposition"] = f'inline; filename="{filename}"'
return response
class ComposeDrafts(LoginRequiredMixin, View):
def get(self, request):
service = _default_service(request.GET.get("service"))