This repository has been archived by the owner on Jul 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package chat | ||
|
||
import ( | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
) | ||
|
||
type Config struct { | ||
Enabled bool | ||
} | ||
|
||
func (Config) Init(cmd *cobra.Command) error { | ||
cmd.PersistentFlags().Bool("chat.enabled", true, "whether to enable chat plugin") | ||
if err := viper.BindPFlag("chat.enabled", cmd.PersistentFlags().Lookup("chat.enabled")); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func (s *Config) Set() { | ||
s.Enabled = viper.GetBool("chat.enabled") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
package chat | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/rs/zerolog" | ||
"github.com/rs/zerolog/log" | ||
|
||
"github.com/demodesk/neko/pkg/auth" | ||
"github.com/demodesk/neko/pkg/types" | ||
"github.com/demodesk/neko/pkg/utils" | ||
) | ||
|
||
func NewManager( | ||
sessions types.SessionManager, | ||
config *Config, | ||
) *Manager { | ||
logger := log.With().Str("module", "chat").Logger() | ||
|
||
return &Manager{ | ||
logger: logger, | ||
config: config, | ||
sessions: sessions, | ||
} | ||
} | ||
|
||
type Manager struct { | ||
logger zerolog.Logger | ||
config *Config | ||
sessions types.SessionManager | ||
} | ||
|
||
func (m *Manager) isEnabledForSession(session types.Session) bool { | ||
return m.config.Enabled && | ||
settingsIsEnabled(m.sessions.Settings()) && | ||
profileIsEnabled(session.Profile()) | ||
} | ||
|
||
func (m *Manager) sendMessage(session types.Session, content Content) { | ||
now := time.Now() | ||
|
||
// get all sessions that have chat enabled | ||
var sessions []types.Session | ||
m.sessions.Range(func(s types.Session) bool { | ||
if m.isEnabledForSession(s) { | ||
sessions = append(sessions, s) | ||
} | ||
// continue iteration over all sessions | ||
return true | ||
}) | ||
|
||
// send content to all sessions | ||
for _, s := range sessions { | ||
s.Send(CHAT_MESSAGE, Message{ | ||
ID: session.ID(), | ||
Created: now, | ||
Content: content, | ||
}) | ||
} | ||
} | ||
|
||
func (m *Manager) Start() error { | ||
// send init message once a user connects | ||
m.sessions.OnConnected(func(session types.Session) { | ||
isEnabled := m.isEnabledForSession(session) | ||
|
||
// send init message | ||
session.Send(CHAT_INIT, Init{ | ||
Enabled: isEnabled, | ||
}) | ||
}) | ||
|
||
// do not proceed if chat is disabled in the config | ||
if !m.config.Enabled { | ||
return nil | ||
} | ||
|
||
// on settings change, reinit if chat is enabled/disabled | ||
m.sessions.OnSettingsChanged(func(session types.Session, new, old types.Settings) { | ||
isEnabled := settingsIsEnabled(new) | ||
wasEnabled := settingsIsEnabled(old) | ||
|
||
if !isEnabled && wasEnabled { | ||
// if chat was enabled and is now disabled, broadcast to all sessions | ||
// because it cannot be overridden by profile settings | ||
m.sessions.Broadcast(CHAT_INIT, Init{ | ||
Enabled: false, | ||
}) | ||
} | ||
if isEnabled && !wasEnabled { | ||
// if chat was disabled and is now enabled, loop over all sessions | ||
// and send the init message (because it can be overridden by profile settings) | ||
for _, s := range m.sessions.List() { | ||
s.Send(CHAT_INIT, Init{ | ||
Enabled: m.isEnabledForSession(s), | ||
}) | ||
} | ||
} | ||
}) | ||
|
||
// on profile change, reinit if chat is enabled/disabled | ||
m.sessions.OnProfileChanged(func(session types.Session, new, old types.MemberProfile) { | ||
isEnabled := profileIsEnabled(new) | ||
wasEnabled := profileIsEnabled(old) | ||
|
||
if isEnabled != wasEnabled { | ||
// only if the chat setting was changed, send the init message | ||
session.Send(CHAT_INIT, Init{ | ||
Enabled: m.isEnabledForSession(session), | ||
}) | ||
} | ||
}) | ||
|
||
return nil | ||
} | ||
|
||
func (m *Manager) Shutdown() error { | ||
return nil | ||
} | ||
|
||
func (m *Manager) Route(r types.Router) { | ||
r.With(auth.AdminsOnly).Post("/", m.sendMessageHandler) | ||
} | ||
|
||
func (m *Manager) WebSocketHandler(session types.Session, msg types.WebSocketMessage) bool { | ||
switch msg.Event { | ||
case CHAT_MESSAGE: | ||
var content Content | ||
if err := json.Unmarshal(msg.Payload, &content); err != nil { | ||
m.logger.Error().Err(err).Msg("failed to unmarshal chat message") | ||
// we processed the message, return true | ||
return true | ||
} | ||
|
||
m.sendMessage(session, content) | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func (m *Manager) sendMessageHandler(w http.ResponseWriter, r *http.Request) error { | ||
session, ok := auth.GetSession(r) | ||
if !ok { | ||
return utils.HttpUnauthorized("session not found") | ||
} | ||
|
||
enabled := m.isEnabledForSession(session) | ||
if !enabled { | ||
return utils.HttpForbidden("chat is disabled") | ||
} | ||
|
||
content := Content{} | ||
if err := utils.HttpJsonRequest(w, r, &content); err != nil { | ||
return err | ||
} | ||
|
||
m.sendMessage(session, content) | ||
return utils.HttpSuccess(w) | ||
} | ||
|
||
func settingsIsEnabled(s types.Settings) bool { | ||
isEnabled := true | ||
|
||
settings, ok := s.Plugins["chat"] | ||
// by default, allow chat if the plugin config is not present | ||
if ok { | ||
isEnabled, ok = settings.(bool) | ||
// if the plugin is present but not a boolean, allow chat | ||
if !ok { | ||
isEnabled = true | ||
} | ||
} | ||
|
||
return isEnabled | ||
} | ||
|
||
func profileIsEnabled(p types.MemberProfile) bool { | ||
isEnabled := true | ||
|
||
settings, ok := p.Plugins["chat"] | ||
// by default, allow chat if the plugin config is not present | ||
if ok { | ||
isEnabled, ok = settings.(bool) | ||
// if the plugin is present but not a boolean, allow chat | ||
if !ok { | ||
isEnabled = true | ||
} | ||
} | ||
|
||
return isEnabled | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package chat | ||
|
||
import ( | ||
"github.com/demodesk/neko/pkg/types" | ||
) | ||
|
||
type Plugin struct { | ||
config *Config | ||
manager *Manager | ||
} | ||
|
||
func NewPlugin() *Plugin { | ||
return &Plugin{ | ||
config: &Config{}, | ||
} | ||
} | ||
|
||
func (p *Plugin) Name() string { | ||
return PluginName | ||
} | ||
|
||
func (p *Plugin) Config() types.PluginConfig { | ||
return p.config | ||
} | ||
|
||
func (p *Plugin) Start(m types.PluginManagers) error { | ||
p.manager = NewManager(m.SessionManager, p.config) | ||
m.ApiManager.AddRouter("/chat", p.manager.Route) | ||
m.WebSocketManager.AddHandler(p.manager.WebSocketHandler) | ||
return p.manager.Start() | ||
} | ||
|
||
func (p *Plugin) Shutdown() error { | ||
return p.manager.Shutdown() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package chat | ||
|
||
import "time" | ||
|
||
const PluginName = "chat" | ||
|
||
const ( | ||
CHAT_INIT = "chat/init" | ||
CHAT_MESSAGE = "chat/message" | ||
) | ||
|
||
type Init struct { | ||
Enabled bool `json:"enabled"` | ||
} | ||
|
||
type Content struct { | ||
Text string `json:"text"` | ||
} | ||
|
||
type Message struct { | ||
ID string `json:"id"` | ||
Created time.Time `json:"created"` | ||
Content Content `json:"content"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters