Refactor Google Meet Transcripts Extension for Local Use

- Removed all cloud-related functionalities, including login prompts and token handling.
- Disabled Laxis cloud features, ensuring no data is sent to external servers.
- Updated manifest to reflect the new local-only functionality.
- Added a new Python server to handle transcripts locally, including WebSocket support.
- Implemented storage management for transcripts, including deduplication and file writing.
- Created a smoke test for the WebSocket server to simulate transcript updates.
- Updated README with setup instructions and usage details for the new local server.
This commit is contained in:
vahidaskari
2026-06-12 00:31:32 +03:30
parent 602dcb7430
commit 7bc34c79ed
35 changed files with 1069 additions and 840 deletions
+155
View File
@@ -0,0 +1,155 @@
"""
mcp_server.py — نقطه‌ی ورود. WebSocket و MCP را با هم در یک پراسس اجرا می‌کند.
- ws_server.run_forever() به‌عنوان background task (گرفتن caption از افزونه)
- FastMCP روی stdio (تا Claude Desktop transcript را بخواند)
هر دو حافظه/فایل‌های مشترکِ storage را می‌بینند.
اجرا:
pip install -r requirements.txt
python mcp_server.py # WebSocket + MCP (برای Claude Desktop)
python mcp_server.py --ws-only # فقط WebSocket (یا مستقیم: python ws_server.py)
کانفیگ Claude Desktop به همین فایل اشاره می‌کند (پایین README).
نکته: stdout برای پروتکل MCP رزرو است؛ همه‌ی لاگ‌ها در storage.log به stderr می‌روند.
"""
import asyncio
import json
import sys
from mcp.server.fastmcp import FastMCP
import storage
from storage import (
log, safe_session_id, latest_session, read_txt, read_srt,
read_segments, json_mtime, fmt_clock, list_session_files,
)
import ws_server
# ---------------------------------------------------------------------------
# MCP
# ---------------------------------------------------------------------------
def build_mcp() -> "FastMCP":
mcp = FastMCP("meet-transcripts")
@mcp.tool()
def list_sessions() -> str:
"""فهرست جلسه‌های ضبط‌شده (از روی دیسک): تعداد segment و آخرین خط."""
files = list_session_files()
if not files:
return "هیچ جلسه‌ای هنوز ضبط نشده است."
latest = latest_session()
out = []
for p in files:
lines = p.read_text(encoding="utf-8").splitlines()
last = lines[-1] if lines else ""
mark = " ← آخرین" if p.stem == latest else ""
out.append(f"- {p.stem}{mark}: {len(lines)} segment | آخرین: {last[:80]}")
return "\n".join(out)
@mcp.tool()
def get_status(session_id: str = "") -> str:
"""وضعیت سبک برای «چیزی تغییر کرد؟» بدون خواندن متن. اگر session_id ندهی، آخرین جلسه.
خروجی JSON: latest_seq، count، live_len، updated_at. اگر latest_seq/updated_at عوض شد،
get_updates بزن."""
sid = safe_session_id(session_id) if session_id else latest_session()
if not sid:
return json.dumps({"session_id": None, "latest_seq": 0, "count": 0,
"live_len": 0, "updated_at": 0}, ensure_ascii=False)
segs = read_segments(sid)
return json.dumps({
"session_id": sid,
"latest_seq": segs[-1]["seq"] if segs else 0,
"count": len(segs),
"live_len": len(segs[-1].get("text", "")) if segs else 0,
"updated_at": round(json_mtime(sid), 3),
}, ensure_ascii=False)
@mcp.tool()
def get_updates(session_id: str = "", after_seq: int = 0) -> str:
"""خواندن افزایشی: فقط segmentهای از after_seq به بعد (نه کل transcript).
بار اول after_seq=0 (همه را می‌گیری). دفعات بعد latest_seqِ قبلی را بده تا فقط
موارد جدید + segmentِ زنده را بگیری. خروجی JSON: {latest_seq, count, segments:[{seq,t,speaker,text}]}."""
sid = safe_session_id(session_id) if session_id else latest_session()
if not sid:
return json.dumps({"session_id": None, "latest_seq": 0, "count": 0,
"segments": []}, ensure_ascii=False)
segs = read_segments(sid)
base = min((s["start"] for s in segs), default=0)
sel = [s for s in segs if s.get("seq", 0) >= after_seq] if after_seq > 0 else segs
out = [{"seq": s.get("seq", 0), "t": fmt_clock(s["start"] - base),
"speaker": s.get("speaker", ""), "text": (s.get("text") or "").strip()}
for s in sel]
return json.dumps({
"session_id": sid,
"latest_seq": segs[-1]["seq"] if segs else 0,
"count": len(segs),
"segments": out,
}, ensure_ascii=False)
@mcp.tool()
def get_latest_transcript() -> str:
"""متن تمیزِ آخرین جلسه (با [mm:ss]، بدون تکرار)."""
sid = latest_session()
if not sid:
return "هنوز هیچ caption ای دریافت نشده است."
return read_txt(sid) or ""
@mcp.tool()
def get_transcript(session_id: str) -> str:
"""متن تمیزِ یک جلسه با sessionId (با [mm:ss])."""
txt = read_txt(session_id)
return txt if txt is not None else f"جلسه‌ای با id «{safe_session_id(session_id)}» نیست."
@mcp.tool()
def get_latest_srt() -> str:
"""زیرنویس SRT آخرین جلسه (با تایم‌کد استاندارد)."""
sid = latest_session()
if not sid:
return "هنوز هیچ caption ای دریافت نشده است."
return read_srt(sid) or ""
@mcp.tool()
def get_srt(session_id: str) -> str:
"""زیرنویس SRT یک جلسه با sessionId."""
srt = read_srt(session_id)
return srt if srt is not None else f"جلسه‌ای با id «{safe_session_id(session_id)}» نیست."
@mcp.resource("transcript://{session_id}")
def transcript_resource(session_id: str) -> str:
return read_txt(session_id) or ""
@mcp.resource("srt://{session_id}")
def srt_resource(session_id: str) -> str:
return read_srt(session_id) or ""
return mcp
# ---------------------------------------------------------------------------
# اجرا
# ---------------------------------------------------------------------------
async def main():
asyncio.create_task(ws_server.run_forever())
log(f"📁 transcripts در: {storage.TRANSCRIPTS_DIR}")
ws_only = "--ws-only" in sys.argv
if FastMCP is not None and not ws_only:
log("🔗 MCP server (stdio) فعال است — منتظر اتصال Claude Desktop …")
await build_mcp().run_stdio_async()
else:
if FastMCP is None and not ws_only:
log("ℹ️ پکیج mcp نصب نیست؛ فقط WebSocket اجرا می‌شود (pip install mcp).")
await asyncio.Future()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
log("\n👋 خاموش شد.")