Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

支持飞书传图GitHub #231

Merged
merged 9 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里改了结构,需要和前端沟通好,免得前端调用接口的时候报错

},
}
)
Expand Down
33 changes: 31 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, 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
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,27 @@ def set_account():
return jsonify({"code": 0, "msg": "success"})


@bp.route("/<team_id>/<message_id>/image/<img_key>", methods=["GET"])
# @authenticated
def get_image(team_id, message_id, img_key):
"""
1. 用 img_key 下载 image(cache)
2. 这个链接需要用户登录信息
1. 公开仓库,不校验
3. 并且需要多加一层权限管理(只有这个team下面的人 ,才能查看这个图)
4. 如果没有登录的时候,这个url返回一张403的图,点击的时候,告诉用户需要登录
1. 如果是github上面查看的时候,通过 判断,展示图片
2. 如果用户点击图片,直接浏览器打开
1. 这个时候rederer是空的,跳转github oauth登录
2. 然后跳转回到这个图片。可以正常查看图片内容
3. 这个时候如果刷新github的issue页面,应该是能正常查看图片的
"""
# 必须要登录,才能查看图片,不然ddos分分钟打爆db
_, 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")


app.register_blueprint(bp)
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, 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, 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)

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


def replace_images_keys_with_url(text, team_id, message_id):
"""
replace image_key with image URL.
![](image_key) -> ![](gitmaya.com/api/<team_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}/{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, comment_text, data, team, *args, **kwargs
)

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

import httpx
from utils.redis import stalecache


def process_image(url, bot):
if (
not url
or not url.startswith("http")
or url.startswith(f"{os.environ.get('DOMAIN')}/api")
):
return ""
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 +39,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
Loading