mirror of
				https://github.com/onyx-and-iris/gobs-cli.git
				synced 2025-10-30 21:01:50 +00:00 
			
		
		
		
	first commit
This commit is contained in:
		
						commit
						2037a64e98
					
				
							
								
								
									
										29
									
								
								.github/workflows/golang-ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.github/workflows/golang-ci.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| name: CI | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     branches: [ "main" ] | ||||
|     paths: | ||||
|       - '**.go' | ||||
|   pull_request: | ||||
|     branches: [ "main" ] | ||||
|     paths: | ||||
|       - '**.go' | ||||
| jobs: | ||||
|   lint: | ||||
|     name: Lint | ||||
|     runs-on: ubuntu-latest | ||||
|     if: github.event_name == 'pull_request' | ||||
|     timeout-minutes: 3 | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 1 | ||||
|       - name: Setup Go | ||||
|         uses: actions/setup-go@v5 | ||||
|         with: | ||||
|           go-version: '1.24' | ||||
|       - name: Install golangci-lint | ||||
|         run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest | ||||
|       - name: Run golangci-lint | ||||
|         run: golangci-lint run ./... | ||||
							
								
								
									
										31
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | ||||
| name: goreleaser | ||||
| 
 | ||||
| on: | ||||
|   push: | ||||
|     tags: | ||||
|       - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 | ||||
| 
 | ||||
| permissions: | ||||
|   contents: write | ||||
| 
 | ||||
| jobs: | ||||
|   goreleaser: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - | ||||
|         name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|       - | ||||
|         name: Set up Go | ||||
|         uses: actions/setup-go@v5 | ||||
|       - | ||||
|         name: Run GoReleaser | ||||
|         uses: goreleaser/goreleaser-action@v6 | ||||
|         with: | ||||
|           distribution: goreleaser | ||||
|           version: '~> v2' | ||||
|           args: release --clean | ||||
|         env: | ||||
|           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||
							
								
								
									
										30
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| # Auto-generated .gitignore by gignore: github.com/onyx-and-iris/gignore | ||||
| 
 | ||||
| ### Go ### | ||||
| # If you prefer the allow list template instead of the deny list, see community template: | ||||
| # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore | ||||
| # | ||||
| # Binaries for programs and plugins | ||||
| *.exe | ||||
| *.exe~ | ||||
| *.dll | ||||
| *.so | ||||
| *.dylib | ||||
| bin/ | ||||
| 
 | ||||
| # Test binary, built with `go test -c` | ||||
| *.test | ||||
| 
 | ||||
| # Output of the go coverage tool, specifically when used with LiteIDE | ||||
| *.out | ||||
| 
 | ||||
| # Dependency directories (remove the comment below to include it) | ||||
| # vendor/ | ||||
| 
 | ||||
| # Go workspace file | ||||
| go.work | ||||
| 
 | ||||
| # End of gignore: github.com/onyx-and-iris/gignore | ||||
| 
 | ||||
| .envrc | ||||
| *_test.go | ||||
							
								
								
									
										52
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								.golangci.yml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| run: | ||||
|   # timeout for analysis, e.g. 30s, 3m, default is 1m | ||||
|   timeout: 3m | ||||
|   # exclude test files | ||||
|   tests: true | ||||
| 
 | ||||
| linters: | ||||
|   # Set to true runs only fast linters. | ||||
|   # Good option for 'lint on save', pre-commit hook or CI. | ||||
|   fast: true | ||||
| 
 | ||||
|   disable-all: true | ||||
| 
 | ||||
|   enable: | ||||
|     - gosimple | ||||
|     - govet | ||||
|     - ineffassign | ||||
|     - staticcheck | ||||
|     - unused | ||||
|     - gofmt | ||||
|     - gofumpt | ||||
|     - misspell | ||||
|     - unparam | ||||
|     - gosec | ||||
|     - asciicheck | ||||
|     - errname | ||||
|     - gci | ||||
|     - godot | ||||
|     - goimports | ||||
|     - revive | ||||
| 
 | ||||
| linters-settings: | ||||
|   gofmt: | ||||
|     rewrite-rules: | ||||
|       - pattern: 'interface{}' | ||||
|         replacement: 'any' | ||||
|       - pattern: 'a[b:len(a)]' | ||||
|         replacement: 'a[b:]' | ||||
| 
 | ||||
|   misspell: | ||||
|     locale: UK | ||||
| 
 | ||||
|   errcheck: | ||||
|     check-type-assertions: true | ||||
| 
 | ||||
| issues: | ||||
|   max-same-issues: 0 | ||||
|   max-issues-per-linter: 0 | ||||
|   exclude-use-default: false | ||||
|   exclude: | ||||
|     # gosec: Duplicated errcheck checks | ||||
|     - G104 | ||||
							
								
								
									
										48
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| version: 2 | ||||
| 
 | ||||
| before: | ||||
|   hooks: | ||||
|     # You may remove this if you don't use go modules. | ||||
|     - go mod tidy | ||||
|     # you may remove this if you don't need go generate | ||||
|     - go generate ./... | ||||
| 
 | ||||
| builds: | ||||
|   - env: | ||||
|       - CGO_ENABLED=0 | ||||
|     goos: | ||||
|       - linux | ||||
|       - darwin | ||||
|       - windows | ||||
|     goarch: | ||||
|       - amd64 | ||||
|       - arm64 | ||||
| 
 | ||||
| archives: | ||||
|   - formats: ['tar.gz'] | ||||
|     # this name template makes the OS and Arch compatible with the results of `uname`. | ||||
|     name_template: >- | ||||
|       {{ .ProjectName }}_ | ||||
|       {{- title .Os }}_ | ||||
|       {{- if eq .Arch "amd64" }}x86_64 | ||||
|       {{- else if eq .Arch "386" }}i386 | ||||
|       {{- else }}{{ .Arch }}{{ end }} | ||||
|       {{- if .Arm }}v{{ .Arm }}{{ end }} | ||||
|     # use zip for windows archives | ||||
|     format_overrides: | ||||
|       - goos: windows | ||||
|         formats: ['zip'] | ||||
| 
 | ||||
| changelog: | ||||
|   sort: asc | ||||
|   filters: | ||||
|     exclude: | ||||
|       - '^docs:' | ||||
|       - '^test:' | ||||
| 
 | ||||
| release: | ||||
|   footer: >- | ||||
| 
 | ||||
|     --- | ||||
| 
 | ||||
|     Released by [GoReleaser](https://github.com/goreleaser/goreleaser). | ||||
							
								
								
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| # Changelog | ||||
| 
 | ||||
| All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), | ||||
| and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||||
| 
 | ||||
| # [0.1.0] - 2025-04-24 | ||||
| 
 | ||||
| ### Added | ||||
| 
 | ||||
| -   Initial release. | ||||
							
								
								
									
										401
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										401
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,401 @@ | ||||
| # gobs-cli | ||||
| 
 | ||||
| A command line interface for OBS Websocket v5 | ||||
| 
 | ||||
| For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md) | ||||
| 
 | ||||
| ## Configuration | ||||
| 
 | ||||
| #### Flags | ||||
| 
 | ||||
| Pass `--host`, `--port` and `--password` as flags to the root command, for example: | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli --host=localhost --port=4455 --password=<websocket password> --help | ||||
| ``` | ||||
| 
 | ||||
| #### Environment Variables | ||||
| 
 | ||||
| Load connection details from your environment: | ||||
| 
 | ||||
| ```bash | ||||
| #!/usr/bin/env bash | ||||
| 
 | ||||
| export OBS_HOST=localhost | ||||
| export OBS_PORT=4455 | ||||
| export OBS_PASSWORD=<websocket password> | ||||
| export OBS_TIMEOUT=5 | ||||
| ``` | ||||
| 
 | ||||
| ## Commands | ||||
| 
 | ||||
| ### VersionCmd | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli version | ||||
| ``` | ||||
| 
 | ||||
| ### SceneCmd | ||||
| 
 | ||||
| -   list: List all scenes. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli scene list | ||||
| ``` | ||||
| 
 | ||||
| -   current: Get the current scene. | ||||
|     -   flags: | ||||
| 
 | ||||
|         *optional* | ||||
|         -   --preview:  Preview scene. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli scene current | ||||
| 
 | ||||
| gobs-cli scene current --preview | ||||
| ``` | ||||
| 
 | ||||
| -   switch: Switch to a scene. | ||||
|     -   flags: | ||||
| 
 | ||||
