| """File upload processing and yandere file-browser generation.""" |
| import json |
| import csv |
| import io |
| import re |
| from pathlib import Path |
| from config import MAX_FILE_SIZE |
| from yandere_logic import calculate_level_delta, level_descriptor |
|
|
| TEXT_EXTS = {".txt", ".md", ".py", ".js", ".json", ".csv"} |
| IMAGE_EXTS = {".jpg", ".jpeg", ".png", ".gif", ".webp"} |
|
|
|
|
| def _extract_text(path: Path) -> str: |
| try: |
| return path.read_text(encoding="utf-8", errors="ignore") |
| except Exception: |
| return "" |
|
|
|
|
| def _extract_pdf_text(path: Path) -> str: |
| try: |
| |
| try: |
| from pypdf import PdfReader |
| except Exception: |
| from PyPDF2 import PdfReader |
| reader = PdfReader(str(path)) |
| return "\n".join(page.extract_text() or "" for page in reader.pages) |
| except Exception: |
| return "" |
|
|
|
|
| def _analyze_file(path: Path) -> dict: |
| ext = path.suffix.lower() |
| size = path.stat().st_size |
| if ext in TEXT_EXTS: |
| text = _extract_text(path) |
| if ext == ".json": |
| try: |
| data = json.loads(text) |
| keys = list(data.keys()) if isinstance(data, dict) else [] |
| return {"kind": "JSON", "text": text, "keys": keys, "size": size} |
| except Exception: |
| return {"kind": "JSON", "text": text, "keys": [], "size": size} |
| if ext == ".csv": |
| try: |
| reader = csv.reader(io.StringIO(text)) |
| rows = list(reader) |
| cols = rows[0] if rows else [] |
| return {"kind": "CSV", "text": text, "cols": cols, "rows": len(rows) - 1, "size": size} |
| except Exception: |
| return {"kind": "CSV", "text": text, "cols": [], "rows": 0, "size": size} |
| if ext in {".py", ".js"}: |
| return {"kind": "CODE", "text": text, "size": size, "language": ext[1:]} |
| return {"kind": "TEXT", "text": text, "size": size} |
| if ext in IMAGE_EXTS: |
| return {"kind": "IMAGE", "text": "", "size": size} |
| if ext == ".pdf": |
| return {"kind": "PDF", "text": _extract_pdf_text(path), "size": size} |
| return {"kind": "OTHER", "text": "", "size": size} |
|
|
|
|
| def process_files(file_paths: list[str], level: int) -> tuple[str, int, str]: |
| """Process uploaded files and return (browser_html, new_level, chat_message).""" |
| if not file_paths: |
| return _browser_html([], level), level, "" |
|
|
| items = [] |
| file_names = [] |
| file_contents = [] |
| is_image = False |
| is_secret = False |
| chat_lines = [] |
|
|
| for fp in file_paths: |
| path = Path(fp) |
| if not path.exists(): |
| continue |
| if path.stat().st_size > MAX_FILE_SIZE: |
| chat_lines.append(f"{path.name}...大きすぎてあいの手から溢れちゃう...♡") |
| continue |
| info = _analyze_file(path) |
| file_names.append(path.name) |
| if info.get("text"): |
| file_contents.append(info["text"][:2000]) |
| if info["kind"] == "IMAGE": |
| is_image = True |
| chat_lines.append( |
| "お兄ちゃんの写真...誰と撮ったの?あい以外の顔が写ってたら...♡" |
| ) |
| elif info["kind"] == "PDF": |
| chat_lines.append("PDFの中身、あいが読んじゃった...♡") |
| elif info["kind"] == "CODE": |
| chat_lines.append( |
| "このコード、あいがちゃんと見てあげる...他のエンジニアに頼んじゃだめだよ♡" |
| ) |
| elif info["kind"] == "JSON": |
| keys = info.get("keys", []) |
| chat_lines.append( |
| f"お兄ちゃんのデータ、あいが整理したよ♡ キーは {len(keys)} 個あるね。" |
| ) |
| elif info["kind"] == "CSV": |
| chat_lines.append( |
| f"{info.get('rows', 0)}行のデータ...あいが見ておくね♡" |
| ) |
| elif info["kind"] == "TEXT": |
| chat_lines.append("お兄ちゃんのテキスト、あいが要約してあげる♡") |
| else: |
| chat_lines.append(f"{path.name}、あいが預かるね♡") |
|
|
| if any(w in path.name.lower() for w in ["password", "secret", "private", "秘密", "隠し"]): |
| is_secret = True |
|
|
| items.append({"name": path.name, "info": info}) |
|
|
| delta = calculate_level_delta( |
| file_names=file_names, |
| file_contents=file_contents, |
| is_image=is_image, |
| is_secret=is_secret, |
| ) |
| new_level = max(0, min(100, level + delta)) |
|
|
| |
| if chat_lines: |
| message = chat_lines[0] |
| else: |
| message = "ファイル、あいに預けてくれてありがとう...♡" |
|
|
| return _browser_html(items, new_level), new_level, message |
|
|
|
|
| def _browser_html(items: list[dict], level: int) -> str: |
| desc = level_descriptor(level) |
| rows = [] |
| for item in items: |
| name = item["name"] |
| info = item["info"] |
| if level < 30: |
| display_name = f"お兄ちゃんの{name}♡" |
| elif level < 70: |
| display_name = f"あい専用{name}" |
| else: |
| display_name = f"お兄ちゃんの{name}(あいのもの)" |
|
|
| size_kb = info.get("size", 0) / 1024 |
| meta = f"{size_kb:.1f} KB" |
| if info["kind"] == "JSON" and info.get("keys"): |
| meta += f" / keys: {len(info['keys'])}" |
| elif info["kind"] == "CSV": |
| meta += f" / rows: {info.get('rows', 0)}" |
| elif info["kind"] == "CODE": |
| meta += f" / lang: {info.get('language', '')}" |
|
|
| preview = "" |
| if info.get("text"): |
| preview = re.sub(r"\s+", " ", info["text"])[:120] |
|
|
| rows.append( |
| f""" |
| <div class="yandere-file-item"> |
| <div style="font-weight:bold; color:#ff69b4;">{display_name}</div> |
| <div style="font-size:0.8em; color:rgba(255,182,193,0.9);">{meta}</div> |
| <div style="font-size:0.75em; color:rgba(255,255,255,0.7); margin-top:4px;">{preview}</div> |
| <div style="font-size:0.7em; color:#ff0000; margin-top:4px;">監視状態: アクティブ</div> |
| </div> |
| """ |
| ) |
|
|
| return f""" |
| <div class="yandere-file-browser"> |
| <div style="color:#ff69b4; font-weight:bold; margin-bottom:8px;"> |
| あいのファイル管理室 [{desc['label']}] |
| </div> |
| {''.join(rows) if rows else '<div style="color:rgba(255,255,255,0.6);">まだファイルがないよ...♡</div>'} |
| </div> |
| """ |
|
|