Skip to content

Commit

Permalink
Merge pull request #231 from ConnectAI-E/freeziyou-022201
Browse files Browse the repository at this point in the history
支持飞书传图GitHub
  • Loading branch information
freeziyou authored Feb 27, 2024
2 parents 7d35b2c + e84ea03 commit b97aa51
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 47 deletions.
48 changes: 24 additions & 24 deletions server/routes/team.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,29 +175,29 @@ def install_im_application_to_team_by_get_method(team_id, platform):
)
app.logger.info("result %r", result)
events = [
"20",
"im.message.message_read_v1",
"im.message.reaction.created_v1",
"im.message.reaction.deleted_v1",
"im.message.recalled_v1",
"im.message.receive_v1",
"20", # 用户和机器人的会话首次被创建
"im.message.message_read_v1", # 消息已读
"im.message.reaction.created_v1", # 新增消息表情回复
"im.message.reaction.deleted_v1", # 删除消息表情回复
"im.message.recalled_v1", # 撤回消息
"im.message.receive_v1", # 接收消息
]
scope_ids = [
"8002",
"100032",
"6081",
"14",
"1",
"21001",
"20001",
"20011",
"3001",
"20012",
"20010",
"3000",
"20008",
"1000",
"20009",
"8002", # 获取应用信息
"100032", # 获取通讯录基本信息
"6081", # 以应用身份读取通讯录
"14", # 获取用户基本信息
"1", # 获取用户邮箱信息
"21001", # 获取与更新群组信息
"20001", # 获取与发送单聊、群组消息
"20011", # 获取用户在群组中 @ 机器人的消息
"3001", # 接收群聊中 @ 机器人消息事件
"20012", # 获取群组中所有消息
"20010", # 获取用户发给机器人的单聊消息
"3000", # 读取用户发给机器人的单聊消息
"20008", # 获取单聊、群组消息
"1000", # 以应用的身份发消息
"20009", # 获取上传图片或文件资源
]
hook_url = f"{os.environ.get('DOMAIN')}/api/feishu/hook/{app_id}"
return redirect(
Expand Down Expand Up @@ -260,9 +260,9 @@ def get_task_result_by_id(team_id, task_id):
"data": {
"task_id": task.id,
"status": task.status,
"result": task.result
if isinstance(task.result, list)
else str(task.result),
"result": (
task.result if isinstance(task.result, list) else str(task.result)
),
},
}
)
Expand Down
44 changes: 42 additions & 2 deletions server/routes/user.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
from app import app
from flask import Blueprint, jsonify, request, session
from model.team import get_team_list_by_user_id, is_team_admin
from flask import Blueprint, Response, abort, jsonify, request, session
from model.team import (
get_application_info_by_team_id,
get_team_list_by_user_id,
is_team_admin,
)
from model.user import get_user_by_id
from tasks.lark.base import get_bot_by_application_id, get_repo_by_repo_id
from utils.auth import authenticated
from utils.utils import download_file

bp = Blueprint("user", __name__, url_prefix="/api")

Expand Down Expand Up @@ -56,4 +62,38 @@ def set_account():
return jsonify({"code": 0, "msg": "success"})


@bp.route("/<team_id>/<repo_id>/<message_id>/image/<img_key>", methods=["GET"])
def get_image(team_id, message_id, repo_id, img_key):
"""
1. 用 img_key 请求飞书接口下载 image
2. 判断请求来源,如果是 GitHub 调用,则直接返回 image
3. 用户调用 校验权限
"""

def download_and_respond():
_, im_application = get_application_info_by_team_id(team_id)
bot, _ = get_bot_by_application_id(im_application.id)
image_content = download_file(img_key, message_id, bot, "image")
return Response(image_content, mimetype="image/png")

# GitHub调用
user_agent = request.headers.get("User-Agent")
if user_agent and user_agent.startswith("github-camo"):
return download_and_respond()

# TODO 用户调用(弱需求, 通常来讲此接口不会被暴露), 需要进一步校验权限
referer = request.headers.get("Referer")
if not referer:
# 公开仓库不校验
repo = get_repo_by_repo_id(repo_id)
is_private = repo.extra.get("private", False)
app.logger.debug(f"is_private: {is_private}")

# 私有仓库校验,先登录
if is_private:
return abort(403)

return download_and_respond()


app.register_blueprint(bp)
7 changes: 6 additions & 1 deletion server/tasks/lark/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ def get_chat_group_by_chat_id(chat_id):


def get_repo_name_by_repo_id(repo_id):
repo = get_repo_by_repo_id(repo_id)
return repo.name


def get_repo_by_repo_id(repo_id):
repo = (
db.session.query(Repo)
.filter(
Expand All @@ -32,7 +37,7 @@ def get_repo_name_by_repo_id(repo_id):
)
.first()
)
return repo.name
return repo


def get_bot_by_application_id(app_id):
Expand Down
49 changes: 42 additions & 7 deletions server/tasks/lark/chat.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import json
import logging
import os
import re
from urllib.parse import urlparse

from celery_app import app, celery
Expand Down Expand Up @@ -313,13 +315,8 @@ def create_issue(
)
assignees = [code_users[openid][1] for openid in users if openid in code_users]

