359 lines
11 KiB
Python
359 lines
11 KiB
Python
import requests
|
||
import http
|
||
import json
|
||
from typing import Dict, List, Optional
|
||
|
||
import hashlib
|
||
import base64
|
||
import hmac
|
||
import time
|
||
|
||
|
||
import lark_oapi as lark
|
||
from lark_oapi.api.bitable.v1 import *
|
||
|
||
|
||
# 配置参数
|
||
CONFIG = {
|
||
"BASE_URL": "https://boss-api.shadow-rpa.net/boss/api/v1",
|
||
"TOKEN": "234e69f9-939d-44e0-8b8f-1560ba77e375",
|
||
"newDutyUuid": "a16edf00-c99f-44ae-bbb9-56d3e3629386",
|
||
"BITTABLE_APP_ID": "cli_a5110cd1da70900b",
|
||
"BITTABLE_APP_SECRET": "XwTUlUq7dGK1XexUS7ljShJJ7t1MIwXF",
|
||
"BITTABLE_APP_TOKEN": "KppebaHOsaHGMxslbLRc82amn5f",
|
||
"BITTABLE_APP_TABLE_ID": "tblH8KwM2grwj9DI",
|
||
}
|
||
|
||
|
||
def gen_sign(timestamp, secret):
|
||
# 拼接timestamp和secret
|
||
string_to_sign = "{}\n{}".format(timestamp, secret)
|
||
hmac_code = hmac.new(
|
||
string_to_sign.encode("utf-8"), digestmod=hashlib.sha256
|
||
).digest()
|
||
|
||
# 对结果进行base64处理
|
||
sign = base64.b64encode(hmac_code).decode("utf-8")
|
||
|
||
return sign
|
||
|
||
|
||
def get_headers(token: Optional[str] = None) -> Dict:
|
||
"""
|
||
生成请求头
|
||
Args:
|
||
token: 认证token,如果为None则使用默认配置
|
||
Returns:
|
||
包含认证信息的请求头字典
|
||
"""
|
||
return {
|
||
"accept": "application/json",
|
||
"authorization": f"Bearer {token or CONFIG['TOKEN']}",
|
||
"content-type": "application/json;charset=UTF-8",
|
||
"origin": "https://boss.shadow-rpa.net",
|
||
"referer": "https://boss.shadow-rpa.net/",
|
||
}
|
||
|
||
|
||
def api_request(method: str, endpoint: str, **kwargs) -> Dict:
|
||
"""
|
||
统一的API请求处理
|
||
Args:
|
||
method: 请求方法(GET/POST)
|
||
endpoint: API端点
|
||
**kwargs: 请求参数
|
||
Returns:
|
||
API响应数据
|
||
"""
|
||
url = f"{CONFIG['BASE_URL']}/{endpoint}"
|
||
response = requests.request(method, url, **kwargs)
|
||
response.raise_for_status()
|
||
return response.json()
|
||
|
||
|
||
def get_all_work_orders(page_size: int = 20, status: str = "new") -> List:
|
||
"""
|
||
获取所有工单列表
|
||
Args:
|
||
page_size: 每页数量
|
||
status: 工单状态
|
||
Returns:
|
||
工单列表
|
||
"""
|
||
all_orders = []
|
||
page = 1
|
||
|
||
while True:
|
||
params = {"page": page, "size": page_size, "status": status}
|
||
res = api_request(
|
||
"GET", "wo/order/woList", headers=get_headers(), params=params
|
||
)
|
||
|
||
data = res.get("data", {})
|
||
print(data)
|
||
pages = res.get("page", {}).get("pages", 0)
|
||
|
||
if not data:
|
||
break
|
||
|
||
all_orders.extend(data)
|
||
if page >= pages:
|
||
break
|
||
page += 1
|
||
# print(all_orders)
|
||
|
||
return all_orders
|
||
|
||
|
||
def process_work_order(order: Dict) -> Dict:
|
||
"""
|
||
处理单个工单信息,获取提交者信息
|
||
Args:
|
||
order: 工单数据
|
||
Returns:
|
||
处理后的工单信息
|
||
"""
|
||
result = {
|
||
"uuid": order["uuid"],
|
||
"title": order.get("title", "").strip(),
|
||
"content": order.get("content", "").strip(),
|
||
"submitter": order.get("submitterId"),
|
||
"enterprise": order.get("enterpriseName"),
|
||
"phone": None,
|
||
}
|
||
|
||
# 获取提交者信息
|
||
if result["submitter"]:
|
||
user_info = query_user_info(result["submitter"])
|
||
user_data = user_info.get("data", {}).get("dataSource", {}).get("data", [])
|
||
|
||
# 查找匹配的用户数据
|
||
matched_user = next(
|
||
(x for x in user_data if x.get("userName") == result["submitter"]), None
|
||
)
|
||
|
||
# 解密电话号码
|
||
if matched_user and matched_user.get("phone__l"):
|
||
encrypt_phone = matched_user["phone__l"].get("encrypte")
|
||
if encrypt_phone:
|
||
decrypted = decrypt_sensitive_data(encrypt_phone)
|
||
result["phone"] = decrypted.get("data", {}).get("decryptValue")
|
||
|
||
return result
|
||
|
||
|
||
def claim_work_order(work_order_uuid):
|
||
url = "https://boss-api.shadow-rpa.net/boss/api/v1/wo/order/claim"
|
||
headers = get_headers()
|
||
data = {"workOrderUuid": work_order_uuid}
|
||
response = requests.post(url, headers=headers, json=data)
|
||
return response.json()
|
||
|
||
|
||
def transfer_work_order(work_order_uuid, new_duty_uuid, remark=""):
|
||
url = "https://boss-api.shadow-rpa.net/boss/api/v1/wo/order/transfer"
|
||
headers = get_headers()
|
||
data = {
|
||
"workOrderUuid": work_order_uuid,
|
||
"remark": remark,
|
||
"newDutyUuid": new_duty_uuid,
|
||
}
|
||
response = requests.post(url, headers=headers, json=data)
|
||
return response.json()
|
||
|
||
|
||
def query_user_info(
|
||
fieldValues, table_code="userInfoList", page=1, size=20, filters=None
|
||
):
|
||
if filters is None:
|
||
filters = [
|
||
{"fieldName": "userName", "fieldValues": fieldValues, "filterType": "WD"}
|
||
]
|
||
|
||
url = "https://boss-api.shadow-rpa.net/boss/api/v1/sys/universal/component/table"
|
||
headers = get_headers()
|
||
data = {
|
||
"source": "",
|
||
"tableCode": table_code,
|
||
"pageQuery": {"page": page, "size": size, "filters": filters, "orders": []},
|
||
}
|
||
response = requests.post(url, headers=headers, json=data)
|
||
return response.json()
|
||
|
||
|
||
def decrypt_sensitive_data(
|
||
encrypt_value, code="userInfoList", code_type="table", field_name="phone"
|
||
):
|
||
url = (
|
||
f"https://boss-api.shadow-rpa.net/boss/api/v1/sys/operateLog/sensitiveDataQuery"
|
||
)
|
||
headers = get_headers()
|
||
params = {
|
||
"encryptValue": encrypt_value,
|
||
"code": code,
|
||
"codeType": code_type,
|
||
"fieldName": field_name,
|
||
}
|
||
response = requests.get(url, headers=headers, params=params)
|
||
return response.json()
|
||
|
||
|
||
def feishu_notify(text):
|
||
|
||
webhook_url = "/open-apis/bot/v2/hook/d6551d8c-d792-46e4-929d-ad0d1b031027"
|
||
keys = "lPQwjE2TqMD0Q4q0zDlUY"
|
||
|
||
conn = http.client.HTTPSConnection("open.feishu.cn")
|
||
payload = json.dumps({"msg_type": "text", "content": {"text": text}})
|
||
headers = {"Content-Type": "application/json"}
|
||
conn.request("POST", webhook_url, payload, headers)
|
||
res = conn.getresponse()
|
||
data = res.read()
|
||
|
||
|
||
def add_bittable_record(data):
|
||
|
||
# 创建client
|
||
client = (
|
||
lark.Client.builder()
|
||
.app_id(CONFIG["BITTABLE_APP_ID"])
|
||
.app_secret(CONFIG["BITTABLE_APP_SECRET"])
|
||
.log_level(lark.LogLevel.DEBUG)
|
||
.build()
|
||
)
|
||
|
||
# 构造请求对象
|
||
request: CreateAppTableRecordRequest = (
|
||
CreateAppTableRecordRequest.builder()
|
||
.app_token(CONFIG["BITTABLE_APP_TOKEN"])
|
||
.table_id(CONFIG["BITTABLE_APP_TABLE_ID"])
|
||
.request_body(AppTableRecord.builder().fields(data).build())
|
||
.build()
|
||
)
|
||
# 发起请求
|
||
response: CreateAppTableRecordResponse = client.bitable.v1.app_table_record.create(
|
||
request
|
||
)
|
||
|
||
# 处理失败返回
|
||
if not response.success():
|
||
lark.logger.error(
|
||
f"client.bitable.v1.app_table_record.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 feishu_notify_to_xbot_content_group(text):
|
||
|
||
webhook_url = "/open-apis/bot/v2/hook/b2db78ac-799a-4251-8341-18d49091fa62"
|
||
Keys = "T8phm8fcfZ04bvOHaI0kzd"
|
||
|
||
timestamp = str(int(time.time()))[:10]
|
||
# print(timestamp)
|
||
sign = gen_sign(timestamp, Keys)
|
||
|
||
conn = http.client.HTTPSConnection("open.feishu.cn")
|
||
payload = json.dumps(
|
||
{
|
||
"msg_type": "text",
|
||
"content": {"text": text},
|
||
"sign": sign,
|
||
"timestamp": timestamp,
|
||
}
|
||
)
|
||
headers = {"Content-Type": "application/json"}
|
||
conn.request("POST", webhook_url, payload, headers)
|
||
res = conn.getresponse()
|
||
data = res.read()
|
||
print(data)
|
||
|
||
|
||
def main():
|
||
"""
|
||
主程序入口
|
||
"""
|
||
messages = []
|
||
message_without_phone = []
|
||
try:
|
||
# 获取所有工单
|
||
work_orders = get_all_work_orders()
|
||
if not work_orders:
|
||
print("未找到工单")
|
||
|
||
# 处理每个工单
|
||
print(f"共找到 {len(work_orders)} 个工单")
|
||
|
||
for order in work_orders:
|
||
processed_order = process_work_order(order)
|
||
|
||
# 打印工单信息
|
||
print(f"\n{'='*40}")
|
||
print(f"工单ID: {processed_order['uuid']}")
|
||
print(f"标题: {processed_order['title']}")
|
||
print(f"内容: {processed_order['content']}")
|
||
print(f"提交者: {processed_order['submitter']}")
|
||
print(f"电话: {processed_order['phone'] or '未找到'}")
|
||
print(f"企业: {processed_order['enterprise'] or '未找到'}")
|
||
print(
|
||
f"邀约: 我是影刀的小伙伴, 想跟你沟通反馈的需求: {processed_order['content']}"
|
||
)
|
||
print(f"{'='*40}\n")
|
||
|
||
# 剑心通知
|
||
messages.append(
|
||
# f"工单ID: {processed_order['uuid']}\n"
|
||
f"提交者: {processed_order['submitter']}\n"
|
||
f"标题: {processed_order['title']}\n"
|
||
f"电话: {processed_order['phone']}\n"
|
||
f"企业: {processed_order['enterprise'] or '未找到'}\n"
|
||
f"邀约: 我是影刀的小伙伴, 想跟你沟通反馈的需求: {processed_order['content']}\n\n"
|
||
)
|
||
|
||
# 群组通知
|
||
message_without_phone.append(
|
||
f"工单ID: {processed_order['uuid']}\n"
|
||
f"提交者: {processed_order['submitter']}\n"
|
||
f"标题: {processed_order['title']}\n"
|
||
f"企业: {processed_order['enterprise'] or '未找到'}\n"
|
||
)
|
||
|
||
# 表格同步
|
||
data = {
|
||
"企业": processed_order["enterprise"] or "社区版",
|
||
"手机号": processed_order["phone"],
|
||
"提交人": processed_order["submitter"],
|
||
"需求ID": processed_order["uuid"],
|
||
"需求内容": processed_order["content"],
|
||
"需求标题": processed_order["title"],
|
||
}
|
||
add_bittable_record(data)
|
||
|
||
transfer_work_order(
|
||
processed_order["uuid"],
|
||
CONFIG.get("newDutyUuid"),
|
||
)
|
||
# break
|
||
# messages = "\n".join(messages)
|
||
|
||
# if messages.strip():
|
||
# print("发送飞书通知")
|
||
# feishu_notify(text=messages)
|
||
|
||
# message_without_phone = "\n".join(message_without_phone)
|
||
|
||
# if message_without_phone.strip():
|
||
# print("发送飞书通知到内容组")
|
||
# feishu_notify_to_xbot_content_group(text=message_without_phone)
|
||
|
||
except requests.RequestException as e:
|
||
print(f"请求发生错误: {e}")
|
||
except Exception as e:
|
||
print(f"程序发生错误: {e}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|