diff --git a/cmd/main.go b/cmd/main.go index a2953ba..cf3d875 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,6 +3,7 @@ package main import ( "database/sql" "fmt" + "github.com/azuki-bar/ceviord/pkg/joinVc" "github.com/azuki-bar/ceviord/pkg/slashCmd" "github.com/azuki-bar/ceviord/pkg/speech/grpc" "log" @@ -73,6 +74,7 @@ func main() { dgSess.AddHandler(func(s *discordgo.Session, _ *discordgo.Connect) { log.Println("connect to discord") }) dgSess.AddHandler(ceviord.MessageCreate) dgSess.AddHandler(slashCmd.InteractionHandler) + dgSess.AddHandler(joinVc.VoiceStateUpdateHandler) // dgSess.Debug = true gTalker, closer := grpc.NewTalker(&conf.auth.CeviordConn, &conf.param.Parameters[0]) defer closer() diff --git a/pkg/ceviord/handleMsg.go b/pkg/ceviord/handleMsg.go index ffacd8b..e587110 100644 --- a/pkg/ceviord/handleMsg.go +++ b/pkg/ceviord/handleMsg.go @@ -227,6 +227,12 @@ func MessageCreate(sess *discordgo.Session, msg *discordgo.MessageCreate) { func RawSpeak(text string, guildId string, sess *discordgo.Session) error { cev, err := Cache.Channels.GetChannel(guildId) + if cev == nil { + return fmt.Errorf("get channel failed") + } + if err != nil { + return err + } isJoined, err := cev.IsActorJoined(sess) if err != nil || !isJoined { return err @@ -272,6 +278,9 @@ func SendMsg(msg string, session *discordgo.Session, guildId string) error { func SendEmbedMsg(embed *discordgo.MessageEmbed, session *discordgo.Session, guildId string) error { cev, err := Cache.Channels.GetChannel(guildId) + if cev == nil { + return err + } isJoined, err := cev.IsActorJoined(session) if err != nil || !isJoined { return err diff --git a/pkg/dgvoice/dgvoice.go b/pkg/dgvoice/dgvoice.go index c13eab6..94c20e8 100644 --- a/pkg/dgvoice/dgvoice.go +++ b/pkg/dgvoice/dgvoice.go @@ -47,9 +47,9 @@ var OnError = func(str string, err error) { prefix := "dgVoice: " + str if err != nil { - os.Stderr.WriteString(prefix + ": " + err.Error()) + os.Stderr.WriteString(prefix + ": " + err.Error() + "\n") } else { - os.Stderr.WriteString(prefix) + os.Stderr.WriteString(prefix + "\n") } } diff --git a/pkg/discord/user.go b/pkg/discord/user.go new file mode 100644 index 0000000..3e0dbd7 --- /dev/null +++ b/pkg/discord/user.go @@ -0,0 +1,33 @@ +package discord + +import "github.com/bwmarrin/discordgo" + +type User struct { + *discordgo.User + sess *discordgo.Session + guildId string +} + +func NewUser(userId string, s *discordgo.Session, guildId string) (User, error) { + u, err := s.User(userId) + if err != nil { + return User{}, err + } + return User{ + User: u, + sess: s, + guildId: guildId, + }, nil +} + +// ScreenName returns NickName if defined, and returns Username in else. +func (u User) ScreenName() (string, error) { + m, err := u.sess.GuildMember(u.guildId, u.ID) + if err != nil { + return "", err + } + if m.Nick != "" { + return m.Nick, nil + } + return u.Username, nil +} diff --git a/pkg/joinVc/handler.go b/pkg/joinVc/handler.go new file mode 100644 index 0000000..3699dbf --- /dev/null +++ b/pkg/joinVc/handler.go @@ -0,0 +1,123 @@ +package joinVc + +import ( + "fmt" + "github.com/azuki-bar/ceviord/pkg/ceviord" + "github.com/azuki-bar/ceviord/pkg/discord" + "github.com/azuki-bar/ceviord/pkg/logging" + "github.com/bwmarrin/discordgo" +) + +type handler struct { + *discordgo.VoiceStateUpdate + session *discordgo.Session + changeState ChangeRoomState + user discord.User + joinedChannels ceviord.Channels +} + +type Handler interface { + handle(speaker func(text, guildId string, session *discordgo.Session) error) error +} + +func (h *handler) handle(speaker func(text string, guildId string, session *discordgo.Session) error) error { + switch h.changeState.(type) { + case intoRoom, outRoom: + return speaker(h.changeState.GetText(), h.VoiceStateUpdate.GuildID, h.session) + default: + return nil + } +} + +type ChangeRoomState interface { + GetText() string +} + +func NewChangeRoomState(vsu *discordgo.VoiceStateUpdate, s *discordgo.Session, cs *ceviord.Channels) ChangeRoomState { + if !cs.IsExistChannel(vsu.GuildID) { + return outOfScope{} + } + c, err := cs.GetChannel(vsu.GuildID) + if err != nil { + return outOfScope{} + } + u, err := discord.NewUser(vsu.UserID, s, vsu.GuildID) + if u.Bot { + return outOfScope{} + } + if err != nil { + ceviord.Logger.Log(logging.WARN, err) + return outOfScope{} + } + scn, err := u.ScreenName() + if err != nil { + ceviord.Logger.Log(logging.WARN, err) + return outOfScope{} + } + var state ChangeRoomState = outOfScope{} + if (vsu.BeforeUpdate == nil || vsu.BeforeUpdate.ChannelID != c.VoiceConn.ChannelID) && vsu.VoiceState != nil { + state = intoRoom{screenName: scn} + } else if vsu.BeforeUpdate != nil && vsu.VoiceState.ChannelID != c.VoiceConn.ChannelID { + state = outRoom{screenName: scn} + } + switch state.(type) { + case intoRoom: + if vsu.ChannelID == c.VoiceConn.ChannelID { + return state + } + case outRoom: + if vsu.BeforeUpdate.ChannelID == c.VoiceConn.ChannelID { + return state + } + default: + return outOfScope{} + } + return outOfScope{} +} + +type intoRoom struct{ screenName string } + +func (r intoRoom) GetText() string { + return fmt.Sprintf("%sさんが入室しました。", r.screenName) +} + +type outRoom struct{ screenName string } + +func (r outRoom) GetText() string { + return fmt.Sprintf("%sさんが退室しました。", r.screenName) +} + +type outOfScope struct{ ChangeRoomState } + +func NewHandler(s *discordgo.Session, vsu *discordgo.VoiceStateUpdate) (Handler, error) { + u, err := discord.NewUser(vsu.UserID, s, vsu.GuildID) + if err != nil { + return nil, err + } + cs := NewChangeRoomState(vsu, s, &ceviord.Cache.Channels) + if cs == nil { + // ignore not covered event + return nil, nil + } + return &handler{ + session: s, + VoiceStateUpdate: vsu, + user: u, + changeState: cs, + joinedChannels: ceviord.Cache.Channels, + }, nil +} + +func VoiceStateUpdateHandler(s *discordgo.Session, vsu *discordgo.VoiceStateUpdate) { + h, err := NewHandler(s, vsu) + if err != nil { + ceviord.Logger.Log(logging.WARN, err) + return + } + err = h.handle(ceviord.RawSpeak) + if err != nil { + ceviord.Logger.Log(logging.WARN, err) + return + } + return +}