完成Help Scout渠道的完整功能实现,包含API客户端、webhook处理、轮询同步、自动回复、工单标签分配、客户资料管理等核心能力,附带完整的配置Schema与插件元信息
150 lines
5.1 KiB
Python
150 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
import logging
|
|
from enum import Enum
|
|
|
|
from yuxi.channel.extensions.helpscout.auth import HelpScoutAuth
|
|
from yuxi.channel.extensions.helpscout.client import HelpScoutClient
|
|
from yuxi.channel.extensions.helpscout.config import resolve_account
|
|
from yuxi.channel.extensions.helpscout.types import OutboundResult
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ApprovalStatus(Enum):
|
|
PENDING = "pending"
|
|
APPROVED = "approved"
|
|
REJECTED = "rejected"
|
|
|
|
|
|
class HelpScoutApproval:
|
|
def __init__(self):
|
|
self._pending: dict[str, dict] = {}
|
|
|
|
async def submit_for_approval(
|
|
self,
|
|
conversation_id: int,
|
|
draft_text: str,
|
|
draft_html: str,
|
|
account_id: str | None = None,
|
|
) -> OutboundResult:
|
|
account = resolve_account(account_id or "default")
|
|
client = self._build_client(account)
|
|
|
|
try:
|
|
result = await client.reply_conversation(
|
|
conversation_id=conversation_id,
|
|
text=draft_text,
|
|
html=draft_html,
|
|
draft=True,
|
|
)
|
|
draft_id = str(result.get("id", ""))
|
|
|
|
note_text = (
|
|
"AI Draft requires review before sending:\n\n"
|
|
f"{draft_text[:500]}{'...' if len(draft_text) > 500 else ''}\n\n"
|
|
"Review the draft in Help Scout and approve or reject."
|
|
)
|
|
await client.create_note(conversation_id=conversation_id, text=note_text)
|
|
|
|
key = f"conv:{conversation_id}"
|
|
self._pending[key] = {
|
|
"conversation_id": conversation_id,
|
|
"draft_id": draft_id,
|
|
"status": ApprovalStatus.PENDING,
|
|
"draft_text": draft_text,
|
|
}
|
|
|
|
logger.info(
|
|
"Help Scout draft submitted for approval: conv=%d draft_id=%s",
|
|
conversation_id,
|
|
draft_id,
|
|
)
|
|
return OutboundResult(success=True, message_id=draft_id)
|
|
except Exception as e:
|
|
logger.exception("Help Scout approval submit failed: conv=%d", conversation_id)
|
|
return OutboundResult(success=False, error="exception", detail=str(e))
|
|
finally:
|
|
await client.close()
|
|
|
|
async def approve_draft(
|
|
self,
|
|
conversation_id: int,
|
|
account_id: str | None = None,
|
|
) -> OutboundResult:
|
|
key = f"conv:{conversation_id}"
|
|
pending = self._pending.get(key)
|
|
|
|
if not pending:
|
|
return OutboundResult(success=False, error="no_pending_draft")
|
|
|
|
pending["status"] = ApprovalStatus.APPROVED
|
|
account = resolve_account(account_id or "default")
|
|
client = self._build_client(account)
|
|
|
|
try:
|
|
result = await client.reply_conversation(
|
|
conversation_id=conversation_id,
|
|
text=pending["draft_text"],
|
|
draft=False,
|
|
)
|
|
self._pending.pop(key, None)
|
|
logger.info("Help Scout draft approved and sent: conv=%d", conversation_id)
|
|
return OutboundResult(success=True, message_id=str(result.get("id", "")))
|
|
except Exception as e:
|
|
logger.exception("Help Scout approve and send failed: conv=%d", conversation_id)
|
|
return OutboundResult(success=False, error="exception", detail=str(e))
|
|
finally:
|
|
await client.close()
|
|
|
|
async def reject_draft(
|
|
self,
|
|
conversation_id: int,
|
|
reason: str = "",
|
|
account_id: str | None = None,
|
|
) -> OutboundResult:
|
|
key = f"conv:{conversation_id}"
|
|
pending = self._pending.get(key)
|
|
|
|
if not pending:
|
|
return OutboundResult(success=False, error="no_pending_draft")
|
|
|
|
pending["status"] = ApprovalStatus.REJECTED
|
|
|
|
account = resolve_account(account_id or "default")
|
|
client = self._build_client(account)
|
|
|
|
try:
|
|
note_text = "AI Draft rejected. "
|
|
if reason:
|
|
note_text += f"Reason: {reason}"
|
|
await client.create_note(conversation_id=conversation_id, text=note_text)
|
|
self._pending.pop(key, None)
|
|
logger.info(
|
|
"Help Scout draft rejected: conv=%d reason=%s",
|
|
conversation_id,
|
|
reason,
|
|
)
|
|
return OutboundResult(success=True, message_id="")
|
|
except Exception as e:
|
|
logger.exception("Help Scout reject failed: conv=%d", conversation_id)
|
|
return OutboundResult(success=False, error="exception", detail=str(e))
|
|
finally:
|
|
await client.close()
|
|
|
|
def get_pending_drafts(self) -> list[dict]:
|
|
return [
|
|
{
|
|
"conversation_id": v["conversation_id"],
|
|
"draft_id": v["draft_id"],
|
|
"status": v["status"].value,
|
|
"preview": v["draft_text"][:200],
|
|
}
|
|
for v in self._pending.values()
|
|
]
|
|
|
|
@staticmethod
|
|
def _build_client(account: dict) -> HelpScoutClient:
|
|
auth = HelpScoutAuth(account["app_id"], account["app_secret"])
|
|
return HelpScoutClient(auth)
|