feat(nextcloudtalk): 新增投票和消息置顶相关功能,优化代码结构
1. 新增close_poll、get_poll_results方法实现投票管理 2. 新增pin_message、unpin_message、list_pins实现消息置顶功能 3. 调整导入顺序优化代码整洁度 4. 修复去重逻辑的判断顺序问题 5. 清理多余的空行和导入冗余
This commit is contained in:
parent
939f1ba82a
commit
b317387e0c
@ -11,11 +11,11 @@ from urllib.parse import urlparse
|
||||
|
||||
from yuxi.channels.base import BaseChannelAdapter
|
||||
from yuxi.channels.capabilities import ChannelCapabilities
|
||||
from yuxi.channels.meta import ChannelMeta
|
||||
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError
|
||||
from yuxi.channels.exceptions import (
|
||||
ChannelAuthenticationError,
|
||||
)
|
||||
from yuxi.channels.infra.circuit_breaker import CircuitBreaker, CircuitBreakerOpenError
|
||||
from yuxi.channels.meta import ChannelMeta
|
||||
from yuxi.channels.models import (
|
||||
ChannelMessage,
|
||||
ChannelResponse,
|
||||
@ -30,12 +30,12 @@ from yuxi.utils.datetime_utils import utc_now_naive
|
||||
|
||||
from .client import NextcloudTalkClient, verify_hmac_signature
|
||||
from .config import (
|
||||
resolve_room_enabled,
|
||||
resolve_room_system_prompt,
|
||||
resolve_room_skills,
|
||||
resolve_dm_system_prompt,
|
||||
resolve_dm_enabled,
|
||||
resolve_dm_skills,
|
||||
resolve_dm_system_prompt,
|
||||
resolve_room_enabled,
|
||||
resolve_room_skills,
|
||||
resolve_room_system_prompt,
|
||||
)
|
||||
from .dedup import NextcloudTalkDedupGuard
|
||||
from .formatter import format_outbound
|
||||
@ -47,15 +47,21 @@ from .pairing import send_pairing_challenge
|
||||
from .probe import probe_capabilities, resolve_api_base, resolve_api_version
|
||||
from .security import check_dm_policy, check_group_policy, check_mention_gate, collect_security_warnings
|
||||
from .send import (
|
||||
send_media,
|
||||
send_reaction,
|
||||
send_text,
|
||||
send_reaction_delete,
|
||||
get_reactions as _get_reactions,
|
||||
edit_message as _send_edit,
|
||||
delete_message as _send_delete,
|
||||
)
|
||||
from .session import resolve_session_route, make_thread_key
|
||||
from .send import (
|
||||
edit_message as _send_edit,
|
||||
)
|
||||
from .send import (
|
||||
get_reactions as _get_reactions,
|
||||
)
|
||||
from .send import (
|
||||
send_media,
|
||||
send_reaction,
|
||||
send_reaction_delete,
|
||||
send_text,
|
||||
)
|
||||
from .session import make_thread_key, resolve_session_route
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@ -109,6 +115,11 @@ class NextcloudTalkAdapter(BaseChannelAdapter):
|
||||
media=True,
|
||||
block_streaming=True,
|
||||
polls=True,
|
||||
approval=True,
|
||||
vision=True,
|
||||
pin=True,
|
||||
unpin=True,
|
||||
list_pins=True,
|
||||
)
|
||||
meta = ChannelMeta(
|
||||
id="nextcloud-talk",
|
||||
@ -790,6 +801,131 @@ class NextcloudTalkAdapter(BaseChannelAdapter):
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
async def close_poll(self, chat_id: str, poll_id: str) -> DeliveryResult:
|
||||
if not self._client:
|
||||
return DeliveryResult(success=False, error="Channel not connected")
|
||||
|
||||
async def _do_close():
|
||||
path = f"{self._api_base}/poll/{chat_id}/{poll_id}"
|
||||
try:
|
||||
result = await self._client.delete(path)
|
||||
ocs = result.get("ocs", {})
|
||||
if ocs.get("meta", {}).get("status") == "ok":
|
||||
return DeliveryResult(success=True, message_id=poll_id)
|
||||
return DeliveryResult(
|
||||
success=False,
|
||||
error=ocs.get("meta", {}).get("message", "Failed to close poll"),
|
||||
)
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
try:
|
||||
return await self._circuit_breaker.call(_do_close)
|
||||
except CircuitBreakerOpenError:
|
||||
return DeliveryResult(success=False, error="Circuit breaker open")
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
async def get_poll_results(self, chat_id: str, poll_id: str) -> DeliveryResult:
|
||||
if not self._client:
|
||||
return DeliveryResult(success=False, error="Channel not connected")
|
||||
|
||||
async def _do_get():
|
||||
path = f"{self._api_base}/poll/{chat_id}/{poll_id}"
|
||||
try:
|
||||
result = await self._client.get(path)
|
||||
ocs = result.get("ocs", {})
|
||||
if ocs.get("meta", {}).get("status") == "ok":
|
||||
data = ocs.get("data", {})
|
||||
return DeliveryResult(
|
||||
success=True,
|
||||
metadata={
|
||||
"question": data.get("question", ""),
|
||||
"options": data.get("options", []),
|
||||
"votes": data.get("votes", {}),
|
||||
"numVoters": data.get("numVoters", 0),
|
||||
"status": data.get("status", "open"),
|
||||
},
|
||||
)
|
||||
return DeliveryResult(
|
||||
success=False,
|
||||
error=ocs.get("meta", {}).get("message", "Failed to get poll results"),
|
||||
)
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
try:
|
||||
return await self._circuit_breaker.call(_do_get)
|
||||
except CircuitBreakerOpenError:
|
||||
return DeliveryResult(success=False, error="Circuit breaker open")
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
async def pin_message(self, chat_id: str, msg_id: str) -> DeliveryResult:
|
||||
if not self._client:
|
||||
return DeliveryResult(success=False, error="Channel not connected")
|
||||
|
||||
async def _do_pin():
|
||||
path = f"{self._api_base}/pin/{chat_id}/{msg_id}"
|
||||
try:
|
||||
result = await self._client.post(path)
|
||||
ocs = result.get("ocs", {})
|
||||
if ocs.get("meta", {}).get("status") == "ok":
|
||||
return DeliveryResult(success=True, message_id=msg_id)
|
||||
return DeliveryResult(
|
||||
success=False,
|
||||
error=ocs.get("meta", {}).get("message", "Failed to pin message"),
|
||||
)
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
try:
|
||||
return await self._circuit_breaker.call(_do_pin)
|
||||
except CircuitBreakerOpenError:
|
||||
return DeliveryResult(success=False, error="Circuit breaker open")
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
async def unpin_message(self, chat_id: str, msg_id: str) -> DeliveryResult:
|
||||
if not self._client:
|
||||
return DeliveryResult(success=False, error="Channel not connected")
|
||||
|
||||
async def _do_unpin():
|
||||
path = f"{self._api_base}/pin/{chat_id}/{msg_id}"
|
||||
try:
|
||||
result = await self._client.delete(path)
|
||||
ocs = result.get("ocs", {})
|
||||
if ocs.get("meta", {}).get("status") == "ok":
|
||||
return DeliveryResult(success=True, message_id=msg_id)
|
||||
return DeliveryResult(
|
||||
success=False,
|
||||
error=ocs.get("meta", {}).get("message", "Failed to unpin message"),
|
||||
)
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
try:
|
||||
return await self._circuit_breaker.call(_do_unpin)
|
||||
except CircuitBreakerOpenError:
|
||||
return DeliveryResult(success=False, error="Circuit breaker open")
|
||||
except Exception as e:
|
||||
return DeliveryResult(success=False, error=str(e))
|
||||
|
||||
async def list_pins(self, chat_id: str) -> list[dict]:
|
||||
if not self._client:
|
||||
return []
|
||||
path = f"{self._api_base}/pin/{chat_id}"
|
||||
try:
|
||||
result = await self._client.get(path)
|
||||
ocs = result.get("ocs", {})
|
||||
if ocs.get("meta", {}).get("status") == "ok":
|
||||
data = ocs.get("data", [])
|
||||
if isinstance(data, list):
|
||||
return data
|
||||
return []
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
async def list_conversations(self) -> list[dict[str, Any]]:
|
||||
if not self._client:
|
||||
return []
|
||||
|
||||
@ -108,10 +108,8 @@ class NextcloudTalkDedupGuard:
|
||||
if not token or not message_id:
|
||||
return False
|
||||
self._load_persisted()
|
||||
key = self._make_key(token, message_id)
|
||||
if key in self._committed:
|
||||
return True
|
||||
self._gc()
|
||||
key = self._make_key(token, message_id)
|
||||
return key in self._committed
|
||||
|
||||
def stats(self) -> dict[str, int]:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, UTC
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from yuxi.channels.models import (
|
||||
@ -14,7 +14,6 @@ from yuxi.channels.models import (
|
||||
MessageType,
|
||||
)
|
||||
|
||||
|
||||
_AS2_TYPE_MAP: dict[str, EventType] = {
|
||||
"Create": EventType.MESSAGE_RECEIVED,
|
||||
"Announce": EventType.MESSAGE_RECEIVED,
|
||||
|
||||
@ -4,7 +4,6 @@ import logging
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
SECRET_TARGETS = [
|
||||
|
||||
Loading…
Reference in New Issue
Block a user