fix: render feishu login with rich
maintainer edit: replace plain progress prints with Rich output for the interactive Feishu QR login flow.
This commit is contained in:
+33
-37
@@ -16,6 +16,10 @@ from dataclasses import dataclass
|
|||||||
from typing import TYPE_CHECKING, Any, Literal
|
from typing import TYPE_CHECKING, Any, Literal
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
from rich.console import Console
|
||||||
|
from rich.markup import escape
|
||||||
|
from rich.panel import Panel
|
||||||
|
from rich.text import Text
|
||||||
|
|
||||||
from nanobot.bus.events import OutboundMessage
|
from nanobot.bus.events import OutboundMessage
|
||||||
from nanobot.bus.queue import MessageBus
|
from nanobot.bus.queue import MessageBus
|
||||||
@@ -29,6 +33,7 @@ if TYPE_CHECKING:
|
|||||||
from lark_oapi.api.im.v1.model import MentionEvent, P2ImMessageReceiveV1
|
from lark_oapi.api.im.v1.model import MentionEvent, P2ImMessageReceiveV1
|
||||||
|
|
||||||
FEISHU_AVAILABLE = importlib.util.find_spec("lark_oapi") is not None
|
FEISHU_AVAILABLE = importlib.util.find_spec("lark_oapi") is not None
|
||||||
|
_LOGIN_CONSOLE = Console()
|
||||||
|
|
||||||
|
|
||||||
def _load_lark_runtime() -> tuple[Any, str, str]:
|
def _load_lark_runtime() -> tuple[Any, str, str]:
|
||||||
@@ -400,10 +405,6 @@ def _poll_registration(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
poll_count += 1
|
poll_count += 1
|
||||||
if poll_count == 1:
|
|
||||||
print("Waiting for authorization", end="", flush=True)
|
|
||||||
elif poll_count % 6 == 0:
|
|
||||||
print(".", end="", flush=True)
|
|
||||||
|
|
||||||
# Domain auto-detection: if the user's tenant is on Lark, switch automatically
|
# Domain auto-detection: if the user's tenant is on Lark, switch automatically
|
||||||
user_info = res.get("user_info") or {}
|
user_info = res.get("user_info") or {}
|
||||||
@@ -413,8 +414,6 @@ def _poll_registration(
|
|||||||
|
|
||||||
# Success
|
# Success
|
||||||
if res.get("client_id") and res.get("client_secret"):
|
if res.get("client_id") and res.get("client_secret"):
|
||||||
if poll_count > 0:
|
|
||||||
print(" done.")
|
|
||||||
return {
|
return {
|
||||||
"app_id": res["client_id"],
|
"app_id": res["client_id"],
|
||||||
"app_secret": res["client_secret"],
|
"app_secret": res["client_secret"],
|
||||||
@@ -424,20 +423,13 @@ def _poll_registration(
|
|||||||
# Terminal errors
|
# Terminal errors
|
||||||
error = res.get("error", "")
|
error = res.get("error", "")
|
||||||
if error in ("access_denied", "expired_token"):
|
if error in ("access_denied", "expired_token"):
|
||||||
if poll_count > 0:
|
_LOGIN_CONSOLE.print("[yellow]Authorization was cancelled or expired.[/yellow]")
|
||||||
print()
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
logger.warning("[Feishu onboard] Registration {}", error)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# authorization_pending or unknown — keep polling
|
# authorization_pending or unknown — keep polling
|
||||||
time.sleep(interval)
|
time.sleep(interval)
|
||||||
|
|
||||||
if poll_count > 0:
|
_LOGIN_CONSOLE.print("[yellow]Authorization timed out.[/yellow]")
|
||||||
print(" timed out.")
|
|
||||||
else:
|
|
||||||
print("Login timed out.")
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -462,7 +454,9 @@ def qr_register(
|
|||||||
try:
|
try:
|
||||||
return _qr_register_inner(initial_domain=initial_domain)
|
return _qr_register_inner(initial_domain=initial_domain)
|
||||||
except (RuntimeError, OSError, json.JSONDecodeError, httpx.HTTPError) as exc:
|
except (RuntimeError, OSError, json.JSONDecodeError, httpx.HTTPError) as exc:
|
||||||
print(f"[Warning] Registration failed: {exc}")
|
_LOGIN_CONSOLE.print(
|
||||||
|
f"[yellow]Unable to start Feishu/Lark login:[/yellow] {escape(str(exc))}"
|
||||||
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -471,15 +465,16 @@ def _print_qr_code(url: str) -> None:
|
|||||||
try:
|
try:
|
||||||
import qrcode as qr_lib
|
import qrcode as qr_lib
|
||||||
|
|
||||||
print("\nScan this QR code with Feishu or Lark on your phone:\n")
|
_LOGIN_CONSOLE.print("\n[bold]Scan with Feishu or Lark[/bold]\n")
|
||||||
qr = qr_lib.QRCode(border=1)
|
qr = qr_lib.QRCode(border=1)
|
||||||
qr.add_data(url)
|
qr.add_data(url)
|
||||||
qr.make(fit=True)
|
qr.make(fit=True)
|
||||||
qr.print_ascii(invert=True)
|
qr.print_ascii(invert=True)
|
||||||
print()
|
_LOGIN_CONSOLE.print()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print("\nOpen this link with Feishu or Lark on your phone:")
|
_LOGIN_CONSOLE.print()
|
||||||
print(f"{url}\n")
|
_LOGIN_CONSOLE.print(Panel.fit(Text(url), title="Open with Feishu or Lark", border_style="cyan"))
|
||||||
|
_LOGIN_CONSOLE.print()
|
||||||
|
|
||||||
|
|
||||||
def _qr_register_inner(
|
def _qr_register_inner(
|
||||||
@@ -487,19 +482,19 @@ def _qr_register_inner(
|
|||||||
initial_domain: str,
|
initial_domain: str,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
"""Run init → begin → poll. Raises on network/protocol errors."""
|
"""Run init → begin → poll. Raises on network/protocol errors."""
|
||||||
print("Preparing Feishu/Lark login...")
|
_LOGIN_CONSOLE.print("[cyan]Preparing Feishu/Lark login...[/cyan]")
|
||||||
_init_registration(initial_domain)
|
_init_registration(initial_domain)
|
||||||
begin = _begin_registration(initial_domain)
|
begin = _begin_registration(initial_domain)
|
||||||
|
|
||||||
_print_qr_code(begin["qr_url"])
|
_print_qr_code(begin["qr_url"])
|
||||||
|
|
||||||
result = _poll_registration(
|
with _LOGIN_CONSOLE.status("Waiting for authorization in Feishu/Lark...", spinner="dots"):
|
||||||
device_code=begin["device_code"],
|
return _poll_registration(
|
||||||
interval=begin["interval"],
|
device_code=begin["device_code"],
|
||||||
expire_in=begin["expire_in"],
|
interval=begin["interval"],
|
||||||
domain=initial_domain,
|
expire_in=begin["expire_in"],
|
||||||
)
|
domain=initial_domain,
|
||||||
return result
|
)
|
||||||
|
|
||||||
|
|
||||||
_STREAM_ELEMENT_ID = "streaming_md"
|
_STREAM_ELEMENT_ID = "streaming_md"
|
||||||
@@ -575,17 +570,18 @@ class FeishuChannel(BaseChannel):
|
|||||||
self.config.app_secret = ""
|
self.config.app_secret = ""
|
||||||
|
|
||||||
if self.config.app_id and self.config.app_secret:
|
if self.config.app_id and self.config.app_secret:
|
||||||
print("Feishu / Lark is already authenticated.")
|
_LOGIN_CONSOLE.print("[green]Feishu/Lark is already authenticated.[/green]")
|
||||||
print("Use --force to re-authenticate with a new bot.")
|
_LOGIN_CONSOLE.print("Use --force to re-authenticate with a new bot.\n")
|
||||||
print()
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
print("Authorize with the mobile app. nanobot will save the new bot credentials.")
|
_LOGIN_CONSOLE.print("Authorize with the mobile app. nanobot will save the new bot credentials.\n")
|
||||||
print()
|
|
||||||
|
|
||||||
result = qr_register(initial_domain=self.config.domain or "feishu")
|
result = qr_register(initial_domain=self.config.domain or "feishu")
|
||||||
if not result:
|
if not result:
|
||||||
print("Login was not completed. Run 'nanobot channels login feishu --force' to retry.")
|
_LOGIN_CONSOLE.print(
|
||||||
|
"[yellow]Login was not completed.[/yellow] "
|
||||||
|
"Run 'nanobot channels login feishu --force' to retry."
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.config.app_id = result["app_id"]
|
self.config.app_id = result["app_id"]
|
||||||
@@ -605,9 +601,9 @@ class FeishuChannel(BaseChannel):
|
|||||||
setattr(full_config.channels, "feishu", feishu_cfg)
|
setattr(full_config.channels, "feishu", feishu_cfg)
|
||||||
save_config(full_config)
|
save_config(full_config)
|
||||||
|
|
||||||
print("\nFeishu/Lark login complete.")
|
_LOGIN_CONSOLE.print("\n[green]Feishu/Lark login complete.[/green]")
|
||||||
print(f"App ID: {result['app_id']}")
|
_LOGIN_CONSOLE.print(f"App ID: {escape(result['app_id'])}")
|
||||||
print(f"Domain: {self.config.domain}")
|
_LOGIN_CONSOLE.print(f"Domain: {escape(self.config.domain)}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user