mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-05 05:16:03 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6071241872 | ||
|
|
ab93c67081 | ||
|
|
7af6b833df | ||
|
|
3e4b85aeb5 | ||
|
|
2b6be04c5d | ||
|
|
b2d1c82196 | ||
|
|
a19da7b923 | ||
|
|
4a9a78d07b | ||
|
|
300dbd076f | ||
|
|
ddf52524a8 | ||
|
|
7dcc44b4fc |
@@ -86,13 +86,13 @@
|
|||||||
| QQ 个人号 | ✅ | QQ 个人号私聊、群聊 |
|
| QQ 个人号 | ✅ | QQ 个人号私聊、群聊 |
|
||||||
| QQ 官方机器人 | ✅ | QQ 官方机器人,支持频道、私聊、群聊 |
|
| QQ 官方机器人 | ✅ | QQ 官方机器人,支持频道、私聊、群聊 |
|
||||||
| 企业微信 | ✅ | |
|
| 企业微信 | ✅ | |
|
||||||
|
| 个人微信 | ✅ | 使用 [Gewechat](https://github.com/Devo919/Gewechat) 接入 |
|
||||||
| 微信公众号 | ✅ | |
|
| 微信公众号 | ✅ | |
|
||||||
| 飞书 | ✅ | |
|
| 飞书 | ✅ | |
|
||||||
|
| 钉钉 | ✅ | |
|
||||||
| Discord | ✅ | |
|
| Discord | ✅ | |
|
||||||
| 个人微信 | ✅ | 使用 [Gewechat](https://github.com/Devo919/Gewechat) 接入 |
|
| Telegram | ✅ | |
|
||||||
| Telegram | 🚧 | |
|
|
||||||
| WhatsApp | 🚧 | |
|
| WhatsApp | 🚧 | |
|
||||||
| 钉钉 | 🚧 | |
|
|
||||||
|
|
||||||
🚧: 正在开发中
|
🚧: 正在开发中
|
||||||
|
|
||||||
|
|||||||
@@ -85,13 +85,12 @@ Directly use the released version to run, see the [Manual Deployment](https://do
|
|||||||
| Personal QQ | ✅ | |
|
| Personal QQ | ✅ | |
|
||||||
| QQ Official API | ✅ | |
|
| QQ Official API | ✅ | |
|
||||||
| WeCom | ✅ | |
|
| WeCom | ✅ | |
|
||||||
| WeChat Official Account | ✅ | |
|
|
||||||
| Lark | ✅ | |
|
|
||||||
| Discord | ✅ | |
|
|
||||||
| Personal WeChat | ✅ | Use [Gewechat](https://github.com/Devo919/Gewechat) to access |
|
| Personal WeChat | ✅ | Use [Gewechat](https://github.com/Devo919/Gewechat) to access |
|
||||||
| Telegram | 🚧 | |
|
| Lark | ✅ | |
|
||||||
|
| DingTalk | ✅ | |
|
||||||
|
| Discord | ✅ | |
|
||||||
|
| Telegram | ✅ | |
|
||||||
| WhatsApp | 🚧 | |
|
| WhatsApp | 🚧 | |
|
||||||
| DingTalk | 🚧 | |
|
|
||||||
|
|
||||||
🚧: In development
|
🚧: In development
|
||||||
|
|
||||||
|
|||||||
@@ -84,12 +84,12 @@ LangBotはBTPanelにリストされています。BTPanelをインストール
|
|||||||
| 個人QQ | ✅ | |
|
| 個人QQ | ✅ | |
|
||||||
| QQ公式API | ✅ | |
|
| QQ公式API | ✅ | |
|
||||||
| WeCom | ✅ | |
|
| WeCom | ✅ | |
|
||||||
| Lark | ✅ | |
|
|
||||||
| Discord | ✅ | |
|
|
||||||
| 個人WeChat | ✅ | [Gewechat](https://github.com/Devo919/Gewechat)を使用して接続 |
|
| 個人WeChat | ✅ | [Gewechat](https://github.com/Devo919/Gewechat)を使用して接続 |
|
||||||
| Telegram | 🚧 | |
|
| Lark | ✅ | |
|
||||||
|
| DingTalk | ✅ | |
|
||||||
|
| Discord | ✅ | |
|
||||||
|
| Telegram | ✅ | |
|
||||||
| WhatsApp | 🚧 | |
|
| WhatsApp | 🚧 | |
|
||||||
| DingTalk | 🚧 | |
|
|
||||||
|
|
||||||
🚧: 開発中
|
🚧: 開発中
|
||||||
|
|
||||||
|
|||||||
29
libs/dingtalk_api/EchoHandler.py
Normal file
29
libs/dingtalk_api/EchoHandler.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import asyncio
|
||||||
|
import dingtalk_stream
|
||||||
|
from dingtalk_stream import AckMessage
|
||||||
|
|
||||||
|
class EchoTextHandler(dingtalk_stream.ChatbotHandler):
|
||||||
|
def __init__(self, client):
|
||||||
|
self.msg_id = ''
|
||||||
|
self.incoming_message = None
|
||||||
|
self.client = client # 用于更新 DingTalkClient 中的 incoming_message
|
||||||
|
|
||||||
|
"""处理钉钉消息"""
|
||||||
|
async def process(self, callback: dingtalk_stream.CallbackMessage):
|
||||||
|
incoming_message = dingtalk_stream.ChatbotMessage.from_dict(callback.data)
|
||||||
|
if incoming_message.message_id != self.msg_id:
|
||||||
|
self.msg_id = incoming_message.message_id
|
||||||
|
|
||||||
|
await self.client.update_incoming_message(incoming_message)
|
||||||
|
|
||||||
|
return AckMessage.STATUS_OK, 'OK'
|
||||||
|
|
||||||
|
async def get_incoming_message(self):
|
||||||
|
"""异步等待消息的到来"""
|
||||||
|
while self.incoming_message is None:
|
||||||
|
await asyncio.sleep(0.1) # 异步等待,避免阻塞
|
||||||
|
return self.incoming_message
|
||||||
|
|
||||||
|
async def get_dingtalk_client(client_id, client_secret):
|
||||||
|
from api import DingTalkClient # 延迟导入,避免循环导入
|
||||||
|
return DingTalkClient(client_id, client_secret)
|
||||||
0
libs/dingtalk_api/__init__.py
Normal file
0
libs/dingtalk_api/__init__.py
Normal file
174
libs/dingtalk_api/api.py
Normal file
174
libs/dingtalk_api/api.py
Normal file
@@ -0,0 +1,174 @@
|
|||||||
|
import base64
|
||||||
|
import time
|
||||||
|
from typing import Callable
|
||||||
|
import dingtalk_stream
|
||||||
|
from .EchoHandler import EchoTextHandler
|
||||||
|
from .dingtalkevent import DingTalkEvent
|
||||||
|
import httpx
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
|
class DingTalkClient:
|
||||||
|
def __init__(self, client_id: str, client_secret: str,robot_name:str,robot_code:str):
|
||||||
|
"""初始化 WebSocket 连接并自动启动"""
|
||||||
|
self.credential = dingtalk_stream.Credential(client_id, client_secret)
|
||||||
|
self.client = dingtalk_stream.DingTalkStreamClient(self.credential)
|
||||||
|
self.key = client_id
|
||||||
|
self.secret = client_secret
|
||||||
|
# 在 DingTalkClient 中传入自己作为参数,避免循环导入
|
||||||
|
self.EchoTextHandler = EchoTextHandler(self)
|
||||||
|
self.client.register_callback_handler(dingtalk_stream.chatbot.ChatbotMessage.TOPIC, self.EchoTextHandler)
|
||||||
|
self._message_handlers = {
|
||||||
|
"example":[],
|
||||||
|
}
|
||||||
|
self.access_token = ''
|
||||||
|
self.robot_name = robot_name
|
||||||
|
self.robot_code = robot_code
|
||||||
|
self.access_token_expiry_time = ''
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async def get_access_token(self):
|
||||||
|
url = "https://api.dingtalk.com/v1.0/oauth2/accessToken"
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
data = {
|
||||||
|
"appKey": self.key,
|
||||||
|
"appSecret": self.secret
|
||||||
|
}
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
response = await client.post(url,json=data,headers=headers)
|
||||||
|
if response.status_code == 200:
|
||||||
|
response_data = response.json()
|
||||||
|
self.access_token = response_data.get("accessToken")
|
||||||
|
expires_in = int(response_data.get("expireIn",7200))
|
||||||
|
self.access_token_expiry_time = time.time() + expires_in - 60
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
|
||||||
|
async def is_token_expired(self):
|
||||||
|
"""检查token是否过期"""
|
||||||
|
if self.access_token_expiry_time is None:
|
||||||
|
return True
|
||||||
|
return time.time() > self.access_token_expiry_time
|
||||||
|
|
||||||
|
async def check_access_token(self):
|
||||||
|
if not self.access_token or await self.is_token_expired():
|
||||||
|
return False
|
||||||
|
return bool(self.access_token and self.access_token.strip())
|
||||||
|
|
||||||
|
async def download_image(self,download_code:str):
|
||||||
|
if not await self.check_access_token():
|
||||||
|
await self.get_access_token()
|
||||||
|
url = 'https://api.dingtalk.com/v1.0/robot/messageFiles/download'
|
||||||
|
params = {
|
||||||
|
"downloadCode":download_code,
|
||||||
|
"robotCode":self.robot_code
|
||||||
|
}
|
||||||
|
headers ={
|
||||||
|
"x-acs-dingtalk-access-token": self.access_token
|
||||||
|
}
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.post(url, headers=headers, json=params)
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
download_url = result.get("downloadUrl")
|
||||||
|
else:
|
||||||
|
raise Exception(f"Error: {response.status_code}, {response.text}")
|
||||||
|
|
||||||
|
if download_url:
|
||||||
|
return await self.download_url_to_base64(download_url)
|
||||||
|
|
||||||
|
async def download_url_to_base64(self,download_url):
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
response = await client.get(download_url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
|
||||||
|
file_bytes = response.content
|
||||||
|
base64_str = base64.b64encode(file_bytes).decode('utf-8') # 返回字符串格式
|
||||||
|
return base64_str
|
||||||
|
else:
|
||||||
|
raise Exception("获取图片失败")
|
||||||
|
|
||||||
|
async def update_incoming_message(self, message):
|
||||||
|
"""异步更新 DingTalkClient 中的 incoming_message"""
|
||||||
|
message_data = await self.get_message(message)
|
||||||
|
if message_data:
|
||||||
|
event = DingTalkEvent.from_payload(message_data)
|
||||||
|
if event:
|
||||||
|
await self._handle_message(event)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_message(self,content:str,incoming_message):
|
||||||
|
self.EchoTextHandler.reply_text(content,incoming_message)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_incoming_message(self):
|
||||||
|
"""获取收到的消息"""
|
||||||
|
return await self.EchoTextHandler.get_incoming_message()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def on_message(self, msg_type: str):
|
||||||
|
def decorator(func: Callable[[DingTalkEvent], None]):
|
||||||
|
if msg_type not in self._message_handlers:
|
||||||
|
self._message_handlers[msg_type] = []
|
||||||
|
self._message_handlers[msg_type].append(func)
|
||||||
|
return func
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
async def _handle_message(self, event: DingTalkEvent):
|
||||||
|
"""
|
||||||
|
处理消息事件。
|
||||||
|
"""
|
||||||
|
msg_type = event.conversation
|
||||||
|
if msg_type in self._message_handlers:
|
||||||
|
for handler in self._message_handlers[msg_type]:
|
||||||
|
await handler(event)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_message(self,incoming_message:dingtalk_stream.chatbot.ChatbotMessage):
|
||||||
|
try:
|
||||||
|
message_data = {
|
||||||
|
"IncomingMessage":incoming_message,
|
||||||
|
}
|
||||||
|
if str(incoming_message.conversation_type) == '1':
|
||||||
|
message_data["conversation_type"] = 'FriendMessage'
|
||||||
|
elif str(incoming_message.conversation_type) == '2':
|
||||||
|
message_data["conversation_type"] = 'GroupMessage'
|
||||||
|
|
||||||
|
|
||||||
|
if incoming_message.message_type == 'richText':
|
||||||
|
|
||||||
|
data = incoming_message.rich_text_content.to_dict()
|
||||||
|
for item in data['richText']:
|
||||||
|
if 'text' in item:
|
||||||
|
message_data["Content"] = item['text']
|
||||||
|
if incoming_message.get_image_list()[0]:
|
||||||
|
message_data["Picture"] = await self.download_image(incoming_message.get_image_list()[0])
|
||||||
|
message_data["Type"] = 'text'
|
||||||
|
|
||||||
|
elif incoming_message.message_type == 'text':
|
||||||
|
message_data['Content'] = incoming_message.get_text_list()[0]
|
||||||
|
|
||||||
|
message_data["Type"] = 'text'
|
||||||
|
elif incoming_message.message_type == 'picture':
|
||||||
|
message_data['Picture'] = await self.download_image(incoming_message.get_image_list()[0])
|
||||||
|
|
||||||
|
message_data['Type'] = 'image'
|
||||||
|
|
||||||
|
# 删掉开头的@消息
|
||||||
|
if message_data["Content"].startswith("@"+self.robot_name):
|
||||||
|
message_data["Content"][len("@"+self.robot_name):]
|
||||||
|
except Exception:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
return message_data
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
"""启动 WebSocket 连接,监听消息"""
|
||||||
|
await self.client.start()
|
||||||
64
libs/dingtalk_api/dingtalkevent.py
Normal file
64
libs/dingtalk_api/dingtalkevent.py
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
from typing import Dict, Any, Optional
|
||||||
|
|
||||||
|
class DingTalkEvent(dict):
|
||||||
|
@staticmethod
|
||||||
|
def from_payload(payload: Dict[str, Any]) -> Optional["DingTalkEvent"]:
|
||||||
|
try:
|
||||||
|
event = DingTalkEvent(payload)
|
||||||
|
return event
|
||||||
|
except KeyError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content(self):
|
||||||
|
return self.get("Content","")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def incoming_message(self):
|
||||||
|
return self.get("IncomingMessage")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
return self.get("Type","")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def picture(self):
|
||||||
|
return self.get("Picture","")
|
||||||
|
|
||||||
|
@property
|
||||||
|
def conversation(self):
|
||||||
|
return self.get("conversation_type","")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __getattr__(self, key: str) -> Optional[Any]:
|
||||||
|
"""
|
||||||
|
允许通过属性访问数据中的任意字段。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): 字段名。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Optional[Any]: 字段值。
|
||||||
|
"""
|
||||||
|
return self.get(key)
|
||||||
|
|
||||||
|
def __setattr__(self, key: str, value: Any) -> None:
|
||||||
|
"""
|
||||||
|
允许通过属性设置数据中的任意字段。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
key (str): 字段名。
|
||||||
|
value (Any): 字段值。
|
||||||
|
"""
|
||||||
|
self[key] = value
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
"""
|
||||||
|
生成事件对象的字符串表示。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: 字符串表示。
|
||||||
|
"""
|
||||||
|
return f"<WecomEvent {super().__repr__()}>"
|
||||||
@@ -129,7 +129,6 @@ class WecomClient():
|
|||||||
self.access_token = await self.get_access_token(self.secret)
|
self.access_token = await self.get_access_token(self.secret)
|
||||||
|
|
||||||
url = self.base_url+'/message/send?access_token='+self.access_token
|
url = self.base_url+'/message/send?access_token='+self.access_token
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
params={
|
params={
|
||||||
"touser" : user_id,
|
"touser" : user_id,
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ required_deps = {
|
|||||||
"discord": "discord.py",
|
"discord": "discord.py",
|
||||||
"cryptography": "cryptography",
|
"cryptography": "cryptography",
|
||||||
"gewechat_client": "gewechat-client",
|
"gewechat_client": "gewechat-client",
|
||||||
|
"dingtalk_stream": "dingtalk_stream",
|
||||||
"dashscope": "dashscope",
|
"dashscope": "dashscope",
|
||||||
|
"telegram": "python-telegram-bot",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ class WecomConfigMigration(migration.Migration):
|
|||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
# for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] == 'wecom':
|
# if adapter['adapter'] == 'wecom':
|
||||||
return False
|
# return False
|
||||||
|
|
||||||
return True
|
# return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ class LarkConfigMigration(migration.Migration):
|
|||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
# for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] == 'lark':
|
# if adapter['adapter'] == 'lark':
|
||||||
return False
|
# return False
|
||||||
|
|
||||||
return True
|
# return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ class DiscordConfigMigration(migration.Migration):
|
|||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
# for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] == 'discord':
|
# if adapter['adapter'] == 'discord':
|
||||||
return False
|
# return False
|
||||||
|
|
||||||
return True
|
# return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ class GewechatConfigMigration(migration.Migration):
|
|||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
# for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] == 'gewechat':
|
# if adapter['adapter'] == 'gewechat':
|
||||||
return False
|
# return False
|
||||||
|
|
||||||
return True
|
# return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ class QQOfficialConfigMigration(migration.Migration):
|
|||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
# for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] == 'qqofficial':
|
# if adapter['adapter'] == 'qqofficial':
|
||||||
return False
|
# return False
|
||||||
|
|
||||||
return True
|
# return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
@@ -10,11 +10,12 @@ class WXOfficialAccountConfigMigration(migration.Migration):
|
|||||||
async def need_migrate(self) -> bool:
|
async def need_migrate(self) -> bool:
|
||||||
"""判断当前环境是否需要运行此迁移"""
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
# for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
if adapter['adapter'] == 'officialaccount':
|
# if adapter['adapter'] == 'officialaccount':
|
||||||
return False
|
# return False
|
||||||
|
|
||||||
return True
|
# return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def run(self):
|
async def run(self):
|
||||||
"""执行迁移"""
|
"""执行迁移"""
|
||||||
|
|||||||
31
pkg/core/migrations/m031_dingtalk_config.py
Normal file
31
pkg/core/migrations/m031_dingtalk_config.py
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .. import migration
|
||||||
|
|
||||||
|
|
||||||
|
@migration.migration_class("dingtalk-config", 31)
|
||||||
|
class DingTalkConfigMigration(migration.Migration):
|
||||||
|
"""迁移"""
|
||||||
|
|
||||||
|
async def need_migrate(self) -> bool:
|
||||||
|
"""判断当前环境是否需要运行此迁移"""
|
||||||
|
|
||||||
|
# for adapter in self.ap.platform_cfg.data['platform-adapters']:
|
||||||
|
# if adapter['adapter'] == 'dingtalk':
|
||||||
|
# return False
|
||||||
|
|
||||||
|
# return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def run(self):
|
||||||
|
"""执行迁移"""
|
||||||
|
self.ap.platform_cfg.data['platform-adapters'].append({
|
||||||
|
"adapter": "dingtalk",
|
||||||
|
"enable": False,
|
||||||
|
"client_id": "",
|
||||||
|
"client_secret": "",
|
||||||
|
"robot_code": "",
|
||||||
|
"robot_name": ""
|
||||||
|
})
|
||||||
|
|
||||||
|
await self.ap.platform_cfg.dump_config()
|
||||||
@@ -10,7 +10,7 @@ from ..migrations import m010_ollama_requester_config, m011_command_prefix_confi
|
|||||||
from ..migrations import m015_gitee_ai_config, m016_dify_service_api, m017_dify_api_timeout_params, m018_xai_config, m019_zhipuai_config
|
from ..migrations import m015_gitee_ai_config, m016_dify_service_api, m017_dify_api_timeout_params, m018_xai_config, m019_zhipuai_config
|
||||||
from ..migrations import m020_wecom_config, m021_lark_config, m022_lmstudio_config, m023_siliconflow_config, m024_discord_config, m025_gewechat_config
|
from ..migrations import m020_wecom_config, m021_lark_config, m022_lmstudio_config, m023_siliconflow_config, m024_discord_config, m025_gewechat_config
|
||||||
from ..migrations import m026_qqofficial_config, m027_wx_official_account_config, m028_aliyun_requester_config
|
from ..migrations import m026_qqofficial_config, m027_wx_official_account_config, m028_aliyun_requester_config
|
||||||
from ..migrations import m029_dashscope_app_api_config, m030_lark_config_cmpl
|
from ..migrations import m029_dashscope_app_api_config, m030_lark_config_cmpl, m031_dingtalk_config
|
||||||
|
|
||||||
|
|
||||||
@stage.stage_class("MigrationStage")
|
@stage.stage_class("MigrationStage")
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ class PlatformManager:
|
|||||||
|
|
||||||
async def initialize(self):
|
async def initialize(self):
|
||||||
|
|
||||||
from .sources import nakuru, aiocqhttp, qqbotpy, qqofficial, wecom, lark, discord, gewechat, officialaccount
|
from .sources import nakuru, aiocqhttp, qqbotpy, qqofficial, wecom, lark, discord, gewechat, officialaccount, telegram, dingtalk
|
||||||
|
|
||||||
async def on_friend_message(event: platform_events.FriendMessage, adapter: msadapter.MessageSourceAdapter):
|
async def on_friend_message(event: platform_events.FriendMessage, adapter: msadapter.MessageSourceAdapter):
|
||||||
|
|
||||||
|
|||||||
182
pkg/platform/sources/dingtalk.py
Normal file
182
pkg/platform/sources/dingtalk.py
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
|
||||||
|
import traceback
|
||||||
|
import typing
|
||||||
|
from libs.dingtalk_api.dingtalkevent import DingTalkEvent
|
||||||
|
from pkg.platform.types import message as platform_message
|
||||||
|
from pkg.platform.adapter import MessageSourceAdapter
|
||||||
|
from pkg.platform.types import events as platform_events, message as platform_message
|
||||||
|
from pkg.core import app
|
||||||
|
from .. import adapter
|
||||||
|
from ...pipeline.longtext.strategies import forward
|
||||||
|
from ...core import app
|
||||||
|
from ..types import message as platform_message
|
||||||
|
from ..types import events as platform_events
|
||||||
|
from ..types import entities as platform_entities
|
||||||
|
from ...command.errors import ParamNotEnoughError
|
||||||
|
from libs.dingtalk_api.api import DingTalkClient
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
|
||||||
|
class DingTalkMessageConverter(adapter.MessageConverter):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def yiri2target(
|
||||||
|
message_chain:platform_message.MessageChain
|
||||||
|
):
|
||||||
|
for msg in message_chain:
|
||||||
|
if type(msg) is platform_message.Plain:
|
||||||
|
return msg.text
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def target2yiri(event:DingTalkEvent):
|
||||||
|
yiri_msg_list = []
|
||||||
|
yiri_msg_list.append(
|
||||||
|
platform_message.Source(id = '0',time=datetime.datetime.now())
|
||||||
|
)
|
||||||
|
|
||||||
|
if event.content:
|
||||||
|
yiri_msg_list.append(platform_message.Plain(text=event.content))
|
||||||
|
if event.picture:
|
||||||
|
yiri_msg_list.append(platform_message.Image(base64=event.picture))
|
||||||
|
|
||||||
|
chain = platform_message.MessageChain(yiri_msg_list)
|
||||||
|
|
||||||
|
return chain
|
||||||
|
|
||||||
|
|
||||||
|
class DingTalkEventConverter(adapter.EventConverter):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def yiri2target(
|
||||||
|
event:platform_events.MessageEvent
|
||||||
|
):
|
||||||
|
return event.source_platform_object
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def target2yiri(
|
||||||
|
event:DingTalkEvent
|
||||||
|
):
|
||||||
|
|
||||||
|
message_chain = await DingTalkMessageConverter.target2yiri(event)
|
||||||
|
|
||||||
|
|
||||||
|
if event.conversation == 'FriendMessage':
|
||||||
|
|
||||||
|
return platform_events.FriendMessage(
|
||||||
|
sender=platform_entities.Friend(
|
||||||
|
id= 0,
|
||||||
|
nickname ='nickname',
|
||||||
|
remark=""
|
||||||
|
),
|
||||||
|
message_chain = message_chain,
|
||||||
|
time = datetime.datetime.now(),
|
||||||
|
source_platform_object=event,
|
||||||
|
)
|
||||||
|
elif event.conversation == 'GroupMessage':
|
||||||
|
message_chain.insert(0, platform_message.At(target="justbot"))
|
||||||
|
sender = platform_entities.GroupMember(
|
||||||
|
id = 111,
|
||||||
|
member_name="name",
|
||||||
|
permission= 'MEMBER',
|
||||||
|
group = platform_entities.Group(
|
||||||
|
id = 111,
|
||||||
|
name = 'MEMBER',
|
||||||
|
permission=platform_entities.Permission.Member
|
||||||
|
),
|
||||||
|
special_title='',
|
||||||
|
join_timestamp=0,
|
||||||
|
last_speak_timestamp=0,
|
||||||
|
mute_time_remaining=0
|
||||||
|
)
|
||||||
|
time = datetime.datetime.now(),
|
||||||
|
return platform_events.GroupMessage(
|
||||||
|
sender =sender,
|
||||||
|
message_chain = message_chain,
|
||||||
|
time = time,
|
||||||
|
source_platform_object=event
|
||||||
|
)
|
||||||
|
|
||||||
|
@adapter.adapter_class("dingtalk")
|
||||||
|
class DingTalkAdapter(adapter.MessageSourceAdapter):
|
||||||
|
bot: DingTalkClient
|
||||||
|
ap: app.Application
|
||||||
|
bot_account_id: str
|
||||||
|
message_converter: DingTalkMessageConverter = DingTalkMessageConverter()
|
||||||
|
event_converter: DingTalkEventConverter = DingTalkEventConverter()
|
||||||
|
config: dict
|
||||||
|
|
||||||
|
def __init__(self,config:dict,ap:app.Application):
|
||||||
|
self.config = config
|
||||||
|
self.ap = ap
|
||||||
|
required_keys = [
|
||||||
|
"client_id",
|
||||||
|
"client_secret",
|
||||||
|
"robot_name",
|
||||||
|
"robot_code",
|
||||||
|
]
|
||||||
|
missing_keys = [key for key in required_keys if key not in config]
|
||||||
|
if missing_keys:
|
||||||
|
raise ParamNotEnoughError("钉钉缺少相关配置项,请查看文档或联系管理员")
|
||||||
|
|
||||||
|
self.bot = DingTalkClient(
|
||||||
|
client_id=config["client_id"],
|
||||||
|
client_secret=config["client_secret"],
|
||||||
|
robot_name = config["robot_name"],
|
||||||
|
robot_code=config["robot_code"]
|
||||||
|
)
|
||||||
|
|
||||||
|
async def reply_message(
|
||||||
|
self,
|
||||||
|
message_source: platform_events.MessageEvent,
|
||||||
|
message: platform_message.MessageChain,
|
||||||
|
quote_origin: bool = False,
|
||||||
|
):
|
||||||
|
event = await DingTalkEventConverter.yiri2target(
|
||||||
|
message_source,
|
||||||
|
)
|
||||||
|
incoming_message = event.incoming_message
|
||||||
|
|
||||||
|
content = await DingTalkMessageConverter.yiri2target(message)
|
||||||
|
await self.bot.send_message(content,incoming_message)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_message(
|
||||||
|
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def register_listener(
|
||||||
|
self,
|
||||||
|
event_type: typing.Type[platform_events.Event],
|
||||||
|
callback: typing.Callable[
|
||||||
|
[platform_events.Event, adapter.MessageSourceAdapter], None
|
||||||
|
],
|
||||||
|
):
|
||||||
|
async def on_message(event: DingTalkEvent):
|
||||||
|
self.bot_account_id = 'justbot'
|
||||||
|
try:
|
||||||
|
return await callback(
|
||||||
|
await self.event_converter.target2yiri(event), self
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
if event_type == platform_events.FriendMessage:
|
||||||
|
self.bot.on_message("FriendMessage")(on_message)
|
||||||
|
elif event_type == platform_events.GroupMessage:
|
||||||
|
self.bot.on_message("GroupMessage")(on_message)
|
||||||
|
|
||||||
|
async def run_async(self):
|
||||||
|
|
||||||
|
await self.bot.start()
|
||||||
|
|
||||||
|
async def kill(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def unregister_listener(
|
||||||
|
self,
|
||||||
|
event_type: type,
|
||||||
|
callback: typing.Callable[[platform_events.Event, MessageSourceAdapter], None],
|
||||||
|
):
|
||||||
|
return super().unregister_listener(event_type, callback)
|
||||||
|
|
||||||
242
pkg/platform/sources/telegram.py
Normal file
242
pkg/platform/sources/telegram.py
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import telegram
|
||||||
|
import telegram.ext
|
||||||
|
from telegram import Update
|
||||||
|
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters
|
||||||
|
|
||||||
|
import typing
|
||||||
|
import asyncio
|
||||||
|
import traceback
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
import base64
|
||||||
|
import uuid
|
||||||
|
import json
|
||||||
|
import datetime
|
||||||
|
import hashlib
|
||||||
|
import base64
|
||||||
|
import aiohttp
|
||||||
|
from Crypto.Cipher import AES
|
||||||
|
|
||||||
|
from flask import jsonify
|
||||||
|
from lark_oapi.api.im.v1 import *
|
||||||
|
from lark_oapi.api.verification.v1 import GetVerificationRequest
|
||||||
|
|
||||||
|
from .. import adapter
|
||||||
|
from ...pipeline.longtext.strategies import forward
|
||||||
|
from ...core import app
|
||||||
|
from ..types import message as platform_message
|
||||||
|
from ..types import events as platform_events
|
||||||
|
from ..types import entities as platform_entities
|
||||||
|
from ...utils import image
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramMessageConverter(adapter.MessageConverter):
|
||||||
|
@staticmethod
|
||||||
|
async def yiri2target(message_chain: platform_message.MessageChain, bot: telegram.Bot) -> list[dict]:
|
||||||
|
components = []
|
||||||
|
|
||||||
|
for component in message_chain:
|
||||||
|
if isinstance(component, platform_message.Plain):
|
||||||
|
components.append({
|
||||||
|
"type": "text",
|
||||||
|
"text": component.text
|
||||||
|
})
|
||||||
|
elif isinstance(component, platform_message.Image):
|
||||||
|
|
||||||
|
photo_bytes = None
|
||||||
|
|
||||||
|
if component.base64:
|
||||||
|
photo_bytes = base64.b64decode(component.base64)
|
||||||
|
elif component.url:
|
||||||
|
async with aiohttp.ClientSession() as session:
|
||||||
|
async with session.get(component.url) as response:
|
||||||
|
photo_bytes = await response.read()
|
||||||
|
elif component.path:
|
||||||
|
with open(component.path, "rb") as f:
|
||||||
|
photo_bytes = f.read()
|
||||||
|
|
||||||
|
components.append({
|
||||||
|
"type": "photo",
|
||||||
|
"photo": photo_bytes
|
||||||
|
})
|
||||||
|
elif isinstance(component, platform_message.Forward):
|
||||||
|
for node in component.node_list:
|
||||||
|
components.extend(await TelegramMessageConverter.yiri2target(node.message_chain, bot))
|
||||||
|
|
||||||
|
return components
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def target2yiri(message: telegram.Message, bot: telegram.Bot, bot_account_id: str):
|
||||||
|
|
||||||
|
message_components = []
|
||||||
|
|
||||||
|
|
||||||
|
def parse_message_text(text: str) -> list[platform_message.MessageComponent]:
|
||||||
|
msg_components = []
|
||||||
|
|
||||||
|
if f'@{bot_account_id}' in text:
|
||||||
|
msg_components.append(platform_message.At(target=bot_account_id))
|
||||||
|
text = text.replace(f'@{bot_account_id}', '')
|
||||||
|
msg_components.append(platform_message.Plain(text=text))
|
||||||
|
|
||||||
|
return msg_components
|
||||||
|
|
||||||
|
if message.text:
|
||||||
|
message_text = message.text
|
||||||
|
message_components.extend(parse_message_text(message_text))
|
||||||
|
|
||||||
|
if message.photo:
|
||||||
|
message_components.extend(parse_message_text(message.caption))
|
||||||
|
|
||||||
|
file = await message.photo[-1].get_file()
|
||||||
|
|
||||||
|
file_bytes = None
|
||||||
|
file_format = ''
|
||||||
|
|
||||||
|
async with aiohttp.ClientSession(trust_env=True) as session:
|
||||||
|
async with session.get(file.file_path) as response:
|
||||||
|
file_bytes = await response.read()
|
||||||
|
file_format = 'image/jpeg'
|
||||||
|
|
||||||
|
message_components.append(platform_message.Image(base64=f"data:{file_format};base64,{base64.b64encode(file_bytes).decode('utf-8')}"))
|
||||||
|
|
||||||
|
return platform_message.MessageChain(message_components)
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramEventConverter(adapter.EventConverter):
|
||||||
|
@staticmethod
|
||||||
|
async def yiri2target(event: platform_events.MessageEvent, bot: telegram.Bot):
|
||||||
|
return event.source_platform_object
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def target2yiri(event: Update, bot: telegram.Bot, bot_account_id: str):
|
||||||
|
|
||||||
|
lb_message = await TelegramMessageConverter.target2yiri(event.message, bot, bot_account_id)
|
||||||
|
|
||||||
|
if event.effective_chat.type == 'private':
|
||||||
|
return platform_events.FriendMessage(
|
||||||
|
sender=platform_entities.Friend(
|
||||||
|
id=event.effective_chat.id,
|
||||||
|
nickname=event.effective_chat.first_name,
|
||||||
|
remark=event.effective_chat.id,
|
||||||
|
),
|
||||||
|
message_chain=lb_message,
|
||||||
|
time=event.message.date.timestamp(),
|
||||||
|
source_platform_object=event
|
||||||
|
)
|
||||||
|
elif event.effective_chat.type == 'group':
|
||||||
|
return platform_events.GroupMessage(
|
||||||
|
sender=platform_entities.GroupMember(
|
||||||
|
id=event.effective_chat.id,
|
||||||
|
member_name=event.effective_chat.title,
|
||||||
|
permission=platform_entities.Permission.Member,
|
||||||
|
group=platform_entities.Group(
|
||||||
|
id=event.effective_chat.id,
|
||||||
|
name=event.effective_chat.title,
|
||||||
|
permission=platform_entities.Permission.Member,
|
||||||
|
),
|
||||||
|
special_title="",
|
||||||
|
join_timestamp=0,
|
||||||
|
last_speak_timestamp=0,
|
||||||
|
mute_time_remaining=0,
|
||||||
|
),
|
||||||
|
message_chain=lb_message,
|
||||||
|
time=event.message.date.timestamp(),
|
||||||
|
source_platform_object=event
|
||||||
|
)
|
||||||
|
|
||||||
|
@adapter.adapter_class("telegram")
|
||||||
|
class TelegramMessageSourceAdapter(adapter.MessageSourceAdapter):
|
||||||
|
|
||||||
|
bot: telegram.Bot
|
||||||
|
application: telegram.ext.Application
|
||||||
|
|
||||||
|
bot_account_id: str
|
||||||
|
|
||||||
|
message_converter: TelegramMessageConverter = TelegramMessageConverter()
|
||||||
|
event_converter: TelegramEventConverter = TelegramEventConverter()
|
||||||
|
|
||||||
|
config: dict
|
||||||
|
ap: app.Application
|
||||||
|
|
||||||
|
listeners: typing.Dict[
|
||||||
|
typing.Type[platform_events.Event],
|
||||||
|
typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None],
|
||||||
|
] = {}
|
||||||
|
|
||||||
|
def __init__(self, config: dict, ap: app.Application):
|
||||||
|
self.config = config
|
||||||
|
self.ap = ap
|
||||||
|
|
||||||
|
async def telegram_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
|
||||||
|
if update.message.from_user.is_bot:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
lb_event = await self.event_converter.target2yiri(update, self.bot, self.bot_account_id)
|
||||||
|
await self.listeners[type(lb_event)](lb_event, self)
|
||||||
|
except Exception as e:
|
||||||
|
print(traceback.format_exc())
|
||||||
|
|
||||||
|
self.application = ApplicationBuilder().token(self.config['token']).build()
|
||||||
|
self.bot = self.application.bot
|
||||||
|
self.application.add_handler(MessageHandler(filters.TEXT | (filters.COMMAND) | filters.PHOTO , telegram_callback))
|
||||||
|
|
||||||
|
async def send_message(
|
||||||
|
self, target_type: str, target_id: str, message: platform_message.MessageChain
|
||||||
|
):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def reply_message(
|
||||||
|
self,
|
||||||
|
message_source: platform_events.MessageEvent,
|
||||||
|
message: platform_message.MessageChain,
|
||||||
|
quote_origin: bool = False,
|
||||||
|
):
|
||||||
|
assert isinstance(message_source.source_platform_object, Update)
|
||||||
|
components = await TelegramMessageConverter.yiri2target(message, self.bot)
|
||||||
|
|
||||||
|
for component in components:
|
||||||
|
if component['type'] == 'text':
|
||||||
|
|
||||||
|
args = {
|
||||||
|
"chat_id": message_source.source_platform_object.effective_chat.id,
|
||||||
|
"text": component['text'],
|
||||||
|
}
|
||||||
|
|
||||||
|
if quote_origin:
|
||||||
|
args['reply_to_message_id'] = message_source.source_platform_object.message.id
|
||||||
|
|
||||||
|
await self.bot.send_message(**args)
|
||||||
|
|
||||||
|
async def is_muted(self, group_id: int) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def register_listener(
|
||||||
|
self,
|
||||||
|
event_type: typing.Type[platform_events.Event],
|
||||||
|
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None],
|
||||||
|
):
|
||||||
|
self.listeners[event_type] = callback
|
||||||
|
|
||||||
|
def unregister_listener(
|
||||||
|
self,
|
||||||
|
event_type: typing.Type[platform_events.Event],
|
||||||
|
callback: typing.Callable[[platform_events.Event, adapter.MessageSourceAdapter], None],
|
||||||
|
):
|
||||||
|
self.listeners.pop(event_type)
|
||||||
|
|
||||||
|
async def run_async(self):
|
||||||
|
await self.application.initialize()
|
||||||
|
self.bot_account_id = (await self.bot.get_me()).username
|
||||||
|
await self.application.updater.start_polling(
|
||||||
|
allowed_updates=Update.ALL_TYPES
|
||||||
|
)
|
||||||
|
await self.application.start()
|
||||||
|
|
||||||
|
async def kill(self) -> bool:
|
||||||
|
await self.application.stop()
|
||||||
|
return True
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
semantic_version = "v3.4.7.2"
|
semantic_version = "v3.4.8"
|
||||||
|
|
||||||
debug_mode = False
|
debug_mode = False
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ lark-oapi
|
|||||||
discord.py
|
discord.py
|
||||||
cryptography
|
cryptography
|
||||||
gewechat-client
|
gewechat-client
|
||||||
|
dingtalk_stream
|
||||||
dashscope
|
dashscope
|
||||||
|
python-telegram-bot
|
||||||
|
|
||||||
# indirect
|
# indirect
|
||||||
taskgroup==0.0.0a4
|
taskgroup==0.0.0a4
|
||||||
@@ -78,6 +78,19 @@
|
|||||||
"AppSecret":"",
|
"AppSecret":"",
|
||||||
"host": "0.0.0.0",
|
"host": "0.0.0.0",
|
||||||
"port": 2287
|
"port": 2287
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"adapter":"dingtalk",
|
||||||
|
"enable": false,
|
||||||
|
"client_id":"",
|
||||||
|
"client_secret":"",
|
||||||
|
"robot_code":"",
|
||||||
|
"robot_name":""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"adapter":"telegram",
|
||||||
|
"enable": false,
|
||||||
|
"token":""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"track-function-calls": true,
|
"track-function-calls": true,
|
||||||
|
|||||||
@@ -391,6 +391,67 @@
|
|||||||
"description": "监听的端口"
|
"description": "监听的端口"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "钉钉适配器",
|
||||||
|
"description": "用于接入钉钉",
|
||||||
|
"properties": {
|
||||||
|
"adapter": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "dingtalk"
|
||||||
|
},
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "是否启用此适配器",
|
||||||
|
"layout": {
|
||||||
|
"comp": "switch",
|
||||||
|
"props": {
|
||||||
|
"color": "primary"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"client_id": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "钉钉的client_id"
|
||||||
|
},
|
||||||
|
"client_secret": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "钉钉的client_secret"
|
||||||
|
},
|
||||||
|
"robot_code": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "钉钉的robot_code"
|
||||||
|
},
|
||||||
|
"robot_name": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "钉钉的robot_name"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Telegram 适配器",
|
||||||
|
"description": "用于接入 Telegram",
|
||||||
|
"properties": {
|
||||||
|
"adapter": {
|
||||||
|
"type": "string",
|
||||||
|
"const": "telegram"
|
||||||
|
},
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "是否启用此适配器"
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Telegram 的 token"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user