| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341 |
- import json
- from app.infra.shared import AsyncHttpClient
- class Feishu:
- # 服务号分组群发监测机器人
- server_account_publish_monitor_bot = "https://open.feishu.cn/open-apis/bot/v2/hook/380fdecf-402e-4426-85b6-7d9dbd2a9f59"
- # 外部服务号投流监测机器人
- outside_gzh_monitor_bot = "https://open.feishu.cn/open-apis/bot/v2/hook/0899d43d-9f65-48ce-a419-f83ac935bf59"
- # 长文 daily 报警机器人
- long_articles_bot = "https://open.feishu.cn/open-apis/bot/v2/hook/b44333f2-16c0-4cb1-af01-d135f8704410"
- # 测试环境报警机器人
- long_articles_bot_dev = "https://open.feishu.cn/open-apis/bot/v2/hook/f32c0456-847f-41f3-97db-33fcc1616bcd"
- # 长文任务报警群
- long_articles_task_bot = "https://open.feishu.cn/open-apis/bot/v2/hook/223b3d72-f2e8-40e0-9b53-6956e0ae7158"
- # cookie 监测机器人
- cookie_monitor_bot = "https://open.feishu.cn/open-apis/bot/v2/hook/51b9c83a-f50d-44dd-939f-bcd10ac6c55a"
- # rank_bot
- rank_monitor_bot = "https://open.feishu.cn/open-apis/bot/v2/hook/f9dae7ba-decf-436b-b438-41994c35af1e"
- # 用户名与手机号映射
- name_phone_dict = {
- "卓异": "18624010360",
- "俊辉": "18801281360",
- "范军": "15200827642",
- "林强": "15810236123",
- }
- def __init__(self):
- self.token = None
- self.headers = {"Content-Type": "application/json"}
- self.mention_all = {
- "content": "<at id=all></at>\n",
- "tag": "lark_md",
- }
- self.not_mention = {}
- async def fetch_token(self):
- url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/"
- post_data = {
- "app_id": "cli_a51114cf8bf8d00c",
- "app_secret": "cNoTAqMpsAm7mPBcpCAXFfvOzCNL27fe",
- }
- async with AsyncHttpClient(default_headers=self.headers) as client:
- response = await client.post(url=url, json=post_data)
- tenant_access_token = response["tenant_access_token"]
- self.token = tenant_access_token
- async def get_user_open_id(self, username):
- """
- 根据用户名获取飞书 open_id
- :param username: 用户名
- :return: open_id
- """
- if not self.token:
- await self.fetch_token()
- mobile = self.name_phone_dict.get(username)
- if not mobile:
- return None
- url = "https://open.feishu.cn/open-apis/user/v1/batch_get_id"
- headers = {
- "Authorization": f"Bearer {self.token}",
- "Content-Type": "application/json; charset=utf-8",
- }
- params = {"mobiles": [mobile]}
- async with AsyncHttpClient() as client:
- response = await client.get(url=url, params=params, headers=headers)
- try:
- open_id = response["data"]["mobile_users"][mobile][0]["open_id"]
- return open_id
- except (KeyError, IndexError):
- return None
- class FeishuSheetApi(Feishu):
- async def prepend_value(self, sheet_token, sheet_id, ranges, values):
- insert_value_url = "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{}/values_prepend".format(
- sheet_token
- )
- headers = {
- "Authorization": "Bearer " + self.token,
- "contentType": "application/json; charset=utf-8",
- }
- body = {
- "valueRange": {"range": "{}!{}".format(sheet_id, ranges), "values": values}
- }
- async with AsyncHttpClient() as client:
- response = await client.post(
- url=insert_value_url, json=body, headers=headers
- )
- print(response)
- async def insert_value(self, sheet_token, sheet_id, ranges, values):
- insert_value_url = (
- "https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{}/values".format(
- sheet_token
- )
- )
- # self.token = 't-g104bpfHNZN45BVJWFSQEM6WD45AAI4FNRWXCZVK'
- headers = {
- "Authorization": "Bearer " + self.token,
- "contentType": "application/json; charset=utf-8",
- }
- body = {
- "valueRange": {"range": "{}!{}".format(sheet_id, ranges), "values": values}
- }
- async with AsyncHttpClient() as client:
- response = await client.put(
- url=insert_value_url, json=body, headers=headers
- )
- class FeishuBotApi(Feishu):
- async def create_mention_text(self, usernames):
- """
- 创建 @ 用户的文本
- :param usernames: 用户名列表
- :return: @ 用户的 markdown 文本
- """
- if not usernames:
- return ""
- mention_parts = []
- for username in usernames:
- open_id = await self.get_user_open_id(username)
- if open_id:
- mention_parts.append(f"<at id={open_id}></at>")
- return " ".join(mention_parts) + "\n" if mention_parts else ""
- @classmethod
- def create_feishu_columns_sheet(
- cls,
- sheet_type,
- sheet_name,
- display_name,
- width="auto",
- vertical_align="top",
- horizontal_align="left",
- number_format=None,
- ):
- match sheet_type:
- case "plain_text":
- return {
- "name": sheet_name,
- "display_name": display_name,
- "width": width,
- "data_type": "text",
- "vertical_align": vertical_align,
- "horizontal_align": horizontal_align,
- }
- case "lark_md":
- return {
- "name": sheet_name,
- "display_name": display_name,
- "data_type": "lark_md",
- }
- case "number":
- return {
- "name": sheet_name,
- "display_name": display_name,
- "data_type": "number",
- "format": number_format,
- "width": width,
- }
- case "date":
- return {
- "name": sheet_name,
- "display_name": display_name,
- "data_type": "date",
- "date_format": "YYYY/MM/DD",
- }
- case "options":
- return {
- "name": sheet_name,
- "display_name": display_name,
- "data_type": "options",
- }
- case _:
- return {
- "name": sheet_name,
- "display_name": display_name,
- "width": width,
- "data_type": "text",
- "vertical_align": vertical_align,
- "horizontal_align": horizontal_align,
- }
- # 表格形式
- async def create_feishu_table(self, title, columns, rows, mention, mention_users=None):
- """
- 创建飞书表格消息
- :param title: 标题
- :param columns: 列定义
- :param rows: 行数据
- :param mention: 是否 @ 所有人
- :param mention_users: 要 @ 的具体用户列表,例如 ["mark"]
- """
- mention_element = self.not_mention
- if mention:
- mention_element = self.mention_all
- elif mention_users:
- mention_text = await self.create_mention_text(mention_users)
- mention_element = {"content": mention_text, "tag": "lark_md"}
- table_base = {
- "header": {
- "template": "blue",
- "title": {"content": title, "tag": "plain_text"},
- },
- "elements": [
- mention_element,
- {
- "tag": "table",
- "page_size": len(rows) + 1,
- "row_height": "low",
- "header_style": {
- "text_align": "left",
- "text_size": "normal",
- "background_style": "grey",
- "text_color": "default",
- "bold": True,
- "lines": 1,
- },
- "columns": columns,
- "rows": rows,
- },
- ],
- }
- return table_base
- async def create_feishu_bot_obj(self, title, mention, detail, mention_users=None):
- """
- create feishu bot object
- :param title: 标题
- :param mention: 是否 @ 所有人
- :param detail: 详细内容
- :param mention_users: 要 @ 的具体用户列表,例如 ["luojunhui"]
- """
- mention_element = self.not_mention
- if mention:
- mention_element = self.mention_all
- elif mention_users:
- mention_text = await self.create_mention_text(mention_users)
- mention_element = {"content": mention_text, "tag": "lark_md"}
- return {
- "elements": [
- {
- "tag": "div",
- "text": mention_element,
- },
- {
- "tag": "div",
- "text": {
- "content": json.dumps(detail, ensure_ascii=False, indent=4),
- "tag": "lark_md",
- },
- },
- ],
- "header": {"title": {"content": title, "tag": "plain_text"}},
- }
- # bot
- async def bot(
- self,
- title,
- detail,
- mention=True,
- table=False,
- env="long_articles_task",
- mention_users=None,
- ):
- """
- 发送飞书机器人消息
- :param title: 标题
- :param detail: 详细内容
- :param mention: 是否 @ 所有人
- :param table: 是否为表格形式
- :param env: 环境,决定发送到哪个机器人
- :param mention_users: 要 @ 的具体用户列表,例如 ["luojunhui"]
- """
- match env:
- case "dev":
- url = self.long_articles_bot_dev
- case "prod":
- url = self.long_articles_bot
- case "outside_gzh_monitor":
- url = self.outside_gzh_monitor_bot
- case "server_account_publish_monitor":
- url = self.server_account_publish_monitor_bot
- case "long_articles_task":
- url = self.long_articles_task_bot
- case "cookie_monitor":
- url = self.cookie_monitor_bot
- case "rank_bot":
- url = self.rank_monitor_bot
- case _:
- url = self.long_articles_bot_dev
- headers = {"Content-Type": "application/json"}
- if table:
- card = await self.create_feishu_table(
- title=title,
- columns=detail["columns"],
- rows=detail["rows"],
- mention=mention,
- mention_users=mention_users,
- )
- else:
- card = await self.create_feishu_bot_obj(
- title=title, mention=mention, detail=detail, mention_users=mention_users
- )
- data = {"msg_type": "interactive", "card": card}
- async with AsyncHttpClient() as client:
- res = await client.post(url=url, headers=headers, json=data)
- return res
|