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:
@@ -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👋 خاموش شد.")
|
||||
Reference in New Issue
Block a user