该提交新增了基于BlueBubbles的iMessage渠道插件,支持单聊和群组消息,包含文本、图片、语音、文件和视频消息收发,支持消息编辑、撤回、回复、 reactions和输入状态提示,同时实现了账号配置、安全校验、配对授权、消息格式化与分片等完整功能。
681 lines
25 KiB
Python
681 lines
25 KiB
Python
from __future__ import annotations
|
|
|
|
import base64
|
|
import logging
|
|
import os
|
|
|
|
import httpx
|
|
|
|
from yuxi.channel.extensions.imessage.errors import IMessageSendError, classify_http_error
|
|
from yuxi.channel.extensions.imessage.format import IMessageFormatAdapter
|
|
from yuxi.channel.protocols import OutboundDeliveryMode, OutboundDeliveryCapabilities, OutboundPresentationCapabilities
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DEFAULT_CHUNK_LIMIT = 4000
|
|
|
|
|
|
class IMessageOutboundAdapter:
|
|
delivery_mode = OutboundDeliveryMode.DIRECT
|
|
chunker_mode = "newline"
|
|
text_chunk_limit = DEFAULT_CHUNK_LIMIT
|
|
poll_max_options = None
|
|
supports_poll_duration_seconds = False
|
|
supports_anonymous_polls = False
|
|
extract_markdown_images = False
|
|
presentation_capabilities = OutboundPresentationCapabilities(
|
|
supported=False,
|
|
buttons=False,
|
|
selects=False,
|
|
context=False,
|
|
divider=False,
|
|
)
|
|
delivery_capabilities = OutboundDeliveryCapabilities(
|
|
pin=False,
|
|
durable_final_text=True,
|
|
durable_final_media=False,
|
|
)
|
|
|
|
def __init__(self, echo_cache=None):
|
|
self._format = IMessageFormatAdapter()
|
|
self._echo_cache = echo_cache
|
|
self._http: httpx.AsyncClient | None = None
|
|
|
|
async def _get_http(self) -> httpx.AsyncClient:
|
|
if self._http is None:
|
|
self._http = httpx.AsyncClient(timeout=httpx.Timeout(30.0))
|
|
return self._http
|
|
|
|
def chunker(self, text: str, limit: int, ctx: object | None = None) -> list[str]:
|
|
return self._format.chunk_text(text, limit)
|
|
|
|
def sanitize_text(self, text: str, payload: object) -> str:
|
|
return text
|
|
|
|
def should_skip_plain_text_sanitization(self, payload: object) -> bool:
|
|
return True
|
|
|
|
def normalize_payload(self, payload: object, config: dict, account_id: str | None = None) -> object | None:
|
|
return payload
|
|
|
|
def resolve_effective_text_chunk_limit(
|
|
self, config: dict, account_id: str | None = None, fallback_limit: int | None = None
|
|
) -> int | None:
|
|
return self.text_chunk_limit
|
|
|
|
async def send_text(
|
|
self,
|
|
target_id: str,
|
|
content: str,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
_account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
|
|
formatted = self._format.format_for_imessage(content)
|
|
if self._echo_cache:
|
|
self._echo_cache.remember(target_id, formatted)
|
|
|
|
payload = {
|
|
"text": formatted,
|
|
"chatGuid": target_id,
|
|
}
|
|
if reply_to_id:
|
|
payload["replyToGuid"] = reply_to_id
|
|
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/text",
|
|
json=payload,
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to send text: {e}") from e
|
|
|
|
async def send_media(
|
|
self,
|
|
target_id: str,
|
|
media_url: str,
|
|
media_type: str,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
_account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
|
|
try:
|
|
media_resp = await http.get(media_url)
|
|
media_resp.raise_for_status()
|
|
media_data = media_resp.content
|
|
except Exception as e:
|
|
raise IMessageSendError(f"Failed to download media: {e}") from e
|
|
|
|
media_b64 = base64.b64encode(media_data).decode("utf-8")
|
|
content_type = media_resp.headers.get("content-type", media_type)
|
|
|
|
payload = {
|
|
"chatGuid": target_id,
|
|
"base64Data": media_b64,
|
|
"mimeType": content_type,
|
|
}
|
|
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/attachment",
|
|
json=payload,
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to send media: {e}") from e
|
|
|
|
async def send_reaction(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
reaction: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
_account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
|
|
payload = {
|
|
"chatGuid": target_id,
|
|
"reaction": reaction,
|
|
}
|
|
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/{message_id}/reaction",
|
|
json=payload,
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to send reaction: {e}") from e
|
|
|
|
async def edit_message(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
content: str,
|
|
*,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
_account: dict | None = None,
|
|
) -> str | None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
|
|
formatted = self._format.format_for_imessage(content)
|
|
payload = {"text": formatted}
|
|
|
|
try:
|
|
resp = await http.patch(
|
|
f"{server_url}/api/v1/message/{message_id}",
|
|
json=payload,
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return message_id
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to edit message: {e}") from e
|
|
|
|
async def unsend_message(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
*,
|
|
account_id: str | None = None,
|
|
_account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
|
|
try:
|
|
resp = await http.delete(
|
|
f"{server_url}/api/v1/message/{message_id}",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return True
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to unsend message: {e}") from e
|
|
|
|
async def fetch_chat_history(
|
|
self,
|
|
chat_guid: str,
|
|
*,
|
|
limit: int = 50,
|
|
before: int | None = None,
|
|
after: int | None = None,
|
|
account_id: str | None = None,
|
|
_account: dict | None = None,
|
|
) -> list[dict]:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
|
|
params: dict = {"limit": limit}
|
|
if before:
|
|
params["before"] = before
|
|
if after:
|
|
params["after"] = after
|
|
|
|
try:
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/message",
|
|
params=params,
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
data = resp.json()
|
|
return data.get("data", [])
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to fetch chat history: {e}") from e
|
|
|
|
async def send_typing_indicator(
|
|
self, chat_guid: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/typing",
|
|
headers={"Password": password},
|
|
)
|
|
except httpx.RequestError as e:
|
|
logger.warning("Failed to send typing indicator: %s", e)
|
|
|
|
async def send_read_receipt(
|
|
self, chat_guid: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/readreceipt",
|
|
headers={"Password": password},
|
|
)
|
|
except httpx.RequestError as e:
|
|
logger.warning("Failed to send read receipt: %s", e)
|
|
|
|
async def rename_group(
|
|
self, chat_guid: str, name: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.patch(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/rename",
|
|
json={"name": name},
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to rename group: {e}") from e
|
|
|
|
async def add_participant(
|
|
self, chat_guid: str, handle: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/addparticipant",
|
|
json={"address": handle},
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to add participant: {e}") from e
|
|
|
|
async def remove_participant(
|
|
self, chat_guid: str, handle: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/removeparticipant",
|
|
json={"address": handle},
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to remove participant: {e}") from e
|
|
|
|
async def leave_group(
|
|
self, chat_guid: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/leave",
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to leave group: {e}") from e
|
|
|
|
async def list_chats(
|
|
self, *, account_id: str | None = None, _account: dict | None = None,
|
|
) -> list[dict]:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/chat",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
data = resp.json()
|
|
return data.get("data", [])
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to list chats: {e}") from e
|
|
|
|
async def get_chat(
|
|
self, chat_guid: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> dict | None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/chat/{chat_guid}",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return resp.json()
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to get chat: {e}") from e
|
|
|
|
async def send_sms(
|
|
self, target_id: str, content: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
|
|
formatted = self._format.format_for_imessage(content)
|
|
payload = {"text": formatted, "chatGuid": target_id, "method": "sms"}
|
|
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/text",
|
|
json=payload,
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to send SMS: {e}") from e
|
|
|
|
async def send_mention(
|
|
self, chat_guid: str, message_id: str, mentions: list[str],
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/{message_id}/sendmention",
|
|
json={"mentions": mentions},
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to send mention: {e}") from e
|
|
|
|
async def send_with_effect(
|
|
self, chat_guid: str, message_id: str, effect: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
valid_effects = {"slam", "loud", "gentle", "invisibleink"}
|
|
if effect not in valid_effects:
|
|
raise ValueError(f"Invalid effect: {effect}. Must be one of {valid_effects}")
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/{message_id}/sendwitheffect",
|
|
json={"effect": effect},
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to send effect: {e}") from e
|
|
|
|
async def send_with_subject(
|
|
self, chat_guid: str, message_id: str, subject: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/{message_id}/sendwithsubject",
|
|
json={"subject": subject},
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to send subject: {e}") from e
|
|
|
|
async def list_handles(
|
|
self, *, account_id: str | None = None, _account: dict | None = None,
|
|
) -> list[dict]:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/handle",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return resp.json().get("data", [])
|
|
|
|
async def get_handle(
|
|
self, address: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> dict | None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/handle/{address}",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return resp.json()
|
|
|
|
async def check_focus_status(
|
|
self, *, account_id: str | None = None, _account: dict | None = None,
|
|
) -> dict:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/focus",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return resp.json()
|
|
|
|
async def check_facetime_status(
|
|
self, *, account_id: str | None = None, _account: dict | None = None,
|
|
) -> dict:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/facetime",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return resp.json()
|
|
|
|
async def create_chat(
|
|
self, handles: list[str],
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> dict | None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/chat/new",
|
|
json={"addresses": handles},
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return resp.json()
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to create chat: {e}") from e
|
|
|
|
async def delete_chat(
|
|
self, chat_guid: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.delete(
|
|
f"{server_url}/api/v1/chat/{chat_guid}",
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to delete chat: {e}") from e
|
|
|
|
async def mark_chat_read(
|
|
self, chat_guid: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/markread",
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
logger.warning("Failed to mark chat read: %s", e)
|
|
return False
|
|
|
|
async def mark_chat_unread(
|
|
self, chat_guid: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/markunread",
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
logger.warning("Failed to mark chat unread: %s", e)
|
|
return False
|
|
|
|
async def force_notify(
|
|
self, message_id: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/message/{message_id}/forcenotify",
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to force notify: {e}") from e
|
|
|
|
async def set_group_icon(
|
|
self, chat_guid: str, icon_path: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> bool:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
try:
|
|
resp = await http.post(
|
|
f"{server_url}/api/v1/chat/{chat_guid}/seticon",
|
|
json={"path": icon_path},
|
|
headers={"Password": password},
|
|
)
|
|
return resp.status_code < 400
|
|
except httpx.RequestError as e:
|
|
raise IMessageSendError(f"Failed to set group icon: {e}") from e
|
|
|
|
async def get_digital_touch(
|
|
self, message_id: str,
|
|
*, account_id: str | None = None, _account: dict | None = None,
|
|
) -> dict | None:
|
|
http = await self._get_http()
|
|
server_url, password = await self._resolve_server(account_id, _account)
|
|
resp = await http.get(
|
|
f"{server_url}/api/v1/message/{message_id}/digitaltouch",
|
|
headers={"Password": password},
|
|
)
|
|
if resp.status_code >= 400:
|
|
raise classify_http_error(resp.status_code, resp.text)
|
|
return resp.json()
|
|
|
|
async def send_card(
|
|
self,
|
|
target_id: str,
|
|
card_content: dict,
|
|
*,
|
|
reply_to_id: str | None = None,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> str | None:
|
|
return None
|
|
|
|
async def edit_card(
|
|
self,
|
|
target_id: str,
|
|
message_id: str,
|
|
card_content: dict,
|
|
*,
|
|
thread_id: str | None = None,
|
|
account_id: str | None = None,
|
|
) -> str | None:
|
|
return None
|
|
|
|
async def send_payload(self, ctx: object) -> object:
|
|
return None
|
|
|
|
async def send_poll(self, ctx: object) -> object:
|
|
return None
|
|
|
|
async def render_presentation(self, payload: object, presentation: object, ctx: object) -> object | None:
|
|
return None
|
|
|
|
async def pin_delivered_message(self, config: dict, target_ref: object, message_id: str, pin: object) -> None:
|
|
pass
|
|
|
|
async def before_deliver_payload(
|
|
self, config: dict, target_ref: object, payload: object, hint: object | None = None
|
|
) -> None:
|
|
pass
|
|
|
|
async def after_deliver_payload(self, config: dict, target_ref: object, payload: object, results: list) -> None:
|
|
pass
|
|
|
|
def resolve_target(
|
|
self,
|
|
to: str | None = None,
|
|
*,
|
|
config: dict | None = None,
|
|
allow_from: list[str] | None = None,
|
|
account_id: str | None = None,
|
|
mode: str | None = None,
|
|
) -> tuple[bool, str]:
|
|
if not to:
|
|
return False, "Target is required"
|
|
return True, to
|
|
|
|
def should_treat_delivered_text_as_visible(self, kind: str, text: str | None = None) -> bool:
|
|
return True
|
|
|
|
async def _resolve_server(self, account_id: str | None = None, _account: dict | None = None) -> tuple[str, str]:
|
|
if _account:
|
|
server_url = _account.get("server_url", "")
|
|
password = _account.get("password", "")
|
|
if server_url and password:
|
|
return server_url, password
|
|
server_url = os.getenv("IMESSAGE_SERVER_URL", "http://localhost:1234")
|
|
password = os.getenv("IMESSAGE_SERVER_PASSWORD", "")
|
|
return server_url, password
|