Improve insights and continue WhatsApp implementation
This commit is contained in:
@@ -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"))
|
||||
|
||||
Reference in New Issue
Block a user