mirror of
https://github.com/m1k1o/neko.git
synced 2026-11-03 00:11:48 -06:00
feat: add openinapp plugin to open chat links in shared neko instance (#682)
* feat: add openinapp plugin to open chat links in shared neko instance * lint. * feat(openinapp): implement open link handler and route for opening links. * feat(openinapp): switched to http backend and removed example in docker-compose.yaml * lint. --------- Co-authored-by: Miroslav Šedivý <sedivy.miro@gmail.com>
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/m1k1o/neko/server/internal/api/room"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/chat"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/filetransfer"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/openinapp"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
"github.com/m1k1o/neko/server/pkg/types/event"
|
||||
"github.com/m1k1o/neko/server/pkg/types/message"
|
||||
@@ -199,6 +200,14 @@ func (s *session) wsToBackend(msg []byte) error {
|
||||
case oldEvent.FILETRANSFER_REFRESH:
|
||||
return s.toBackend(filetransfer.FILETRANSFER_UPDATE, nil)
|
||||
|
||||
// Open In App Events
|
||||
case openinapp.OPENINAPP_OPENLINK:
|
||||
request := &openinapp.Url{}
|
||||
if err := json.Unmarshal(msg, request); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.apiReq(http.MethodPost, "/api/openinapp/openlink", request, nil)
|
||||
|
||||
// Screen Events
|
||||
case oldEvent.SCREEN_RESOLUTION:
|
||||
response := &types.ScreenSize{}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/m1k1o/neko/server/internal/plugins/chat"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/filetransfer"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/openinapp"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
"github.com/m1k1o/neko/server/pkg/types/event"
|
||||
"github.com/m1k1o/neko/server/pkg/types/message"
|
||||
@@ -615,6 +616,21 @@ func (s *session) wsToClient(msg []byte) error {
|
||||
Files: files,
|
||||
})
|
||||
|
||||
// Open In App Events
|
||||
case openinapp.OPENINAPP_INIT:
|
||||
request := &openinapp.Init{}
|
||||
if err := json.Unmarshal(data.Payload, request); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.toClient(&struct {
|
||||
Event string `json:"event"`
|
||||
Enabled bool `json:"enabled"`
|
||||
}{
|
||||
Event: openinapp.OPENINAPP_INIT,
|
||||
Enabled: request.Enabled,
|
||||
})
|
||||
|
||||
|
||||
// Screen Events
|
||||
case event.SCREEN_UPDATED:
|
||||
request := &message.ScreenSizeUpdate{}
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/m1k1o/neko/server/internal/config"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/chat"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/filetransfer"
|
||||
"github.com/m1k1o/neko/server/internal/plugins/openinapp"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
)
|
||||
|
||||
@@ -47,6 +48,7 @@ func New(config *config.Plugins) *ManagerCtx {
|
||||
// add built-in plugins
|
||||
manager.plugins.addPlugin(filetransfer.NewPlugin())
|
||||
manager.plugins.addPlugin(chat.NewPlugin())
|
||||
manager.plugins.addPlugin(openinapp.NewPlugin())
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package openinapp
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Enabled bool
|
||||
OpenCommand string
|
||||
}
|
||||
|
||||
func (Config) Init(cmd *cobra.Command) error {
|
||||
cmd.PersistentFlags().Bool("openinapp.enabled", false, "whether to enable openinapp plugin")
|
||||
if err := viper.BindPFlag("openinapp.enabled", cmd.PersistentFlags().Lookup("openinapp.enabled")); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.PersistentFlags().String("openinapp.open_command", "xdg-open", "command used to open URLs inside the app")
|
||||
if err := viper.BindPFlag("openinapp.open_command", cmd.PersistentFlags().Lookup("openinapp.open_command")); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Config) Set() {
|
||||
s.Enabled = viper.GetBool("openinapp.enabled")
|
||||
s.OpenCommand = viper.GetString("openinapp.open_command")
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package openinapp
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"github.com/m1k1o/neko/server/pkg/auth"
|
||||
"github.com/m1k1o/neko/server/pkg/types"
|
||||
"github.com/m1k1o/neko/server/pkg/utils"
|
||||
)
|
||||
|
||||
func NewManager(
|
||||
sessions types.SessionManager,
|
||||
config *Config,
|
||||
) *Manager {
|
||||
logger := log.With().Str("module", "openinapp").Logger()
|
||||
|
||||
return &Manager{
|
||||
logger: logger,
|
||||
config: config,
|
||||
sessions: sessions,
|
||||
}
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
logger zerolog.Logger
|
||||
config *Config
|
||||
sessions types.SessionManager
|
||||
}
|
||||
|
||||
func (m *Manager) Route(r types.Router) {
|
||||
r.Post("/openlink", m.openLinkHandler)
|
||||
}
|
||||
|
||||
func (m *Manager) openLinkHandler(w http.ResponseWriter, r *http.Request) error {
|
||||
session, ok := auth.GetSession(r)
|
||||
if !ok {
|
||||
return utils.HttpUnauthorized("session not found")
|
||||
}
|
||||
|
||||
if !m.config.Enabled {
|
||||
return utils.HttpForbidden("openinapp is disabled")
|
||||
}
|
||||
|
||||
if !session.IsHost() && (!session.Profile().CanHost || !m.sessions.Settings().ImplicitHosting) {
|
||||
return utils.HttpForbidden("only the host can open links")
|
||||
}
|
||||
|
||||
var req Url
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
return utils.HttpBadRequest().WithInternalErr(err).Msg("failed to parse request body")
|
||||
}
|
||||
|
||||
parsedUrl, err := url.Parse(req.Text)
|
||||
if err != nil {
|
||||
return utils.HttpBadRequest().WithInternalErr(err).Msg("failed to parse URL")
|
||||
}
|
||||
|
||||
if parsedUrl.Scheme != "http" && parsedUrl.Scheme != "https" {
|
||||
return utils.HttpBadRequest().Msg("URL must use http or https scheme")
|
||||
}
|
||||
|
||||
parts := strings.Fields(m.config.OpenCommand)
|
||||
if len(parts) == 0 {
|
||||
return utils.HttpInternalServerError().Msg("open command is not configured")
|
||||
}
|
||||
|
||||
if err := exec.Command(parts[0], append(parts[1:], parsedUrl.String())...).Start(); err != nil {
|
||||
return utils.HttpInternalServerError().WithInternalErr(err).Msg("failed to start open command")
|
||||
}
|
||||
|
||||
return utils.HttpSuccess(w, nil)
|
||||
}
|
||||
|
||||
func (m *Manager) Start() error {
|
||||
// send init message once a user connects
|
||||
m.sessions.OnConnected(func(session types.Session) {
|
||||
session.Send(OPENINAPP_INIT, Init{
|
||||
Enabled: m.config.Enabled,
|
||||
})
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) Shutdown() error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package openinapp
|
||||
|
||||
import (
|
||||
"github.com/m1k1o/neko/server/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("/openinapp", p.manager.Route)
|
||||
return p.manager.Start()
|
||||
}
|
||||
|
||||
func (p *Plugin) Shutdown() error {
|
||||
return p.manager.Shutdown()
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package openinapp
|
||||
|
||||
const PluginName = "openinapp"
|
||||
|
||||
const (
|
||||
OPENINAPP_INIT = "openinapp/init"
|
||||
OPENINAPP_OPENLINK = "openinapp/openlink"
|
||||
)
|
||||
|
||||
type Init struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
type Url struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
Reference in New Issue
Block a user