package main
import ( "bytes" "encoding/json" "flag" "fmt" "io" "mime/multipart" "net/http" "os" "path/filepath" )
type FeishuClient struct { AppID string AppSecret string TenantAccessToken string }
type TokenResponse struct { Code int `json:"code"` Msg string `json:"msg"` TenantAccessToken string `json:"tenant_access_token"` Expire int `json:"expire"` }
type ImageUploadResponse struct { Code int `json:"code"` Msg string `json:"msg"` Data struct { ImageKey string `json:"image_key"` } `json:"data"` }
type MessageResponse struct { Code int `json:"code"` Msg string `json:"msg"` }
func NewFeishuClient(appID, appSecret string) *FeishuClient { return &FeishuClient{ AppID: appID, AppSecret: appSecret, } }
func (c *FeishuClient) GetTenantAccessToken() error { url := "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" payload := map[string]string{ "app_id": c.AppID, "app_secret": c.AppSecret, } jsonData, _ := json.Marshal(payload) resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("请求 token 失败: %v", err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result TokenResponse if err := json.Unmarshal(body, &result); err != nil { return fmt.Errorf("解析 token 响应失败: %v", err) } if result.Code != 0 { return fmt.Errorf("获取 token 失败: %s", result.Msg) } c.TenantAccessToken = result.TenantAccessToken return nil }
func (c *FeishuClient) UploadImage(imagePath string) (string, error) { if c.TenantAccessToken == "" { if err := c.GetTenantAccessToken(); err != nil { return "", err } } url := "https://open.feishu.cn/open-apis/im/v1/images" file, err := os.Open(imagePath) if err != nil { return "", fmt.Errorf("打开图片失败: %v", err) } defer file.Close() var body bytes.Buffer writer := multipart.NewWriter(&body) _ = writer.WriteField("image_type", "message") part, err := writer.CreateFormFile("image", filepath.Base(imagePath)) if err != nil { return "", fmt.Errorf("创建 form file 失败: %v", err) } _, err = io.Copy(part, file) if err != nil { return "", fmt.Errorf("复制文件内容失败: %v", err) } writer.Close() req, err := http.NewRequest("POST", url, &body) if err != nil { return "", fmt.Errorf("创建请求失败: %v", err) } req.Header.Set("Authorization", "Bearer "+c.TenantAccessToken) req.Header.Set("Content-Type", writer.FormDataContentType()) client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", fmt.Errorf("上传图片请求失败: %v", err) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) var result ImageUploadResponse if err := json.Unmarshal(respBody, &result); err != nil { return "", fmt.Errorf("解析上传响应失败: %v", err) } if result.Code != 0 { if result.Code == 99991672 { return "", fmt.Errorf("权限不足: %s\n请访问 https://open.feishu.cn/app/%s/auth 开通权限: im:resource, im:message:send", result.Msg, c.AppID) } return "", fmt.Errorf("上传图片失败 [%d]: %s", result.Code, result.Msg) } return result.Data.ImageKey, nil }
func (c *FeishuClient) SendImageMessage(chatID, imageKey string) error { if c.TenantAccessToken == "" { if err := c.GetTenantAccessToken(); err != nil { return err } } url := "https://open.feishu.cn/open-apis/im/v1/messages" content := map[string]string{ "image_key": imageKey, } contentBytes, _ := json.Marshal(content) payload := map[string]interface{}{ "receive_id": chatID, "msg_type": "image", "content": string(contentBytes), } jsonData, _ := json.Marshal(payload) req, err := http.NewRequest("POST", url+"?receive_id_type=chat_id", bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("创建消息请求失败: %v", err) } req.Header.Set("Authorization", "Bearer "+c.TenantAccessToken) req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) if err != nil { return fmt.Errorf("发送消息请求失败: %v", err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result MessageResponse if err := json.Unmarshal(body, &result); err != nil { return fmt.Errorf("解析消息响应失败: %v", err) } if result.Code != 0 { return fmt.Errorf("发送消息失败: %s", result.Msg) } return nil }
func main() { var ( appID = flag.String("app-id", "", "飞书 App ID") appSecret = flag.String("app-secret", "", "飞书 App Secret") chatID = flag.String("chat-id", "", "飞书群聊 ID (chat_id)") imagePath = flag.String("image", "", "本地图片路径") getKey = flag.Bool("get-key", false, "仅获取 image_key,不发送消息") ) flag.Parse() if *appID == "" || *appSecret == "" || *imagePath == "" { fmt.Fprintf(os.Stderr, "用法: %s -app-id <app_id> -app-secret <app_secret> -image <path> [-chat-id <chat_id>]\n", os.Args[0]) flag.PrintDefaults() os.Exit(1) } if !*getKey && *chatID == "" { fmt.Fprintf(os.Stderr, "错误: 发送消息需要提供 -chat-id 参数,或使用 -get-key 仅获取 image_key\n") os.Exit(1) } client := NewFeishuClient(*appID, *appSecret) fmt.Printf("正在上传图片: %s\n", *imagePath) imageKey, err := client.UploadImage(*imagePath) if err != nil { fmt.Fprintf(os.Stderr, "上传图片失败: %v\n", err) os.Exit(1) } fmt.Printf("图片上传成功,image_key: %s\n", imageKey) if *getKey { fmt.Println(imageKey) os.Exit(0) } fmt.Printf("正在发送消息到群聊: %s\n", *chatID) if err := client.SendImageMessage(*chatID, imageKey); err != nil { fmt.Fprintf(os.Stderr, "发送消息失败: %v\n", err) os.Exit(1) } fmt.Println("图片消息发送成功!") }
|
评论
0 条评论