Files
tavern/core/group_chat.go
2023-07-27 17:45:51 +09:00

223 lines
6.8 KiB
Go

package core
import (
"encoding/json"
"net/http"
"reflect"
"repositories.action2quare.com/ayo/gocommon"
"repositories.action2quare.com/ayo/gocommon/logger"
"repositories.action2quare.com/ayo/gocommon/wshandler"
)
func init() {
groupTypeContainer()["chat"] = reflect.TypeOf(&groupChat{})
}
type channelID = string
type chatConfig struct {
DefaultCapacity int64 `json:"default_capacity"`
Channels map[string]map[string]any `json:"channels"`
}
type groupChat struct {
chatConfig
rh *gocommon.RedisonHandler
incSizeScript string
decSizeScript string
enterRoom func(channelID, accountID)
leaveRoom func(channelID, accountID)
sendUpstreamMessage func(msg *wshandler.UpstreamMessage)
}
var increaseSizeScript = `
local cap = redis.call("HGET", KEYS[1], "capacity")
local newseq = redis.call("HINCRBY", KEYS[1], "seq", 1)
local newsize = redis.call("HINCRBY", KEYS[1], "size", 1)
if tonumber(cap) < newsize then
redis.call("HINCRBY", KEYS[1], "size", -1)
return {err = "channel is full"}
end
redis.call("HSET", "_m_"..KEYS[1], KEYS[2], ARGV[1])
return {newsize, newseq}
`
var decreaseSizeScript = `
local newseq = redis.call("HINCRBY", KEYS[1], "seq", 1)
local newsize = redis.call("HINCRBY", KEYS[1], "size", -1)
redis.call("HDEL", "_m_"..KEYS[1], KEYS[2])
return {newsize, newseq}
`
func (gc *groupChat) Initialize(sub *subTavern, cfg configDocument) error {
incScript, err := sub.redisClient.ScriptLoad(sub.redisClient.Context(), increaseSizeScript).Result()
if err != nil {
return err
}
decScript, err := sub.redisClient.ScriptLoad(sub.redisClient.Context(), decreaseSizeScript).Result()
if err != nil {
return err
}
// newsize, err := sub.redisClient.EvalSha(sub.redisClient.Context(), incScript, []string{"myhash", "alias"}, "accid").Result()
// if err != nil {
// return err
// }
// logger.Println(newsize.([]any))
// newsize, err = sub.redisClient.EvalSha(sub.redisClient.Context(), decScript, []string{"myhash", "alias"}).Result()
// if err != nil {
// return err
// }
// logger.Println(newsize.([]any))
rem, _ := json.Marshal(cfg)
if err = json.Unmarshal(rem, &gc.chatConfig); err != nil {
return err
}
gc.enterRoom = func(chanid channelID, accid accountID) {
sub.wsh.EnterRoom(sub.region, string(chanid), accid)
}
gc.leaveRoom = func(chanid channelID, accid accountID) {
sub.wsh.LeaveRoom(sub.region, string(chanid), accid)
}
gc.sendUpstreamMessage = func(msg *wshandler.UpstreamMessage) {
sub.wsh.SendUpstreamMessage(sub.region, msg)
}
gc.rh = gocommon.NewRedisonHandler(sub.redisClient.Context(), sub.redisClient)
gc.incSizeScript = incScript
gc.decSizeScript = decScript
sub.apiFuncs.registApiFunction("CreateChattingChannel", gc.CreateChattingChannel)
sub.apiFuncs.registApiFunction("FetchChattingChannels", gc.FetchChattingChannels)
for name, cfg := range gc.chatConfig.Channels {
if _, ok := cfg["capacity"]; !ok {
cfg["capacity"] = gc.chatConfig.DefaultCapacity
}
cfg["key"] = name
_, err := gc.rh.HMSet(gc.rh.Context(), name, cfg).Result()
if err != nil {
return err
}
}
return nil
}
func (gc *groupChat) ClientMessageReceived(sender *wshandler.Sender, mt wshandler.WebSocketMessageType, message any) {
if mt == wshandler.Disconnected {
rooms := message.([]string)
for _, chanid := range rooms {
sizeseq, err := gc.rh.EvalSha(gc.rh.Context(), gc.decSizeScript, []string{chanid, sender.Alias}).Result()
if err == nil {
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"size": sizeseq.([]any)[0], "seq": sizeseq.([]any)[1]},
Tag: []string{"ChattingChannelProperties"},
})
}
}
} else if mt == wshandler.BinaryMessage {
commandline := message.([]any)
cmd := commandline[0].(string)
args := commandline[1:]
switch cmd {
case "EnterChattingChannel":
chanid := args[0].(string)
sizeseq, err := gc.rh.EvalSha(gc.rh.Context(), gc.incSizeScript, []string{chanid, sender.Alias}, sender.Accid.Hex()).Result()
if err == nil {
gc.enterRoom(chanid, sender.Accid)
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"size": sizeseq.([]any)[0], "seq": sizeseq.([]any)[1]},
Tag: []string{"ChattingChannelProperties"},
})
} else {
// 입장 실패 알림
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "@" + sender.Accid.Hex(),
Body: map[string]string{"id": chanid, "err": err.Error()},
Tag: []string{"EnterChattingChannelFailed"},
})
}
case "LeaveChattingChannel":
chanid := args[0].(string)
gc.leaveRoom(chanid, sender.Accid)
sizeseq, err := gc.rh.EvalSha(gc.rh.Context(), gc.decSizeScript, []string{chanid, sender.Alias}).Result()
if err == nil {
gc.sendUpstreamMessage(&wshandler.UpstreamMessage{
Target: "#" + chanid,
Body: map[string]any{"size": sizeseq.([]any)[0], "seq": sizeseq.([]any)[1]},
Tag: []string{"ChattingChannelProperties"},
})
}
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"},
})
}
}
}
func (gc *groupChat) CreateChattingChannel(w http.ResponseWriter, r *http.Request) {
chanstr, _ := gocommon.ReadStringFormValue(r.Form, "name")
if len(chanstr) == 0 {
logger.Println("CreateChattingChannel failed. name is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
capacity, _ := gocommon.ReadIntegerFormValue(r.Form, "capacity")
if capacity == 0 {
capacity = gc.chatConfig.DefaultCapacity
}
_, err := gc.rh.HSetNX(gc.rh.Context(), chanstr, "capacity", capacity).Result()
if err != nil {
logger.Println("CreateChattingChannel failed. HSetNX returns err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
}
func (gc *groupChat) FetchChattingChannels(w http.ResponseWriter, r *http.Request) {
prefix, _ := gocommon.ReadStringFormValue(r.Form, "prefix")
if len(prefix) == 0 {
logger.Println("FetchChattingChannel failed. prefix is missing")
w.WriteHeader(http.StatusBadRequest)
return
}
keys, err := gc.rh.Keys(gc.rh.Context(), prefix+"*").Result()
if err != nil {
logger.Println("FetchChattingChannel failed. Keys return err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
var channels []map[string]string
for _, key := range keys {
onechan, err := gc.rh.HGetAll(gc.rh.Context(), key).Result()
if err != nil {
logger.Println("FetchChattingChannel failed. HGetAll return err :", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
channels = append(channels, onechan)
}
enc := json.NewEncoder(w)
enc.Encode(channels)
}