349 lines
9.8 KiB
Go
349 lines
9.8 KiB
Go
package core
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/go-redis/redis/v8"
|
|
"github.com/gorilla/websocket"
|
|
"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"`
|
|
Members map[string]int32 `json:"members"`
|
|
emptyJson string
|
|
inoutChan chan 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))
|
|
cfg.Members = make(map[string]int32)
|
|
_, 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
|
|
}
|
|
|
|
inoutchan := make(chan string, 10)
|
|
cfg.inoutChan = inoutchan
|
|
|
|
go func(chanid string) {
|
|
var cur []string
|
|
tick := time.After(3 * time.Second)
|
|
for {
|
|
select {
|
|
case <-tick:
|
|
tick = time.After(3 * time.Second)
|
|
if len(cur) > 0 {
|
|
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + chanid,
|
|
Body: map[string]any{"inout": cur},
|
|
Tag: []string{"ChattingChannelProperties"},
|
|
})
|
|
cur = nil
|
|
}
|
|
|
|
case m := <-inoutchan:
|
|
cur = append(cur, m)
|
|
}
|
|
}
|
|
}(name)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (gc *groupChat) ClientConnected(conn *websocket.Conn, callby *wshandler.Sender) {
|
|
}
|
|
|
|
func (gc *groupChat) ClientDisconnected(msg string, callby *wshandler.Sender) {
|
|
if msg == wshandler.ForceShutdownCloseMessage {
|
|
gc.rh.Del(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
|
|
return
|
|
}
|
|
|
|
chans, _ := gc.rh.HGetAll(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
|
|
gc.rh.Del(gc.rh.Context(), "gc-"+callby.Accid.Hex()).Result()
|
|
|
|
for typename, chanid := range chans {
|
|
gc.leaveRoom(chanid, callby.Accid)
|
|
if typename == "public" {
|
|
cnt, err := gc.rh.JSONDel(chanid, "$.members."+callby.Alias)
|
|
if cnt > 0 {
|
|
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
|
if cfg, ok := gc.chatConfig.Channels[chanid]; ok {
|
|
cfg.inoutChan <- "-" + callby.Alias
|
|
}
|
|
} else if err != nil {
|
|
logger.Println("groupchat ClientDisconnected JSONDel err :", err, callby.Alias)
|
|
} else {
|
|
logger.Println("groupchat ClientDisconnected JSONDel cnt 0 :", callby.Alias)
|
|
}
|
|
} else {
|
|
logger.Println("groupchat ClientDisconnected leave private channel :", chanid, callby.Alias)
|
|
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + chanid,
|
|
Body: map[string]any{"sender": callby.Alias},
|
|
Tag: []string{typename + ".LeavePrivateChannel"},
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func (gc *groupChat) EnterPublicChannel(ctx wshandler.ApiCallContext) {
|
|
gc.LeavePublicChannel(ctx)
|
|
|
|
chanid := ctx.Arguments[0].(string)
|
|
cfg, ok := gc.chatConfig.Channels[chanid]
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
atomicsize, _ := gc.rh.JSONNumIncrBy(chanid, "$.size", 1)
|
|
if len(atomicsize) == 0 {
|
|
return
|
|
}
|
|
|
|
if atomicsize[0] > cfg.Capacity {
|
|
logger.Println("chatting channel is full :", chanid, atomicsize[0]-1, cfg.Capacity)
|
|
gc.rh.JSONNumIncrBy(chanid, "$.size", -1)
|
|
return
|
|
}
|
|
|
|
cnt, _ := gc.rh.HSet(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public", chanid).Result()
|
|
if cnt == 0 {
|
|
logger.Println("HSet cnt 0 :", chanid, ctx.CallBy.Alias)
|
|
return
|
|
}
|
|
|
|
ok, err := gc.rh.JSONSet(chanid, "$.members."+ctx.CallBy.Alias, 1)
|
|
if err != nil {
|
|
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
|
|
logger.Println("JSONSet $.members failed :", err, chanid, ctx.CallBy.Alias)
|
|
return
|
|
}
|
|
|
|
if !ok {
|
|
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
|
|
logger.Println("JSONSet $.members not ok :", chanid, ctx.CallBy.Alias)
|
|
return
|
|
}
|
|
|
|
logger.Println("groupchat EnterPublicChannel JSONSet :", chanid, ctx.CallBy.Alias)
|
|
gc.enterRoom(chanid, ctx.CallBy.Accid)
|
|
cfg.inoutChan <- "+" + ctx.CallBy.Alias
|
|
|
|
members, _ := gc.rh.JSONGetDocuments(chanid, "$.members")
|
|
var toarr []string
|
|
if len(members) > 0 {
|
|
toarr = make([]string, 0, len(members[0]))
|
|
for k := range members[0] {
|
|
toarr = append(toarr, k)
|
|
}
|
|
} else {
|
|
toarr = []string{}
|
|
}
|
|
|
|
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: ctx.CallBy.Accid.Hex(),
|
|
Body: map[string]any{"members": toarr},
|
|
Tag: []string{"ChattingChannelProperties"},
|
|
})
|
|
}
|
|
|
|
func (gc *groupChat) LeavePublicChannel(ctx wshandler.ApiCallContext) {
|
|
oldchan, _ := gc.rh.HGet(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public").Result()
|
|
if len(oldchan) > 0 {
|
|
gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), "public")
|
|
|
|
cnt, err := gc.rh.JSONDel(oldchan, "$.members."+ctx.CallBy.Alias)
|
|
if cnt > 0 {
|
|
gc.rh.JSONNumIncrBy(oldchan, "$.size", -1)
|
|
logger.Println("groupchat LeavePublicChannel JSONDel :", oldchan, ctx.CallBy.Alias)
|
|
gc.leaveRoom(oldchan, ctx.CallBy.Accid)
|
|
if cfg, ok := gc.chatConfig.Channels[oldchan]; ok {
|
|
cfg.inoutChan <- "-" + ctx.CallBy.Alias
|
|
}
|
|
} else if err != nil {
|
|
logger.Println("groupchat LeavePublicChannel JSONDel err :", err, oldchan, ctx.CallBy.Alias)
|
|
} else {
|
|
logger.Println("groupchat LeavePublicChannel JSONDel cnt 0 :", oldchan, ctx.CallBy.Alias)
|
|
}
|
|
}
|
|
}
|
|
|
|
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.HSetNX(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), typename, channel).Result()
|
|
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,
|
|
},
|
|
Tag: []string{typename + ".EnterPrivateChannel"},
|
|
})
|
|
}
|
|
|
|
func (gc *groupChat) LeavePrivateChannel(ctx wshandler.ApiCallContext) {
|
|
typename := ctx.Arguments[0].(string)
|
|
chanid := ctx.Arguments[1].(string)
|
|
cnt, _ := gc.rh.HDel(gc.rh.Context(), "gc-"+ctx.CallBy.Accid.Hex(), typename).Result()
|
|
if cnt > 0 {
|
|
gc.leaveRoom(chanid, ctx.CallBy.Accid)
|
|
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
|
|
Target: "#" + chanid,
|
|
Body: map[string]any{"sender": ctx.CallBy.Alias},
|
|
Tag: []string{typename + ".LeavePrivateChannel"},
|
|
})
|
|
}
|
|
}
|
|
|
|
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.HGetAll(gc.rh.Context(), "gc-"+accid.Hex()).Result()
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if sub == nil {
|
|
sub = make(map[string]string)
|
|
}
|
|
gocommon.MakeEncoder(w, r).Encode(sub)
|
|
}
|
|
|
|
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)
|
|
}
|