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) }