From 691f2dcbbb1dbd9af06bfce93f1be63c6e96a534 Mon Sep 17 00:00:00 2001 From: blob42 Date: Mon, 16 Jan 2023 14:19:53 +0100 Subject: [PATCH] initial --- .vscode/launch.json | 95 ++++++++++++ go.mod | 18 +++ go.sum | 24 +++ main.go | 353 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 490 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..1375396 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,95 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "toggle source", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 38697, + "host": "127.0.0.1", + "program": "${workspaceFolder}", + "env": { + }, + "args": ["-srn", "BRB", "source", "-t"], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env", + "buildFlags":"" + }, + { + "name": "Debug Program", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 38697, + "host": "127.0.0.1", + "program": "${workspaceFolder}", + "env": { + }, + "args": ["-h"], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env", + "buildFlags":"" + }, + { + "name": "Debug current package", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 38697, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": { + }, + "args": [], + "cwd": "${workspaceFolder}", + "envFile": "${workspaceFolder}/.env", + "buildFlags":"" + }, + { + "name": "Launch test function", + "type": "go", + "request": "launch", + "mode": "test", + "program": "${workspaceFolder}", + "args": [ + "-test.run", + "MyTestFunction" + ] + }, + { + "name": "Attach main", + "type": "go", + "request": "attach", + "mode": "debug", + "remotePath": "", + "port": 38697, + "host": "127.0.0.1", + "program": "${workspaceFolder}/main.go", + "env": { + }, + "args": [], + "cwd": "${workspaceFolder}", + "processId":"", + "envFile": "${workspaceFolder}/.env", + "buildFlags":"" + }, + { + "name": "Attach to Process", + "type": "go", + "request": "attach", + "mode": "local", + "processId": 0 + }, + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${file}" + } + ] +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..91e1c13 --- /dev/null +++ b/go.mod @@ -0,0 +1,18 @@ +module blob42/remove-users + +go 1.19 + +require ( + github.com/BurntSushi/toml v1.2.1 + github.com/andreykaipov/goobs v0.12.0 + github.com/kr/pretty v0.3.1 +) + +require ( + github.com/buger/jsonparser v1.1.1 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect + github.com/rogpeppe/go-internal v1.9.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5c2e3da --- /dev/null +++ b/go.sum @@ -0,0 +1,24 @@ +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/andreykaipov/goobs v0.12.0 h1:EsV3QzqWO+FsrLFbzzKsZuHsdFAGp7F0nG9OiDWdaVs= +github.com/andreykaipov/goobs v0.12.0/go.mod h1:EqG73Uu/4npyhXIWWszgRelNkEeIz+d0slUT6NKWYs4= +github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= +github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= diff --git a/main.go b/main.go new file mode 100644 index 0000000..16735c6 --- /dev/null +++ b/main.go @@ -0,0 +1,353 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + + "github.com/andreykaipov/goobs" + "github.com/andreykaipov/goobs/api/requests/sceneitems" + "github.com/andreykaipov/goobs/api/typedefs" + + // "github.com/andreykaipov/goobs/api/requests/scenes" + + // toml library + "github.com/BurntSushi/toml" + pretty "github.com/kr/pretty" +) + +var ( + Client *goobs.Client // OBS websocket client + Commands = []string{"source", "scene", "list-scenes", "list-sources"} +) + +const ( + Help = `Usage: obs [options] [command] + +COMMANDS: + source + scene + list-scenes + list-sources + +FLAGS:` + +) + +type Config struct { + Host string `toml:"obs_host"` + Pass string `toml:"obs_pass"` +} + +type Source struct { + enabled bool + SceneItem *typedefs.SceneItem + Scene +} + +type Scene struct { + id int + name string +} + +type CliFlags struct { + show *bool + hide *bool + toggle *bool + sourceName *string + sceneName *string +} + +// Return true if elm in list +func InArray[T comparable](list []T, elm T) bool { + for _, v := range list { + if elm == v { + return true + } + } + + return false +} + +func (s *Source) sync() error { + resp, err := Client.SceneItems.SetSceneItemEnabled(&sceneitems.SetSceneItemEnabledParams{ + SceneItemEnabled: &s.enabled, + SceneItemId: float64(s.SceneItem.SceneItemID), + SceneName: s.Scene.name, + }) + if err != nil { + return err + } + + pretty.Println(resp) + return nil +} + +func (s *Source) Show() { + s.enabled = true + err := s.sync() + if err != nil { + log.Fatal(err) + } +} + +func (s *Source) Hide() { + s.enabled = false + err := s.sync() + if err != nil { + log.Fatal(err) + } +} + +func (s *Source) Toggle() { + s.enabled = !s.SceneItem.SceneItemEnabled + err := s.sync() + if err != nil { + log.Fatal(err) + } +} + +func getSourceByName(sources []*Source, name string) *Source { + for _, v := range sources { + if v.SceneItem.SourceName == name { + return v + } + } + return nil +} + +// let the user choose a source from active scene +func selectSource(scene Scene) *Source { + sources := getSources(scene) + for i, v := range sources { + // Print index, name, type left aligned + fmt.Printf("%2d %-20s %-20s\n", i, v.SceneItem.SourceName, v.SceneItem.SourceType) + } + fmt.Print("Select a source by number> ") + var input int + fmt.Scanln(&input) + + return sources[input] +} + +// return all scenes +func getScenes() []Scene { + var scenes []Scene + if Client == nil { + log.Fatal("Client is nil") + } + resp, err := Client.Scenes.GetSceneList() + if err != nil { + log.Fatal(err) + } + for _, v := range resp.Scenes { + scenes = append(scenes, Scene{id: v.SceneIndex, name: v.SceneName}) + } + + return scenes +} + +// returns all sources in a scene +func getSources(scene Scene) []*Source { + var sources []*Source + resp, err := Client.SceneItems.GetSceneItemList(&sceneitems.GetSceneItemListParams{ + SceneName: scene.name, + }) + + if err != nil { + log.Fatal(err) + } + + for _, v := range resp.SceneItems { + sources = append(sources, &Source{SceneItem: v, Scene: scene}) + } + + return sources +} + +func getCurrentScene() Scene { + if Client == nil { + log.Fatal("Client is nil") + } + resp, err := Client.Scenes.GetCurrentProgramScene() + if err != nil { + log.Fatal(err) + } + return Scene{name: resp.CurrentProgramSceneName} +} + +// Display list of scenes and let the user select a scene by number +// Returns a pointer to the selected scene +func promptScene() Scene { + var scene Scene + + resp, err := Client.Scenes.GetSceneList() + if err != nil { + log.Fatal(err) + } + // Print list of scenes + for i, v := range resp.Scenes { + fmt.Printf("%2d %s\n", i, v.SceneName) + } + + // Ask user to select a scene + fmt.Print("Select a scene by number> ") + var input int + fmt.Scanln(&input) + + scene.id = resp.Scenes[input].SceneIndex + scene.name = resp.Scenes[input].SceneName + + return scene +} + +func handleSourceCommand(source *Source, flags CliFlags) { + if *flags.show { + source.Show() + } else if *flags.hide { + source.Hide() + } else if *flags.toggle { + source.Toggle() + } +} + +func main() { + var err error + + // load host and pass from config + config := loadConfig() + Client, err = goobs.New(config.Host, goobs.WithPassword(config.Pass)) + if err != nil { + log.Fatal(err) + } + defer Client.Disconnect() + + version, _ := Client.General.GetVersion() + fmt.Printf("OBS Studio version: %s\n", version.ObsVersion) + fmt.Printf("Websocket server version: %s\n", version.ObsWebSocketVersion) + + // Handle CLI + // Commands: + // - source + // - scene + // - list-scenes + // - list-sources + + // Flags: + // -s = show + // -d = hide + // -t = toggle + // -scn = scene name + // -srn = source name + + // Examples: + // obsctl source -s will show the selected source + + // Parse CLI flags + + help := flag.Bool("h", false, "Show help") + flags := CliFlags{ + show: flag.Bool("s", false, "Show source"), + hide: flag.Bool("d", false, "Hide source"), + toggle: flag.Bool("t", false, "Toggle source"), + sourceName: flag.String("srn", "", "Source name"), + sceneName: flag.String("scn", "", "Source name"), + } + flag.Parse() + + if *flags.show && *flags.hide { + log.Fatal("Cannot show and hide at the same time") + } + + if *help { + fmt.Println(Help) + flag.PrintDefaults() + } + + // parse commands using os.Args + if len(os.Args) < 2 { + log.Fatal("No command given") + } + + // find command in args + var cmd string + for _, v := range os.Args { + // test if v contains a command + if InArray(Commands, v) { + cmd = v + } + } + + + switch cmd { + case "source": + // assume current active scene + var source *Source + if *flags.sourceName == "" { + source = selectSource(getCurrentScene()) + } else { + fmt.Println("handling source", *flags.sourceName) + source = getSourceByName(getSources(getCurrentScene()), *flags.sourceName) + } + handleSourceCommand(source, flags) + case "scene": + // prompt user to select a scene + scene := promptScene() + source := selectSource(scene) + handleSourceCommand(source, flags) + + case "list-scenes": + scenes := getScenes() + for _, v := range scenes { + fmt.Printf("%s\n", v.name) + } + + case "list-sources": + // If no scene selected assume current scene + if flags.sceneName == nil || *flags.sceneName == "" { + sources := getSources(getCurrentScene()) + for _, v := range sources { + fmt.Printf("%s\n", v.SceneItem.SourceName) + } + } + + } + + + // select a scene + // selected := promptScene(client) + // fmt.Println(selected) + + // select source from currently active scene + // scene := getCurrentScene() + // source := selectSource(scene) + // source.Hide() + // pretty.Println(source) + + // Get current scene + // scene := getCurrentScene(client) + // fmt.Println(scene) + + // resp, _ := client.Scenes.GetSceneList() + // for _, v := range resp.Scenes { + // fmt.Printf("%2d %s\n", v.SceneIndex, v.SceneName) + // } + // + // newScene := &scenes.SetCurrentProgramSceneParams{ + // SceneName: "Coding", + // } + // _, err = client.Scenes.SetCurrentProgramScene(newScene) + // if err != nil { + // log.Fatal(err) + // } +} + +func loadConfig() Config { + var config Config + if _, err := toml.DecodeFile("config.toml", &config); err != nil { + log.Fatal(err) + } + return config +} +