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,51 @@
|
||||
# Action — Doc Writing
|
||||
|
||||
با قواعد اینجا باید اکشنها رو بسازی که داکهای من و خودت هست و بعد از اجرا اگر
|
||||
جزییاتی داشت که من نیاز داشت بدونم توی ریزالت یک ریزالت با همین نام میسازی و
|
||||
نتیجهی کارت رو داک میکنی.
|
||||
اکشنها جنرال هستن و روی موقعیتهای مشابه ممکنه همون داک رو مجدد بهت بدم.
|
||||
اما ریزالت میتونه داخلش اطلاعات اون اجرات و درسآموختههات باشه.
|
||||
|
||||
---
|
||||
|
||||
## ساختار فایلها
|
||||
|
||||
```
|
||||
docs/
|
||||
├── action/ ← عمومی، قابل استفاده مجدد
|
||||
└── result/ ← مخصوص هر اجرا
|
||||
```
|
||||
|
||||
**فرمت نام فایل:**
|
||||
```
|
||||
{sequential_numbering_xxx}_action_{short_title_of_doc}.md
|
||||
{sequential_numbering_xxx}_result_{short_title_of_doc}.md
|
||||
```
|
||||
|
||||
- شمارهی `NNN` بین جفت action/result **مشترک** است.
|
||||
- عنوان کوتاه (`short_title`) در هر دو **یکسان** است.
|
||||
|
||||
---
|
||||
|
||||
## action چیست؟
|
||||
|
||||
- **جنرال** — هر بار که موقعیت مشابه پیش آمد، همین فایل داده میشود.
|
||||
- **بدون جزئیات اجرا** — هیچ نام فایل، تاریخ، یا تصمیم مخصوص یک run توش نباشد.
|
||||
- میتواند شامل: هدف، چکلیست کارها، قواعد، یا context کلی باشد.
|
||||
- اگر در حین اجرا متوجه شدی که action ناقص یا گمراهکننده بوده، **آن را بهبود بده**.
|
||||
|
||||
## result چیست؟
|
||||
|
||||
- **مخصوص همان اجرا** — تاریخ، فایلهای ساختهشده، تصمیمهای گرفتهشده.
|
||||
- **درسآموخته** — چیزی که دفعهی بعد باید متفاوت انجام شود.
|
||||
- **وضعیت** — چه چیزی انجام شد، چه چیزی باقی ماند.
|
||||
- **نیاز به تصمیم** — اگر چیزی هست که کاربر باید تصمیم بگیرد، اینجا بنویس.
|
||||
|
||||
---
|
||||
|
||||
## قواعد کلی
|
||||
|
||||
- result را **بعد از اجرا** بساز، نه قبل از آن.
|
||||
- اگر result از قبل وجود داشت (اجرای مجدد همان action)، یا آپدیتش کن یا یک بخش
|
||||
جدید با تاریخ جدید اضافه کن — فایل را overwrite نکن.
|
||||
- اطلاعاتی که «کاربر نیاز دارد بداند» را در result بنویس، نه فقط خلاصهی فنی.
|
||||
@@ -0,0 +1,77 @@
|
||||
- [ ] از اسکریپت laxi هرچیزی که اضافه هست رو حذف کنیم.
|
||||
- [ ] برای لوگو یک لوگو از خودمون میذاریم.
|
||||
- [ ] مثلا لاگین یا اگر دادهای به جایی میفرسته نباید بفرسته اینا هم اضافی هستن
|
||||
- [ ] هدف نهایی: بتونیم با استفاده از این پلاگین کپشن رو بگیریم
|
||||
- [ ] راهکارها:
|
||||
- [ ] پلاگین خودش یک mcp سرویس بده روی یک پورت
|
||||
|
||||
---
|
||||
<!-- Claude Code comments -->
|
||||
|
||||
**درباره حذف کدهای laxis:**
|
||||
فایلهایی که احتمالاً باید بررسی بشن: `analytics.js`، `login.js`، `runtime.js` (در `google-meet-transcripts-extension/`) — اینا بیشترین احتمال ارتباط با سرور laxis رو دارن. `config/` هم ممکنه endpoint داشته باشه. پیشنهاد: اول یه `grep` روی همه فایلها برای `laxis` و `api.` و `fetch(` بزن تا دقیق بفهمیم کجاها data میفرسته.
|
||||
|
||||
**درباره MCP روی پورت:**
|
||||
این رویکرد خوبیه ولی یه محدودیت داره — Chrome extension نمیتونه مستقیم TCP server باز کنه. راهحلهای ممکن:
|
||||
1. **Native Messaging:** extension با یه اپ native (Python/Node) صحبت میکنه که اون اپ MCP server هست — پیچیدهتر ولی استانداردتر
|
||||
2. **WebSocket از extension:** extension یه WebSocket server روی `localhost` باز میکنه از طریق یه background service worker + `chrome.sockets` API — محدودیتهای manifest v3 داره
|
||||
3. **Polling روی clipboard یا فایل:** سادهترین راه — extension هر X ثانیه caption رو در یه فایل یا clipboard میریزه، اپ دیگهای میخونه
|
||||
|
||||
کدوم راهکار رو در نظر داری؟
|
||||
|
||||
---
|
||||
|
||||
# پیادهسازی — معماری Bridge
|
||||
|
||||
> راهکار انتخابی: **افزونه بهعنوان WebSocket _client_** به یک سرور پایتونِ بیرون مرورگر وصل میشود.
|
||||
> (نه راهکار ۲ که افزونه خودش server باشد — در MV3 سرویسورکر نمیتواند پورت TCP باز کند.)
|
||||
|
||||
## دیاگرام جریان داده
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Google Meet<br/>caption روی صفحه"] -->|"DOM observer"| B["content script<br/>captionProcessing.js"]
|
||||
B -->|"setSpeaker()"| C["storage.js<br/>نقطهی choke هر caption"]
|
||||
C -->|"chrome.runtime.sendMessage<br/>{type: TRANSCRIPT_UPDATE}"| D["service worker<br/>bridge.js"]
|
||||
D -->|"WebSocket<br/>ws://127.0.0.1:8765"| E["سرور پایتون<br/>bridge/server.py"]
|
||||
E -->|"ذخیره"| F["transcripts/<sessionId>.txt"]
|
||||
E -.->|"ACK / PONG"| D
|
||||
E ==>|"گام بعدی"| G["MCP Server"]
|
||||
G ==> H["Claude<br/>transcript زنده را میخواند"]
|
||||
|
||||
style E fill:#2F5496,color:#fff
|
||||
style G fill:#888,color:#fff,stroke-dasharray: 5 5
|
||||
style H fill:#888,color:#fff,stroke-dasharray: 5 5
|
||||
```
|
||||
|
||||
## چرا client و نه server؟
|
||||
|
||||
- **CSP گوگل میت:** اگر WebSocket را از content script باز کنیم، `connect-src` صفحه بلاکش میکند.
|
||||
سرویسورکر تابع CSP صفحه نیست → اتصال از آنجا باز میشود.
|
||||
- **MV3 لایفسایکل:** سرویسورکر بعد ~۳۰ ثانیه میخوابد. دو مکانیزم نگهش میدارد:
|
||||
(۱) هر پیام جدید از content script بیدارش میکند، (۲) heartbeat (PING) هر ۲۰ ثانیه
|
||||
(از Chrome 116+ پیام WebSocket زیر ۳۰ ثانیه ورکر را زنده نگه میدارد).
|
||||
|
||||
## وضعیت فعلی — ✅ گام ۱ انجام شد (MVP اتصال)
|
||||
|
||||
| قطعه | فایل | کار |
|
||||
|------|------|-----|
|
||||
| سرور پایتون | `bridge/server.py` | WebSocket روی `127.0.0.1:8765`، ذخیره در `transcripts/` |
|
||||
| WS client | `google-meet-transcripts-extension/bridge.js` | اتصال + reconnect + heartbeat |
|
||||
| ایمپورت در ورکر | `login.js` | `importScripts("bridge.js")` |
|
||||
| نقطهی hook | `storage.js` → `setSpeaker` | ارسال `TRANSCRIPT_UPDATE` |
|
||||
| دسترسی شبکه | `manifest.json` | `ws://127.0.0.1:8765/*` در host_permissions |
|
||||
|
||||
تست end-to-end سمت پایتون با `_smoke_test.py` پاس شد (PONG + ACK + ذخیرهی فایل).
|
||||
|
||||
## مراحل بعدی
|
||||
- [ ] **پاکسازی کد laxis** (طبق بالای همین سند): حذف لاگین، analytics، و fetch به `domainUrl`.
|
||||
- [ ] **تست میدانی با Chrome واقعی:** سرور را بالا بیاور، افزونه را Reload کن، در یک Meet
|
||||
caption را روشن کن و خروجی را در ترمینال/فایل ببین.
|
||||
- [ ] **افزودن MCP server** روی همین `server.py` (مثلاً با `mcp` SDK پایتون) تا Claude
|
||||
بتواند transcript زنده را بهعنوان resource/tool بخواند.
|
||||
- [ ] **لوگوی اختصاصی** جایگزین لوگوی laxis.
|
||||
|
||||
## محیط توسعه
|
||||
|
||||
پایتون داخل **venv** اجرا میشود تا سیستم تمیز بماند (راهنما: `bridge/README.md`).
|
||||
@@ -0,0 +1,52 @@
|
||||
# Result — 001 Plan Doc Writing
|
||||
|
||||
> اکشن مرتبط: [`action/001_action_plan_doc_writing.md`](001_action_doc_writing.md)
|
||||
> تاریخ اجرا: 2026-06-09
|
||||
|
||||
این ریزالتِ اجرای قاعدهی «نوشتن داک پلن» است. خودِ اکشن جنرال است؛ اینجا فقط
|
||||
جزئیات این اجرا و درسآموختهها ثبت میشود تا دفعهی بعد سریعتر و بیخطا انجام شود.
|
||||
|
||||
## قاعدهای که فهمیدم و اعمال کردم
|
||||
|
||||
سیستم دو لایه است:
|
||||
|
||||
| لایه | مسیر | ماهیت |
|
||||
|------|------|-------|
|
||||
| Action | `docs/action/NNN_action_*.md` | عمومی، قابلاستفادهی مجدد، ورودیِ کار |
|
||||
| Result | `docs/result/NNN_result_*.md` | مخصوص همان اجرا، شامل درسآموختهها، خروجیِ کار |
|
||||
|
||||
قواعد:
|
||||
- نام result دقیقاً قرینهی action است: `NNN_action_X` → `NNN_result_X`.
|
||||
- شمارهی `NNN` بین جفتِ action/result مشترک است.
|
||||
- چون action ممکن است دوباره برای موقعیت مشابه داده شود، **هیچ جزئیاتِ مخصوصِ یک اجرا نباید داخل action برود** — همهی آنها در result.
|
||||
- result باید «چیزهایی که کاربر لازم است بداند» + «درسآموخته» را داشته باشد.
|
||||
|
||||
## چه چیزی در این اجرا تولید شد
|
||||
|
||||
سند پلن (`docs/Plan.md`) با یک سکشن **«پیادهسازی — معماری Bridge»** گسترش یافت:
|
||||
- دیاگرام Mermaid از جریان داده (Meet → content script → service worker → پایتون → فایل → MCP/Claude)
|
||||
- توضیح «چرا client نه server» (CSP گوگل میت + لایفسایکل MV3)
|
||||
- جدول وضعیت گام ۱ + چکلیست مراحل بعدی
|
||||
|
||||
همین محتوا در `docs/action/002_action_plan.md` بهعنوان نسخهی action نگه داشته شده.
|
||||
|
||||
## درسآموختهها (برای دفعهی بعد)
|
||||
|
||||
1. **Mermaid در Obsidian:** متنِ هر node را داخل `"..."` بگذار و برای شکستن خط از
|
||||
`<br/>` استفاده کن. کاراکترهای `<` و `>` داخل لیبل را بهصورت `<` و `>`
|
||||
بنویس وگرنه پارسر Mermaid خطا میدهد (مثلاً `<sessionId>` → `<sessionId>`).
|
||||
2. **جهتدهی فارسی + کد:** عنوانها و توضیح فارسی، ولی نام فایل/کد/پروتکل را داخل
|
||||
backtick انگلیسی نگه دار تا RTL آنها را بههم نریزد.
|
||||
3. **لینکهای نسبی:** result در `docs/result/` است و action در `docs/action/`؛ برای
|
||||
ارجاع از داخل result به action از `../action/...` استفاده کن (که در Obsidian و
|
||||
هم در GitHub درست باز میشود).
|
||||
4. **`002_result_plan.md` خالی مانده:** قرینهاش (`002_action_plan`) پر است ولی
|
||||
ریزالتش هنوز نوشته نشده — اگر آن اجرا کامل شد باید پر شود.
|
||||
5. **همگامسازی action 002 با Plan.md:** الان `docs/Plan.md` و
|
||||
`docs/action/002_action_plan.md` محتوای یکسانی دارند؛ اگر یکی تغییر کرد باید
|
||||
تصمیم بگیریم کدام «منبع حقیقت» است تا واگرا نشوند.
|
||||
|
||||
## باز است / نیاز به تصمیم
|
||||
|
||||
- منبع حقیقتِ پلن کدام است: `docs/Plan.md` یا `docs/action/002_action_plan.md`؟
|
||||
- آیا result ها هم باید در Obsidian قابلناوبری باشند (مثلاً با لینک دوطرفه به action)؟
|
||||
@@ -0,0 +1,116 @@
|
||||
# Result — 002 Planning
|
||||
|
||||
> اکشن مرتبط: [`action/002_action_planning.md`](../action/002_action_planning.md)
|
||||
> تاریخ اجرا: 2026-06-09
|
||||
|
||||
---
|
||||
|
||||
## هدف این جلسه
|
||||
|
||||
خواندن پروژه، فهمیدن ساختار، و پیادهسازی گام اول MVP:
|
||||
اتصال افزونهی Chrome به یک سرور پایتون بیرون مرورگر از طریق WebSocket.
|
||||
|
||||
---
|
||||
|
||||
## تصمیمهای کلیدی که گرفته شد
|
||||
|
||||
### معماری — چرا extension بهعنوان WS client؟
|
||||
|
||||
افزونه نمیتواند خودش سرور باشد (MV3 سرویسورکر TCP port نمیتواند باز کند).
|
||||
پس **افزونه client است و پایتون server** — افزونه به `ws://127.0.0.1:8765` وصل میشود.
|
||||
|
||||
همچنین WS باید از service worker باز شود، نه content script؛ چون content script
|
||||
تابع CSP صفحهی گوگل میت است و `connect-src` اتصال را بلاک میکند.
|
||||
|
||||
### نقطهی hook در کد افزونه
|
||||
|
||||
`setSpeaker()` در `storage.js` — هر پاراگراف caption نهایی از این تابع رد میشود.
|
||||
یک `chrome.runtime.sendMessage` کوچک به آن اضافه شد که background را خبر میکند.
|
||||
|
||||
### venv برای پایتون
|
||||
|
||||
تصمیم گرفته شد پایتون داخل `.venv` اجرا شود تا سیستم کثیف نشود.
|
||||
|
||||
---
|
||||
|
||||
## چه چیزی ساخته شد
|
||||
|
||||
| فایل | نوع | کار |
|
||||
|------|-----|-----|
|
||||
| `bridge/server.py` | جدید | WebSocket server پایتون روی `127.0.0.1:8765` |
|
||||
| `bridge/requirements.txt` | جدید | فقط `websockets>=12.0` |
|
||||
| `bridge/.gitignore` | جدید | `.venv/` و `transcripts/` از git خارج |
|
||||
| `bridge/README.md` | جدید | راهنمای اجرا با venv |
|
||||
| `bridge/_smoke_test.py` | جدید | کلاینت تستی بدون Chrome |
|
||||
| `bridge/.venv/` | جدید | محیط ایزوله پایتون |
|
||||
| `google-meet-transcripts-extension/bridge.js` | جدید | WS client داخل service worker |
|
||||
| `login.js` | ویرایش | اضافه شدن `importScripts("bridge.js")` |
|
||||
| `storage.js` | ویرایش | `setSpeaker` حالا `TRANSCRIPT_UPDATE` میفرستد |
|
||||
| `manifest.json` | ویرایش | `ws://127.0.0.1:8765/*` به `host_permissions` اضافه شد |
|
||||
|
||||
---
|
||||
|
||||
## تست انجام شده
|
||||
|
||||
سرور پایتون با `_smoke_test.py` end-to-end تست شد:
|
||||
- PING → PONG ✅
|
||||
- سه `TRANSCRIPT_UPDATE` → سه ACK ✅
|
||||
- فایل `transcripts/meeting-abc123.txt` با متن فارسی درست ذخیره شد ✅
|
||||
|
||||
تست با Chrome واقعی هنوز انجام نشده (نیاز به Reload افزونه و یک جلسهی Meet).
|
||||
|
||||
---
|
||||
|
||||
## وضعیت چکلیست اصلی (از action)
|
||||
|
||||
- [x] حذف کدهای laxis — **انجام شد** (اجرای ۲۰۲۶-۰۶-۱۰، پایین را ببین)
|
||||
- [x] لوگوی اختصاصی — **انجام شد** (لوگوی جدید جایگزین شد)
|
||||
- [x] گرفتن caption از افزونه — گام ۱ (اتصال) انجام شد
|
||||
- [x] سرویس MCP روی پورت — **انجام شد** (روی همان `server.py`)
|
||||
- [ ] تست میدانی با Chrome واقعی — باقیمانده (نیاز به مرورگر و یک جلسهی Meet)
|
||||
|
||||
---
|
||||
|
||||
## اجرای دوم — پاکسازی laxis + MCP + لوگو (۲۰۲۶-۰۶-۱۰)
|
||||
|
||||
### پاکسازی laxis (هیچ دادهای دیگر بیرون نمیرود)
|
||||
|
||||
- `analytics.js`: کاملاً خالی شد (از قبل کامنتشده و مرده بود؛ در manifest هم لود نمیشد).
|
||||
- `login.js` (service worker): بازنویسی شد — حذف `tabs.onRemoved` (که transcript را آپلود میکرد)،
|
||||
حذف `onMessageExternal` (دریافت توکن) و `getTopics`. فقط import ها + نگهداشتن وضعیت لوکال ماند.
|
||||
- `runtime.js` (content script): حذف فراخوانی `addRemindLogin()` و کل listener دریافت توکن/fetch.
|
||||
- `meetingInfo.js`: `checkToken` و `renewToken` با `return;` در ابتدا خنثی شدند؛ آپلود پایان جلسه
|
||||
(`Export2App` + باز کردن `domainUrl/transcript`) حذف شد.
|
||||
- `transcript.js`: `Export2App` بلافاصله reject میکند (آپلود ابری قطع)، `getTopics` خنثی شد،
|
||||
`addRemindLogin` خالی شد، منوی دانلود از حالت پیشفرضِ «app/cloud» به دانلود محلی `txt` تغییر کرد.
|
||||
- `panel/main.js`: لینک لوگو → `domainUrl/login` در هر سه جا حذف شد؛ متنهای برند «Laxis…» حذف شدند.
|
||||
- `manifest.json`: `update_url` فروشگاه حذف شد (که افزونه با ID لاکسیس آپدیت نشود)؛ name/description
|
||||
به نسخهی محلی و بدون cloud تغییر کرد.
|
||||
|
||||
### MCP روی server.py
|
||||
|
||||
سرور حالا در یک پراسس هم WebSocket (برای افزونه) و هم MCP stdio (برای Claude) را اجرا میکند و
|
||||
حافظهی `sessions` را share میکنند. ابزارها: `list_sessions`، `get_transcript`،
|
||||
`get_latest_transcript` و resource با الگوی `transcript://{session_id}`. کانفیگ Claude Desktop در
|
||||
`bridge/README.md`.
|
||||
|
||||
### درسآموختهها
|
||||
|
||||
- **stdout برای MCP رزرو است:** همهی `print` ها به stderr منتقل شدند، وگرنه استریم JSON-RPC خراب میشود.
|
||||
برای تست دستیِ WebSocket باید از `python server.py --ws-only` استفاده کرد.
|
||||
- **کد افزونه minified است:** برای فایلهای فشرده (`meetingInfo.js`) بهجای حذفِ بدنهها، با `return;`
|
||||
در ابتدای تابع خنثیسازی شد (کمریسکتر). کدِ مرده باقی میماند ولی هرگز اجرا نمیشود.
|
||||
- **یک رشتهی غیرفعال باقیست:** متنِ «Autosave to Laxis cloud…» در `displayGoogleMeetQuota`
|
||||
(`transcript.js`) داخل کدِ مرده مانده — هرگز نمایش داده نمیشود چون فراخوانهایش خنثیاند.
|
||||
|
||||
### تأیید (validation)
|
||||
|
||||
- syntax همهی فایلهای JS با `node --check` پاس شد.
|
||||
- منطق `server.py` با AST + یک smoke test واقعیِ WebSocket (PONG/ACK + ذخیرهی صحیح فارسی) تأیید شد.
|
||||
- ابزار/resource های MCP با `mcp` SDK ساخته و اجرا شدند (`get_latest_transcript` متن را درست برگرداند).
|
||||
|
||||
### باقیمانده برای تو
|
||||
|
||||
تست میدانی: venv را بساز/فعال کن، `python server.py --ws-only` را اجرا کن، افزونه را در
|
||||
`chrome://extensions` **Reload** کن، در یک Meet واقعی caption را روشن کن و خروجی را در
|
||||
`bridge/transcripts/` ببین. بعد کانفیگ MCP را به Claude Desktop اضافه کن.
|
||||
Reference in New Issue
Block a user