jjlj
This commit is contained in:
@@ -1660,7 +1660,7 @@ class FileProvider(ABC):
|
||||
self.name = self.__class__.__name__.replace("FileProvider", "").lower()
|
||||
|
||||
@abstractmethod
|
||||
def upload(self, file_path: str) -> str:
|
||||
def upload(self, file_path: str, **kwargs: Any) -> str:
|
||||
"""Upload a file and return the URL."""
|
||||
pass
|
||||
|
||||
@@ -1677,7 +1677,7 @@ class ZeroXZeroFileProvider(FileProvider):
|
||||
self.name = "0x0"
|
||||
self.base_url = "https://0x0.st"
|
||||
|
||||
def upload(self, file_path: str) -> str:
|
||||
def upload(self, file_path: str, **kwargs: Any) -> str:
|
||||
"""Upload file to 0x0.st."""
|
||||
from helper.http_client import HTTPClient
|
||||
import os
|
||||
@@ -1707,9 +1707,137 @@ class ZeroXZeroFileProvider(FileProvider):
|
||||
return True
|
||||
|
||||
|
||||
class MatrixFileProvider(FileProvider):
|
||||
"""File provider for Matrix (Element) chat rooms."""
|
||||
|
||||
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
||||
super().__init__(config)
|
||||
self.name = "matrix"
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Check if Matrix is configured."""
|
||||
if not self.config: return False
|
||||
matrix_conf = self.config.get('storage', {}).get('matrix', {})
|
||||
return bool(matrix_conf.get('homeserver') and matrix_conf.get('room_id') and (matrix_conf.get('access_token') or matrix_conf.get('password')))
|
||||
|
||||
def upload(self, file_path: str, **kwargs: Any) -> str:
|
||||
"""Upload file to Matrix room."""
|
||||
import requests
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
import json
|
||||
|
||||
debug(f"[Matrix] Starting upload for: {file_path}")
|
||||
debug(f"[Matrix] kwargs: {kwargs}")
|
||||
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
raise FileNotFoundError(f"File not found: {file_path}")
|
||||
|
||||
matrix_conf = self.config.get('storage', {}).get('matrix', {})
|
||||
homeserver = matrix_conf.get('homeserver')
|
||||
access_token = matrix_conf.get('access_token')
|
||||
room_id = matrix_conf.get('room_id')
|
||||
|
||||
if not homeserver.startswith('http'):
|
||||
homeserver = f"https://{homeserver}"
|
||||
|
||||
# 1. Upload Media
|
||||
# Use v3 API
|
||||
upload_url = f"{homeserver}/_matrix/media/v3/upload"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {access_token}",
|
||||
"Content-Type": "application/octet-stream"
|
||||
}
|
||||
|
||||
mime_type, _ = mimetypes.guess_type(path)
|
||||
if mime_type:
|
||||
headers["Content-Type"] = mime_type
|
||||
|
||||
filename = path.name
|
||||
|
||||
debug(f"[Matrix] Uploading media to {upload_url} with mime_type: {mime_type}")
|
||||
|
||||
with open(path, 'rb') as f:
|
||||
resp = requests.post(upload_url, headers=headers, data=f, params={"filename": filename})
|
||||
|
||||
if resp.status_code != 200:
|
||||
raise Exception(f"Matrix upload failed: {resp.text}")
|
||||
|
||||
content_uri = resp.json().get('content_uri')
|
||||
if not content_uri:
|
||||
raise Exception("No content_uri returned from Matrix upload")
|
||||
|
||||
debug(f"[Matrix] Media uploaded, content_uri: {content_uri}")
|
||||
|
||||
# 2. Send Message
|
||||
# Use v3 API
|
||||
send_url = f"{homeserver}/_matrix/client/v3/rooms/{room_id}/send/m.room.message"
|
||||
|
||||
# Determine msgtype with better fallback for audio
|
||||
msgtype = "m.file"
|
||||
ext = path.suffix.lower()
|
||||
|
||||
# Explicit check for common audio extensions to force m.audio
|
||||
# This prevents audio files being treated as generic files or video
|
||||
AUDIO_EXTS = {'.mp3', '.flac', '.wav', '.m4a', '.aac', '.ogg', '.opus', '.wma', '.mka', '.alac'}
|
||||
VIDEO_EXTS = {'.mp4', '.mkv', '.webm', '.mov', '.avi', '.flv', '.mpg', '.mpeg', '.ts', '.m4v', '.wmv'}
|
||||
IMAGE_EXTS = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff'}
|
||||
|
||||
if ext in AUDIO_EXTS:
|
||||
msgtype = "m.audio"
|
||||
elif ext in VIDEO_EXTS:
|
||||
msgtype = "m.video"
|
||||
elif ext in IMAGE_EXTS:
|
||||
msgtype = "m.image"
|
||||
elif mime_type:
|
||||
if mime_type.startswith("audio/"): msgtype = "m.audio"
|
||||
elif mime_type.startswith("video/"): msgtype = "m.video"
|
||||
elif mime_type.startswith("image/"): msgtype = "m.image"
|
||||
|
||||
debug(f"[Matrix] Determined msgtype: {msgtype} (ext: {ext}, mime: {mime_type})")
|
||||
|
||||
info = {
|
||||
"mimetype": mime_type,
|
||||
"size": path.stat().st_size
|
||||
}
|
||||
|
||||
# Try to get duration for audio/video
|
||||
if msgtype in ("m.audio", "m.video"):
|
||||
try:
|
||||
# Try mutagen first (lightweight)
|
||||
# Use dynamic import to avoid top-level dependency if not installed
|
||||
# Note: mutagen.File is available at package level at runtime but type checkers might miss it
|
||||
import mutagen # type: ignore
|
||||
m = mutagen.File(str(path)) # type: ignore
|
||||
if m and m.info and hasattr(m.info, 'length'):
|
||||
duration_ms = int(m.info.length * 1000)
|
||||
info['duration'] = duration_ms
|
||||
debug(f"[Matrix] Extracted duration: {duration_ms}ms")
|
||||
except Exception as e:
|
||||
debug(f"[Matrix] Failed to extract duration: {e}")
|
||||
|
||||
payload = {
|
||||
"msgtype": msgtype,
|
||||
"body": filename,
|
||||
"url": content_uri,
|
||||
"info": info
|
||||
}
|
||||
|
||||
debug(f"[Matrix] Sending message payload: {json.dumps(payload, indent=2)}")
|
||||
|
||||
resp = requests.post(send_url, headers=headers, json=payload)
|
||||
if resp.status_code != 200:
|
||||
raise Exception(f"Matrix send message failed: {resp.text}")
|
||||
|
||||
event_id = resp.json().get('event_id')
|
||||
return f"https://matrix.to/#/{room_id}/{event_id}"
|
||||
|
||||
|
||||
# File provider registry
|
||||
_FILE_PROVIDERS = {
|
||||
"0x0": ZeroXZeroFileProvider,
|
||||
"matrix": MatrixFileProvider,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user