1 parent
0e315a6cae
commit
28740517cf
3 files changed
+374
-3
No files matched your search
+17
-2
@@ -131,8 +131,9 @@ def do_bitable_record_add(data) -> None:
|
||||
if field.get("field_id") == "fldaxMbywh":
|
||||
# 有可能值为空
|
||||
if field.get("field_value"):
|
||||
action_data["deal_person"] = json.loads(field.get("field_value")).get("users")[0].get("name")
|
||||
|
||||
action_data["deal_person"] = (
|
||||
json.loads(field.get("field_value")).get("users")[0].get("name")
|
||||
)
|
||||
|
||||
print(f"table_id: {table_id}")
|
||||
print(f"record_id: {record_id}")
|
||||
@@ -196,6 +197,20 @@ def create_app() -> Flask:
|
||||
def index():
|
||||
return {"test": "ok"}
|
||||
|
||||
@app.route("/webhook", methods=["GET"])
|
||||
def push_xbot_activity_bot():
|
||||
# 解析请求体中的json数据
|
||||
data = json.loads(request.data)
|
||||
print(f"webhook receive data: {data}")
|
||||
open_id = data.get("open_id")
|
||||
activity_code = data.get("activity_code")
|
||||
if not open_id or not activity_code:
|
||||
return {"status": "error", "msg": "open_id or activity_code is not exist"}
|
||||
|
||||
process_push_xbot_activity_bot.delay(activity_code, open_id)
|
||||
|
||||
return {"status": "ok"}
|
||||
|
||||
return app
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -17,6 +17,7 @@ from app.utils.feishu import (
|
||||
)
|
||||
from app.utils.utils import update_record, add_record
|
||||
from app.utils.yd_utils import new_blank_robot, new_blank_activity
|
||||
from app.utils.xbot_utils import push_xbot_activity_bot
|
||||
|
||||
|
||||
def parse_history_message(data):
|
||||
@@ -33,7 +34,7 @@ def parse_history_message(data):
|
||||
sender_id_item = item.get("sender").get("id")
|
||||
if index == 0:
|
||||
sender_id = sender_id_item
|
||||
|
||||
|
||||
print(f"raw msg: {item}")
|
||||
|
||||
print("***************")
|
||||
@@ -397,3 +398,8 @@ def process_new_robot(app_id, app_secret, action_data):
|
||||
table_id = "tbl2GcBvkvMtOBnl"
|
||||
|
||||
add_record(client, bitable_token, table_id, catagory_table_record)
|
||||
|
||||
|
||||
@shared_task(include_result=False)
|
||||
def process_push_xbot_activity_bot(activity_code, open_id):
|
||||
push_xbot_activity_bot(activity_code, open_id)
|
||||
@@ -0,0 +1,350 @@
|
||||
# import requests
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import zipfile
|
||||
import shutil
|
||||
import uuid
|
||||
|
||||
import os
|
||||
import json
|
||||
|
||||
from urllib import parse
|
||||
|
||||
APPS_COUNT = 0
|
||||
SDKS_COUNT = 0
|
||||
|
||||
import json
|
||||
import os
|
||||
import lark_oapi as lark
|
||||
from lark_oapi.api.im.v1 import *
|
||||
|
||||
sdk_external_dependencies = []
|
||||
|
||||
|
||||
class CustomFormatter(logging.Formatter):
|
||||
grey = "\x1b[38;20m"
|
||||
yellow = "\x1b[33;20m"
|
||||
red = "\x1b[31;20m"
|
||||
bold_red = "\x1b[31;1m"
|
||||
reset = "\x1b[0m"
|
||||
green = "\x1b[32m"
|
||||
|
||||
format = "%(asctime)s: %(message)s"
|
||||
|
||||
FORMATS = {
|
||||
logging.DEBUG: grey + format + reset,
|
||||
logging.INFO: green + format + reset,
|
||||
logging.WARNING: yellow + format + reset,
|
||||
logging.ERROR: red + format + reset,
|
||||
logging.CRITICAL: bold_red + format + reset,
|
||||
}
|
||||
date_format = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
def format(self, record):
|
||||
log_fmt = self.FORMATS.get(record.levelno)
|
||||
formatter = logging.Formatter(log_fmt, self.date_format)
|
||||
return formatter.format(record)
|
||||
|
||||
|
||||
# 初始化日志
|
||||
logger = logging.getLogger("App")
|
||||
logger.setLevel(logging.DEBUG)
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(CustomFormatter())
|
||||
logger.addHandler(ch)
|
||||
|
||||
|
||||
def sanitize_filename(filename, replace_char="_"):
|
||||
"""
|
||||
去除 Windows 文件名中的非法字符
|
||||
|
||||
参数:
|
||||
filename (str): 要处理的原始文件名
|
||||
replace_char (str): 替换非法字符的字符,默认为下划线
|
||||
|
||||
返回:
|
||||
str: 处理后的合法文件名
|
||||
"""
|
||||
# Windows 文件系统不允许的字符
|
||||
illegal_chars = r'[<>:"/\\|?*\x00-\x1f]'
|
||||
|
||||
# 替换非法字符
|
||||
sanitized = re.sub(illegal_chars, replace_char, filename)
|
||||
|
||||
# 同时去除文件名开头和结尾的空格和点
|
||||
sanitized = sanitized.strip(" .")
|
||||
|
||||
# 保留文件扩展名中的点
|
||||
return sanitized
|
||||
|
||||
|
||||
def get_token(username, password):
|
||||
"""
|
||||
利用网页端获取 token
|
||||
:param username: 用户名
|
||||
:param password: 密码
|
||||
:return: token
|
||||
"""
|
||||
|
||||
url = "https://api.winrobot360.com/oauth/token"
|
||||
data = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
"grant_type": "password",
|
||||
"scope": "all",
|
||||
}
|
||||
|
||||
payload = parse.urlencode(data)
|
||||
headers = {
|
||||
"accept": "application/json",
|
||||
"authorization": "Basic Y29uc29sZTpzVVdpeTZzTmlYZDBlNUxo",
|
||||
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
|
||||
}
|
||||
response = requests.request("POST", url, headers=headers, data=payload).json()
|
||||
|
||||
if response.get("success") == "false":
|
||||
raise ValueError(response.get("msg"))
|
||||
return response.get("access_token")
|
||||
|
||||
|
||||
def get_client_version(token):
|
||||
"""获取当期登录账号客户端的版本"""
|
||||
url = "https://api.winrobot360.com/api/v1/user/info/rich"
|
||||
|
||||
payload = {}
|
||||
headers = {
|
||||
"Authorization": f"bearer {token}",
|
||||
"Accept": "application/json, text/json, text/x-json, text/javascript, application/xml, text/xml",
|
||||
}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
return response.json().get("data").get("clientVersion")
|
||||
|
||||
|
||||
def get_activity_detail(token, sdk_uuid):
|
||||
"""
|
||||
获取自定义指令的相关信息
|
||||
:param token: token
|
||||
:param sdk: 自定义指令
|
||||
:return: package_name, package_url
|
||||
"""
|
||||
name, version = sdk_uuid.split("==")
|
||||
url = f"https://api.winrobot360.com/api/v1/robot/use/activity/package/detail?packageName={name}&version={version}"
|
||||
payload = {}
|
||||
headers = {
|
||||
"Host": "api.winrobot360.com",
|
||||
"Authorization": f"bearer {token}",
|
||||
"Accept": "application/json, text/json, text/x-json, text/javascript, application/xml, text/xml",
|
||||
"User-Agent": "RestSharp/107.3.0.0",
|
||||
"Accept-Encoding": "json",
|
||||
}
|
||||
|
||||
response = requests.request("GET", url, headers=headers, data=payload)
|
||||
return response.json().get("data")
|
||||
|
||||
|
||||
def download_file(url, save_path, filename):
|
||||
"""
|
||||
根据链接下载文件
|
||||
:param url: 链接
|
||||
:param save_path: 保存路径
|
||||
:param filename: 保存文件名
|
||||
:return: 保存文件文件名
|
||||
"""
|
||||
res = requests.get(url)
|
||||
with open(os.path.join(save_path, filename), "wb") as f:
|
||||
f.write(res.content)
|
||||
|
||||
return os.path.join(save_path, filename)
|
||||
|
||||
|
||||
def zip_file(startdir, file_news=None):
|
||||
"""
|
||||
压缩文件
|
||||
:param startdir: 要压缩的文件夹路径
|
||||
:param file_news: 压缩后文件名
|
||||
"""
|
||||
file_news = file_news if file_news else startdir + ".bot"
|
||||
z = zipfile.ZipFile(file_news, "w", zipfile.ZIP_DEFLATED)
|
||||
for dirpath, dirnames, filenames in os.walk(startdir):
|
||||
fpath = dirpath.replace(startdir, "")
|
||||
fpath = fpath and fpath + os.sep or ""
|
||||
for filename in filenames:
|
||||
z.write(os.path.join(dirpath, filename), fpath + filename)
|
||||
z.close()
|
||||
return file_news
|
||||
|
||||
|
||||
def unzip_file(zip_file_path, unzip_path):
|
||||
"""
|
||||
解压文件
|
||||
:param zip_file_path: 解压的文件路径
|
||||
:param unzip_path: 解压(输出)的路径
|
||||
:return:
|
||||
"""
|
||||
zip_file = zipfile.ZipFile(zip_file_path)
|
||||
for file in zip_file.namelist():
|
||||
zip_file.extract(file, unzip_path)
|
||||
zip_file.close()
|
||||
|
||||
|
||||
def modify_package_version(package_dirctory: str, version: str):
|
||||
"""
|
||||
修改 package.json 文件中的版本号
|
||||
"""
|
||||
package_json_path = os.path.join(package_dirctory, "package.json")
|
||||
with open(package_json_path, encoding="utf8") as f:
|
||||
package_json = json.load(f)
|
||||
package_json["version"] = version
|
||||
with open(package_json_path, "w") as f:
|
||||
json.dump(package_json, f, indent=4, ensure_ascii=False)
|
||||
|
||||
|
||||
def package_activity(token, robot_uuid, basedir):
|
||||
"""
|
||||
根据 uuid 将应用打包成 bot 文件, 并存到指定的文件中
|
||||
:param token:
|
||||
:param roobt_uuid:
|
||||
:param basedir: 文件存放路径
|
||||
"""
|
||||
# 1. 根据机器人 uuid 获取机器人详细信息
|
||||
activity_detail = get_activity_detail(token=token, sdk_uuid=robot_uuid)
|
||||
activity_name = activity_detail.get("name")
|
||||
version = activity_detail.get("releaseVersion")
|
||||
packageUrl = activity_detail.get("packageUrl")
|
||||
internal_dependencies = activity_detail.get("flatInternalDependencies")
|
||||
|
||||
logger.info(f"打包指令: {activity_name}")
|
||||
|
||||
# 去除不能作为windows文件名的字符
|
||||
activity_name = sanitize_filename(activity_name, "")
|
||||
|
||||
# 2. 下载 activity 文件
|
||||
activity_path = os.path.join(basedir, activity_name)
|
||||
os.makedirs(activity_path)
|
||||
download_name = download_file(packageUrl, activity_path, "xbot_robot.zip")
|
||||
xbot_robot_path = os.path.join(activity_path, "xbot_robot")
|
||||
os.makedirs(xbot_robot_path)
|
||||
unzip_file(download_name, xbot_robot_path)
|
||||
os.remove(download_name)
|
||||
modify_package_version(xbot_robot_path, version)
|
||||
|
||||
# 3. 下载 activity 的 internal dependence
|
||||
xbot_extensions_path = os.path.join(activity_path, "xbot_extensions")
|
||||
os.makedirs(xbot_extensions_path)
|
||||
|
||||
with open(os.path.join(xbot_extensions_path, "requirement.txt"), "w") as f:
|
||||
f.write("\n".join(internal_dependencies))
|
||||
|
||||
with open(os.path.join(xbot_extensions_path, "__init__.py"), "w") as f:
|
||||
f.write("\n".join([f"from . import {item}" for item in internal_dependencies]))
|
||||
|
||||
for dependence in internal_dependencies:
|
||||
dependence_detail = get_activity_detail(token, dependence)
|
||||
name = dependence_detail.get("packageName")
|
||||
version = dependence_detail.get("releaseVersion")
|
||||
package_url = dependence_detail.get("packageUrl")
|
||||
dependence_save_path = os.path.join(xbot_extensions_path, name)
|
||||
os.makedirs(dependence_save_path)
|
||||
download_name = download_file(package_url, xbot_extensions_path, "package.zip")
|
||||
unzip_file(download_name, dependence_save_path)
|
||||
modify_package_version(dependence_save_path, version)
|
||||
os.remove(download_name)
|
||||
|
||||
activity_save_path = zip_file(activity_path)
|
||||
|
||||
shutil.rmtree(activity_path)
|
||||
return activity_save_path
|
||||
|
||||
|
||||
def send_message(file_path, open_id):
|
||||
# 创建client
|
||||
|
||||
YOUR_APP_ID = "cli_a5110cd1da70900b"
|
||||
YOUR_APP_SECRET = "XwTUlUq7dGK1XexUS7ljShJJ7t1MIwXF"
|
||||
|
||||
client = (
|
||||
lark.Client.builder()
|
||||
.app_id(YOUR_APP_ID)
|
||||
.app_secret(YOUR_APP_SECRET)
|
||||
.log_level(lark.LogLevel.DEBUG)
|
||||
.build()
|
||||
)
|
||||
|
||||
# 构造请求对象
|
||||
file = open(file_path, "rb")
|
||||
_, filename = os.path.split(file_path)
|
||||
request: CreateFileRequest = (
|
||||
CreateFileRequest.builder()
|
||||
.request_body(
|
||||
CreateFileRequestBody.builder()
|
||||
.file_type("stream")
|
||||
.file_name(filename)
|
||||
.file(file)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
|
||||
# 发起请求
|
||||
response: CreateFileResponse = client.im.v1.file.create(request)
|
||||
|
||||
# 处理失败返回
|
||||
if not response.success():
|
||||
lark.logger.error(
|
||||
f"client.im.v1.file.create failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}"
|
||||
)
|
||||
|
||||
content = {"file_key": response.data.file_key}
|
||||
|
||||
uuid_str = str(uuid.uuid4())
|
||||
|
||||
request: CreateMessageRequest = (
|
||||
CreateMessageRequest.builder()
|
||||
.receive_id_type("open_id")
|
||||
.request_body(
|
||||
CreateMessageRequestBody.builder()
|
||||
.receive_id(open_id)
|
||||
.msg_type("file")
|
||||
.content(json.dumps(content))
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
# 发起请求
|
||||
response: CreateMessageResponse = client.im.v1.message.create(request)
|
||||
|
||||
# 处理失败返回
|
||||
if not response.success():
|
||||
lark.logger.error(
|
||||
f"client.im.v1.message.create failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}"
|
||||
)
|
||||
return
|
||||
|
||||
# 处理业务结果
|
||||
lark.logger.info(lark.JSON.marshal(response.data, indent=4))
|
||||
|
||||
|
||||
def push_xbot_activity_bot(activity_code, open_id):
|
||||
"""自动推送官方指令bot包到指定用户
|
||||
:param activity_code: 指令代码带版本号
|
||||
:param open_id: 接收者open_id
|
||||
"""
|
||||
username = "剑心@fckj"
|
||||
password = "320753691love"
|
||||
# open_id = "ou_d542891996bf1961f9a94563e6bd4d49"
|
||||
token = get_token(username=username, password=password)
|
||||
|
||||
logger.error(f"使用 {username} 账号登陆")
|
||||
version = get_client_version(token=token)
|
||||
|
||||
logger.error(f"当前账号客户端版本号为: {version}")
|
||||
|
||||
base_dir = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
activity_path = package_activity(
|
||||
token=token, robot_uuid=activity_code, basedir=base_dir
|
||||
)
|
||||
send_message(activity_path, open_id)
|
||||
Reference in new issue
Block a user