|         *optional* | ||||
|         -   --preview:  Preview scene. | ||||
|     -   args: SceneName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli scene switch LIVE | ||||
| 
 | ||||
| gobs-cli scene switch --preview LIVE | ||||
| ``` | ||||
| 
 | ||||
| ### SceneItemCmd | ||||
| 
 | ||||
| -   list: List all scene items. | ||||
|     -   args: SceneName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli sceneitem list LIVE | ||||
| ``` | ||||
| 
 | ||||
| -   show: Show scene item. | ||||
|     -   flags: | ||||
| 
 | ||||
|         *optional* | ||||
|         -   --parent: Parent group name. | ||||
|     -   args: SceneName ItemName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli sceneitem show START "Colour Source" | ||||
| ``` | ||||
| 
 | ||||
| -   hide: Hide scene item. | ||||
|     -   flags: | ||||
| 
 | ||||
|         *optional* | ||||
|         -   --parent: Parent group name. | ||||
|     -   args: SceneName ItemName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli sceneitem hide START "Colour Source" | ||||
| ``` | ||||
| 
 | ||||
| -   toggle: Toggle scene item. | ||||
|     -   flags: | ||||
| 
 | ||||
|         *optional* | ||||
|         -   --parent: Parent group name. | ||||
|     -   args: SceneName ItemName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli sceneitem toggle --parent=test_group START "Colour Source 3" | ||||
| ``` | ||||
| 
 | ||||
| -   visible: Get scene item visibility. | ||||
|     -   flags: | ||||
| 
 | ||||
|         *optional* | ||||
|         -   --parent: Parent group name. | ||||
|     -   args: SceneName ItemName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli sceneitem visible --parent=test_group START "Colour Source 4" | ||||
| ``` | ||||
| 
 | ||||
| ### GroupCmd | ||||
| 
 | ||||
| -   list: List all groups. | ||||
|     -   args: SceneName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli group list START | ||||
| ``` | ||||
| 
 | ||||
| -   show: Show group details. | ||||
|     -   args: SceneName GroupName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli group show START "test_group" | ||||
| ``` | ||||
| 
 | ||||
| -   hide: Hide group. | ||||
|     -   args: SceneName GroupName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli group hide START "test_group" | ||||
| ``` | ||||
| 
 | ||||
| -   toggle: Toggle group. | ||||
|     -   args: SceneName GroupName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli group toggle START "test_group" | ||||
| ``` | ||||
| 
 | ||||
| -   status: Get group status. | ||||
|     -   args: SceneName GroupName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli group status START "test_group" | ||||
| ``` | ||||
| 
 | ||||
| ### InputCmd | ||||
| 
 | ||||
| -   list: List all inputs. | ||||
|     -   flags: | ||||
| 
 | ||||
|         *optional* | ||||
|         -   --input: List all inputs. | ||||
|         -   --output: List all outputs. | ||||
|         -   --colour: List all colour sources. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli input list | ||||
| 
 | ||||
| gobs-cli input list --input --colour | ||||
| ``` | ||||
| 
 | ||||
| -   mute: Mute input. | ||||
|     -   args: InputName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli input mute "Mic/Aux" | ||||
| ``` | ||||
| 
 | ||||
| -   unmute: Unmute input. | ||||
|     -   args: InputName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli input unmute "Mic/Aux" | ||||
| ``` | ||||
| 
 | ||||
| -   toggle: Toggle input. | ||||
|     -   args: InputName | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli input toggle "Mic/Aux" | ||||
| ``` | ||||
| 
 | ||||
| ### RecordCmd | ||||
| 
 | ||||
| -   start: Start recording. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli record start | ||||
| ``` | ||||
| 
 | ||||
| -   stop: Stop recording. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli record stop | ||||
| ``` | ||||
| 
 | ||||
| -   status: Get recording status. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli record status | ||||
| ``` | ||||
| 
 | ||||
| -   toggle: Toggle recording. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli record toggle | ||||
| ``` | ||||
| 
 | ||||
| -   pause: Pause recording. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli record pause | ||||
| ``` | ||||
| 
 | ||||
| -   resume: Resume recording. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli record resume | ||||
| ``` | ||||
| 
 | ||||
| ### StreamCmd | ||||
| 
 | ||||
| -   start: Start streaming. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli stream start | ||||
| ``` | ||||
| 
 | ||||
| -   stop: Stop streaming. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli stream stop | ||||
| ``` | ||||
| 
 | ||||
| -   status: Get streaming status. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli stream status | ||||
| ``` | ||||
| 
 | ||||
| -   toggle: Toggle streaming. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli stream toggle | ||||
| ``` | ||||
| 
 | ||||
| ### SceneCollectionCmd | ||||
| 
 | ||||
| -   list: List scene collections. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli scenecollection list | ||||
| ``` | ||||
| 
 | ||||
| -   current: Get current scene collection. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli scenecollection current | ||||
| ``` | ||||
| 
 | ||||
| -   switch: "Switch scene collection. | ||||
|     -   args: Name | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli scenecollection switch test-collection | ||||
| ``` | ||||
| 
 | ||||
| -   create: Create scene collection. | ||||
|     -   args: Name | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli scenecollection create test-collection | ||||
| ``` | ||||
| 
 | ||||
| ### ProfileCmd | ||||
| 
 | ||||
| -   list: List profiles. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli profile list | ||||
| ``` | ||||
| 
 | ||||
| -   current: Get current profile. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli profile current | ||||
| ``` | ||||
| 
 | ||||
| -   switch: Switch profile. | ||||
|     -   args: Name | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli profile switch test-collection | ||||
| ``` | ||||
| 
 | ||||
| -   create: Create profile. | ||||
|     -   args: Name | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli profile create test-collection | ||||
| ``` | ||||
| 
 | ||||
| -   remove: Remove profile. | ||||
|     -   args: Name | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli profile create test-collection | ||||
| ``` | ||||
| 
 | ||||
| ### ReplayBufferCmd | ||||
| 
 | ||||
| -   start: Start replay buffer. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli replaybuffer start | ||||
| ``` | ||||
| 
 | ||||
| -   stop: Stop replay buffer. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli replaybuffer stop | ||||
| ``` | ||||
| 
 | ||||
| -   status: Get replay buffer status. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli replaybuffer status | ||||
| ``` | ||||
| 
 | ||||
| -   save: Save replay buffer. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli replaybuffer save | ||||
| ``` | ||||
| 
 | ||||
| ### StudioModeCmd | ||||
| 
 | ||||
| -   enable: Enable studio mode. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli studiomode enable | ||||
| ``` | ||||
| 
 | ||||
| -   disable: Disable studio mode. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli studiomode disable | ||||
| ``` | ||||
| 
 | ||||
| -   toggle: Toggle studio mode. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli studiomode toggle | ||||
| ``` | ||||
| 
 | ||||
| -   status: Get studio mode status. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli studiomode status | ||||
| ``` | ||||
| 
 | ||||
| ### VirtualCamCmd | ||||
| 
 | ||||
| -   start: Start virtual camera. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli virtualcam start | ||||
| ``` | ||||
| 
 | ||||
| -   stop: Stop virtual camera. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli virtualcam stop | ||||
| ``` | ||||
| 
 | ||||
