HEX
Server: Apache/2.4.54 (Win64) OpenSSL/1.1.1p PHP/7.4.30
System: Windows NT website-api 10.0 build 20348 (Windows Server 2016) AMD64
User: SYSTEM (0)
PHP: 7.4.30
Disabled: NONE
Upload Files
File: C:/github_repos/casibase_customer_0058/controllers/wecom_bot.go
// Copyright 2025 The Casibase Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package controllers

import (
	"encoding/json"
	"fmt"

	"github.com/beego/beego/logs"
	"github.com/casibase/casibase/model"
	"github.com/casibase/casibase/object"
	"github.com/casibase/casibase/util"
	"github.com/workweixin/weworkapi_golang/json_callback/wxbizjsonmsgcrypt"
)

// WecomBotVerifyUrl verify WeChat work bot callback URL
// @Title WecomBotVerifyUrl
// @Tag WechatWork Bot API
// @Description verify WeChat work bot callback URL
// @router /api/wecom-bot/callback/:botId [get]
func (c *ApiController) WecomBotVerifyUrl() {
	botId := c.Ctx.Input.Param(":botId")
	msgSignature := c.GetString("msg_signature")
	timestamp := c.GetString("timestamp")
	nonce := c.GetString("nonce")
	echoStr := c.GetString("echostr")

	token, encodingAESKey, err := object.GetWecomBotTokenAndKey(botId)
	if err != nil {
		c.Ctx.ResponseWriter.Write([]byte(fmt.Sprintf("verify fail: %v", err)))
		return
	}

	wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAESKey, "", wxbizjsonmsgcrypt.JsonType)

	result, cryptErr := wxcpt.VerifyURL(msgSignature, timestamp, nonce, echoStr)
	if cryptErr != nil {
		c.Ctx.ResponseWriter.Write([]byte(fmt.Sprintf("verify fail: %v", cryptErr)))
		return
	}

	c.Ctx.ResponseWriter.Write(result)
}

// WecomBotHandleMessage process WeChat work bot messages
// @Title WecomBotHandleMessage
// @Tag WechatWork Bot API
// @Description handle WeChat work bot messages
// @router /api/wecom-bot/callback/:botId [post]
func (c *ApiController) WecomBotHandleMessage() {
	botId := c.Ctx.Input.Param(":botId")
	msgSignature := c.GetString("msg_signature")
	timestamp := c.GetString("timestamp")
	nonce := c.GetString("nonce")

	token, encodingAESKey, err := object.GetWecomBotTokenAndKey(botId)
	if err != nil {
		logs.Error("verify fail: %v", err)
		c.Ctx.ResponseWriter.Write([]byte(fmt.Sprintf("verify fail: %v", err)))
		return
	}

	wxcpt := wxbizjsonmsgcrypt.NewWXBizMsgCrypt(token, encodingAESKey, "", wxbizjsonmsgcrypt.JsonType)

	postData := c.Ctx.Input.RequestBody
	plaintext, cryptErr := wxcpt.DecryptMsg(msgSignature, timestamp, nonce, postData)
	if cryptErr != nil {
		logs.Error("[WechatWork Bot] Decrypt message error: %v\n", cryptErr)
		c.Ctx.ResponseWriter.Write([]byte("error"))
		return
	}

	var message object.WecomBotMessage
	if err := json.Unmarshal(plaintext, &message); err != nil {
		logs.Error("[WechatWork Bot] Parse message error: %v\n", err)
		c.Ctx.ResponseWriter.Write([]byte("error"))
		return
	}

	var responseMsg string
	switch message.MsgType {
	case "text", "stream":
		responseMsg, cryptErr = c.handleTextMessage(&message, wxcpt, nonce, timestamp, c.GetAcceptLanguage())
	default:
		logs.Error("[WechatWork Bot] Unsupported message type: %s\n", message.MsgType)
		c.Ctx.ResponseWriter.Write([]byte("success"))
		return
	}

	if cryptErr != nil {
		logs.Error("[WechatWork Bot] Handle message error: %v\n", cryptErr)
		c.Ctx.ResponseWriter.Write([]byte("error"))
		return
	}

	c.Ctx.ResponseWriter.Write([]byte(responseMsg))
}

func (c *ApiController) handleTextMessage(message *object.WecomBotMessage, wxcpt *wxbizjsonmsgcrypt.WXBizMsgCrypt, nonce, timestamp string, lang string) (string, *wxbizjsonmsgcrypt.CryptError) {
	content := ""
	if message.Text != nil {
		content = message.Text.Content
	}

	var streamId string
	if message.Stream != nil && message.Stream.Id != "" {
		streamId = message.Stream.Id
	} else {
		streamId = util.GenerateId()
	}

	answer := ""
	ans, ok := object.WecomBotMessageCache[streamId]
	if !ok {
		object.WecomBotMessageCache[streamId] = ""
		go func() {
			store, err := object.GetDefaultStore("admin")
			if err != nil {
				return
			}
			response, _ := sendMessage(store, content, lang)
			if err != nil {
				delete(object.WecomBotMessageCache, streamId)
				return
			}
			object.WecomBotMessageCache[streamId] = response
		}()
	} else if ans != "" {
		answer = ans
		delete(object.WecomBotMessageCache, streamId)
	}

	resp, err := object.MakeMsgResponse(answer, streamId)
	if err != nil {
		return "", wxbizjsonmsgcrypt.NewCryptError(wxbizjsonmsgcrypt.GenJsonError, err.Error())
	}

	encryptedMsg, cryptErr := wxcpt.EncryptMsg(resp, timestamp, nonce)
	if cryptErr != nil {
		return "", cryptErr
	}

	return string(encryptedMsg), nil
}

func sendMessage(store *object.Store, question string, lang string) (string, error) {
	modelProvider, _, err := object.GetModelProviderFromContext("admin", store.ModelProvider, lang)
	if err != nil {
		return "", err
	}

	embeddingProvider, embeddingProviderObj, err := object.GetEmbeddingProviderFromContext("admin", store.EmbeddingProvider, lang)
	if err != nil {
		return "", err
	}

	knowledge, _, _, err := object.GetNearestKnowledge(store.Name, store.VectorStores, store.SearchProvider, embeddingProvider, embeddingProviderObj, modelProvider, "admin", question, store.KnowledgeCount, lang)
	if err != nil {
		return "", err
	}
	var history []*model.RawMessage
	answer, _, err := object.GetAnswerWithContext(store.ModelProvider, question, history, knowledge, store.Prompt, lang)
	if err != nil {
		return "", err
	}
	return answer, nil
}