Send a DM message (text, file, GIF, and/or voice). Important stability notes: - The message and its attachments are committed atomically (single DB transaction), so other clients never see a "blank" placeholder that requires refresh. - AJAX returns a fully-rendered row for i
(username: str)
| 13437 | cur.execute( |
| 13438 | "INSERT INTO discussion_files(msg_id, filename, mime, stored_path, size, created_at) VALUES(?,?,?,?,?,?)", |
| 13439 | (msg_id, "gif.gif", "image/gif", "PENDING", 0, now_z()), |
| 13440 | ) |
| 13441 | fid = cur.lastrowid |
| 13442 | stored_path = os.path.join(DISC_FILES_DIR, f"discussion_file_{fid}.bin") |
| 13443 | |
| 13444 | max_bytes = CHAT_MAX_BYTES # 33 MB |
| 13445 | size = 0 |
| 13446 | mime = "image/gif" |
| 13447 | filename = "gif.gif" |
| 13448 | |
| 13449 | req = urllib.request.Request( |
| 13450 | gif_url, |
| 13451 | headers={ |
| 13452 | "User-Agent": "ButSystem/1.0 (+https://example.invalid)", |
| 13453 | "Accept": "image/gif,image/*,video/*,*/*;q=0.8", |
| 13454 | }, |
| 13455 | method="GET", |
| 13456 | ) |
| 13457 | with _safe_urlopen(req, timeout=12) as resp: |
| 13458 | ctype = (resp.headers.get("Content-Type") or "").split(";")[0].strip().lower() |
| 13459 | if ctype: |
| 13460 | mime = ctype |
| 13461 | if not (mime == "image/gif" or mime.startswith("video/")): |
| 13462 | path = (u.path or "").lower() |
| 13463 | if not (path.endswith(".gif") or path.endswith(".mp4") or path.endswith(".webm")): |
| 13464 | raise ValueError(f"Unsupported GIF mime: {mime}") |
| 13465 | if path.endswith(".mp4"): |
| 13466 | mime = "video/mp4" |
| 13467 | elif path.endswith(".webm"): |
| 13468 | mime = "video/webm" |
| 13469 | else: |
| 13470 | mime = "image/gif" |
| 13471 | |
| 13472 | if mime == "image/gif": |
| 13473 | filename = f"gif_{fid}.gif" |
| 13474 | elif mime == "video/mp4": |
| 13475 | filename = f"gif_{fid}.mp4" |
| 13476 | elif mime == "video/webm": |
| 13477 | filename = f"gif_{fid}.webm" |
| 13478 | else: |
| 13479 | filename = f"file_{fid}" |
| 13480 | |
| 13481 | with open(stored_path, "wb") as f: |
| 13482 | while True: |
| 13483 | chunk = resp.read(64 * 1024) |
| 13484 | if not chunk: |
| 13485 | break |
| 13486 | size += len(chunk) |
| 13487 | if size > max_bytes: |
| 13488 | raise ValueError("GIF too large") |
| 13489 | f.write(chunk) |
| 13490 | |
| 13491 | conn.execute( |
| 13492 | "UPDATE discussion_files SET stored_path=?, size=?, mime=?, filename=? WHERE id=?", |
| 13493 | (stored_path, int(size), mime, filename, fid), |
| 13494 | ) |
| 13495 | conn.execute("UPDATE discussion_messages SET has_file=1 WHERE id=?", (msg_id,)) |
| 13496 | return {"id": fid, "mime": mime, "filename": filename, "size": int(size), "stored_path": stored_path} |
nothing calls this directly
no test coverage detected