#!/usr/bin/env python3 """TeamPulse — Teams meeting chat audit tool. Usage: .venv/bin/python main.py [meeting-url] If no URL is given, the clipboard is checked for a Teams meeting link. If neither yields a URL, navigate manually in the browser window. Post !start "Presenter Name" and !stop in the chat to define a time window. A Markdown memo is saved to the current directory when the window closes. """ import platform import re import subprocess import sys from pathlib import Path _TEAMS_URL_RE = re.compile(r'https://teams(?:\.live)?\.microsoft\.com/\S+') sys.path.insert(0, str(Path(__file__).parent / "src")) from playwright.sync_api import sync_playwright from teampulse.auth import create_context, ensure_logged_in from teampulse.memo import generate_memo, save_memo from teampulse.models import AuditEntry from teampulse.monitor import Monitor from teampulse.resolver import Resolver CACHE_PATH = Path.home() / ".teampulse" / "cache.json" TEAMS_URL = "https://teams.microsoft.com" def _read_clipboard() -> str: try: system = platform.system() if system == "Darwin": return subprocess.check_output(["pbpaste"]).decode("utf-8").strip() elif system == "Linux": return subprocess.check_output(["xclip", "-selection", "clipboard", "-o"]).decode("utf-8").strip() elif system == "Windows": return subprocess.check_output( ["powershell", "-command", "Get-Clipboard"], text=True ).strip() except Exception: pass return "" def _extract_teams_url(text: str) -> str | None: m = _TEAMS_URL_RE.search(text) return m.group(0) if m else None def _get_meeting_url() -> str | None: if len(sys.argv) > 1: url = _extract_teams_url(sys.argv[1]) if url: print(f"Meeting-URL aus Argument: {url[:60]}...") return url clip = _read_clipboard() url = _extract_teams_url(clip) if url: print(f"Meeting-URL aus Clipboard: {url[:60]}...") return url return None def main(): meeting_url = _get_meeting_url() with sync_playwright() as playwright: print("Starte Browser...") context = create_context(playwright, headless=False) ensure_logged_in(context) page = context.new_page() # Always load Teams main first so user-name selectors are available page.goto(TEAMS_URL) monitor = Monitor(page=page, current_user="") print("Lese eingeloggten Nutzer...") current_user = monitor.get_current_user_display_name() monitor._current_user = current_user print(f"Eingeloggt als: {current_user}") if meeting_url: print("Navigiere zur Meeting-URL...") page.goto(meeting_url) try: page.wait_for_selector( "[data-tid='channel-pane-message'], [data-tid='chat-pane-message']", timeout=6000, ) print("Chat direkt erreicht.") except Exception: # Join-Lobby or redirect — navigate back to Teams main. # User navigates to the meeting chat via the Chat sidebar (no call join needed). print("Join-Lobby erkannt — bitte im Browser zum Chat navigieren:") print(" Linke Sidebar → Chat (Icon) → laufendes Meeting anklicken\n") page.goto(TEAMS_URL) else: print("\nBitte im Browser-Fenster zum Meeting-Chat navigieren:") print(" Linke Sidebar → Chat → laufendes Meeting anklicken\n") print("\nPoste '!start Name' im Chat um zu beginnen.\n") window = monitor.run() resolver = Resolver(cache_path=CACHE_PATH, page=page) print(f"\nLöse {len(window.entries)} E-Mail-Adresse(n) auf...") resolved_entries = [] for entry in window.entries: email = resolver.resolve(entry.display_name) print(f" {entry.display_name} → {email}") resolved_entries.append(AuditEntry(display_name=entry.display_name, email=email)) window.entries = resolved_entries memo_content = generate_memo(window) path = save_memo(memo_content) print(f"\nMemo gespeichert: {path}") print("\n" + memo_content) context.close() if __name__ == "__main__": try: main() except KeyboardInterrupt: print("\nAbgebrochen.")