| -   toggle: Toggle virtual camera. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli virtualcam toggle | ||||
| ``` | ||||
| 
 | ||||
| -   status: Get virtual camera status. | ||||
| 
 | ||||
| ```console | ||||
| gobs-cli virtualcam status | ||||
| ``` | ||||
							
								
								
									
										52
									
								
								Taskfile.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Taskfile.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| version: '3' | ||||
| 
 | ||||
| vars: | ||||
|   PROGRAM: gobs-cli | ||||
|   SHELL: '{{if eq .OS "Windows_NT"}}powershell{{end}}' | ||||
|   BIN_DIR: bin | ||||
| 
 | ||||
| tasks: | ||||
|   default: | ||||
|     desc: Build the gobs-cli project | ||||
|     cmds: | ||||
|       - task: build | ||||
| 
 | ||||
|   build: | ||||
|     desc: Build the gobs-cli project | ||||
|     deps: [vet] | ||||
|     cmds: | ||||
|       - task: build-windows | ||||
|       - task: build-linux | ||||
| 
 | ||||
|   vet: | ||||
|     desc: Vet the code | ||||
|     deps: [fmt] | ||||
|     cmds: | ||||
|       - go vet ./... | ||||
| 
 | ||||
|   fmt: | ||||
|     desc: Fmt the code | ||||
|     cmds: | ||||
|       - go fmt ./... | ||||
| 
 | ||||
|   build-windows: | ||||
|     desc: Build the gobs-cli project for Windows | ||||
|     cmds: | ||||
|       - GOOS=windows GOARCH=amd64 go build -o {{.BIN_DIR}}/{{.PROGRAM}}_windows_amd64.exe | ||||
|     internal: true | ||||
| 
 | ||||
|   build-linux: | ||||
|     desc: Build the gobs-cli project for Linux | ||||
|     cmds: | ||||
|       - GOOS=linux GOARCH=amd64 go build -o {{.BIN_DIR}}/{{.PROGRAM}}_linux_amd64 | ||||
|     internal: true | ||||
| 
 | ||||
|   test: | ||||
|     desc: Run tests | ||||
|     cmds: | ||||
|       - go test ./... | ||||
| 
 | ||||
|   clean: | ||||
|     desc: Clean the build artifacts | ||||
|     cmds: | ||||
|       - '{{.SHELL}} rm -r {{.BIN_DIR}}' | ||||
							
								
								
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								go.mod
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| module github.com/onyx-and-iris/gobs-cli | ||||
| 
 | ||||
| go 1.24.0 | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/alecthomas/kong v1.10.0 | ||||
| 	github.com/andreykaipov/goobs v1.5.6 | ||||
| ) | ||||
| 
 | ||||
| require ( | ||||
| 	github.com/buger/jsonparser v1.1.1 // indirect | ||||
| 	github.com/gorilla/websocket v1.5.3 // indirect | ||||
| 	github.com/hashicorp/logutils v1.0.0 // indirect | ||||
| 	github.com/mitchellh/mapstructure v1.5.0 // indirect | ||||
| 	github.com/mmcloughlin/profile v0.1.1 // indirect | ||||
| 	github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect | ||||
| ) | ||||
							
								
								
									
										30
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								go.sum
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,30 @@ | ||||
| github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= | ||||
| github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= | ||||
| github.com/alecthomas/kong v1.10.0 h1:8K4rGDpT7Iu+jEXCIJUeKqvpwZHbsFRoebLbnzlmrpw= | ||||
| github.com/alecthomas/kong v1.10.0/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU= | ||||
| github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= | ||||
| github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= | ||||
| github.com/andreykaipov/goobs v1.5.6 h1:eIkEqYN99+2VJvmlY/56Ah60nkRKS6efMQvpM3oUgPQ= | ||||
| github.com/andreykaipov/goobs v1.5.6/go.mod h1:iSZP93FJ4d9X/U1x4DD4IyILLtig+vViqZWBGjLywcY= | ||||
| 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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||
| github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||
| github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= | ||||
| github.com/gorilla/websocket v1.5.3/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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= | ||||
| github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= | ||||
| github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= | ||||
| github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= | ||||
| github.com/mmcloughlin/profile v0.1.1 h1:jhDmAqPyebOsVDOCICJoINoLb/AnLBaUw58nFzxWS2w= | ||||
| github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= | ||||
| 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||||
| gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||
							
								
								
									
										173
									
								
								group.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								group.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,173 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/andreykaipov/goobs/api/requests/sceneitems" | ||||
| ) | ||||
| 
 | ||||
| // GroupCmd provides commands to manage groups in OBS Studio. | ||||
| type GroupCmd struct { | ||||
| 	List   GroupListCmd   `cmd:"" help:"List all groups."    aliases:"ls"` | ||||
| 	Show   GroupShowCmd   `cmd:"" help:"Show group details." aliases:"sh"` | ||||
| 	Hide   GroupHideCmd   `cmd:"" help:"Hide group."         aliases:"h"` | ||||
| 	Toggle GroupToggleCmd `cmd:"" help:"Toggle group."       aliases:"tg"` | ||||
| 	Status GroupStatusCmd `cmd:"" help:"Get group status."   aliases:"ss"` | ||||
| } | ||||
| 
 | ||||
| // GroupListCmd provides a command to list all groups in a scene. | ||||
| type GroupListCmd struct { | ||||
| 	SceneName string `arg:"" help:"Name of the scene to list groups from."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to list all groups in a scene. | ||||
| func (cmd *GroupListCmd) Run(ctx *context) error { | ||||
| 	resp, err := ctx.Client.SceneItems.GetSceneItemList(sceneitems.NewGetSceneItemListParams(). | ||||
| 		WithSceneName(cmd.SceneName)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene item list: %w", err) | ||||
| 	} | ||||
| 	for _, item := range resp.SceneItems { | ||||
| 		if item.IsGroup { | ||||
| 			fmt.Fprintf(ctx.Out, "Group ID: %d, Source Name: %s\n", item.SceneItemID, item.SourceName) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GroupShowCmd provides a command to show a group in a scene. | ||||
| type GroupShowCmd struct { | ||||
| 	SceneName string `arg:"" help:"Name of the scene to show group from."` | ||||
| 	GroupName string `arg:"" help:"Name of the group to show."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to show a group in a scene. | ||||
| func (cmd *GroupShowCmd) Run(ctx *context) error { | ||||
| 	resp, err := ctx.Client.SceneItems.GetSceneItemList(sceneitems.NewGetSceneItemListParams(). | ||||
| 		WithSceneName(cmd.SceneName)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene item list: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var found bool | ||||
| 	for _, item := range resp.SceneItems { | ||||
| 		if item.IsGroup && item.SourceName == cmd.GroupName { | ||||
| 			_, err := ctx.Client.SceneItems.SetSceneItemEnabled(sceneitems.NewSetSceneItemEnabledParams(). | ||||
| 				WithSceneName(cmd.SceneName). | ||||
| 				WithSceneItemId(item.SceneItemID). | ||||
| 				WithSceneItemEnabled(true)) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("failed to set scene item enabled: %w", err) | ||||
| 			} | ||||
| 			fmt.Fprintf(ctx.Out, "Group %s is now shown.\n", cmd.GroupName) | ||||
| 			found = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if !found { | ||||
| 		return fmt.Errorf("group '%s' not found", cmd.GroupName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GroupHideCmd provides a command to hide a group in a scene. | ||||
| type GroupHideCmd struct { | ||||
| 	SceneName string `arg:"" help:"Name of the scene to hide group from."` | ||||
| 	GroupName string `arg:"" help:"Name of the group to hide."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to hide a group in a scene. | ||||
| func (cmd *GroupHideCmd) Run(ctx *context) error { | ||||
| 	resp, err := ctx.Client.SceneItems.GetSceneItemList(sceneitems.NewGetSceneItemListParams(). | ||||
| 		WithSceneName(cmd.SceneName)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene item list: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var found bool | ||||
| 	for _, item := range resp.SceneItems { | ||||
| 		if item.IsGroup && item.SourceName == cmd.GroupName { | ||||
| 			_, err := ctx.Client.SceneItems.SetSceneItemEnabled(sceneitems.NewSetSceneItemEnabledParams(). | ||||
| 				WithSceneName(cmd.SceneName). | ||||
| 				WithSceneItemId(item.SceneItemID). | ||||
| 				WithSceneItemEnabled(false)) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("failed to set scene item enabled: %w", err) | ||||
| 			} | ||||
| 			fmt.Fprintf(ctx.Out, "Group %s is now hidden.\n", cmd.GroupName) | ||||
| 			found = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if !found { | ||||
| 		return fmt.Errorf("group '%s' not found", cmd.GroupName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GroupToggleCmd provides a command to toggle a group in a scene. | ||||
| type GroupToggleCmd struct { | ||||
| 	SceneName string `arg:"" help:"Name of the scene to toggle group from."` | ||||
| 	GroupName string `arg:"" help:"Name of the group to toggle."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to toggle a group in a scene. | ||||
| func (cmd *GroupToggleCmd) Run(ctx *context) error { | ||||
| 	resp, err := ctx.Client.SceneItems.GetSceneItemList(sceneitems.NewGetSceneItemListParams(). | ||||
| 		WithSceneName(cmd.SceneName)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene item list: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	var found bool | ||||
| 	for _, item := range resp.SceneItems { | ||||
| 		if item.IsGroup && item.SourceName == cmd.GroupName { | ||||
| 			newState := !item.SceneItemEnabled | ||||
| 			_, err := ctx.Client.SceneItems.SetSceneItemEnabled(sceneitems.NewSetSceneItemEnabledParams(). | ||||
| 				WithSceneName(cmd.SceneName). | ||||
| 				WithSceneItemId(item.SceneItemID). | ||||
| 				WithSceneItemEnabled(newState)) | ||||
| 			if err != nil { | ||||
| 				return fmt.Errorf("failed to set scene item enabled: %w", err) | ||||
| 			} | ||||
| 			if newState { | ||||
| 				fmt.Fprintf(ctx.Out, "Group %s is now shown.\n", cmd.GroupName) | ||||
| 			} else { | ||||
| 				fmt.Fprintf(ctx.Out, "Group %s is now hidden.\n", cmd.GroupName) | ||||
| 			} | ||||
| 			found = true | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| 	if !found { | ||||
| 		return fmt.Errorf("group '%s' not found", cmd.GroupName) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // GroupStatusCmd provides a command to get the status of a group in a scene. | ||||
| type GroupStatusCmd struct { | ||||
| 	SceneName string `arg:"" help:"Name of the scene to get group status from."` | ||||
| 	GroupName string `arg:"" help:"Name of the group to get status."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to get the status of a group in a scene. | ||||
| func (cmd *GroupStatusCmd) Run(ctx *context) error { | ||||
| 	resp, err := ctx.Client.SceneItems.GetSceneItemList(sceneitems.NewGetSceneItemListParams(). | ||||
| 		WithSceneName(cmd.SceneName)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene item list: %w", err) | ||||
| 	} | ||||
| 	for _, item := range resp.SceneItems { | ||||
| 		if item.IsGroup && item.SourceName == cmd.GroupName { | ||||
| 			if item.SceneItemEnabled { | ||||
| 				fmt.Fprintf(ctx.Out, "Group %s is shown.\n", cmd.GroupName) | ||||
| 			} else { | ||||
| 				fmt.Fprintf(ctx.Out, "Group %s is hidden.\n", cmd.GroupName) | ||||
| 			} | ||||
| 			return nil | ||||
| 		} | ||||
| 	} | ||||
| 	return fmt.Errorf("group '%s' not found", cmd.GroupName) | ||||
| } | ||||
							
								
								
									
										114
									
								
								input.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								input.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/andreykaipov/goobs/api/requests/inputs" | ||||
| ) | ||||
| 
 | ||||
| // InputCmd provides commands to manage inputs in OBS Studio. | ||||
| type InputCmd struct { | ||||
| 	List   InputListCmd   `cmd:"" help:"List all inputs." aliases:"ls"` | ||||
| 	Mute   InputMuteCmd   `cmd:"" help:"Mute input."      aliases:"m"` | ||||
| 	Unmute InputUnmuteCmd `cmd:"" help:"Unmute input."    aliases:"um"` | ||||
| 	Toggle InputToggleCmd `cmd:"" help:"Toggle input."    aliases:"tg"` | ||||
| } | ||||
| 
 | ||||
| // InputListCmd provides a command to list all inputs. | ||||
| type InputListCmd struct { | ||||
| 	Input  bool `flag:"" help:"List all inputs."         aliases:"i"` | ||||
| 	Output bool `flag:"" help:"List all outputs."        aliases:"o"` | ||||
| 	Colour bool `flag:"" help:"List all colour sources." aliases:"c"` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to list all inputs. | ||||
| func (cmd *InputListCmd) Run(ctx *context) error { | ||||
| 	resp, err := ctx.Client.Inputs.GetInputList(inputs.NewGetInputListParams()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	for _, input := range resp.Inputs { | ||||
| 		if cmd.Input && strings.Contains(input.InputKind, "input") { | ||||
| 			fmt.Fprintln(ctx.Out, "Input:", input.InputName) | ||||
| 		} | ||||
| 		if cmd.Output && strings.Contains(input.InputKind, "output") { | ||||
| 			fmt.Fprintln(ctx.Out, "Output:", input.InputName) | ||||
| 		} | ||||
| 		if cmd.Colour && strings.Contains(input.InputKind, "color") { // nolint | ||||
| 			fmt.Fprintln(ctx.Out, "Colour Source:", input.InputName) | ||||
| 		} | ||||
| 
 | ||||
| 		if !cmd.Input && !cmd.Output && !cmd.Colour { | ||||
| 			fmt.Fprintln(ctx.Out, "Source:", input.InputName) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // InputMuteCmd provides a command to mute an input. | ||||
| type InputMuteCmd struct { | ||||
| 	InputName string `arg:"" help:"Name of the input to mute."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to mute an input. | ||||
| func (cmd *InputMuteCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Inputs.SetInputMute( | ||||
| 		inputs.NewSetInputMuteParams().WithInputName(cmd.InputName).WithInputMuted(true), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to mute input: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintf(ctx.Out, "Muted input: %s\n", cmd.InputName) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // InputUnmuteCmd provides a command to unmute an input. | ||||
| type InputUnmuteCmd struct { | ||||
| 	InputName string `arg:"" help:"Name of the input to unmute."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to unmute an input. | ||||
| func (cmd *InputUnmuteCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Inputs.SetInputMute( | ||||
| 		inputs.NewSetInputMuteParams().WithInputName(cmd.InputName).WithInputMuted(false), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to unmute input: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintf(ctx.Out, "Unmuted input: %s\n", cmd.InputName) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // InputToggleCmd provides a command to toggle the mute state of an input. | ||||
| type InputToggleCmd struct { | ||||
| 	InputName string `arg:"" help:"Name of the input to toggle."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to toggle the mute state of an input. | ||||
| func (cmd *InputToggleCmd) Run(ctx *context) error { | ||||
| 	// Get the current mute state of the input | ||||
| 	resp, err := ctx.Client.Inputs.GetInputMute( | ||||
| 		inputs.NewGetInputMuteParams().WithInputName(cmd.InputName), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get input mute state: %w", err) | ||||
| 	} | ||||
| 	// Toggle the mute state | ||||
| 	newMuteState := !resp.InputMuted | ||||
| 	_, err = ctx.Client.Inputs.SetInputMute( | ||||
| 		inputs.NewSetInputMuteParams().WithInputName(cmd.InputName).WithInputMuted(newMuteState), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to toggle input mute state: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if newMuteState { | ||||
| 		fmt.Fprintf(ctx.Out, "Muted input: %s\n", cmd.InputName) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(ctx.Out, "Unmuted input: %s\n", cmd.InputName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										94
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								main.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| // Package main provides a command-line interface (CLI) tool for interacting with OBS WebSocket. | ||||
| // It allows users to manage various aspects of OBS, such as scenes, inputs, recording, streaming, | ||||
| // and more, by leveraging the goobs library for communication with the OBS WebSocket server. | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"os" | ||||
| 	"time" | ||||
| 
 | ||||
| 	"github.com/alecthomas/kong" | ||||
| 	"github.com/andreykaipov/goobs" | ||||
| ) | ||||
| 
 | ||||
| // ObsConfig holds the configuration for connecting to the OBS WebSocket server. | ||||
| type ObsConfig struct { | ||||
| 	Host     string `flag:"host"     help:"Host to connect to."          default:"localhost" env:"OBS_HOST"` | ||||
| 	Port     int    `flag:"port"     help:"Port to connect to."          default:"4455"      env:"OBS_PORT"` | ||||
| 	Password string `flag:"password" help:"Password for authentication." default:""          env:"OBS_PASSWORD"` | ||||
| 	Timeout  int    `flag:"timeout"  help:"Timeout in seconds."          default:"5"         env:"OBS_TIMEOUT"` | ||||
| } | ||||
| 
 | ||||
| // cli is the main command line interface structure. | ||||
| // It embeds the ObsConfig struct to inherit its fields and flags. | ||||
| type cli struct { | ||||
| 	ObsConfig `embed:"" help:"OBS WebSocket configuration."` | ||||
| 
 | ||||
| 	Version         VersionCmd         `help:"Show version."             cmd:"" aliases:"v"` | ||||
| 	Scene           SceneCmd           `help:"Manage scenes."            cmd:"" aliases:"sc"` | ||||
| 	Sceneitem       SceneItemCmd       `help:"Manage scene items."       cmd:"" aliases:"si"` | ||||
| 	Group           GroupCmd           `help:"Manage groups."            cmd:"" aliases:"g"` | ||||
| 	Input           InputCmd           `help:"Manage inputs."            cmd:"" aliases:"i"` | ||||
| 	Record          RecordCmd          `help:"Manage recording."         cmd:"" aliases:"rec"` | ||||
| 	Stream          StreamCmd          `help:"Manage streaming."         cmd:"" aliases:"st"` | ||||
| 	Scenecollection SceneCollectionCmd `help:"Manage scene collections." cmd:"" aliases:"scn"` | ||||
| 	Profile         ProfileCmd         `help:"Manage profiles."          cmd:"" aliases:"p"` | ||||
| 	Replaybuffer    ReplayBufferCmd    `help:"Manage replay buffer."     cmd:"" aliases:"rb"` | ||||
| 	Studiomode      StudioModeCmd      `help:"Manage studio mode."       cmd:"" aliases:"sm"` | ||||
| 	Virtualcam      VirtualCamCmd      `help:"Manage virtual camera."    cmd:"" aliases:"vc"` | ||||
| } | ||||
| 
 | ||||
| type context struct { | ||||
| 	Client *goobs.Client | ||||
| 	Out    io.Writer | ||||
| } | ||||
| 
 | ||||
| func main() { | ||||
| 	var client *goobs.Client | ||||
| 	cli := cli{} | ||||
| 	ctx := kong.Parse( | ||||
| 		&cli, | ||||
| 		kong.Name("GOBS-CLI"), | ||||
| 		kong.Description("A command line tool to interact with OBS Websocket."), | ||||
| 	) | ||||
| 
 | ||||
| 	client, err := connectObs(cli.ObsConfig) | ||||
| 	if err != nil { | ||||
| 		ctx.FatalIfErrorf(err) | ||||
| 	} | ||||
| 
 | ||||
| 	ctx.Bind(&context{ | ||||
| 		Client: client, | ||||
| 		Out:    os.Stdout, | ||||
| 	}) | ||||
| 
 | ||||
| 	ctx.FatalIfErrorf(run(ctx, client)) | ||||
| } | ||||
| 
 | ||||
| // connectObs creates a new OBS client and connects to the OBS WebSocket server. | ||||
| func connectObs(cfg ObsConfig) (*goobs.Client, error) { | ||||
| 	client, err := goobs.New( | ||||
| 		fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), | ||||
| 		goobs.WithPassword(cfg.Password), | ||||
| 		goobs.WithResponseTimeout(time.Duration(cfg.Timeout)*time.Second), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return client, nil | ||||
| } | ||||
| 
 | ||||
| // run executes the command line interface. | ||||
| // It disconnects the OBS client after the command is executed. | ||||
| func run(ctx *kong.Context, client *goobs.Client) error { | ||||
| 	defer func() error { | ||||
| 		if err := client.Disconnect(); err != nil { | ||||
| 			return fmt.Errorf("failed to disconnect from OBS: %w", err) | ||||
| 		} | ||||
| 		return nil | ||||
| 	}() | ||||
| 
 | ||||
| 	return ctx.Run() | ||||
| } | ||||
							
								
								
									
										131
									
								
								profile.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								profile.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,131 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"slices" | ||||
| 
 | ||||
| 	"github.com/andreykaipov/goobs/api/requests/config" | ||||
| ) | ||||
| 
 | ||||
| // ProfileCmd provides commands to manage profiles in OBS Studio. | ||||
| type ProfileCmd struct { | ||||
| 	List    ListProfileCmd    `help:"List profiles."       cmd:"" aliases:"ls"` | ||||
| 	Current CurrentProfileCmd `help:"Get current profile." cmd:"" aliases:"c"` | ||||
| 	Switch  SwitchProfileCmd  `help:"Switch profile."      cmd:"" aliases:"sw"` | ||||
| 	Create  CreateProfileCmd  `help:"Create profile."      cmd:"" aliases:"cr"` | ||||
| 	Remove  RemoveProfileCmd  `help:"Remove profile."      cmd:"" aliases:"rm"` | ||||
| } | ||||
| 
 | ||||
| // ListProfileCmd provides a command to list all profiles. | ||||
| type ListProfileCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to list all profiles. | ||||
| func (cmd *ListProfileCmd) Run(ctx *context) error { | ||||
| 	profiles, err := ctx.Client.Config.GetProfileList() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	for _, profile := range profiles.Profiles { | ||||
| 		fmt.Fprintln(ctx.Out, profile) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CurrentProfileCmd provides a command to get the current profile. | ||||
| type CurrentProfileCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to get the current profile. | ||||
| func (cmd *CurrentProfileCmd) Run(ctx *context) error { | ||||
| 	profiles, err := ctx.Client.Config.GetProfileList() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintf(ctx.Out, "Current profile: %s\n", profiles.CurrentProfileName) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SwitchProfileCmd provides a command to switch to a different profile. | ||||
| type SwitchProfileCmd struct { | ||||
| 	Name string `arg:"" help:"Name of the profile to switch to." required:""` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to switch to a different profile. | ||||
| func (cmd *SwitchProfileCmd) Run(ctx *context) error { | ||||
| 	profiles, err := ctx.Client.Config.GetProfileList() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	current := profiles.CurrentProfileName | ||||
| 
 | ||||
| 	if current == cmd.Name { | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.Config.SetCurrentProfile(config.NewSetCurrentProfileParams().WithProfileName(cmd.Name)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintf(ctx.Out, "Switched from profile %s to %s\n", current, cmd.Name) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CreateProfileCmd provides a command to create a new profile. | ||||
| type CreateProfileCmd struct { | ||||
| 	Name string `arg:"" help:"Name of the profile to create." required:""` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to create a new profile. | ||||
| func (cmd *CreateProfileCmd) Run(ctx *context) error { | ||||
| 	profiles, err := ctx.Client.Config.GetProfileList() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if slices.Contains(profiles.Profiles, cmd.Name) { | ||||
| 		return fmt.Errorf("profile %s already exists", cmd.Name) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.Config.CreateProfile(config.NewCreateProfileParams().WithProfileName(cmd.Name)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintf(ctx.Out, "Created profile: %s\n", cmd.Name) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RemoveProfileCmd provides a command to remove an existing profile. | ||||
| type RemoveProfileCmd struct { | ||||
| 	Name string `arg:"" help:"Name of the profile to delete." required:""` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to remove an existing profile. | ||||
| func (cmd *RemoveProfileCmd) Run(ctx *context) error { | ||||
| 	profiles, err := ctx.Client.Config.GetProfileList() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if !slices.Contains(profiles.Profiles, cmd.Name) { | ||||
| 		return fmt.Errorf("profile %s does not exist", cmd.Name) | ||||
| 	} | ||||
| 
 | ||||
| 	if profiles.CurrentProfileName == cmd.Name { | ||||
| 		return fmt.Errorf("cannot delete current profile %s", cmd.Name) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.Config.RemoveProfile(config.NewRemoveProfileParams().WithProfileName(cmd.Name)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintf(ctx.Out, "Deleted profile: %s\n", cmd.Name) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										119
									
								
								record.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								record.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,119 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // RecordCmd handles the recording commands. | ||||
| type RecordCmd struct { | ||||
| 	Start  RecordStartCmd  `cmd:"" help:"Start recording."  aliases:"s"` | ||||
| 	Stop   RecordStopCmd   `cmd:"" help:"Stop recording."   aliases:"st"` | ||||
| 	Toggle RecordToggleCmd `cmd:"" help:"Toggle recording." aliases:"tg"` | ||||
| 	Pause  RecordPauseCmd  `cmd:"" help:"Pause recording."  aliases:"p"` | ||||
| 	Resume RecordResumeCmd `cmd:"" help:"Resume recording." aliases:"r"` | ||||
| } | ||||
| 
 | ||||
| // RecordStartCmd starts the recording. | ||||
| type RecordStartCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to start recording. | ||||
| func (cmd *RecordStartCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Record.StartRecord() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintln(ctx.Out, "Recording started successfully.") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RecordStopCmd stops the recording. | ||||
| type RecordStopCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to stop recording. | ||||
| func (cmd *RecordStopCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Record.StopRecord() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintln(ctx.Out, "Recording stopped successfully.") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RecordToggleCmd toggles the recording state. | ||||
| type RecordToggleCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to toggle recording. | ||||
| func (cmd *RecordToggleCmd) Run(ctx *context) error { | ||||
| 	// Check if recording is in progress | ||||
| 	status, err := ctx.Client.Record.GetRecordStatus() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if status.OutputActive { | ||||
| 		_, err = ctx.Client.Record.StopRecord() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintln(ctx.Out, "Recording stopped successfully.") | ||||
| 	} else { | ||||
| 		_, err = ctx.Client.Record.StartRecord() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintln(ctx.Out, "Recording started successfully.") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RecordPauseCmd pauses the recording. | ||||
| type RecordPauseCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to pause recording. | ||||
| func (cmd *RecordPauseCmd) Run(ctx *context) error { | ||||
| 	// Check if recording in progress and not already paused | ||||
| 	status, err := ctx.Client.Record.GetRecordStatus() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !status.OutputActive { | ||||
| 		return fmt.Errorf("recording is not in progress") | ||||
| 	} | ||||
| 	if status.OutputPaused { | ||||
| 		return fmt.Errorf("recording is already paused") | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.Record.PauseRecord() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintln(ctx.Out, "Recording paused successfully.") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // RecordResumeCmd resumes the recording. | ||||
| type RecordResumeCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to resume recording. | ||||
| func (cmd *RecordResumeCmd) Run(ctx *context) error { | ||||
| 	// Check if recording in progress and not already resumed | ||||
| 	status, err := ctx.Client.Record.GetRecordStatus() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if !status.OutputActive { | ||||
| 		return fmt.Errorf("recording is not in progress") | ||||
| 	} | ||||
| 	if !status.OutputPaused { | ||||
| 		return fmt.Errorf("recording is not paused") | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.Record.ResumeRecord() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintln(ctx.Out, "Recording resumed successfully.") | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										63
									
								
								replaybuffer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								replaybuffer.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // ReplayBufferCmd handles the recording commands. | ||||
| type ReplayBufferCmd struct { | ||||
| 	Start  ReplayBufferStartCmd  `help:"Start replay buffer."      cmd:"" aliases:"s"` | ||||
| 	Stop   ReplayBufferStopCmd   `help:"Stop replay buffer."       cmd:"" aliases:"st"` | ||||
| 	Status ReplayBufferStatusCmd `help:"Get replay buffer status." cmd:"" aliases:"ss"` | ||||
| 	Save   ReplayBufferSaveCmd   `help:"Save replay buffer."       cmd:"" aliases:"sv"` | ||||
| } | ||||
| 
 | ||||
| // ReplayBufferStartCmd starts the replay buffer. | ||||
| type ReplayBufferStartCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to start the replay buffer. | ||||
| func (cmd *ReplayBufferStartCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Outputs.StartReplayBuffer() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ReplayBufferStopCmd stops the replay buffer. | ||||
| type ReplayBufferStopCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to stop the replay buffer. | ||||
| func (cmd *ReplayBufferStopCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Outputs.StopReplayBuffer() | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| // ReplayBufferStatusCmd retrieves the status of the replay buffer. | ||||
| type ReplayBufferStatusCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to get the replay buffer status. | ||||
| func (cmd *ReplayBufferStatusCmd) Run(ctx *context) error { | ||||
| 	status, err := ctx.Client.Outputs.GetReplayBufferStatus() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if status.OutputActive { | ||||
| 		fmt.Fprintln(ctx.Out, "Replay buffer is active.") | ||||
| 	} else { | ||||
| 		fmt.Fprintln(ctx.Out, "Replay buffer is not active.") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // ReplayBufferSaveCmd saves the replay buffer. | ||||
| type ReplayBufferSaveCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to save the replay buffer. | ||||
| func (cmd *ReplayBufferSaveCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Outputs.SaveReplayBuffer() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to save replay buffer: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintln(ctx.Out, "Replay buffer saved") | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										83
									
								
								scene.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								scene.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"slices" | ||||
| 
 | ||||
| 	"github.com/andreykaipov/goobs/api/requests/scenes" | ||||
| ) | ||||
| 
 | ||||
| // SceneCmd provides commands to manage scenes in OBS Studio. | ||||
| type SceneCmd struct { | ||||
| 	List    SceneListCmd    `cmd:"" help:"List all scenes."       aliases:"ls"` | ||||
| 	Current SceneCurrentCmd `cmd:"" help:"Get the current scene." aliases:"c"` | ||||
| 	Switch  SceneSwitchCmd  `cmd:"" help:"Switch to a scene."     aliases:"sw"` | ||||
| } | ||||
| 
 | ||||
| // SceneListCmd provides a command to list all scenes. | ||||
| type SceneListCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to list all scenes. | ||||
| func (cmd *SceneListCmd) Run(ctx *context) error { | ||||
| 	scenes, err := ctx.Client.Scenes.GetSceneList() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	slices.Reverse(scenes.Scenes) | ||||
| 	for _, scene := range scenes.Scenes { | ||||
| 		fmt.Fprintln(ctx.Out, scene.SceneName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SceneCurrentCmd provides a command to get the current scene. | ||||
| type SceneCurrentCmd struct { | ||||
| 	Preview bool `flag:"" help:"Preview scene."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to get the current scene. | ||||
| func (cmd *SceneCurrentCmd) Run(ctx *context) error { | ||||
| 	if cmd.Preview { | ||||
| 		scene, err := ctx.Client.Scenes.GetCurrentPreviewScene() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintln(ctx.Out, scene.SceneName) | ||||
| 	} else { | ||||
| 		scene, err := ctx.Client.Scenes.GetCurrentProgramScene() | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 		fmt.Fprintln(ctx.Out, scene.SceneName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SceneSwitchCmd provides a command to switch to a different scene. | ||||
| type SceneSwitchCmd struct { | ||||
| 	Preview  bool   `flag:"" help:"Preview scene."` | ||||
| 	NewScene string `        help:"Scene name to switch to." arg:""` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to switch to a different scene. | ||||
| func (cmd *SceneSwitchCmd) Run(ctx *context) error { | ||||
| 	if cmd.Preview { | ||||
| 		_, err := ctx.Client.Scenes.SetCurrentPreviewScene(scenes.NewSetCurrentPreviewSceneParams(). | ||||
| 			WithSceneName(cmd.NewScene)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		fmt.Fprintln(ctx.Out, "Switched to preview scene:", cmd.NewScene) | ||||
| 	} else { | ||||
| 		_, err := ctx.Client.Scenes.SetCurrentProgramScene(scenes.NewSetCurrentProgramSceneParams(). | ||||
| 			WithSceneName(cmd.NewScene)) | ||||
| 		if err != nil { | ||||
| 			return err | ||||
| 		} | ||||
| 
 | ||||
| 		fmt.Fprintln(ctx.Out, "Switched to program scene:", cmd.NewScene) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										93
									
								
								scenecollection.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								scenecollection.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,93 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/andreykaipov/goobs/api/requests/config" | ||||
| ) | ||||
| 
 | ||||
| // SceneCollectionCmd provides commands to manage scene collections in OBS Studio. | ||||
| type SceneCollectionCmd struct { | ||||
| 	List    ListSceneCollectionCmd    `help:"List scene collections."       cmd:"" aliases:"ls"` | ||||
| 	Current CurrentSceneCollectionCmd `help:"Get current scene collection." cmd:"" aliases:"c"` | ||||
| 	Switch  SwitchSceneCollectionCmd  `help:"Switch scene collection."      cmd:"" aliases:"sw"` | ||||
| 	Create  CreateSceneCollectionCmd  `help:"Create scene collection."      cmd:"" aliases:"cr"` | ||||
| } | ||||
| 
 | ||||
| // ListSceneCollectionCmd provides a command to list all scene collections. | ||||
| type ListSceneCollectionCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to list all scene collections. | ||||
| func (cmd *ListSceneCollectionCmd) Run(ctx *context) error { | ||||
| 	collections, err := ctx.Client.Config.GetSceneCollectionList() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene collection list: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, collection := range collections.SceneCollections { | ||||
| 		fmt.Fprintln(ctx.Out, collection) | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CurrentSceneCollectionCmd provides a command to get the current scene collection. | ||||
| type CurrentSceneCollectionCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to get the current scene collection. | ||||
| func (cmd *CurrentSceneCollectionCmd) Run(ctx *context) error { | ||||
| 	collections, err := ctx.Client.Config.GetSceneCollectionList() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene collection list: %w", err) | ||||
| 	} | ||||
| 	fmt.Fprintln(ctx.Out, collections.CurrentSceneCollectionName) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SwitchSceneCollectionCmd provides a command to switch to a different scene collection. | ||||
| type SwitchSceneCollectionCmd struct { | ||||
| 	Name string `arg:"" help:"Name of the scene collection to switch to." required:""` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to switch to a different scene collection. | ||||
| func (cmd *SwitchSceneCollectionCmd) Run(ctx *context) error { | ||||
| 	collections, err := ctx.Client.Config.GetSceneCollectionList() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	current := collections.CurrentSceneCollectionName | ||||
| 
 | ||||
| 	if current == cmd.Name { | ||||
| 		return fmt.Errorf("scene collection %s is already active", cmd.Name) | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.Config.SetCurrentSceneCollection( | ||||
| 		config.NewSetCurrentSceneCollectionParams().WithSceneCollectionName(cmd.Name), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to switch scene collection: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintf(ctx.Out, "Switched to scene collection: %s\n", cmd.Name) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // CreateSceneCollectionCmd provides a command to create a new scene collection. | ||||
| type CreateSceneCollectionCmd struct { | ||||
| 	Name string `arg:"" help:"Name of the scene collection to create." required:""` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to create a new scene collection. | ||||
| func (cmd *CreateSceneCollectionCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Config.CreateSceneCollection( | ||||
| 		config.NewCreateSceneCollectionParams().WithSceneCollectionName(cmd.Name), | ||||
| 	) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to create scene collection: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	fmt.Fprintf(ctx.Out, "Created scene collection: %s\n", cmd.Name) | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										183
									
								
								sceneitem.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								sceneitem.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,183 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/andreykaipov/goobs" | ||||
| 	"github.com/andreykaipov/goobs/api/requests/sceneitems" | ||||
| ) | ||||
| 
 | ||||
| // SceneItemCmd provides commands to manage scene items in OBS Studio. | ||||
| type SceneItemCmd struct { | ||||
| 	List    SceneItemListCmd    `cmd:"" help:"List all scene items."      aliases:"ls"` | ||||
| 	Show    SceneItemShowCmd    `cmd:"" help:"Show scene item."           aliases:"sh"` | ||||
| 	Hide    SceneItemHideCmd    `cmd:"" help:"Hide scene item."           aliases:"h"` | ||||
| 	Toggle  SceneItemToggleCmd  `cmd:"" help:"Toggle scene item."         aliases:"tg"` | ||||
| 	Visible SceneItemVisibleCmd `cmd:"" help:"Get scene item visibility." aliases:"v"` | ||||
| } | ||||
| 
 | ||||
| // SceneItemListCmd provides a command to list all scene items in a scene. | ||||
| type SceneItemListCmd struct { | ||||
| 	SceneName string `arg:"" help:"Scene name."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to list all scene items in a scene. | ||||
| func (cmd *SceneItemListCmd) Run(ctx *context) error { | ||||
| 	resp, err := ctx.Client.SceneItems.GetSceneItemList(sceneitems.NewGetSceneItemListParams(). | ||||
| 		WithSceneName(cmd.SceneName)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get scene item list: %w", err) | ||||
| 	} | ||||
| 	for _, item := range resp.SceneItems { | ||||
| 		fmt.Fprintf(ctx.Out, "Item ID: %d, Source Name: %s\n", item.SceneItemID, item.SourceName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func getSceneNameAndItemID( | ||||
| 	client *goobs.Client, | ||||
| 	sceneName string, | ||||
| 	itemName string, | ||||
| 	parent string, | ||||
| ) (string, int, error) { | ||||
| 	if parent != "" { | ||||
| 		resp, err := client.SceneItems.GetGroupSceneItemList(sceneitems.NewGetGroupSceneItemListParams(). | ||||
| 			WithSceneName(parent)) | ||||
| 		if err != nil { | ||||
| 			return "", 0, err | ||||
| 		} | ||||
| 		for _, item := range resp.SceneItems { | ||||
| 			if item.SourceName == itemName { | ||||
| 				return parent, int(item.SceneItemID), nil | ||||
| 			} | ||||
| 		} | ||||
| 		return "", 0, fmt.Errorf("item '%s' not found in scene '%s'", itemName, sceneName) | ||||
| 	} | ||||
| 
 | ||||
| 	itemID, err := client.SceneItems.GetSceneItemId(sceneitems.NewGetSceneItemIdParams(). | ||||
| 		WithSceneName(sceneName). | ||||
| 		WithSourceName(itemName)) | ||||
| 	if err != nil { | ||||
| 		return "", 0, err | ||||
| 	} | ||||
| 	return sceneName, int(itemID.SceneItemId), nil | ||||
| } | ||||
| 
 | ||||
| // SceneItemShowCmd provides a command to show a scene item. | ||||
| type SceneItemShowCmd struct { | ||||
| 	Parent string `flag:"" help:"Parent group name."` | ||||
| 
 | ||||
| 	SceneName string `arg:"" help:"Scene name."` | ||||
| 	ItemName  string `arg:"" help:"Item name."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to show a scene item. | ||||
| func (cmd *SceneItemShowCmd) Run(ctx *context) error { | ||||
| 	sceneName, sceneItemID, err := getSceneNameAndItemID(ctx.Client, cmd.SceneName, cmd.ItemName, cmd.Parent) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.SceneItems.SetSceneItemEnabled(sceneitems.NewSetSceneItemEnabledParams(). | ||||
| 		WithSceneName(sceneName). | ||||
| 		WithSceneItemId(sceneItemID). | ||||
| 		WithSceneItemEnabled(true)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SceneItemHideCmd provides a command to hide a scene item. | ||||
| type SceneItemHideCmd struct { | ||||
| 	Parent string `flag:"" help:"Parent group name."` | ||||
| 
 | ||||
| 	SceneName string `arg:"" help:"Scene name."` | ||||
| 	ItemName  string `arg:"" help:"Item name."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to hide a scene item. | ||||
| func (cmd *SceneItemHideCmd) Run(ctx *context) error { | ||||
| 	sceneName, sceneItemID, err := getSceneNameAndItemID(ctx.Client, cmd.SceneName, cmd.ItemName, cmd.Parent) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.SceneItems.SetSceneItemEnabled(sceneitems.NewSetSceneItemEnabledParams(). | ||||
| 		WithSceneName(sceneName). | ||||
| 		WithSceneItemId(sceneItemID). | ||||
| 		WithSceneItemEnabled(false)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // getItemEnabled retrieves the enabled status of a scene item. | ||||
| func getItemEnabled(client *goobs.Client, sceneName string, itemID int) (bool, error) { | ||||
| 	item, err := client.SceneItems.GetSceneItemEnabled(sceneitems.NewGetSceneItemEnabledParams(). | ||||
| 		WithSceneName(sceneName). | ||||
| 		WithSceneItemId(itemID)) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
| 	return item.SceneItemEnabled, nil | ||||
| } | ||||
| 
 | ||||
| // SceneItemToggleCmd provides a command to toggle the visibility of a scene item. | ||||
| type SceneItemToggleCmd struct { | ||||
| 	Parent string `flag:"" help:"Parent group name."` | ||||
| 
 | ||||
| 	SceneName string `arg:"" help:"Scene name."` | ||||
| 	ItemName  string `arg:"" help:"Item name."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to toggle the visibility of a scene item. | ||||
| func (cmd *SceneItemToggleCmd) Run(ctx *context) error { | ||||
| 	sceneName, sceneItemID, err := getSceneNameAndItemID(ctx.Client, cmd.SceneName, cmd.ItemName, cmd.Parent) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	itemEnabled, err := getItemEnabled(ctx.Client, sceneName, sceneItemID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	_, err = ctx.Client.SceneItems.SetSceneItemEnabled(sceneitems.NewSetSceneItemEnabledParams(). | ||||
| 		WithSceneName(sceneName). | ||||
| 		WithSceneItemId(sceneItemID). | ||||
| 		WithSceneItemEnabled(!itemEnabled)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // SceneItemVisibleCmd provides a command to check the visibility of a scene item. | ||||
| type SceneItemVisibleCmd struct { | ||||
| 	Parent string `flag:"" help:"Parent group name."` | ||||
| 
 | ||||
| 	SceneName string `arg:"" help:"Scene name."` | ||||
| 	ItemName  string `arg:"" help:"Item name."` | ||||
| } | ||||
| 
 | ||||
| // Run executes the command to check the visibility of a scene item. | ||||
| func (cmd *SceneItemVisibleCmd) Run(ctx *context) error { | ||||
| 	sceneName, sceneItemID, err := getSceneNameAndItemID(ctx.Client, cmd.SceneName, cmd.ItemName, cmd.Parent) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	itemEnabled, err := getItemEnabled(ctx.Client, sceneName, sceneItemID) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 
 | ||||
| 	if itemEnabled { | ||||
| 		fmt.Fprintf(ctx.Out, "Scene item '%s' in scene '%s' is visible.\n", cmd.ItemName, cmd.SceneName) | ||||
| 	} else { | ||||
| 		fmt.Fprintf(ctx.Out, "Scene item '%s' in scene '%s' is hidden.\n", cmd.ItemName, cmd.SceneName) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										82
									
								
								stream.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								stream.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // StreamCmd handles the streaming commands. | ||||
| type StreamCmd struct { | ||||
| 	Start  StreamStartCmd  `cmd:"" help:"Start streaming."      aliases:"s"` | ||||
| 	Stop   StreamStopCmd   `cmd:"" help:"Stop streaming."       aliases:"st"` | ||||
| 	Toggle StreamToggleCmd `cmd:"" help:"Toggle streaming."     aliases:"tg"` | ||||
| 	Status StreamStatusCmd `cmd:"" help:"Get streaming status." aliases:"ss"` | ||||
| } | ||||
| 
 | ||||
| // StreamStartCmd starts the stream. | ||||
| type StreamStartCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to start streaming. | ||||
| func (cmd *StreamStartCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Stream.StartStream() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StreamStopCmd stops the stream. | ||||
| type StreamStopCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to stop streaming. | ||||
| func (cmd *StreamStopCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Stream.StopStream() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StreamToggleCmd toggles the stream status. | ||||
| type StreamToggleCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to toggle streaming. | ||||
| func (cmd *StreamToggleCmd) Run(ctx *context) error { | ||||
| 	status, err := ctx.Client.Stream.GetStreamStatus() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if status.OutputActive { | ||||
| 		_, err = ctx.Client.Stream.StopStream() | ||||
| 		fmt.Fprintf(ctx.Out, "Stopping stream...\n") | ||||
| 	} else { | ||||
| 		_, err = ctx.Client.Stream.StartStream() | ||||
| 		fmt.Fprintf(ctx.Out, "Starting stream...\n") | ||||
| 	} | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StreamStatusCmd retrieves the status of the stream. | ||||
| type StreamStatusCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to get the stream status. | ||||
| func (cmd *StreamStatusCmd) Run(ctx *context) error { | ||||
| 	status, err := ctx.Client.Stream.GetStreamStatus() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintf(ctx.Out, "Output active: %v\n", status.OutputActive) | ||||
| 	if status.OutputActive { | ||||
| 		seconds := status.OutputDuration / 1000 | ||||
| 		minutes := int(seconds / 60) | ||||
| 		secondsInt := int(seconds) % 60 | ||||
| 		if minutes > 0 { | ||||
| 			fmt.Fprintf(ctx.Out, "Output duration: %d minutes and %d seconds\n", minutes, secondsInt) | ||||
| 		} else { | ||||
| 			fmt.Fprintf(ctx.Out, "Output duration: %d seconds\n", secondsInt) | ||||
| 		} | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										81
									
								
								studiomode.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								studiomode.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,81 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 
 | ||||
| 	"github.com/andreykaipov/goobs/api/requests/ui" | ||||
| ) | ||||
| 
 | ||||
| // StudioModeCmd provides commands to manage studio mode in OBS Studio. | ||||
| type StudioModeCmd struct { | ||||
| 	Enable  StudioModeEnableCmd  `cmd:"enable"  help:"Enable studio mode."     aliases:"on"` | ||||
| 	Disable StudioModeDisableCmd `cmd:"disable" help:"Disable studio mode."    aliases:"off"` | ||||
| 	Toggle  StudioModeToggleCmd  `cmd:"toggle"  help:"Toggle studio mode."     aliases:"tg"` | ||||
| 	Status  StudioModeStatusCmd  `cmd:"status"  help:"Get studio mode status." aliases:"ss"` | ||||
| } | ||||
| 
 | ||||
| // StudioModeEnableCmd provides a command to enable studio mode. | ||||
| type StudioModeEnableCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to enable studio mode. | ||||
| func (cmd *StudioModeEnableCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Ui.SetStudioModeEnabled(ui.NewSetStudioModeEnabledParams().WithStudioModeEnabled(true)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to enable studio mode: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StudioModeDisableCmd provides a command to disable studio mode. | ||||
| type StudioModeDisableCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to disable studio mode. | ||||
| func (cmd *StudioModeDisableCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Ui.SetStudioModeEnabled(ui.NewSetStudioModeEnabledParams().WithStudioModeEnabled(false)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to disable studio mode: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StudioModeToggleCmd provides a command to toggle studio mode. | ||||
| type StudioModeToggleCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to toggle studio mode. | ||||
| func (cmd *StudioModeToggleCmd) Run(ctx *context) error { | ||||
| 	status, err := ctx.Client.Ui.GetStudioModeEnabled(&ui.GetStudioModeEnabledParams{}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get studio mode status: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	newStatus := !status.StudioModeEnabled | ||||
| 	_, err = ctx.Client.Ui.SetStudioModeEnabled(ui.NewSetStudioModeEnabledParams().WithStudioModeEnabled(newStatus)) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to toggle studio mode: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if newStatus { | ||||
| 		fmt.Fprintln(ctx.Out, "Studio mode is now enabled") | ||||
| 	} else { | ||||
| 		fmt.Fprintln(ctx.Out, "Studio mode is now disabled") | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StudioModeStatusCmd provides a command to get the status of studio mode. | ||||
| type StudioModeStatusCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to get the status of studio mode. | ||||
| func (cmd *StudioModeStatusCmd) Run(ctx *context) error { | ||||
| 	status, err := ctx.Client.Ui.GetStudioModeEnabled(&ui.GetStudioModeEnabledParams{}) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get studio mode status: %w", err) | ||||
| 	} | ||||
| 	if status.StudioModeEnabled { | ||||
| 		fmt.Fprintln(ctx.Out, "Studio mode is enabled") | ||||
| 	} else { | ||||
| 		fmt.Fprintln(ctx.Out, "Studio mode is disabled") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										24
									
								
								version.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								version.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // VersionCmd handles the version command. | ||||
| type VersionCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to get the OBS client version. | ||||
| func (cmd *VersionCmd) Run(ctx *context) error { | ||||
| 	version, err := ctx.Client.General.GetVersion() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	fmt.Fprintf( | ||||
| 		ctx.Out, | ||||
| 		"OBS Client Version: %s with Websocket Version: %s\n", | ||||
| 		version.ObsVersion, | ||||
| 		version.ObsWebSocketVersion, | ||||
| 	) | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										69
									
								
								virtualcam.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								virtualcam.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,69 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| ) | ||||
| 
 | ||||
| // VirtualCamCmd handles the virtual camera commands. | ||||
| type VirtualCamCmd struct { | ||||
| 	Start  StartVirtualCamCmd  `help:"Start virtual camera."      cmd:"" aliases:"s"` | ||||
| 	Stop   StopVirtualCamCmd   `help:"Stop virtual camera."       cmd:"" aliases:"st"` | ||||
| 	Toggle ToggleVirtualCamCmd `help:"Toggle virtual camera."     cmd:"" aliases:"tg"` | ||||
| 	Status StatusVirtualCamCmd `help:"Get virtual camera status." cmd:"" aliases:"ss"` | ||||
| } | ||||
| 
 | ||||
| // StartVirtualCamCmd starts the virtual camera. | ||||
| type StartVirtualCamCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to start the virtual camera. | ||||
| func (c *StartVirtualCamCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Outputs.StartVirtualCam() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to start virtual camera: %w", err) | ||||
| 	} | ||||
| 	fmt.Fprintln(ctx.Out, "Virtual camera started.") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StopVirtualCamCmd stops the virtual camera. | ||||
| type StopVirtualCamCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to stop the virtual camera. | ||||
| func (c *StopVirtualCamCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Outputs.StopVirtualCam() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to stop virtual camera: %w", err) | ||||
| 	} | ||||
| 	fmt.Fprintln(ctx.Out, "Virtual camera stopped.") | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // ToggleVirtualCamCmd toggles the virtual camera. | ||||
| type ToggleVirtualCamCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to toggle the virtual camera. | ||||
| func (c *ToggleVirtualCamCmd) Run(ctx *context) error { | ||||
| 	_, err := ctx.Client.Outputs.ToggleVirtualCam() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to toggle virtual camera: %w", err) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| // StatusVirtualCamCmd retrieves the status of the virtual camera. | ||||
| type StatusVirtualCamCmd struct{} // size = 0x0 | ||||
| 
 | ||||
| // Run executes the command to get the status of the virtual camera. | ||||
| func (c *StatusVirtualCamCmd) Run(ctx *context) error { | ||||
| 	status, err := ctx.Client.Outputs.GetVirtualCamStatus() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to get virtual camera status: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	if status.OutputActive { | ||||
| 		fmt.Fprintln(ctx.Out, "Virtual camera is active.") | ||||
| 	} else { | ||||
| 		fmt.Fprintln(ctx.Out, "Virtual camera is inactive.") | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user