# 判断 content 中是否有 at
if "mentions" in data["event"]["message"]:
# 替换 content 中的 im_name 为 code_name
body = replace_im_name_to_github_name(
app_id, message_id, {"text": body}, data, team, *args, **kwargs
)
body = body.replace("\n", "\r\n")
# 处理 body
body = process_desc(app_id, message_id, repo.id, body, data, team, *args, **kwargs)

response = github_app.create_issue(
team.name, repo.name, title, body, assignees, labels
Expand All @@ -331,6 +328,44 @@ def create_issue(
return response


def process_desc(app_id, message_id, repo_id, desc, data, team, *args, **kwargs):
"""
处理发给 github 的 desc, 转换@、处理图片、换行
"""
# 1. 判断 body 中是否有 at
if "mentions" in data["event"]["message"]:
# 替换 body 中的 im_name 为 code_name
desc = replace_im_name_to_github_name(
app_id, message_id, {"text": desc}, data, team, *args, **kwargs
)

# 2. 处理 body 中的图片
desc = replace_images_keys_with_url(desc, team.id, message_id, repo_id)

# github 只支持 \r\n
return desc.replace("\n", "\r\n")


def replace_images_keys_with_url(text, team_id, message_id, repo_id):
"""
replace image_key with image URL.
![](image_key) -> ![](gitmaya.com/api/<team_id>/<repo_id>/<message_id>/image/<image_key>)
Args:
text (str): original text
Returns:
str: replaced text
"""
host = os.environ.get("DOMAIN")
replaced_text = re.sub(
r"!\[.*?\]\((.*?)\)",
lambda match: f"![]({host}/api/{team_id}/{repo_id}/{message_id}/image/{match.group(1)})",
text,
)

return replaced_text


@celery.task()
def sync_issue(
issue_id, issue_link, app_id, message_id, content, data, *args, **kwargs
Expand Down
22 changes: 11 additions & 11 deletions server/tasks/lark/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from utils.lark.issue_manual_help import IssueManualHelp, IssueView
from utils.lark.issue_tip_failed import IssueTipFailed
from utils.lark.issue_tip_success import IssueTipSuccess
from utils.utils import upload_image
from utils.utils import process_image

from .base import (
get_bot_by_application_id,
Expand Down Expand Up @@ -122,12 +122,12 @@ def gen_issue_card_by_issue(bot, issue, repo_url, team, maunal=False):
tags=tags,
)

# 处理 description 中的图片
# 处理从 github 创建 Issue 时, description 中的图片
description = replace_images_with_keys(
issue.description if issue.description else "", bot
)

# 处理 description 中的at
# 处理从 github 创建 Issue 时, description 中的 at
description = replace_code_name_to_im_name(description)

return IssueCard(
Expand Down Expand Up @@ -160,15 +160,15 @@ def replace_images_with_keys(text, bot):
markdown_pattern = r"!\[.*?\]\((.*?)\)"
replaced_text = re.sub(
markdown_pattern,
lambda match: f"![]({upload_image(match.group(1), bot)})",
lambda match: f"![]({process_image(match.group(1), bot)})",
text,
)

# Replace HTML image syntax
html_pattern = r"<img.*?src=\"(.*?)\".*?>"
replaced_text = re.sub(
html_pattern,
lambda match: f"![]({upload_image(match.group(1), bot)})",
lambda match: f"![]({process_image(match.group(1), bot)})",
replaced_text,
)

Expand Down Expand Up @@ -601,12 +601,12 @@ def create_issue_comment(app_id, message_id, content, data, *args, **kwargs):
)
comment_text = content["text"]

# 判断 content 中是否有 at
if "mentions" in data["event"]["message"]:
# 替换 content 中的 im_name 为 code_name
comment_text = replace_im_name_to_github_name(
app_id, message_id, content, data, team, *args, **kwargs
)
from tasks.lark.chat import process_desc

# 处理 desc
comment_text = process_desc(
app_id, message_id, repo.id, comment_text, data, team, *args, **kwargs
)

response = github_app.create_issue_comment(
team.name, repo.name, issue.issue_number, comment_text
Expand Down
24 changes: 22 additions & 2 deletions server/utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
import logging
import os

import httpx
from utils.redis import stalecache


def process_image(url, bot):
if not url or not url.startswith("http"):
return ""

if url.startswith(f"{os.environ.get('DOMAIN')}/api"):
return url.split("/")[-1]
return upload_image(url, bot)


# 使用 stalecache 装饰器,以 url 作为缓存键
@stalecache(expire=3600, stale=600)
def upload_image(url, bot):
logging.info("upload image: %s", url)
if not url or not url.startswith("http"):
return ""
response = httpx.get(url, follow_redirects=True)
if response.status_code == 200:
# 函数返回值: iamg_key 存到缓存中
Expand All @@ -30,6 +38,18 @@ def upload_image_binary(img_bin, bot):
return response["data"]["image_key"]


@stalecache(expire=3600, stale=600)
def download_file(file_key, message_id, bot, file_type="image"):
"""
获取消息中的资源文件,包括音频,视频,图片和文件,暂不支持表情包资源下载。当前仅支持 100M 以内的资源文件的下载
"""
# open-apis/im/v1/images/{img_key} 接口只能下载机器人自己上传的图片
url = f"{bot.host}/open-apis/im/v1/messages/{message_id}/resources/{file_key}?type={file_type}"

response = bot.get(url)
return response.content


def query_one_page(query, page, size):
offset = (page - 1) * int(size)
return (
Expand Down

0 comments on commit b97aa51

Please sign in to comment.