diff --git a/cmd/api/bot-api/main.go b/cmd/api/bot-api/main.go new file mode 100644 index 000000000..e70a7c298 --- /dev/null +++ b/cmd/api/bot-api/main.go @@ -0,0 +1,26 @@ +// Copyright © 2023 OpenIM open source community. 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 main + +import ( + "github.com/openimsdk/chat/pkg/common/cmd" + "github.com/openimsdk/tools/system/program" +) + +func main() { + if err := cmd.NewBotApiCmd().Exec(); err != nil { + program.ExitWithError(err) + } +} diff --git a/cmd/rpc/bot-rpc/main.go b/cmd/rpc/bot-rpc/main.go new file mode 100644 index 000000000..65b19609b --- /dev/null +++ b/cmd/rpc/bot-rpc/main.go @@ -0,0 +1,26 @@ +// Copyright © 2023 OpenIM open source community. 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 main + +import ( + "github.com/openimsdk/chat/pkg/common/cmd" + "github.com/openimsdk/tools/system/program" +) + +func main() { + if err := cmd.NewBotRpcCmd().Exec(); err != nil { + program.ExitWithError(err) + } +} diff --git a/config/chat-api-bot.yml b/config/chat-api-bot.yml new file mode 100644 index 000000000..cc401af03 --- /dev/null +++ b/config/chat-api-bot.yml @@ -0,0 +1,6 @@ +api: + # Listening IP; 0.0.0.0 means both internal and external IPs are listened to, default is recommended + listenIP: 0.0.0.0 + # Listening ports; if multiple are configured, multiple instances will be launched + ports: [ 10010 ] + diff --git a/config/chat-rpc-bot.yml b/config/chat-rpc-bot.yml new file mode 100644 index 000000000..3d8d8c6f2 --- /dev/null +++ b/config/chat-rpc-bot.yml @@ -0,0 +1,10 @@ +rpc: + # The IP address where this RPC service registers itself; if left blank, it defaults to the internal network IP + registerIP: '' + # IP address that the RPC service listens on; setting to 0.0.0.0 listens on both internal and external IPs. If left blank, it automatically uses the internal network IP + listenIP: 0.0.0.0 + # List of ports that the RPC service listens on; configuring multiple ports will launch multiple instances. + ports: [ 30400 ] + +# Model request timeout. Unit: s +timeout: 20 diff --git a/config/discovery.yml b/config/discovery.yml index 1c34af784..5022a6abb 100644 --- a/config/discovery.yml +++ b/config/discovery.yml @@ -11,3 +11,4 @@ kubernetes: rpcService: chat: chat-rpc-service admin: admin-rpc-service + bot: bot-rpc-service diff --git a/config/share.yml b/config/share.yml index 907b569ca..20ea77b54 100644 --- a/config/share.yml +++ b/config/share.yml @@ -5,6 +5,8 @@ openIM: secret: openIM123 # OpenIM administrator userID, must be consistent with OpenIM adminUserID: imAdmin + # Refresh OpenIM token interval + tokenRefreshInterval: 120 # unit: minute chatAdmin: # Default username and password for the admin diff --git a/go.mod b/go.mod index f3a151874..8ef823dd0 100644 --- a/go.mod +++ b/go.mod @@ -30,11 +30,13 @@ require ( github.com/openimsdk/protocol v0.0.73-alpha.5 github.com/openimsdk/tools v0.0.50-alpha.65 github.com/redis/go-redis/v9 v9.5.1 + github.com/sashabaranov/go-openai v1.38.1 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 github.com/xuri/excelize/v2 v2.8.0 go.etcd.io/etcd/client/v3 v3.5.13 go.mongodb.org/mongo-driver v1.14.0 + golang.org/x/sync v0.10.0 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) @@ -123,14 +125,13 @@ require ( go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/crypto v0.27.0 // indirect + golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - golang.org/x/text v0.18.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/term v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect diff --git a/go.sum b/go.sum index 0ac87813f..60c80de74 100644 --- a/go.sum +++ b/go.sum @@ -282,6 +282,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sashabaranov/go-openai v1.38.1 h1:TtZabbFQZa1nEni/IhVtDF/WQjVqDgd+cWR5OeddzF8= +github.com/sashabaranov/go-openai v1.38.1/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -380,8 +382,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8= @@ -403,8 +405,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -413,8 +415,8 @@ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -431,8 +433,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -440,8 +442,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -452,8 +454,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/internal/api/admin/start.go b/internal/api/admin/start.go index 880f86cc7..b8d23931c 100644 --- a/internal/api/admin/start.go +++ b/internal/api/admin/start.go @@ -19,6 +19,7 @@ import ( disetcd "github.com/openimsdk/chat/pkg/common/kdisc/etcd" adminclient "github.com/openimsdk/chat/pkg/protocol/admin" chatclient "github.com/openimsdk/chat/pkg/protocol/chat" + "github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery/etcd" "github.com/openimsdk/tools/errs" @@ -52,6 +53,10 @@ func Start(ctx context.Context, index int, config *Config) error { if err != nil { return err } + rdb, err := redisutil.NewRedisClient(ctx, config.Redis.Build()) + if err != nil { + return err + } chatConn, err := client.GetConn(ctx, config.Discovery.RpcService.Chat, grpc.WithTransportCredentials(insecure.NewCredentials()), mw.GrpcClient()) if err != nil { @@ -63,7 +68,7 @@ func Start(ctx context.Context, index int, config *Config) error { } chatClient := chatclient.NewChatClient(chatConn) adminClient := adminclient.NewAdminClient(adminConn) - im := imapi.New(config.Share.OpenIM.ApiURL, config.Share.OpenIM.Secret, config.Share.OpenIM.AdminUserID) + im := imapi.New(config.Share.OpenIM.ApiURL, config.Share.OpenIM.Secret, config.Share.OpenIM.AdminUserID, rdb, config.Share.OpenIM.TokenRefreshInterval) base := util.Api{ ImUserID: config.Share.OpenIM.AdminUserID, ProxyHeader: config.Share.ProxyHeader, diff --git a/internal/api/bot/bot.go b/internal/api/bot/bot.go new file mode 100644 index 000000000..952aafbc6 --- /dev/null +++ b/internal/api/bot/bot.go @@ -0,0 +1,177 @@ +package bot + +import ( + "encoding/json" + "sort" + "strings" + + "github.com/gin-gonic/gin" + "github.com/openimsdk/chat/internal/api/util" + "github.com/openimsdk/chat/pkg/botstruct" + "github.com/openimsdk/chat/pkg/common/imwebhook" + "github.com/openimsdk/chat/pkg/protocol/bot" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/a2r" + "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/errs" + "golang.org/x/sync/errgroup" +) + +func New(botClient bot.BotClient, api *util.Api) *Api { + return &Api{ + Api: api, + botClient: botClient, + } +} + +type Api struct { + *util.Api + botClient bot.BotClient +} + +func (o *Api) CreateAgent(c *gin.Context) { + a2r.Call(c, bot.BotClient.CreateAgent, o.botClient) +} + +func (o *Api) DeleteAgent(c *gin.Context) { + a2r.Call(c, bot.BotClient.DeleteAgent, o.botClient) +} + +func (o *Api) UpdateAgent(c *gin.Context) { + a2r.Call(c, bot.BotClient.UpdateAgent, o.botClient) +} + +func (o *Api) PageFindAgent(c *gin.Context) { + a2r.Call(c, bot.BotClient.PageFindAgent, o.botClient) +} + +func (o *Api) AfterSendSingleMsg(c *gin.Context) { + var ( + req = imwebhook.CallbackAfterSendSingleMsgReq{} + ) + + if err := c.BindJSON(&req); err != nil { + apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap()) + return + } + if req.ContentType != constant.Text { + apiresp.GinSuccess(c, nil) + return + } + isAgent := botstruct.IsAgentUserID(req.RecvID) + if !isAgent { + apiresp.GinSuccess(c, nil) + return + } + + var elem botstruct.TextElem + err := json.Unmarshal([]byte(req.Content), &elem) + if err != nil { + apiresp.GinError(c, errs.ErrArgs.WrapMsg("json unmarshal error: "+err.Error())) + return + } + convID := getConversationIDByMsg(req.SessionType, req.SendID, req.RecvID, "") + + key, ok := c.GetQuery(botstruct.Key) + if !ok { + apiresp.GinError(c, errs.ErrArgs.WithDetail("missing key in query").Wrap()) + return + } + res, err := o.botClient.SendBotMessage(c, &bot.SendBotMessageReq{ + AgentID: req.RecvID, + ConversationID: convID, + ContentType: req.ContentType, + Content: elem.Content, + Ex: req.Ex, + Key: key, + }) + if err != nil { + apiresp.GinError(c, err) + return + } + apiresp.GinSuccess(c, res) +} + +func (o *Api) AfterSendGroupMsg(c *gin.Context) { + var ( + req = imwebhook.CallbackAfterSendSingleMsgReq{} + ) + if err := c.BindJSON(&req); err != nil { + apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap()) + return + } + + if req.ContentType != constant.AtText { + apiresp.GinSuccess(c, nil) + } + key, ok := c.GetQuery(botstruct.Key) + if !ok { + apiresp.GinError(c, errs.ErrArgs.WithDetail("missing key in query").Wrap()) + return + } + + var ( + elem botstruct.AtElem + reqs []*bot.SendBotMessageReq + ) + + convID := getConversationIDByMsg(req.SessionType, req.SendID, req.RecvID, "") + err := json.Unmarshal([]byte(req.Content), &elem) + if err != nil { + apiresp.GinError(c, errs.ErrArgs.WrapMsg("json unmarshal error: "+err.Error())) + } + for _, userID := range elem.AtUserList { + if botstruct.IsAgentUserID(userID) { + reqs = append(reqs, &bot.SendBotMessageReq{ + AgentID: userID, + ConversationID: convID, + ContentType: req.ContentType, + Content: elem.Text, + Ex: req.Ex, + Key: key, + }) + } + } + if len(reqs) == 0 { + apiresp.GinSuccess(c, nil) + } + + g := errgroup.Group{} + g.SetLimit(min(len(reqs), 5)) + for i := 0; i < len(reqs); i++ { + i := i + g.Go(func() error { + _, err := o.botClient.SendBotMessage(c, reqs[i]) + if err != nil { + return err + } + return nil + }) + } + + err = g.Wait() + if err != nil { + apiresp.GinError(c, err) + return + } + + apiresp.GinSuccess(c, nil) +} + +func getConversationIDByMsg(sessionType int32, sendID, recvID, groupID string) string { + switch sessionType { + case constant.SingleChatType: + l := []string{sendID, recvID} + sort.Strings(l) + return "si_" + strings.Join(l, "_") // single chat + case constant.WriteGroupChatType: + return "g_" + groupID // group chat + case constant.ReadGroupChatType: + return "sg_" + groupID // super group chat + case constant.NotificationChatType: + l := []string{sendID, recvID} + sort.Strings(l) + return "sn_" + strings.Join(l, "_") + } + return "" +} diff --git a/internal/api/bot/start.go b/internal/api/bot/start.go new file mode 100644 index 000000000..b840d1139 --- /dev/null +++ b/internal/api/bot/start.go @@ -0,0 +1,132 @@ +package bot + +import ( + "context" + "errors" + "fmt" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" + chatmw "github.com/openimsdk/chat/internal/api/mw" + "github.com/openimsdk/chat/internal/api/util" + "github.com/openimsdk/chat/pkg/common/config" + "github.com/openimsdk/chat/pkg/common/kdisc" + disetcd "github.com/openimsdk/chat/pkg/common/kdisc/etcd" + adminclient "github.com/openimsdk/chat/pkg/protocol/admin" + botclient "github.com/openimsdk/chat/pkg/protocol/bot" + "github.com/openimsdk/tools/discovery/etcd" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/mw" + "github.com/openimsdk/tools/system/program" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/runtimeenv" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" +) + +type Config struct { + ApiConfig config.APIBot + Discovery config.Discovery + Share config.Share + Redis config.Redis + + RuntimeEnv string +} + +func Start(ctx context.Context, index int, cfg *Config) error { + cfg.RuntimeEnv = runtimeenv.PrintRuntimeEnvironment() + apiPort, err := datautil.GetElemByIndex(cfg.ApiConfig.Api.Ports, index) + if err != nil { + return err + } + client, err := kdisc.NewDiscoveryRegister(&cfg.Discovery, cfg.RuntimeEnv, nil) + if err != nil { + return err + } + + botConn, err := client.GetConn(ctx, cfg.Discovery.RpcService.Bot, grpc.WithTransportCredentials(insecure.NewCredentials()), mw.GrpcClient()) + if err != nil { + return err + } + adminConn, err := client.GetConn(ctx, cfg.Discovery.RpcService.Admin, grpc.WithTransportCredentials(insecure.NewCredentials()), mw.GrpcClient()) + if err != nil { + return err + } + adminClient := adminclient.NewAdminClient(adminConn) + botClient := botclient.NewBotClient(botConn) + base := util.Api{ + ImUserID: cfg.Share.OpenIM.AdminUserID, + ProxyHeader: cfg.Share.ProxyHeader, + ChatAdminUserID: cfg.Share.ChatAdmin[0], + } + botApi := New(botClient, &base) + mwApi := chatmw.New(adminClient) + gin.SetMode(gin.ReleaseMode) + engine := gin.New() + engine.Use(gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID()) + SetBotRoute(engine, botApi, mwApi) + + var ( + netDone = make(chan struct{}, 1) + netErr error + ) + server := http.Server{Addr: fmt.Sprintf(":%d", apiPort), Handler: engine} + go func() { + err = server.ListenAndServe() + if err != nil && !errors.Is(err, http.ErrServerClosed) { + netErr = errs.WrapMsg(err, fmt.Sprintf("api start err: %s", server.Addr)) + netDone <- struct{}{} + } + }() + if cfg.Discovery.Enable == kdisc.ETCDCONST { + cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), + []string{ + config.ChatAPIBotCfgFileName, + config.DiscoveryConfigFileName, + config.ShareFileName, + config.LogConfigFileName, + }, + ) + cm.Watch(ctx) + } + shutdown := func() error { + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + err := server.Shutdown(ctx) + if err != nil { + return errs.WrapMsg(err, "shutdown err") + } + return nil + } + disetcd.RegisterShutDown(shutdown) + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGTERM) + select { + case <-sigs: + program.SIGTERMExit() + if err := shutdown(); err != nil { + return err + } + case <-netDone: + close(netDone) + return netErr + } + return nil +} + +func SetBotRoute(router gin.IRouter, bot *Api, mw *chatmw.MW) { + account := router.Group("/agent") + account.POST("/create", mw.CheckAdmin, bot.CreateAgent) + account.POST("/delete", mw.CheckAdmin, bot.DeleteAgent) + account.POST("/update", mw.CheckAdmin, bot.UpdateAgent) + account.POST("/page", mw.CheckToken, bot.PageFindAgent) + + imwebhook := router.Group("/im_callback") + imwebhook.POST("/callbackAfterSendSingleMsgCommand", bot.AfterSendSingleMsg) + imwebhook.POST("/callbackAfterSendGroupMsgCommand", bot.AfterSendGroupMsg) +} diff --git a/internal/api/chat/start.go b/internal/api/chat/start.go index df14893c0..feb32a051 100644 --- a/internal/api/chat/start.go +++ b/internal/api/chat/start.go @@ -19,6 +19,7 @@ import ( disetcd "github.com/openimsdk/chat/pkg/common/kdisc/etcd" adminclient "github.com/openimsdk/chat/pkg/protocol/admin" chatclient "github.com/openimsdk/chat/pkg/protocol/chat" + "github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/tools/discovery/etcd" "github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/mw" @@ -33,6 +34,7 @@ type Config struct { ApiConfig config.API Discovery config.Discovery Share config.Share + Redis config.Redis RuntimeEnv string } @@ -51,6 +53,10 @@ func Start(ctx context.Context, index int, cfg *Config) error { if err != nil { return err } + rdb, err := redisutil.NewRedisClient(ctx, cfg.Redis.Build()) + if err != nil { + return err + } chatConn, err := client.GetConn(ctx, cfg.Discovery.RpcService.Chat, grpc.WithTransportCredentials(insecure.NewCredentials()), mw.GrpcClient()) if err != nil { @@ -62,7 +68,7 @@ func Start(ctx context.Context, index int, cfg *Config) error { } chatClient := chatclient.NewChatClient(chatConn) adminClient := adminclient.NewAdminClient(adminConn) - im := imapi.New(cfg.Share.OpenIM.ApiURL, cfg.Share.OpenIM.Secret, cfg.Share.OpenIM.AdminUserID) + im := imapi.New(cfg.Share.OpenIM.ApiURL, cfg.Share.OpenIM.Secret, cfg.Share.OpenIM.AdminUserID, rdb, cfg.Share.OpenIM.TokenRefreshInterval) base := util.Api{ ImUserID: cfg.Share.OpenIM.AdminUserID, ProxyHeader: cfg.Share.ProxyHeader, diff --git a/internal/rpc/bot/agent.go b/internal/rpc/bot/agent.go new file mode 100644 index 000000000..c04d604fe --- /dev/null +++ b/internal/rpc/bot/agent.go @@ -0,0 +1,153 @@ +package bot + +import ( + "context" + "crypto/rand" + "time" + + "github.com/openimsdk/chat/pkg/common/constant" + "github.com/openimsdk/chat/pkg/common/convert" + "github.com/openimsdk/chat/pkg/common/mctx" + "github.com/openimsdk/chat/pkg/protocol/bot" + pbconstant "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/utils/datautil" +) + +func (b *botSvr) CreateAgent(ctx context.Context, req *bot.CreateAgentReq) (*bot.CreateAgentResp, error) { + if req.Agent == nil { + return nil, errs.ErrArgs.WrapMsg("req.Agent is nil") + } + + now := time.Now() + imToken, err := b.imCaller.ImAdminTokenWithDefaultAdmin(ctx) + if err != nil { + return nil, err + } + ctx = mctx.WithApiToken(ctx, imToken) + if req.Agent.UserID != "" { + req.Agent.UserID = constant.AgentUserIDPrefix + req.Agent.UserID + users, err := b.imCaller.GetUsersInfo(ctx, []string{req.Agent.UserID}) + if err != nil { + return nil, err + } + if len(users) > 0 { + return nil, errs.ErrDuplicateKey.WrapMsg("agent userID already exists") + } + } else { + randUserIDs := make([]string, 5) + for i := range randUserIDs { + randUserIDs[i] = constant.AgentUserIDPrefix + genID(10) + } + users, err := b.imCaller.GetUsersInfo(ctx, randUserIDs) + if err != nil { + return nil, err + } + if len(users) == len(randUserIDs) { + return nil, errs.ErrDuplicateKey.WrapMsg("gen agent userID already exists, please try again") + } + userIDs := datautil.Batch(func(u *sdkws.UserInfo) string { return u.UserID }, users) + for _, uid := range randUserIDs { + if datautil.Contain(uid, userIDs...) { + continue + } + req.Agent.UserID = uid + break + } + } + + if err := b.imCaller.AddNotificationAccount(ctx, &user.AddNotificationAccountReq{ + UserID: req.Agent.UserID, + NickName: req.Agent.Nickname, + FaceURL: req.Agent.FaceURL, + AppMangerLevel: pbconstant.AppRobotAdmin, + }); err != nil { + return nil, err + } + dbagent := convert.PB2DBAgent(req.Agent) + dbagent.CreateTime = now + err = b.database.CreateAgent(ctx, dbagent) + if err != nil { + return nil, err + } + return &bot.CreateAgentResp{}, nil +} + +func (b *botSvr) UpdateAgent(ctx context.Context, req *bot.UpdateAgentReq) (*bot.UpdateAgentResp, error) { + if _, err := b.database.TakeAgent(ctx, req.UserID); err != nil { + return nil, errs.ErrArgs.Wrap() + } + + if req.FaceURL != nil || req.Nickname != nil { + imReq := &user.UpdateNotificationAccountInfoReq{ + UserID: req.UserID, + } + if req.Nickname != nil { + imReq.NickName = *req.Nickname + } + if req.FaceURL != nil { + imReq.FaceURL = *req.FaceURL + } + imToken, err := b.imCaller.ImAdminTokenWithDefaultAdmin(ctx) + if err != nil { + return nil, err + } + ctx = mctx.WithApiToken(ctx, imToken) + err = b.imCaller.UpdateNotificationAccount(ctx, imReq) + if err != nil { + return nil, err + } + } + + update := ToDBAgentUpdate(req) + err := b.database.UpdateAgent(ctx, req.UserID, update) + if err != nil { + return nil, err + } + + return &bot.UpdateAgentResp{}, nil +} + +func (b *botSvr) PageFindAgent(ctx context.Context, req *bot.PageFindAgentReq) (*bot.PageFindAgentResp, error) { + total, agents, err := b.database.PageAgents(ctx, req.UserIDs, req.Pagination) + if err != nil { + return nil, err + } + //_, userType, err := mctx.Check(ctx) + //if err != nil { + // return nil, err + //} + //if userType != constant.AdminUser { + for i := range agents { + agents[i].Key = "" + } + //} + return &bot.PageFindAgentResp{ + Total: total, + Agents: convert.BatchDB2PBAgent(agents), + }, nil +} + +func (b *botSvr) DeleteAgent(ctx context.Context, req *bot.DeleteAgentReq) (*bot.DeleteAgentResp, error) { + err := b.database.DeleteAgents(ctx, req.UserIDs) + if err != nil { + return nil, err + } + return &bot.DeleteAgentResp{}, nil +} + +func genID(l int) string { + data := make([]byte, l) + _, _ = rand.Read(data) + chars := []byte("0123456789") + for i := 0; i < len(data); i++ { + if i == 0 { + data[i] = chars[1:][data[i]%9] + } else { + data[i] = chars[data[i]%10] + } + } + return string(data) +} diff --git a/internal/rpc/bot/send.go b/internal/rpc/bot/send.go new file mode 100644 index 000000000..88cc9c17b --- /dev/null +++ b/internal/rpc/bot/send.go @@ -0,0 +1,95 @@ +package bot + +import ( + "context" + "encoding/json" + "errors" + "time" + + "github.com/openimsdk/chat/pkg/botstruct" + "github.com/openimsdk/chat/pkg/common/imapi" + "github.com/openimsdk/chat/pkg/common/mctx" + "github.com/openimsdk/chat/pkg/protocol/bot" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/errs" + "github.com/sashabaranov/go-openai" + "go.mongodb.org/mongo-driver/mongo" +) + +func (b *botSvr) SendBotMessage(ctx context.Context, req *bot.SendBotMessageReq) (*bot.SendBotMessageResp, error) { + agent, err := b.database.TakeAgent(ctx, req.AgentID) + if err != nil { + return nil, errs.ErrArgs.WrapMsg("agent not found") + } + convRespID, err := b.database.TakeConversationRespID(ctx, req.ConversationID, req.AgentID) + if err != nil && !errors.Is(err, mongo.ErrNoDocuments) { + return nil, err + } + var respID string + if convRespID != nil { + respID = convRespID.PreviousResponseID + } + + aiCfg := openai.DefaultConfig(agent.Key) + aiCfg.BaseURL = agent.Url + aiCfg.HTTPClient = b.httpClient + aiCfg.OrgID = respID + client := openai.NewClientWithConfig(aiCfg) + aiReq := openai.ChatCompletionRequest{ + Model: agent.Model, + Messages: []openai.ChatCompletionMessage{ + { + Role: openai.ChatMessageRoleSystem, + Content: agent.Prompts, + }, + { + Role: openai.ChatMessageRoleUser, + Content: req.Content, + }, + }, + } + aiCtx, cancel := context.WithTimeout(ctx, time.Duration(b.timeout)*time.Second) + defer cancel() + completion, err := client.CreateChatCompletion(aiCtx, aiReq) + if err != nil { + return nil, errs.Wrap(err) + } + + imToken, err := b.imCaller.ImAdminTokenWithDefaultAdmin(ctx) + if err != nil { + return nil, err + } + ctx = mctx.WithApiToken(ctx, imToken) + + content := "no response" + if len(completion.Choices) > 0 { + content = completion.Choices[0].Message.Content + } + err = b.imCaller.SendSimpleMsg(ctx, &imapi.SendSingleMsgReq{ + SendID: agent.UserID, + Content: content, + }, req.Key) + if err != nil { + return nil, err + } + + err = b.database.UpdateConversationRespID(ctx, req.ConversationID, agent.UserID, ToDBConversationRespIDUpdate(completion.ID)) + if err != nil { + return nil, err + } + return &bot.SendBotMessageResp{}, nil +} + +func getContent(contentType int32, content string) (string, error) { + switch contentType { + case constant.Text: + var elem botstruct.TextElem + err := json.Unmarshal([]byte(content), &elem) + if err != nil { + return "", errs.ErrArgs.WrapMsg(err.Error()) + } + return elem.Content, nil + default: + return "", errs.New("un support contentType").Wrap() + } +} diff --git a/internal/rpc/bot/start.go b/internal/rpc/bot/start.go new file mode 100644 index 000000000..da3108cf5 --- /dev/null +++ b/internal/rpc/bot/start.go @@ -0,0 +1,58 @@ +package bot + +import ( + "context" + "net/http" + "time" + + "github.com/openimsdk/chat/pkg/common/config" + "github.com/openimsdk/chat/pkg/common/db/database" + "github.com/openimsdk/chat/pkg/common/imapi" + "github.com/openimsdk/chat/pkg/protocol/bot" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/tools/discovery" + "google.golang.org/grpc" +) + +type Config struct { + RpcConfig config.Bot + RedisConfig config.Redis + MongodbConfig config.Mongo + Discovery config.Discovery + Share config.Share +} + +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) + if err != nil { + return err + } + var srv botSvr + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) + if err != nil { + return err + } + + srv.database, err = database.NewBotDatabase(mgocli) + if err != nil { + return err + } + srv.timeout = config.RpcConfig.Timeout + srv.httpClient = &http.Client{ + Timeout: time.Duration(config.RpcConfig.Timeout) * time.Second, + } + im := imapi.New(config.Share.OpenIM.ApiURL, config.Share.OpenIM.Secret, config.Share.OpenIM.AdminUserID, rdb, config.Share.OpenIM.TokenRefreshInterval) + srv.imCaller = im + bot.RegisterBotServer(server, &srv) + return nil +} + +type botSvr struct { + bot.UnimplementedBotServer + database database.BotDatabase + httpClient *http.Client + timeout int + imCaller imapi.CallerInterface + //Admin *chatClient.AdminClient +} diff --git a/internal/rpc/bot/update.go b/internal/rpc/bot/update.go new file mode 100644 index 000000000..84a4fd6b4 --- /dev/null +++ b/internal/rpc/bot/update.go @@ -0,0 +1,37 @@ +package bot + +import "github.com/openimsdk/chat/pkg/protocol/bot" + +func ToDBAgentUpdate(req *bot.UpdateAgentReq) map[string]any { + update := make(map[string]any) + if req.Key != nil { + update["key"] = req.Key + } + if req.Prompts != nil { + update["prompts"] = req.Prompts + } + if req.Model != nil { + update["model"] = req.Model + } + if req.FaceURL != nil { + update["face_url"] = req.FaceURL + } + if req.Nickname != nil { + update["nick_name"] = req.Nickname + } + if req.Identity != nil { + update["identity"] = req.Identity + } + if req.Url != nil { + update["url"] = req.Url + } + + return update +} + +func ToDBConversationRespIDUpdate(respID string) map[string]any { + update := map[string]any{ + "previous_response_id": respID, + } + return update +} diff --git a/pkg/botstruct/check.go b/pkg/botstruct/check.go new file mode 100644 index 000000000..7032de389 --- /dev/null +++ b/pkg/botstruct/check.go @@ -0,0 +1,11 @@ +package botstruct + +import ( + "strings" + + "github.com/openimsdk/chat/pkg/common/constant" +) + +func IsAgentUserID(userID string) bool { + return strings.HasPrefix(userID, constant.AgentUserIDPrefix) +} diff --git a/pkg/botstruct/const.go b/pkg/botstruct/const.go new file mode 100644 index 000000000..ac5296fe9 --- /dev/null +++ b/pkg/botstruct/const.go @@ -0,0 +1,11 @@ +package botstruct + +const ( + Key = "key" + AgentIDPrefix = "agent_" +) + +const ( + RoleDeveloper = "developer" + RoleUser = "user" +) diff --git a/pkg/botstruct/msg.go b/pkg/botstruct/msg.go new file mode 100644 index 000000000..053269e48 --- /dev/null +++ b/pkg/botstruct/msg.go @@ -0,0 +1,13 @@ +package botstruct + +// see openim-sdk-core\sdk_struct\sdk_struct.go + +type TextElem struct { + Content string `json:"content"` +} + +type AtElem struct { + Text string `mapstructure:"text"` + AtUserList []string `mapstructure:"atUserList" validate:"required,max=1000"` + IsAtSelf bool `mapstructure:"isAtSelf"` +} diff --git a/pkg/common/cmd/bot_api.go b/pkg/common/cmd/bot_api.go new file mode 100644 index 000000000..a8577a175 --- /dev/null +++ b/pkg/common/cmd/bot_api.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "context" + + "github.com/openimsdk/chat/internal/api/bot" + "github.com/openimsdk/chat/pkg/common/config" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type BotApiCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + apiConfig bot.Config +} + +func NewBotApiCmd() *BotApiCmd { + ret := BotApiCmd{apiConfig: bot.Config{}} + ret.configMap = map[string]any{ + config.DiscoveryConfigFileName: &ret.apiConfig.Discovery, + config.ChatAPIBotCfgFileName: &ret.apiConfig.ApiConfig, + config.ShareFileName: &ret.apiConfig.Share, + config.RedisConfigFileName: &ret.apiConfig.Redis, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return &ret +} + +func (a *BotApiCmd) Exec() error { + return a.Execute() +} + +func (a *BotApiCmd) runE() error { + return bot.Start(a.ctx, a.Index(), &a.apiConfig) +} diff --git a/pkg/common/cmd/bot_rpc.go b/pkg/common/cmd/bot_rpc.go new file mode 100644 index 000000000..c05c382b4 --- /dev/null +++ b/pkg/common/cmd/bot_rpc.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "context" + + "github.com/openimsdk/chat/internal/rpc/bot" + "github.com/openimsdk/chat/pkg/common/config" + "github.com/openimsdk/chat/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type BotRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + botConfig bot.Config +} + +func NewBotRpcCmd() *BotRpcCmd { + var ret BotRpcCmd + ret.configMap = map[string]any{ + config.ChatRPCBotCfgFileName: &ret.botConfig.RpcConfig, + config.RedisConfigFileName: &ret.botConfig.RedisConfig, + config.DiscoveryConfigFileName: &ret.botConfig.Discovery, + config.MongodbConfigFileName: &ret.botConfig.MongodbConfig, + config.ShareFileName: &ret.botConfig.Share, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return &ret +} + +func (a *BotRpcCmd) Exec() error { + return a.Execute() +} + +func (a *BotRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.botConfig.Discovery, a.botConfig.RpcConfig.RPC.ListenIP, + a.botConfig.RpcConfig.RPC.RegisterIP, a.botConfig.RpcConfig.RPC.Ports, + a.Index(), a.botConfig.Discovery.RpcService.Bot, &a.botConfig.Share, &a.botConfig, + []string{ + config.ChatRPCBotCfgFileName, + config.RedisConfigFileName, + config.DiscoveryConfigFileName, + config.MongodbConfigFileName, + config.ShareFileName, + config.LogConfigFileName, + }, nil, + bot.Start) +} diff --git a/pkg/common/cmd/chat_api.go b/pkg/common/cmd/chat_api.go index 9c8da44e3..7dfad92ec 100644 --- a/pkg/common/cmd/chat_api.go +++ b/pkg/common/cmd/chat_api.go @@ -22,6 +22,7 @@ func NewChatApiCmd() *ChatApiCmd { config.ShareFileName: &ret.apiConfig.Share, config.ChatAPIChatCfgFileName: &ret.apiConfig.ApiConfig, config.DiscoveryConfigFileName: &ret.apiConfig.Discovery, + config.RedisConfigFileName: &ret.apiConfig.Redis, } ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) ret.ctx = context.WithValue(context.Background(), "version", config.Version) diff --git a/pkg/common/config/config.go b/pkg/common/config/config.go index 04a83db21..c3d1c5e13 100644 --- a/pkg/common/config/config.go +++ b/pkg/common/config/config.go @@ -16,9 +16,10 @@ var ( type Share struct { OpenIM struct { - ApiURL string `mapstructure:"apiURL"` - Secret string `mapstructure:"secret"` - AdminUserID string `mapstructure:"adminUserID"` + ApiURL string `mapstructure:"apiURL"` + Secret string `mapstructure:"secret"` + AdminUserID string `mapstructure:"adminUserID"` + TokenRefreshInterval int `mapstructure:"tokenRefreshInterval"` } `mapstructure:"openIM"` ChatAdmin []string `mapstructure:"chatAdmin"` ProxyHeader string `mapstructure:"proxyHeader"` @@ -27,6 +28,7 @@ type Share struct { type RpcService struct { Chat string `mapstructure:"chat"` Admin string `mapstructure:"admin"` + Bot string `mapstructure:"bot"` } func (r *RpcService) GetServiceNames() []string { @@ -43,6 +45,13 @@ type API struct { } `mapstructure:"api"` } +type APIBot struct { + Api struct { + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"api"` +} + type Mongo struct { URI string `mapstructure:"uri"` Address []string `mapstructure:"address"` @@ -121,6 +130,14 @@ type Chat struct { AllowRegister bool `mapstructure:"allowRegister"` } +type Bot struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Timeout int `mapstructure:"timeout"` +} type VerifyCode struct { ValidTime int `mapstructure:"validTime"` ValidCount int `mapstructure:"validCount"` diff --git a/pkg/common/config/env.go b/pkg/common/config/env.go index aa3170cce..33f0d0768 100644 --- a/pkg/common/config/env.go +++ b/pkg/common/config/env.go @@ -12,8 +12,10 @@ var ( LogConfigFileName = "log.yml" ChatAPIAdminCfgFileName = "chat-api-admin.yml" ChatAPIChatCfgFileName = "chat-api-chat.yml" + ChatAPIBotCfgFileName = "chat-api-bot.yml" ChatRPCAdminCfgFileName = "chat-rpc-admin.yml" ChatRPCChatCfgFileName = "chat-rpc-chat.yml" + ChatRPCBotCfgFileName = "chat-rpc-bot.yml" ) var EnvPrefixMap map[string]string diff --git a/pkg/common/constant/user_id.go b/pkg/common/constant/user_id.go new file mode 100644 index 000000000..5cc00da8a --- /dev/null +++ b/pkg/common/constant/user_id.go @@ -0,0 +1,5 @@ +package constant + +const ( + AgentUserIDPrefix = "bot_" +) diff --git a/pkg/common/convert/agent.go b/pkg/common/convert/agent.go new file mode 100644 index 000000000..15eb49b57 --- /dev/null +++ b/pkg/common/convert/agent.go @@ -0,0 +1,41 @@ +package convert + +import ( + "time" + + "github.com/openimsdk/chat/pkg/common/db/table/bot" + pbbot "github.com/openimsdk/chat/pkg/protocol/bot" + "github.com/openimsdk/tools/utils/datautil" +) + +func DB2PBAgent(a *bot.Agent) *pbbot.Agent { + return &pbbot.Agent{ + UserID: a.UserID, + Nickname: a.NickName, + FaceURL: a.FaceURL, + Url: a.Url, + Key: a.Key, + Identity: a.Identity, + Model: a.Model, + Prompts: a.Prompts, + CreateTime: a.CreateTime.UnixMilli(), + } +} + +func PB2DBAgent(a *pbbot.Agent) *bot.Agent { + return &bot.Agent{ + UserID: a.UserID, + NickName: a.Nickname, + FaceURL: a.FaceURL, + Key: a.Key, + Url: a.Url, + Identity: a.Identity, + Model: a.Model, + Prompts: a.Prompts, + CreateTime: time.UnixMilli(a.CreateTime), + } +} + +func BatchDB2PBAgent(a []*bot.Agent) []*pbbot.Agent { + return datautil.Batch(DB2PBAgent, a) +} diff --git a/pkg/common/db/cache/imtoken.go b/pkg/common/db/cache/imtoken.go new file mode 100644 index 000000000..3d038d009 --- /dev/null +++ b/pkg/common/db/cache/imtoken.go @@ -0,0 +1,50 @@ +package cache + +import ( + "context" + "time" + + "github.com/openimsdk/tools/errs" + "github.com/redis/go-redis/v9" +) + +const ( + chatPrefix = "CHAT:" + imToken = chatPrefix + "IM_TOKEN:" +) + +func getIMTokenKey(userID string) string { + return imToken + userID +} + +type IMTokenInterface interface { + GetToken(ctx context.Context, userID string) (string, error) + SetToken(ctx context.Context, userID, token string) error +} + +type imTokenCacheRedis struct { + rdb redis.UniversalClient + expire time.Duration +} + +func NewIMTokenInterface(rdb redis.UniversalClient, expire int) IMTokenInterface { + return &imTokenCacheRedis{rdb: rdb, expire: time.Duration(expire) * time.Minute} +} + +func (i *imTokenCacheRedis) GetToken(ctx context.Context, userID string) (string, error) { + key := getIMTokenKey(userID) + token, err := i.rdb.Get(ctx, key).Result() + if err != nil { + return "", errs.Wrap(err) + } + return token, nil +} + +func (i *imTokenCacheRedis) SetToken(ctx context.Context, userID, token string) error { + key := getIMTokenKey(userID) + err := i.rdb.Set(ctx, key, token, i.expire).Err() + if err != nil { + return errs.Wrap(err) + } + return nil +} diff --git a/pkg/common/db/cache/token.go b/pkg/common/db/cache/token.go index a57571ebc..1857b1207 100644 --- a/pkg/common/db/cache/token.go +++ b/pkg/common/db/cache/token.go @@ -16,9 +16,9 @@ package cache import ( "context" + "time" "github.com/openimsdk/tools/utils/stringutil" - "time" "github.com/openimsdk/tools/errs" "github.com/redis/go-redis/v9" diff --git a/pkg/common/db/database/bot.go b/pkg/common/db/database/bot.go new file mode 100644 index 000000000..6db664b41 --- /dev/null +++ b/pkg/common/db/database/bot.go @@ -0,0 +1,77 @@ +package database + +import ( + "context" + + "github.com/openimsdk/chat/pkg/common/db/model/bot" + tablebot "github.com/openimsdk/chat/pkg/common/db/table/bot" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/db/tx" +) + +type BotDatabase interface { + CreateAgent(ctx context.Context, jobs ...*tablebot.Agent) error + TakeAgent(ctx context.Context, userID string) (*tablebot.Agent, error) + FindAgents(ctx context.Context, userIDs []string) ([]*tablebot.Agent, error) + UpdateAgent(ctx context.Context, userID string, data map[string]any) error + DeleteAgents(ctx context.Context, userIDs []string) error + PageAgents(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*tablebot.Agent, error) + + TakeConversationRespID(ctx context.Context, convID, agentID string) (*tablebot.ConversationRespID, error) + UpdateConversationRespID(ctx context.Context, convID, agentID string, data map[string]any) error +} + +type botDatabase struct { + tx tx.Tx + agent tablebot.AgentInterface + convRespID tablebot.ConversationRespIDInterface +} + +func NewBotDatabase(cli *mongoutil.Client) (BotDatabase, error) { + agent, err := bot.NewAgent(cli.GetDB()) + if err != nil { + return nil, err + } + convRespID, err := bot.NewConversationRespID(cli.GetDB()) + if err != nil { + return nil, err + } + return &botDatabase{ + tx: cli.GetTx(), + agent: agent, + convRespID: convRespID, + }, nil +} + +func (a *botDatabase) CreateAgent(ctx context.Context, agents ...*tablebot.Agent) error { + return a.agent.Create(ctx, agents...) +} + +func (a *botDatabase) TakeAgent(ctx context.Context, userID string) (*tablebot.Agent, error) { + return a.agent.Take(ctx, userID) +} + +func (a *botDatabase) FindAgents(ctx context.Context, userIDs []string) ([]*tablebot.Agent, error) { + return a.agent.Find(ctx, userIDs) +} + +func (a *botDatabase) UpdateAgent(ctx context.Context, userID string, data map[string]any) error { + return a.agent.Update(ctx, userID, data) +} + +func (a *botDatabase) DeleteAgents(ctx context.Context, userIDs []string) error { + return a.agent.Delete(ctx, userIDs) +} + +func (a *botDatabase) PageAgents(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*tablebot.Agent, error) { + return a.agent.Page(ctx, userIDs, pagination) +} + +func (a *botDatabase) UpdateConversationRespID(ctx context.Context, convID, agentID string, data map[string]any) error { + return a.convRespID.Update(ctx, convID, agentID, data) +} + +func (a *botDatabase) TakeConversationRespID(ctx context.Context, convID, agentID string) (*tablebot.ConversationRespID, error) { + return a.convRespID.Take(ctx, convID, agentID) +} diff --git a/pkg/common/db/database/imtoken.go b/pkg/common/db/database/imtoken.go new file mode 100644 index 000000000..959d3207c --- /dev/null +++ b/pkg/common/db/database/imtoken.go @@ -0,0 +1,31 @@ +package database + +import ( + "context" + + "github.com/openimsdk/chat/pkg/common/db/cache" + "github.com/redis/go-redis/v9" +) + +type IMTokenDatabase interface { + GetIMToken(ctx context.Context, userID string) (string, error) + SetIMToken(ctx context.Context, userID, token string) error +} + +type imTokenDatabase struct { + db cache.IMTokenInterface +} + +func NewIMTokenDatabase(rdb redis.UniversalClient, expire int) IMTokenDatabase { + return &imTokenDatabase{ + db: cache.NewIMTokenInterface(rdb, expire), + } +} + +func (i *imTokenDatabase) GetIMToken(ctx context.Context, userID string) (string, error) { + return i.db.GetToken(ctx, userID) +} + +func (i *imTokenDatabase) SetIMToken(ctx context.Context, userID, token string) error { + return i.db.SetToken(ctx, userID, token) +} diff --git a/pkg/common/db/model/bot/agent.go b/pkg/common/db/model/bot/agent.go new file mode 100644 index 000000000..526aed07c --- /dev/null +++ b/pkg/common/db/model/bot/agent.go @@ -0,0 +1,65 @@ +package bot + +import ( + "context" + + "github.com/openimsdk/chat/pkg/common/db/table/bot" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/errs" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func NewAgent(db *mongo.Database) (bot.AgentInterface, error) { + coll := db.Collection("agent") + _, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{ + Keys: bson.D{ + {Key: "user_id", Value: 1}, + }, + Options: options.Index().SetUnique(true), + }) + if err != nil { + return nil, errs.Wrap(err) + } + return &Agent{coll: coll}, nil +} + +type Agent struct { + coll *mongo.Collection +} + +func (o *Agent) Create(ctx context.Context, elems ...*bot.Agent) error { + return mongoutil.InsertMany(ctx, o.coll, elems) +} + +func (o *Agent) Take(ctx context.Context, userId string) (*bot.Agent, error) { + return mongoutil.FindOne[*bot.Agent](ctx, o.coll, bson.M{"user_id": userId}) +} + +func (o *Agent) Find(ctx context.Context, userIDs []string) ([]*bot.Agent, error) { + return mongoutil.Find[*bot.Agent](ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}}) +} + +func (o *Agent) Update(ctx context.Context, userID string, data map[string]any) error { + if len(data) == 0 { + return nil + } + return mongoutil.UpdateOne(ctx, o.coll, bson.M{"user_id": userID}, bson.M{"$set": data}, false) +} + +func (o *Agent) Delete(ctx context.Context, userIDs []string) error { + if len(userIDs) == 0 { + return nil + } + return mongoutil.DeleteMany(ctx, o.coll, bson.M{"user_id": bson.M{"$in": userIDs}}) +} + +func (o *Agent) Page(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*bot.Agent, error) { + filter := bson.M{} + if len(userIDs) > 0 { + filter["user_id"] = bson.M{"$in": userIDs} + } + return mongoutil.FindPage[*bot.Agent](ctx, o.coll, filter, pagination) +} diff --git a/pkg/common/db/model/bot/conversation_resp_id.go b/pkg/common/db/model/bot/conversation_resp_id.go new file mode 100644 index 000000000..9ec5b2a86 --- /dev/null +++ b/pkg/common/db/model/bot/conversation_resp_id.go @@ -0,0 +1,50 @@ +package bot + +import ( + "context" + + "github.com/openimsdk/chat/pkg/common/db/table/bot" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/errs" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func NewConversationRespID(db *mongo.Database) (bot.ConversationRespIDInterface, error) { + coll := db.Collection("conversation_resp_id") + _, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{ + Keys: bson.D{ + {Key: "conversation_id", Value: 1}, + {Key: "agent_id", Value: 1}, + }, + Options: options.Index().SetUnique(true), + }) + if err != nil { + return nil, errs.Wrap(err) + } + return &ConversationRespID{coll: coll}, nil +} + +type ConversationRespID struct { + coll *mongo.Collection +} + +func (o *ConversationRespID) Create(ctx context.Context, elems ...*bot.ConversationRespID) error { + return mongoutil.InsertMany(ctx, o.coll, elems) +} + +func (o *ConversationRespID) Take(ctx context.Context, convID, agentID string) (*bot.ConversationRespID, error) { + return mongoutil.FindOne[*bot.ConversationRespID](ctx, o.coll, bson.M{"conversation_id": convID, "agent_id": agentID}) +} + +func (o *ConversationRespID) Update(ctx context.Context, convID, agentID string, data map[string]any) error { + if len(data) == 0 { + return nil + } + return mongoutil.UpdateOne(ctx, o.coll, bson.M{"conv_id": convID, "agent_id": agentID}, bson.M{"$set": data}, false, options.Update().SetUpsert(true)) +} + +func (o *ConversationRespID) Delete(ctx context.Context, convID, agentID string) error { + return mongoutil.DeleteMany(ctx, o.coll, bson.M{"conv_id": convID, "agent_id": agentID}) +} diff --git a/pkg/common/db/table/bot/agent.go b/pkg/common/db/table/bot/agent.go new file mode 100644 index 000000000..a78dd3046 --- /dev/null +++ b/pkg/common/db/table/bot/agent.go @@ -0,0 +1,33 @@ +package bot + +import ( + "context" + "time" + + "github.com/openimsdk/tools/db/pagination" +) + +type Agent struct { + UserID string `bson:"user_id"` + NickName string `bson:"nick_name"` + FaceURL string `bson:"face_url"` + Key string `bson:"key"` + Url string `bson:"url"` + Identity string `bson:"identity"` + Model string `bson:"model"` + Prompts string `bson:"prompts"` + CreateTime time.Time `bson:"create_time"` +} + +func (Agent) TableName() string { + return "agent" +} + +type AgentInterface interface { + Create(ctx context.Context, elems ...*Agent) error + Take(ctx context.Context, userID string) (*Agent, error) + Find(ctx context.Context, userIDs []string) ([]*Agent, error) + Update(ctx context.Context, userID string, data map[string]any) error + Delete(ctx context.Context, userIDs []string) error + Page(ctx context.Context, userIDs []string, pagination pagination.Pagination) (int64, []*Agent, error) +} diff --git a/pkg/common/db/table/bot/conversation_resp_id.go b/pkg/common/db/table/bot/conversation_resp_id.go new file mode 100644 index 000000000..ada503e94 --- /dev/null +++ b/pkg/common/db/table/bot/conversation_resp_id.go @@ -0,0 +1,22 @@ +package bot + +import ( + "context" +) + +type ConversationRespID struct { + ConversationID string `bson:"conversation_id"` + AgentID string `bson:"agent_id"` + PreviousResponseID string `bson:"previous_response_id"` +} + +func (ConversationRespID) TableName() string { + return "conversation_resp_id" +} + +type ConversationRespIDInterface interface { + Create(ctx context.Context, elems ...*ConversationRespID) error + Take(ctx context.Context, convID, agentID string) (*ConversationRespID, error) + Update(ctx context.Context, convID, agentID string, data map[string]any) error + Delete(ctx context.Context, convID, agentID string) error +} diff --git a/pkg/common/imapi/api.go b/pkg/common/imapi/api.go index 60891be0f..0e9c4bdb8 100644 --- a/pkg/common/imapi/api.go +++ b/pkg/common/imapi/api.go @@ -23,15 +23,24 @@ import ( // im caller. var ( - importFriend = NewApiCaller[relation.ImportFriendReq, relation.ImportFriendResp]("/friend/import_friend") - getAdminToken = NewApiCaller[auth.GetAdminTokenReq, auth.GetAdminTokenResp]("/auth/get_admin_token") - getuserToken = NewApiCaller[auth.GetUserTokenReq, auth.GetUserTokenResp]("/auth/get_user_token") - inviteToGroup = NewApiCaller[group.InviteUserToGroupReq, group.InviteUserToGroupResp]("/group/invite_user_to_group") - updateUserInfo = NewApiCaller[user.UpdateUserInfoReq, user.UpdateUserInfoResp]("/user/update_user_info") - registerUser = NewApiCaller[user.UserRegisterReq, user.UserRegisterResp]("/user/user_register") - forceOffLine = NewApiCaller[auth.ForceLogoutReq, auth.ForceLogoutResp]("/auth/force_logout") - getGroupsInfo = NewApiCaller[group.GetGroupsInfoReq, group.GetGroupsInfoResp]("/group/get_groups_info") + getAdminToken = NewApiCaller[auth.GetAdminTokenReq, auth.GetAdminTokenResp]("/auth/get_admin_token") + getuserToken = NewApiCaller[auth.GetUserTokenReq, auth.GetUserTokenResp]("/auth/get_user_token") + forceOffLine = NewApiCaller[auth.ForceLogoutReq, auth.ForceLogoutResp]("/auth/force_logout") + + updateUserInfo = NewApiCaller[user.UpdateUserInfoReq, user.UpdateUserInfoResp]("/user/update_user_info") + registerUser = NewApiCaller[user.UserRegisterReq, user.UserRegisterResp]("/user/user_register") + getUserInfo = NewApiCaller[user.GetDesignateUsersReq, user.GetDesignateUsersResp]("/user/get_users_info") + accountCheck = NewApiCaller[user.AccountCheckReq, user.AccountCheckResp]("/user/account_check") + addNotificationAccount = NewApiCaller[user.AddNotificationAccountReq, user.AddNotificationAccountResp]("/user/add_notification_account") + updateNotificationAccount = NewApiCaller[user.UpdateNotificationAccountInfoReq, user.UpdateNotificationAccountInfoResp]("/user/update_notification_account") + + getGroupsInfo = NewApiCaller[group.GetGroupsInfoReq, group.GetGroupsInfoResp]("/group/get_groups_info") + inviteToGroup = NewApiCaller[group.InviteUserToGroupReq, group.InviteUserToGroupResp]("/group/invite_user_to_group") + registerUserCount = NewApiCaller[user.UserRegisterCountReq, user.UserRegisterCountResp]("/statistics/user/register") - friendUserIDs = NewApiCaller[relation.GetFriendIDsReq, relation.GetFriendIDsResp]("/friend/get_friend_id") - accountCheck = NewApiCaller[user.AccountCheckReq, user.AccountCheckResp]("/user/account_check") + + friendUserIDs = NewApiCaller[relation.GetFriendIDsReq, relation.GetFriendIDsResp]("/friend/get_friend_id") + importFriend = NewApiCaller[relation.ImportFriendReq, relation.ImportFriendResp]("/friend/import_friend") + + sendSimpleMsg = NewApiCaller[SendSingleMsgReq, SendSingleMsgResp]("/msg/send_simple_msg") ) diff --git a/pkg/common/imapi/call.go b/pkg/common/imapi/call.go index 2b5646e3c..968262160 100644 --- a/pkg/common/imapi/call.go +++ b/pkg/common/imapi/call.go @@ -20,6 +20,7 @@ import ( "encoding/json" "io" "net/http" + "net/url" "time" "github.com/openimsdk/chat/pkg/common/constant" @@ -42,6 +43,7 @@ var client = &http.Client{ type ApiCaller[Req, Resp any] interface { Call(ctx context.Context, apiPrefix string, req *Req) (*Resp, error) + CallWithQuery(ctx context.Context, apiPrefix string, req *Req, queryParams map[string]string) (*Resp, error) } func NewApiCaller[Req, Resp any](api string) ApiCaller[Req, Resp] { @@ -58,7 +60,7 @@ func (a caller[Req, Resp]) Call(ctx context.Context, apiPrefix string, req *Req) start := time.Now() resp, err := a.call(ctx, apiPrefix, req) if err != nil { - log.ZError(ctx, "api caller failed", err, "api", a.api, "duration", time.Since(start), "req", req) + log.ZError(ctx, "api caller failed", err, "api", a.api, "duration", time.Since(start), "req", req, "resp", resp) return nil, err } log.ZInfo(ctx, "api caller success resp", "api", a.api, "duration", time.Since(start), "req", req, "resp", resp) @@ -98,3 +100,61 @@ func (a caller[Req, Resp]) call(ctx context.Context, apiPrefix string, req *Req) } return resp.Data, nil } + +func (a caller[Req, Resp]) CallWithQuery(ctx context.Context, apiPrefix string, req *Req, queryParams map[string]string) (*Resp, error) { + start := time.Now() + resp, err := a.callWithQuery(ctx, apiPrefix, req, queryParams) + if err != nil { + log.ZError(ctx, "api caller failed", err, "api", a.api, "duration", time.Since(start), "req", req, "resp", resp) + return nil, err + } + log.ZInfo(ctx, "api caller success resp", "api", a.api, "duration", time.Since(start), "req", req, "resp", resp) + return resp, nil +} + +func (a caller[Req, Resp]) callWithQuery(ctx context.Context, apiPrefix string, req *Req, queryParams map[string]string) (*Resp, error) { + fullURL := apiPrefix + a.api + parsedURL, err := url.Parse(fullURL) + if err != nil { + return nil, errs.WrapMsg(err, "failed to parse URL", fullURL) + } + + query := parsedURL.Query() + + for key, value := range queryParams { + query.Set(key, value) + } + + parsedURL.RawQuery = query.Encode() + fullURL = parsedURL.String() + reqBody, err := json.Marshal(req) + if err != nil { + return nil, err + } + request, err := http.NewRequestWithContext(ctx, http.MethodPost, fullURL, bytes.NewReader(reqBody)) + if err != nil { + return nil, err + } + operationID := utils.ToString(ctx.Value(constantpb.OperationID)) + request.Header.Set(constantpb.OperationID, operationID) + if token, _ := ctx.Value(constant.CtxApiToken).(string); token != "" { + request.Header.Set(constantpb.Token, token) + } + response, err := client.Do(request) + if err != nil { + return nil, err + } + defer response.Body.Close() + data, err := io.ReadAll(response.Body) + if err != nil { + return nil, errs.WrapMsg(err, "read http response body", "fullUrl", fullURL, "code", response.StatusCode) + } + var resp baseApiResponse[Resp] + if err := json.Unmarshal(data, &resp); err != nil { + return nil, errs.WrapMsg(err, string(data)) + } + if resp.ErrCode != 0 { + return nil, errs.NewCodeError(resp.ErrCode, resp.ErrMsg).WithDetail(resp.ErrDlt).Wrap() + } + return resp.Data, nil +} diff --git a/pkg/common/imapi/caller.go b/pkg/common/imapi/caller.go index 1e091ec1a..298bdfe00 100644 --- a/pkg/common/imapi/caller.go +++ b/pkg/common/imapi/caller.go @@ -2,15 +2,19 @@ package imapi import ( "context" + "errors" "sync" "time" + "github.com/openimsdk/chat/pkg/botstruct" + "github.com/openimsdk/chat/pkg/common/db/database" + "github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/log" + "github.com/redis/go-redis/v9" "github.com/openimsdk/chat/pkg/eerrs" "github.com/openimsdk/protocol/auth" "github.com/openimsdk/protocol/constant" - constantpb "github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/group" "github.com/openimsdk/protocol/relation" "github.com/openimsdk/protocol/sdkws" @@ -22,14 +26,22 @@ type CallerInterface interface { ImportFriend(ctx context.Context, ownerUserID string, friendUserID []string) error GetUserToken(ctx context.Context, userID string, platform int32) (string, error) GetAdminTokenCache(ctx context.Context, userID string) (string, error) + GetAdminTokenServer(ctx context.Context, userID string) (string, error) InviteToGroup(ctx context.Context, userID string, groupIDs []string) error + UpdateUserInfo(ctx context.Context, userID string, nickName string, faceURL string) error + GetUserInfo(ctx context.Context, userID string) (*sdkws.UserInfo, error) + GetUsersInfo(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error) + AddNotificationAccount(ctx context.Context, req *user.AddNotificationAccountReq) error + UpdateNotificationAccount(ctx context.Context, req *user.UpdateNotificationAccountInfoReq) error + ForceOffLine(ctx context.Context, userID string) error RegisterUser(ctx context.Context, users []*sdkws.UserInfo) error FindGroupInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) UserRegisterCount(ctx context.Context, start int64, end int64) (map[string]int64, int64, error) FriendUserIDs(ctx context.Context, userID string) ([]string, error) AccountCheckSingle(ctx context.Context, userID string) (bool, error) + SendSimpleMsg(ctx context.Context, req *SendSingleMsgReq, key string) error } type authToken struct { @@ -43,15 +55,18 @@ type Caller struct { defaultIMUserID string tokenCache map[string]*authToken lock sync.RWMutex + tokenDB database.IMTokenDatabase } -func New(imApi string, imSecret string, defaultIMUserID string) CallerInterface { +func New(imApi string, imSecret string, defaultIMUserID string, rdb redis.UniversalClient, refreshInterval int) CallerInterface { + tokenDB := database.NewIMTokenDatabase(rdb, refreshInterval) return &Caller{ imApi: imApi, imSecret: imSecret, defaultIMUserID: defaultIMUserID, tokenCache: make(map[string]*authToken), lock: sync.RWMutex{}, + tokenDB: tokenDB, } } @@ -79,20 +94,30 @@ func (c *Caller) GetAdminTokenCache(ctx context.Context, userID string) (string, defer c.lock.Unlock() t, ok = c.tokenCache[userID] if !ok || t.timeout.Before(time.Now()) { - token, err := c.getAdminTokenServer(ctx, userID) - if err != nil { - log.ZError(ctx, "get im admin token", err, "userID", userID) + token, err := c.tokenDB.GetIMToken(ctx, userID) + if err != nil && !errors.Is(err, redis.Nil) { return "", err + } else if errors.Is(err, redis.Nil) { + // no token in redis cache + token, err = c.GetAdminTokenServer(ctx, userID) + if err != nil { + log.ZError(ctx, "get im admin token from server", err, "userID", userID) + return "", err + } + t = &authToken{token: token, timeout: time.Now().Add(time.Minute * 4)} + c.tokenCache[userID] = t + } else { + log.ZDebug(ctx, "get im admin token from cache", "userID", userID, "token", token) + t = &authToken{token: token, timeout: time.Now().Add(time.Minute * 4)} + c.tokenCache[userID] = t } - log.ZDebug(ctx, "get im admin token", "userID", userID) - t = &authToken{token: token, timeout: time.Now().Add(time.Minute * 5)} - c.tokenCache[userID] = t + } } return t.token, nil } -func (c *Caller) getAdminTokenServer(ctx context.Context, userID string) (string, error) { +func (c *Caller) GetAdminTokenServer(ctx context.Context, userID string) (string, error) { resp, err := getAdminToken.Call(ctx, c.imApi, &auth.GetAdminTokenReq{ Secret: c.imSecret, UserID: userID, @@ -100,6 +125,11 @@ func (c *Caller) getAdminTokenServer(ctx context.Context, userID string) (string if err != nil { return "", err } + log.ZDebug(ctx, "get im admin token from server", "userID", userID, "token", resp.Token) + err = c.tokenDB.SetIMToken(ctx, userID, resp.Token) + if err != nil { + log.ZWarn(ctx, "set im admin token to redis failed", err, "userID", userID) + } return resp.Token, nil } @@ -134,6 +164,27 @@ func (c *Caller) UpdateUserInfo(ctx context.Context, userID string, nickName str return err } +func (c *Caller) GetUserInfo(ctx context.Context, userID string) (*sdkws.UserInfo, error) { + resp, err := c.GetUsersInfo(ctx, []string{userID}) + if err != nil { + return nil, err + } + if len(resp) == 0 { + return nil, errs.ErrRecordNotFound.WrapMsg("record not found") + } + return resp[0], nil +} + +func (c *Caller) GetUsersInfo(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error) { + resp, err := getUserInfo.Call(ctx, c.imApi, &user.GetDesignateUsersReq{ + UserIDs: userIDs, + }) + if err != nil { + return nil, err + } + return resp.UsersInfo, nil +} + func (c *Caller) RegisterUser(ctx context.Context, users []*sdkws.UserInfo) error { _, err := registerUser.Call(ctx, c.imApi, &user.UserRegisterReq{ Users: users, @@ -142,7 +193,7 @@ func (c *Caller) RegisterUser(ctx context.Context, users []*sdkws.UserInfo) erro } func (c *Caller) ForceOffLine(ctx context.Context, userID string) error { - for id := range constantpb.PlatformID2Name { + for id := range constant.PlatformID2Name { _, _ = forceOffLine.Call(ctx, c.imApi, &auth.ForceLogoutReq{ PlatformID: int32(id), UserID: userID, @@ -191,3 +242,18 @@ func (c *Caller) AccountCheckSingle(ctx context.Context, userID string) (bool, e } return true, nil } + +func (c *Caller) SendSimpleMsg(ctx context.Context, req *SendSingleMsgReq, key string) error { + _, err := sendSimpleMsg.CallWithQuery(ctx, c.imApi, req, map[string]string{botstruct.Key: key}) + return err +} + +func (c *Caller) AddNotificationAccount(ctx context.Context, req *user.AddNotificationAccountReq) error { + _, err := addNotificationAccount.Call(ctx, c.imApi, req) + return err +} + +func (c *Caller) UpdateNotificationAccount(ctx context.Context, req *user.UpdateNotificationAccountInfoReq) error { + _, err := updateNotificationAccount.Call(ctx, c.imApi, req) + return err +} diff --git a/pkg/common/imapi/model.go b/pkg/common/imapi/model.go new file mode 100644 index 000000000..c2db36be4 --- /dev/null +++ b/pkg/common/imapi/model.go @@ -0,0 +1,13 @@ +package imapi + +import "github.com/openimsdk/protocol/sdkws" + +// SendSingleMsgReq defines the structure for sending a message to multiple recipients. +type SendSingleMsgReq struct { + // groupMsg should appoint sendID + SendID string `json:"sendID"` + Content string `json:"content" binding:"required"` + OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"` + Ex string `json:"ex"` +} +type SendSingleMsgResp struct{} diff --git a/pkg/common/imwebhook/message.go b/pkg/common/imwebhook/message.go new file mode 100644 index 000000000..f09280a85 --- /dev/null +++ b/pkg/common/imwebhook/message.go @@ -0,0 +1,48 @@ +package imwebhook + +type CommonCallbackReq struct { + SendID string `json:"sendID"` + CallbackCommand string `json:"callbackCommand"` + ServerMsgID string `json:"serverMsgID"` + ClientMsgID string `json:"clientMsgID"` + OperationID string `json:"operationID"` + SenderPlatformID int32 `json:"senderPlatformID"` + SenderNickname string `json:"senderNickname"` + SessionType int32 `json:"sessionType"` + MsgFrom int32 `json:"msgFrom"` + ContentType int32 `json:"contentType"` + Status int32 `json:"status"` + SendTime int64 `json:"sendTime"` + CreateTime int64 `json:"createTime"` + Content string `json:"content"` + Seq uint32 `json:"seq"` + AtUserIDList []string `json:"atUserList"` + SenderFaceURL string `json:"faceURL"` + Ex string `json:"ex"` +} + +type CommonCallbackResp struct { + ActionCode int32 `json:"actionCode"` + ErrCode int32 `json:"errCode"` + ErrMsg string `json:"errMsg"` + ErrDlt string `json:"errDlt"` + NextCode int32 `json:"nextCode"` +} + +type CallbackAfterSendSingleMsgReq struct { + CommonCallbackReq + RecvID string `json:"recvID"` +} + +type CallbackAfterSendSingleMsgResp struct { + CommonCallbackResp +} + +type CallbackAfterSendGroupMsgReq struct { + CommonCallbackReq + GroupID string `json:"groupID"` +} + +type CallbackAfterSendGroupMsgResp struct { + CommonCallbackResp +} diff --git a/pkg/protocol/bot/bot.pb.go b/pkg/protocol/bot/bot.pb.go new file mode 100644 index 000000000..3376342f9 --- /dev/null +++ b/pkg/protocol/bot/bot.pb.go @@ -0,0 +1,829 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.1 +// protoc v5.29.0 +// source: bot/bot.proto + +package bot + +import ( + sdkws "github.com/openimsdk/protocol/sdkws" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Agent struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserID string `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID"` + Nickname string `protobuf:"bytes,2,opt,name=nickname,proto3" json:"nickname"` + FaceURL string `protobuf:"bytes,3,opt,name=faceURL,proto3" json:"faceURL"` + Url string `protobuf:"bytes,4,opt,name=url,proto3" json:"url"` + Key string `protobuf:"bytes,5,opt,name=key,proto3" json:"key"` + Identity string `protobuf:"bytes,6,opt,name=identity,proto3" json:"identity"` + Model string `protobuf:"bytes,7,opt,name=model,proto3" json:"model"` + Prompts string `protobuf:"bytes,8,opt,name=prompts,proto3" json:"prompts"` + CreateTime int64 `protobuf:"varint,9,opt,name=createTime,proto3" json:"createTime"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *Agent) Reset() { + *x = Agent{} + mi := &file_bot_bot_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *Agent) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Agent) ProtoMessage() {} + +func (x *Agent) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Agent.ProtoReflect.Descriptor instead. +func (*Agent) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{0} +} + +func (x *Agent) GetUserID() string { + if x != nil { + return x.UserID + } + return "" +} + +func (x *Agent) GetNickname() string { + if x != nil { + return x.Nickname + } + return "" +} + +func (x *Agent) GetFaceURL() string { + if x != nil { + return x.FaceURL + } + return "" +} + +func (x *Agent) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Agent) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Agent) GetIdentity() string { + if x != nil { + return x.Identity + } + return "" +} + +func (x *Agent) GetModel() string { + if x != nil { + return x.Model + } + return "" +} + +func (x *Agent) GetPrompts() string { + if x != nil { + return x.Prompts + } + return "" +} + +func (x *Agent) GetCreateTime() int64 { + if x != nil { + return x.CreateTime + } + return 0 +} + +type CreateAgentReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Agent *Agent `protobuf:"bytes,1,opt,name=agent,proto3" json:"agent"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAgentReq) Reset() { + *x = CreateAgentReq{} + mi := &file_bot_bot_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAgentReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAgentReq) ProtoMessage() {} + +func (x *CreateAgentReq) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAgentReq.ProtoReflect.Descriptor instead. +func (*CreateAgentReq) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{1} +} + +func (x *CreateAgentReq) GetAgent() *Agent { + if x != nil { + return x.Agent + } + return nil +} + +type CreateAgentResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *CreateAgentResp) Reset() { + *x = CreateAgentResp{} + mi := &file_bot_bot_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *CreateAgentResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CreateAgentResp) ProtoMessage() {} + +func (x *CreateAgentResp) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CreateAgentResp.ProtoReflect.Descriptor instead. +func (*CreateAgentResp) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{2} +} + +type UpdateAgentReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserID string `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID"` + Nickname *string `protobuf:"bytes,2,opt,name=nickname,proto3,oneof" json:"nickname"` + FaceURL *string `protobuf:"bytes,3,opt,name=faceURL,proto3,oneof" json:"faceURL"` + Url *string `protobuf:"bytes,4,opt,name=url,proto3,oneof" json:"url"` + Key *string `protobuf:"bytes,5,opt,name=key,proto3,oneof" json:"key"` + Identity *string `protobuf:"bytes,6,opt,name=identity,proto3,oneof" json:"identity"` + Model *string `protobuf:"bytes,7,opt,name=model,proto3,oneof" json:"model"` + Prompts *string `protobuf:"bytes,8,opt,name=prompts,proto3,oneof" json:"prompts"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateAgentReq) Reset() { + *x = UpdateAgentReq{} + mi := &file_bot_bot_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAgentReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAgentReq) ProtoMessage() {} + +func (x *UpdateAgentReq) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAgentReq.ProtoReflect.Descriptor instead. +func (*UpdateAgentReq) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{3} +} + +func (x *UpdateAgentReq) GetUserID() string { + if x != nil { + return x.UserID + } + return "" +} + +func (x *UpdateAgentReq) GetNickname() string { + if x != nil && x.Nickname != nil { + return *x.Nickname + } + return "" +} + +func (x *UpdateAgentReq) GetFaceURL() string { + if x != nil && x.FaceURL != nil { + return *x.FaceURL + } + return "" +} + +func (x *UpdateAgentReq) GetUrl() string { + if x != nil && x.Url != nil { + return *x.Url + } + return "" +} + +func (x *UpdateAgentReq) GetKey() string { + if x != nil && x.Key != nil { + return *x.Key + } + return "" +} + +func (x *UpdateAgentReq) GetIdentity() string { + if x != nil && x.Identity != nil { + return *x.Identity + } + return "" +} + +func (x *UpdateAgentReq) GetModel() string { + if x != nil && x.Model != nil { + return *x.Model + } + return "" +} + +func (x *UpdateAgentReq) GetPrompts() string { + if x != nil && x.Prompts != nil { + return *x.Prompts + } + return "" +} + +type UpdateAgentResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *UpdateAgentResp) Reset() { + *x = UpdateAgentResp{} + mi := &file_bot_bot_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UpdateAgentResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UpdateAgentResp) ProtoMessage() {} + +func (x *UpdateAgentResp) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UpdateAgentResp.ProtoReflect.Descriptor instead. +func (*UpdateAgentResp) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{4} +} + +type PageFindAgentReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + Pagination *sdkws.RequestPagination `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination"` + UserIDs []string `protobuf:"bytes,2,rep,name=userIDs,proto3" json:"userIDs"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PageFindAgentReq) Reset() { + *x = PageFindAgentReq{} + mi := &file_bot_bot_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PageFindAgentReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PageFindAgentReq) ProtoMessage() {} + +func (x *PageFindAgentReq) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PageFindAgentReq.ProtoReflect.Descriptor instead. +func (*PageFindAgentReq) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{5} +} + +func (x *PageFindAgentReq) GetPagination() *sdkws.RequestPagination { + if x != nil { + return x.Pagination + } + return nil +} + +func (x *PageFindAgentReq) GetUserIDs() []string { + if x != nil { + return x.UserIDs + } + return nil +} + +type PageFindAgentResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + Total int64 `protobuf:"varint,1,opt,name=total,proto3" json:"total"` + Agents []*Agent `protobuf:"bytes,2,rep,name=agents,proto3" json:"agents"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *PageFindAgentResp) Reset() { + *x = PageFindAgentResp{} + mi := &file_bot_bot_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *PageFindAgentResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*PageFindAgentResp) ProtoMessage() {} + +func (x *PageFindAgentResp) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use PageFindAgentResp.ProtoReflect.Descriptor instead. +func (*PageFindAgentResp) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{6} +} + +func (x *PageFindAgentResp) GetTotal() int64 { + if x != nil { + return x.Total + } + return 0 +} + +func (x *PageFindAgentResp) GetAgents() []*Agent { + if x != nil { + return x.Agents + } + return nil +} + +type DeleteAgentReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + UserIDs []string `protobuf:"bytes,1,rep,name=userIDs,proto3" json:"userIDs"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAgentReq) Reset() { + *x = DeleteAgentReq{} + mi := &file_bot_bot_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAgentReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAgentReq) ProtoMessage() {} + +func (x *DeleteAgentReq) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAgentReq.ProtoReflect.Descriptor instead. +func (*DeleteAgentReq) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{7} +} + +func (x *DeleteAgentReq) GetUserIDs() []string { + if x != nil { + return x.UserIDs + } + return nil +} + +type DeleteAgentResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteAgentResp) Reset() { + *x = DeleteAgentResp{} + mi := &file_bot_bot_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteAgentResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteAgentResp) ProtoMessage() {} + +func (x *DeleteAgentResp) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteAgentResp.ProtoReflect.Descriptor instead. +func (*DeleteAgentResp) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{8} +} + +type SendBotMessageReq struct { + state protoimpl.MessageState `protogen:"open.v1"` + AgentID string `protobuf:"bytes,1,opt,name=agentID,proto3" json:"agentID"` + ConversationID string `protobuf:"bytes,2,opt,name=conversationID,proto3" json:"conversationID"` + ContentType int32 `protobuf:"varint,3,opt,name=contentType,proto3" json:"contentType"` + Content string `protobuf:"bytes,4,opt,name=content,proto3" json:"content"` + Ex string `protobuf:"bytes,5,opt,name=ex,proto3" json:"ex"` + Key string `protobuf:"bytes,6,opt,name=key,proto3" json:"key"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendBotMessageReq) Reset() { + *x = SendBotMessageReq{} + mi := &file_bot_bot_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendBotMessageReq) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendBotMessageReq) ProtoMessage() {} + +func (x *SendBotMessageReq) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendBotMessageReq.ProtoReflect.Descriptor instead. +func (*SendBotMessageReq) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{9} +} + +func (x *SendBotMessageReq) GetAgentID() string { + if x != nil { + return x.AgentID + } + return "" +} + +func (x *SendBotMessageReq) GetConversationID() string { + if x != nil { + return x.ConversationID + } + return "" +} + +func (x *SendBotMessageReq) GetContentType() int32 { + if x != nil { + return x.ContentType + } + return 0 +} + +func (x *SendBotMessageReq) GetContent() string { + if x != nil { + return x.Content + } + return "" +} + +func (x *SendBotMessageReq) GetEx() string { + if x != nil { + return x.Ex + } + return "" +} + +func (x *SendBotMessageReq) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +type SendBotMessageResp struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *SendBotMessageResp) Reset() { + *x = SendBotMessageResp{} + mi := &file_bot_bot_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *SendBotMessageResp) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*SendBotMessageResp) ProtoMessage() {} + +func (x *SendBotMessageResp) ProtoReflect() protoreflect.Message { + mi := &file_bot_bot_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use SendBotMessageResp.ProtoReflect.Descriptor instead. +func (*SendBotMessageResp) Descriptor() ([]byte, []int) { + return file_bot_bot_proto_rawDescGZIP(), []int{10} +} + +var File_bot_bot_proto protoreflect.FileDescriptor + +var file_bot_bot_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x62, 0x6f, 0x74, 0x2f, 0x62, 0x6f, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x0a, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x1a, 0x11, 0x73, 0x64, 0x6b, + 0x77, 0x73, 0x2f, 0x73, 0x64, 0x6b, 0x77, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe5, + 0x01, 0x0a, 0x05, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, + 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, + 0x12, 0x1a, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x66, 0x61, 0x63, 0x65, 0x55, 0x52, 0x4c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x66, + 0x61, 0x63, 0x65, 0x55, 0x52, 0x4c, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x69, 0x64, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x12, 0x18, 0x0a, 0x07, + 0x70, 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x70, + 0x72, 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x54, 0x69, 0x6d, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x63, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x22, 0x39, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x27, 0x0a, 0x05, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, + 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x05, 0x61, 0x67, 0x65, 0x6e, + 0x74, 0x22, 0x11, 0x0a, 0x0f, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x52, 0x65, 0x73, 0x70, 0x22, 0xbd, 0x02, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x16, 0x0a, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, + 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x12, + 0x1f, 0x0a, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x00, 0x52, 0x08, 0x6e, 0x69, 0x63, 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, + 0x12, 0x1d, 0x0a, 0x07, 0x66, 0x61, 0x63, 0x65, 0x55, 0x52, 0x4c, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x48, 0x01, 0x52, 0x07, 0x66, 0x61, 0x63, 0x65, 0x55, 0x52, 0x4c, 0x88, 0x01, 0x01, 0x12, + 0x15, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x03, + 0x75, 0x72, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x15, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x03, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x88, 0x01, 0x01, 0x12, 0x1f, 0x0a, + 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x48, + 0x04, 0x52, 0x08, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x88, 0x01, 0x01, 0x12, 0x19, + 0x0a, 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x48, 0x05, 0x52, + 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x1d, 0x0a, 0x07, 0x70, 0x72, 0x6f, + 0x6d, 0x70, 0x74, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x48, 0x06, 0x52, 0x07, 0x70, 0x72, + 0x6f, 0x6d, 0x70, 0x74, 0x73, 0x88, 0x01, 0x01, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x6e, 0x69, 0x63, + 0x6b, 0x6e, 0x61, 0x6d, 0x65, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x66, 0x61, 0x63, 0x65, 0x55, 0x52, + 0x4c, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x75, 0x72, 0x6c, 0x42, 0x06, 0x0a, 0x04, 0x5f, 0x6b, 0x65, + 0x79, 0x42, 0x0b, 0x0a, 0x09, 0x5f, 0x69, 0x64, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x42, 0x08, + 0x0a, 0x06, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x42, 0x0a, 0x0a, 0x08, 0x5f, 0x70, 0x72, 0x6f, + 0x6d, 0x70, 0x74, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0x6d, 0x0a, 0x10, 0x50, 0x61, 0x67, 0x65, 0x46, + 0x69, 0x6e, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x3f, 0x0a, 0x0a, 0x70, + 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x1f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x73, 0x64, 0x6b, 0x77, 0x73, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x50, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x0a, 0x70, 0x61, 0x67, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x18, 0x0a, 0x07, + 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x75, + 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x22, 0x54, 0x0a, 0x11, 0x50, 0x61, 0x67, 0x65, 0x46, 0x69, + 0x6e, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x14, 0x0a, 0x05, 0x74, + 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x74, 0x6f, 0x74, 0x61, + 0x6c, 0x12, 0x29, 0x0a, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x41, + 0x67, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x73, 0x22, 0x2a, 0x0a, 0x0e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x12, 0x18, + 0x0a, 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x07, 0x75, 0x73, 0x65, 0x72, 0x49, 0x44, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x22, 0xb3, 0x01, 0x0a, 0x11, + 0x53, 0x65, 0x6e, 0x64, 0x42, 0x6f, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x71, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x61, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x44, 0x12, 0x26, 0x0a, 0x0e, 0x63, + 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x73, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x49, 0x44, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, + 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, + 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x12, + 0x0e, 0x0a, 0x02, 0x65, 0x78, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x65, 0x78, 0x12, + 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x22, 0x14, 0x0a, 0x12, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x6f, 0x74, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x32, 0xfc, 0x02, 0x0a, 0x03, 0x62, 0x6f, 0x74, 0x12, + 0x46, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1a, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6f, 0x70, 0x65, + 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, + 0x62, 0x6f, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, + 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, + 0x4c, 0x0a, 0x0d, 0x50, 0x61, 0x67, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x74, + 0x12, 0x1c, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x50, 0x61, + 0x67, 0x65, 0x46, 0x69, 0x6e, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x1d, + 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x50, 0x61, 0x67, 0x65, + 0x46, 0x69, 0x6e, 0x64, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x46, 0x0a, + 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x12, 0x1a, 0x2e, 0x6f, + 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x1a, 0x1b, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, + 0x6d, 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x41, 0x67, 0x65, 0x6e, + 0x74, 0x52, 0x65, 0x73, 0x70, 0x12, 0x4f, 0x0a, 0x0e, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x6f, 0x74, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1d, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, + 0x2e, 0x62, 0x6f, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x6f, 0x74, 0x4d, 0x65, 0x73, 0x73, + 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x1a, 0x1e, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x2e, + 0x62, 0x6f, 0x74, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x42, 0x6f, 0x74, 0x4d, 0x65, 0x73, 0x73, 0x61, + 0x67, 0x65, 0x52, 0x65, 0x73, 0x70, 0x42, 0x2c, 0x5a, 0x2a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x69, 0x6d, 0x73, 0x64, 0x6b, 0x2f, 0x63, + 0x68, 0x61, 0x74, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, + 0x2f, 0x62, 0x6f, 0x74, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_bot_bot_proto_rawDescOnce sync.Once + file_bot_bot_proto_rawDescData = file_bot_bot_proto_rawDesc +) + +func file_bot_bot_proto_rawDescGZIP() []byte { + file_bot_bot_proto_rawDescOnce.Do(func() { + file_bot_bot_proto_rawDescData = protoimpl.X.CompressGZIP(file_bot_bot_proto_rawDescData) + }) + return file_bot_bot_proto_rawDescData +} + +var file_bot_bot_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_bot_bot_proto_goTypes = []any{ + (*Agent)(nil), // 0: openim.bot.Agent + (*CreateAgentReq)(nil), // 1: openim.bot.CreateAgentReq + (*CreateAgentResp)(nil), // 2: openim.bot.CreateAgentResp + (*UpdateAgentReq)(nil), // 3: openim.bot.UpdateAgentReq + (*UpdateAgentResp)(nil), // 4: openim.bot.UpdateAgentResp + (*PageFindAgentReq)(nil), // 5: openim.bot.PageFindAgentReq + (*PageFindAgentResp)(nil), // 6: openim.bot.PageFindAgentResp + (*DeleteAgentReq)(nil), // 7: openim.bot.DeleteAgentReq + (*DeleteAgentResp)(nil), // 8: openim.bot.DeleteAgentResp + (*SendBotMessageReq)(nil), // 9: openim.bot.SendBotMessageReq + (*SendBotMessageResp)(nil), // 10: openim.bot.SendBotMessageResp + (*sdkws.RequestPagination)(nil), // 11: openim.sdkws.RequestPagination +} +var file_bot_bot_proto_depIdxs = []int32{ + 0, // 0: openim.bot.CreateAgentReq.agent:type_name -> openim.bot.Agent + 11, // 1: openim.bot.PageFindAgentReq.pagination:type_name -> openim.sdkws.RequestPagination + 0, // 2: openim.bot.PageFindAgentResp.agents:type_name -> openim.bot.Agent + 1, // 3: openim.bot.bot.CreateAgent:input_type -> openim.bot.CreateAgentReq + 3, // 4: openim.bot.bot.UpdateAgent:input_type -> openim.bot.UpdateAgentReq + 5, // 5: openim.bot.bot.PageFindAgent:input_type -> openim.bot.PageFindAgentReq + 7, // 6: openim.bot.bot.DeleteAgent:input_type -> openim.bot.DeleteAgentReq + 9, // 7: openim.bot.bot.SendBotMessage:input_type -> openim.bot.SendBotMessageReq + 2, // 8: openim.bot.bot.CreateAgent:output_type -> openim.bot.CreateAgentResp + 4, // 9: openim.bot.bot.UpdateAgent:output_type -> openim.bot.UpdateAgentResp + 6, // 10: openim.bot.bot.PageFindAgent:output_type -> openim.bot.PageFindAgentResp + 8, // 11: openim.bot.bot.DeleteAgent:output_type -> openim.bot.DeleteAgentResp + 10, // 12: openim.bot.bot.SendBotMessage:output_type -> openim.bot.SendBotMessageResp + 8, // [8:13] is the sub-list for method output_type + 3, // [3:8] is the sub-list for method input_type + 3, // [3:3] is the sub-list for extension type_name + 3, // [3:3] is the sub-list for extension extendee + 0, // [0:3] is the sub-list for field type_name +} + +func init() { file_bot_bot_proto_init() } +func file_bot_bot_proto_init() { + if File_bot_bot_proto != nil { + return + } + file_bot_bot_proto_msgTypes[3].OneofWrappers = []any{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_bot_bot_proto_rawDesc, + NumEnums: 0, + NumMessages: 11, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_bot_bot_proto_goTypes, + DependencyIndexes: file_bot_bot_proto_depIdxs, + MessageInfos: file_bot_bot_proto_msgTypes, + }.Build() + File_bot_bot_proto = out.File + file_bot_bot_proto_rawDesc = nil + file_bot_bot_proto_goTypes = nil + file_bot_bot_proto_depIdxs = nil +} diff --git a/pkg/protocol/bot/bot.proto b/pkg/protocol/bot/bot.proto new file mode 100644 index 000000000..6f05d82cc --- /dev/null +++ b/pkg/protocol/bot/bot.proto @@ -0,0 +1,73 @@ +syntax = "proto3"; +package openim.bot; + +import "sdkws/sdkws.proto"; + +option go_package = "github.com/openimsdk/chat/pkg/protocol/bot"; + +message Agent { + string userID = 1; + string nickname = 2; + string faceURL = 3; + string url = 4; + string key = 5; + string identity = 6; + string model = 7; + string prompts = 8; + int64 createTime = 9; +} + +message CreateAgentReq { + Agent agent = 1; +} + +message CreateAgentResp { +} + +message UpdateAgentReq { + string userID = 1; + optional string nickname = 2; + optional string faceURL = 3; + optional string url = 4; + optional string key = 5; + optional string identity = 6; + optional string model = 7; + optional string prompts = 8; +} + +message UpdateAgentResp { +} + +message PageFindAgentReq { + openim.sdkws.RequestPagination pagination = 1; + repeated string userIDs = 2; +} + +message PageFindAgentResp { + int64 total = 1; + repeated Agent agents = 2; +} + +message DeleteAgentReq{ + repeated string userIDs = 1; +} +message DeleteAgentResp{} + +message SendBotMessageReq{ + string agentID = 1; + string conversationID = 2; + int32 contentType = 3; + string content = 4; + string ex = 5; + string key = 6; +} +message SendBotMessageResp{} + +service bot { + rpc CreateAgent(CreateAgentReq) returns (CreateAgentResp); + rpc UpdateAgent(UpdateAgentReq) returns (UpdateAgentResp); + rpc PageFindAgent(PageFindAgentReq) returns (PageFindAgentResp); + rpc DeleteAgent(DeleteAgentReq) returns (DeleteAgentResp); + + rpc SendBotMessage(SendBotMessageReq) returns (SendBotMessageResp); +} diff --git a/pkg/protocol/bot/bot_grpc.pb.go b/pkg/protocol/bot/bot_grpc.pb.go new file mode 100644 index 000000000..98f38ccb3 --- /dev/null +++ b/pkg/protocol/bot/bot_grpc.pb.go @@ -0,0 +1,273 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.29.0 +// source: bot/bot.proto + +package bot + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 + +const ( + Bot_CreateAgent_FullMethodName = "/openim.bot.bot/CreateAgent" + Bot_UpdateAgent_FullMethodName = "/openim.bot.bot/UpdateAgent" + Bot_PageFindAgent_FullMethodName = "/openim.bot.bot/PageFindAgent" + Bot_DeleteAgent_FullMethodName = "/openim.bot.bot/DeleteAgent" + Bot_SendBotMessage_FullMethodName = "/openim.bot.bot/SendBotMessage" +) + +// BotClient is the client API for Bot service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type BotClient interface { + CreateAgent(ctx context.Context, in *CreateAgentReq, opts ...grpc.CallOption) (*CreateAgentResp, error) + UpdateAgent(ctx context.Context, in *UpdateAgentReq, opts ...grpc.CallOption) (*UpdateAgentResp, error) + PageFindAgent(ctx context.Context, in *PageFindAgentReq, opts ...grpc.CallOption) (*PageFindAgentResp, error) + DeleteAgent(ctx context.Context, in *DeleteAgentReq, opts ...grpc.CallOption) (*DeleteAgentResp, error) + SendBotMessage(ctx context.Context, in *SendBotMessageReq, opts ...grpc.CallOption) (*SendBotMessageResp, error) +} + +type botClient struct { + cc grpc.ClientConnInterface +} + +func NewBotClient(cc grpc.ClientConnInterface) BotClient { + return &botClient{cc} +} + +func (c *botClient) CreateAgent(ctx context.Context, in *CreateAgentReq, opts ...grpc.CallOption) (*CreateAgentResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(CreateAgentResp) + err := c.cc.Invoke(ctx, Bot_CreateAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *botClient) UpdateAgent(ctx context.Context, in *UpdateAgentReq, opts ...grpc.CallOption) (*UpdateAgentResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UpdateAgentResp) + err := c.cc.Invoke(ctx, Bot_UpdateAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *botClient) PageFindAgent(ctx context.Context, in *PageFindAgentReq, opts ...grpc.CallOption) (*PageFindAgentResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(PageFindAgentResp) + err := c.cc.Invoke(ctx, Bot_PageFindAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *botClient) DeleteAgent(ctx context.Context, in *DeleteAgentReq, opts ...grpc.CallOption) (*DeleteAgentResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(DeleteAgentResp) + err := c.cc.Invoke(ctx, Bot_DeleteAgent_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *botClient) SendBotMessage(ctx context.Context, in *SendBotMessageReq, opts ...grpc.CallOption) (*SendBotMessageResp, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(SendBotMessageResp) + err := c.cc.Invoke(ctx, Bot_SendBotMessage_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BotServer is the server API for Bot service. +// All implementations must embed UnimplementedBotServer +// for forward compatibility. +type BotServer interface { + CreateAgent(context.Context, *CreateAgentReq) (*CreateAgentResp, error) + UpdateAgent(context.Context, *UpdateAgentReq) (*UpdateAgentResp, error) + PageFindAgent(context.Context, *PageFindAgentReq) (*PageFindAgentResp, error) + DeleteAgent(context.Context, *DeleteAgentReq) (*DeleteAgentResp, error) + SendBotMessage(context.Context, *SendBotMessageReq) (*SendBotMessageResp, error) + mustEmbedUnimplementedBotServer() +} + +// UnimplementedBotServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedBotServer struct{} + +func (UnimplementedBotServer) CreateAgent(context.Context, *CreateAgentReq) (*CreateAgentResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateAgent not implemented") +} +func (UnimplementedBotServer) UpdateAgent(context.Context, *UpdateAgentReq) (*UpdateAgentResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpdateAgent not implemented") +} +func (UnimplementedBotServer) PageFindAgent(context.Context, *PageFindAgentReq) (*PageFindAgentResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method PageFindAgent not implemented") +} +func (UnimplementedBotServer) DeleteAgent(context.Context, *DeleteAgentReq) (*DeleteAgentResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method DeleteAgent not implemented") +} +func (UnimplementedBotServer) SendBotMessage(context.Context, *SendBotMessageReq) (*SendBotMessageResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method SendBotMessage not implemented") +} +func (UnimplementedBotServer) mustEmbedUnimplementedBotServer() {} +func (UnimplementedBotServer) testEmbeddedByValue() {} + +// UnsafeBotServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to BotServer will +// result in compilation errors. +type UnsafeBotServer interface { + mustEmbedUnimplementedBotServer() +} + +func RegisterBotServer(s grpc.ServiceRegistrar, srv BotServer) { + // If the following call pancis, it indicates UnimplementedBotServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&Bot_ServiceDesc, srv) +} + +func _Bot_CreateAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateAgentReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BotServer).CreateAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Bot_CreateAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BotServer).CreateAgent(ctx, req.(*CreateAgentReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bot_UpdateAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateAgentReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BotServer).UpdateAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Bot_UpdateAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BotServer).UpdateAgent(ctx, req.(*UpdateAgentReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bot_PageFindAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PageFindAgentReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BotServer).PageFindAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Bot_PageFindAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BotServer).PageFindAgent(ctx, req.(*PageFindAgentReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bot_DeleteAgent_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteAgentReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BotServer).DeleteAgent(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Bot_DeleteAgent_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BotServer).DeleteAgent(ctx, req.(*DeleteAgentReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _Bot_SendBotMessage_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SendBotMessageReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BotServer).SendBotMessage(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Bot_SendBotMessage_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BotServer).SendBotMessage(ctx, req.(*SendBotMessageReq)) + } + return interceptor(ctx, in, info, handler) +} + +// Bot_ServiceDesc is the grpc.ServiceDesc for Bot service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var Bot_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "openim.bot.bot", + HandlerType: (*BotServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateAgent", + Handler: _Bot_CreateAgent_Handler, + }, + { + MethodName: "UpdateAgent", + Handler: _Bot_UpdateAgent_Handler, + }, + { + MethodName: "PageFindAgent", + Handler: _Bot_PageFindAgent_Handler, + }, + { + MethodName: "DeleteAgent", + Handler: _Bot_DeleteAgent_Handler, + }, + { + MethodName: "SendBotMessage", + Handler: _Bot_SendBotMessage_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "bot/bot.proto", +} diff --git a/pkg/protocol/gen.cmd b/pkg/protocol/gen.cmd index 8584cb0f4..77d464f17 100644 --- a/pkg/protocol/gen.cmd +++ b/pkg/protocol/gen.cmd @@ -2,7 +2,7 @@ setlocal rem Define array elements -set "PROTO_NAMES=admin chat common" +set "PROTO_NAMES=admin chat common bot" rem Loop through each element in the array for %%i in (%PROTO_NAMES%) do ( diff --git a/start-config.yml b/start-config.yml index 0d6707834..1fb653bdb 100644 --- a/start-config.yml +++ b/start-config.yml @@ -3,6 +3,8 @@ serviceBinaries: chat-rpc: 1 admin-api: 1 admin-rpc: 1 + bot-api: 1 + bot-rpc: 1 toolBinaries: - check-component - attribute-to-credential diff --git a/tools/check-component/main.go b/tools/check-component/main.go index 00206abbe..2433a9172 100644 --- a/tools/check-component/main.go +++ b/tools/check-component/main.go @@ -50,9 +50,13 @@ func CheckRedis(ctx context.Context, config *config.Redis) error { return redisutil.Check(ctx, config.Build()) } -func CheckOpenIM(ctx context.Context, apiURL, secret, adminUserID string) error { - imAPI := imapi.New(apiURL, secret, adminUserID) - _, err := imAPI.GetAdminTokenCache(mcontext.SetOperationID(ctx, "CheckOpenIM"+idutil.OperationIDGenerator()), adminUserID) +func CheckOpenIM(ctx context.Context, apiURL, secret, adminUserID string, redisConf *config.Redis, interval int) error { + rdb, err := redisutil.NewRedisClient(ctx, redisConf.Build()) + if err != nil { + return err + } + imAPI := imapi.New(apiURL, secret, adminUserID, rdb, interval) + _, err = imAPI.GetAdminTokenServer(mcontext.SetOperationID(ctx, "CheckOpenIM"+idutil.OperationIDGenerator()), adminUserID) return err } @@ -123,7 +127,7 @@ func performChecks(ctx context.Context, mongoConfig *config.Mongo, redisConfig * return CheckRedis(ctx, redisConfig) }, "OpenIM": func(ctx context.Context) error { - return CheckOpenIM(ctx, shareConfig.OpenIM.ApiURL, shareConfig.OpenIM.Secret, shareConfig.OpenIM.AdminUserID) + return CheckOpenIM(ctx, shareConfig.OpenIM.ApiURL, shareConfig.OpenIM.Secret, shareConfig.OpenIM.AdminUserID, redisConfig, shareConfig.OpenIM.TokenRefreshInterval) }, }