380
core/group_chat.go
Normal file
380
core/group_chat.go
Normal file
@ -0,0 +1,380 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-redis/redis/v8"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"repositories.action2quare.com/ayo/gocommon"
|
||||
"repositories.action2quare.com/ayo/gocommon/logger"
|
||||
"repositories.action2quare.com/ayo/gocommon/wshandler"
|
||||
)
|
||||
|
||||
type channelID = string
|
||||
type channelConfig struct {
|
||||
Capacity int64 `json:"capacity"`
|
||||
Size int64 `json:"size"`
|
||||
Key string `json:"key"`
|
||||
emptyJson string
|
||||
}
|
||||
|
||||
type chatConfig struct {
|
||||
DefaultCapacity int64 `json:"default_capacity"`
|
||||
Channels map[string]*channelConfig `json:"channels"`
|
||||
}
|
||||
|
||||
type groupChat struct {
|
||||
chatConfig
|
||||
rh *gocommon.RedisonHandler
|
||||
enterRoom func(channelID, accountID)
|
||||
leaveRoom func(channelID, accountID)
|
||||
sendUpstreamMessage func(msg *wshandler.UpstreamMessage)
|
||||
}
|
||||
|
||||
func (gc *groupChat) Initialize(tv *Tavern, cfg configDocument) error {
|
||||
rem, _ := json.Marshal(cfg)
|
||||
if err := json.Unmarshal(rem, &gc.chatConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gc.enterRoom = func(chanid channelID, accid accountID) {
|
||||
tv.wsh.EnterRoom(string(chanid), accid)
|
||||
}
|
||||
gc.leaveRoom = func(chanid channelID, accid accountID) {
|
||||
tv.wsh.LeaveRoom(string(chanid), accid)
|
||||
}
|
||||
gc.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
|
||||
tv.wsh.SendUpstreamMessage(msg)
|
||||
}
|
||||
|
||||
gc.rh = tv.redison
|
||||
|
||||
for name, cfg := range gc.chatConfig.Channels {
|
||||
if cfg.Capacity == 0 {
|
||||
cfg.Capacity = gc.chatConfig.DefaultCapacity
|
||||
}
|
||||
cfg.Key = name
|
||||
cfg.Size = 0
|
||||
|
||||
jm, _ := json.Marshal(cfg)
|
||||
cfg.emptyJson = fmt.Sprintf("[%s]", string(jm))
|
||||
|
||||
_, err := gc.rh.JSONSet(name, "$", cfg)
|
||||
if *devflag && err != nil {
|
||||
gc.rh.JSONDel(name, "$")
|
||||
_, err = gc.rh.JSONSet(name, "$", cfg)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gc *groupChat) ClientConnected(ctx wshandler.ApiCallContext) {
|
||||
gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel", map[string]any{})
|
||||
}
|
||||
|
||||
func (gc *groupChat) ClientDisconnected(ctx wshandler.ApiCallContext) {
|
||||
docs, _ := gc.rh.JSONGetDocuments(ctx.CallBy.Accid.Hex(), "$.channel")
|
||||
|
||||
if len(docs) > 0 {
|
||||
for k, v := range docs[0] {
|
||||
typename := k
|
||||
chanid := v.(string)
|
||||
gc.leaveRoom(chanid, ctx.CallBy.Accid)
|
||||
if k == "public" {
|
||||
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
||||
} else {
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"sender": ctx.CallBy.Alias, "typename": typename},
|
||||
Tag: []string{"LeavePrivateChannel"},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) EnterPublicChannel(ctx wshandler.ApiCallContext) {
|
||||
chanid := ctx.Arguments[0].(string)
|
||||
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
|
||||
size, err := gc.rh.JSONGetInt64(chanid, "$.size")
|
||||
if err != nil || len(size) == 0 {
|
||||
logger.Println("JSONGetInt64 failed :", chanid, err)
|
||||
} else if size[0] < cfg.Capacity {
|
||||
// 입장
|
||||
newsize, err := gc.rh.JSONNumIncrBy(chanid, "$.size", 1)
|
||||
if err == nil {
|
||||
gc.enterRoom(chanid, ctx.CallBy.Accid)
|
||||
gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel.public", chanid)
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"size": newsize[0]},
|
||||
Tag: []string{"ChattingChannelProperties"},
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 풀방
|
||||
logger.Println("chatting channel is full :", chanid, size, cfg.Capacity)
|
||||
}
|
||||
} else {
|
||||
logger.Println("chatting channel not valid :", chanid)
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) LeavePublicChannel(ctx wshandler.ApiCallContext) {
|
||||
chanid := ctx.Arguments[0].(string)
|
||||
cnt, _ := gc.rh.JSONDel(ctx.CallBy.Accid.Hex(), "$.channel.public")
|
||||
if cnt > 0 {
|
||||
gc.leaveRoom(chanid, ctx.CallBy.Accid)
|
||||
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) TextMessage(ctx wshandler.ApiCallContext) {
|
||||
chanid := ctx.Arguments[0].(string)
|
||||
msg := ctx.Arguments[1].(string)
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"sender": ctx.CallBy.Alias, "msg": msg},
|
||||
Tag: []string{"TextMessage"},
|
||||
})
|
||||
}
|
||||
|
||||
func (gc *groupChat) EnterPrivateChannel(ctx wshandler.ApiCallContext) {
|
||||
typename := ctx.Arguments[0].(string)
|
||||
channel := ctx.Arguments[1].(string)
|
||||
var reason string
|
||||
if len(ctx.Arguments) > 2 {
|
||||
reason = ctx.Arguments[2].(string)
|
||||
}
|
||||
|
||||
if len(reason) > 0 {
|
||||
// 수락
|
||||
ok, err := gc.rh.JSONSet(ctx.CallBy.Accid.Hex(), "$.channel."+typename, channel, gocommon.RedisonSetOptionNX)
|
||||
if err != nil || !ok {
|
||||
// 이미 다른 private channel 참여 중
|
||||
logger.Println("EnterPrivateChannel failed. HSetNX return err :", err, ctx.CallBy.Accid.Hex(), typename, channel)
|
||||
return
|
||||
}
|
||||
gc.enterRoom(channel, ctx.CallBy.Accid)
|
||||
}
|
||||
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + channel,
|
||||
Body: map[string]any{
|
||||
"sender": ctx.CallBy.Alias,
|
||||
"msg": reason,
|
||||
"typename": typename,
|
||||
},
|
||||
Tag: []string{"EnterPrivateChannel"},
|
||||
})
|
||||
}
|
||||
|
||||
func (gc *groupChat) LeavePrivateChannel(ctx wshandler.ApiCallContext) {
|
||||
typename := ctx.Arguments[0].(string)
|
||||
chanid := ctx.Arguments[1].(string)
|
||||
cnt, _ := gc.rh.JSONDel(ctx.CallBy.Accid.Hex(), "$.channel."+typename)
|
||||
if cnt > 0 {
|
||||
gc.leaveRoom(chanid, ctx.CallBy.Accid)
|
||||
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
Target: "#" + chanid,
|
||||
Body: map[string]any{"sender": ctx.CallBy.Alias, "typename": typename},
|
||||
Tag: []string{"LeavePrivateChannel"},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// func (gc *groupChat) ClientMessageReceived(sender *wshandler.Sender, mt wshandler.WebSocketMessageType, message any) {
|
||||
// if mt == wshandler.Disconnected {
|
||||
// if _, err := gc.rh.Del(gc.rh.Context(), accidHex(sender.Accid)).Result(); err != nil {
|
||||
// logger.Println(err)
|
||||
// }
|
||||
// } else if mt == wshandler.BinaryMessage {
|
||||
// commandline := message.([]any)
|
||||
// cmd := commandline[0].(string)
|
||||
// args := commandline[1:]
|
||||
// switch cmd {
|
||||
// case "EnterPublicChannel":
|
||||
// chanid := args[0].(string)
|
||||
// if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
|
||||
// size, err := gc.rh.JSONGetInt64(chanid, "$.size")
|
||||
// if err != nil || len(size) == 0 {
|
||||
// logger.Println("JSONGetInt64 failed :", chanid, err)
|
||||
// } else if size[0] < cfg.Capacity {
|
||||
// // 입장
|
||||
// newsize, err := gc.rh.JSONNumIncrBy(chanid, "$.size", 1)
|
||||
// if err == nil {
|
||||
// gc.enterRoom(chanid, sender.Accid)
|
||||
// sender.RegistDisconnectedCallback(chanid, func() {
|
||||
// size, err := gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
||||
// if err == nil {
|
||||
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
// Target: "#" + chanid,
|
||||
// Body: map[string]any{"size": size},
|
||||
// Tag: []string{"ChattingChannelProperties"},
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
|
||||
// gc.rh.HSet(gc.rh.Context(), accidHex(sender.Accid), "cc_pub", chanid)
|
||||
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
// Target: "#" + chanid,
|
||||
// Body: map[string]any{"size": newsize[0]},
|
||||
// Tag: []string{"ChattingChannelProperties"},
|
||||
// })
|
||||
// }
|
||||
// } else {
|
||||
// // 풀방
|
||||
// logger.Println("chatting channel is full :", chanid, size, cfg.Capacity)
|
||||
// }
|
||||
// } else {
|
||||
// logger.Println("chatting channel not valid :", chanid)
|
||||
// }
|
||||
|
||||
// case "LeavePublicChannel":
|
||||
// chanid := args[0].(string)
|
||||
// gc.rh.HDel(gc.rh.Context(), accidHex(sender.Accid), "cc_pub")
|
||||
// gc.leaveRoom(chanid, sender.Accid)
|
||||
// if f := sender.PopDisconnectedCallback(chanid); f != nil {
|
||||
// f()
|
||||
// }
|
||||
|
||||
// case "TextMessage":
|
||||
// chanid := args[0].(string)
|
||||
// msg := args[1].(string)
|
||||
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
// Target: "#" + chanid,
|
||||
// Body: map[string]any{"sender": sender.Alias, "msg": msg},
|
||||
// Tag: []string{"TextMessage"},
|
||||
// })
|
||||
|
||||
// case "EnterPrivateChannel":
|
||||
// typename := args[0].(string)
|
||||
// channel := args[1].(string)
|
||||
// var reason string
|
||||
// if len(args) > 2 {
|
||||
// reason = args[2].(string)
|
||||
// }
|
||||
|
||||
// if len(reason) > 0 {
|
||||
// // 수락
|
||||
// // 이거 HSet 하면 안되겠는데? JSONSet해야할 듯?
|
||||
// ok, err := gc.rh.HSetNX(gc.rh.Context(), accidHex(sender.Accid), "cc_"+typename, channel).Result()
|
||||
// if err != nil || !ok {
|
||||
// // 이미 다른 private channel 참여 중
|
||||
// logger.Println("EnterPrivateChannel failed. HSetNX return err :", err, sender.Accid.Hex(), typename, channel)
|
||||
// return
|
||||
// }
|
||||
// gc.enterRoom(channel, sender.Accid)
|
||||
|
||||
// sender.RegistDisconnectedCallback(channel, func() {
|
||||
// gc.rh.JSONDel(channel, "$."+sender.Accid.Hex())
|
||||
// // 이거 HDel 하면 안되겠는데? JSONDel해야할 듯?
|
||||
// cnt, _ := gc.rh.HDel(gc.rh.Context(), accidHex(sender.Accid), "cc_"+typename).Result()
|
||||
// if cnt > 0 {
|
||||
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
// Target: "#" + channel,
|
||||
// Body: map[string]any{"sender": sender.Alias, "typename": typename},
|
||||
// Tag: []string{"LeavePrivateChannel"},
|
||||
// })
|
||||
// }
|
||||
// })
|
||||
// } else {
|
||||
// // 내가 이미 private channel에 있다는 것을 다른 사람들에게 알려주기 위함
|
||||
// }
|
||||
|
||||
// gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
||||
// Target: "#" + channel,
|
||||
// Body: map[string]any{
|
||||
// "sender": sender.Alias,
|
||||
// "msg": reason,
|
||||
// "typename": typename,
|
||||
// },
|
||||
// Tag: []string{"EnterPrivateChannel"},
|
||||
// })
|
||||
|
||||
// case "LeavePrivateChannel":
|
||||
// channel := args[1].(string)
|
||||
// gc.leaveRoom(channel, sender.Accid)
|
||||
// if f := sender.PopDisconnectedCallback(channel); f != nil {
|
||||
// f()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func (gc *groupChat) FetchChattingChannels(w http.ResponseWriter, r *http.Request) {
|
||||
var prefix string
|
||||
if err := gocommon.MakeDecoder(r).Decode(&prefix); err != nil {
|
||||
logger.Println("FetchChattingChannels failed. ReadJsonDocumentFromBody returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(prefix) == 0 {
|
||||
logger.Println("FetchChattingChannel failed. prefix is missing")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var rows []string
|
||||
for name, cfg := range gc.chatConfig.Channels {
|
||||
if len(prefix) > 0 {
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
onechan, err := gc.rh.JSONGet(name, "$")
|
||||
if err != nil && err != redis.Nil {
|
||||
logger.Println("FetchChattingChannel failed. HGetAll return err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err == redis.Nil || onechan == nil {
|
||||
rows = append(rows, cfg.emptyJson)
|
||||
} else {
|
||||
// json array로 나온다
|
||||
rows = append(rows, strings.Trim(onechan.(string), "[]"))
|
||||
}
|
||||
}
|
||||
|
||||
gocommon.MakeEncoder(w, r).Encode(rows)
|
||||
}
|
||||
|
||||
func (gc *groupChat) QueryPlayerChattingChannel(w http.ResponseWriter, r *http.Request) {
|
||||
var accid primitive.ObjectID
|
||||
if err := gocommon.MakeDecoder(r).Decode(&accid); err != nil {
|
||||
logger.Println("QueryPlayerChattingChannel failed. ReadJsonDocumentFromBody returns err :", err)
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sub, err := gc.rh.JSONGetDocuments(accid.Hex(), "$.channel")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(sub) > 0 {
|
||||
gocommon.MakeEncoder(w, r).Encode(sub[0])
|
||||
}
|
||||
}
|
||||
|
||||
func (gc *groupChat) SendMessageOnChannel(w http.ResponseWriter, r *http.Request) {
|
||||
var msg wshandler.UpstreamMessage
|
||||
if err := gocommon.MakeDecoder(r).Decode(&msg); err != nil {
|
||||
logger.Println("SendMessageOnChannel failed. ReadJsonDocumentFromBody return err :", err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
gc.sendUpstreamMessage(&msg)
|
||||
}
|
||||
Reference in New Issue
Block a user