Skip to content

Instantly share code, notes, and snippets.

@geopelia
Created August 13, 2025 23:35
Show Gist options
  • Select an option

  • Save geopelia/f180453a154c903f53f2e6e4f357f03a to your computer and use it in GitHub Desktop.

Select an option

Save geopelia/f180453a154c903f53f2e6e4f357f03a to your computer and use it in GitHub Desktop.
Golang client to connect OBS using Websockets
package main
import (
"crypto/sha256"
"encoding/base64"
"encoding/json"
"log"
"net/url"
"os"
"time"
"github.com/google/uuid"
"github.com/gorilla/websocket"
"github.com/joho/godotenv"
)
const (
Hello = 0
Identify = 1
Identified = 2
Reidentify = 3
Event = 5
Request = 6
RequestResponse = 7
RequestBatch = 8
RequestBatchResponse = 9
)
type AuthData struct {
Challenge string `json:"challenge"`
Salt string `json:"salt"`
}
type HelloDataStruct struct {
Auth AuthData `json:"authentication"`
ObsWebSocketVersion string `json:"obsWebSocketVersion"`
RpcVersion int8 `json:"rpcVersion"`
}
type HelloStruct struct {
MainMessage
HelloDataStruct `json:"d"`
}
type IdentifyDataStruct struct {
Autentication string `json:"authentication"`
RpcVersion int8 `json:"rpcVersion"`
}
type IdentifyStruct struct {
MainMessage
IdentifyDataStruct `json:"d"`
}
type IdentifiedDataStruct struct {
RpcVersion int8 `json:"negotiatedRpcVersion"`
}
type IdentifiedStruct struct {
MainMessage
IdentifiedDataStruct `json:"d"`
}
type EventDataStruct struct {
EventType string `json:"eventType"`
EventIntent int32 `json:"eventIntent"`
EventData any `json:"eventData,omitempty"`
}
type EventStruct struct {
MainMessage
EventDataStruct `json:"d"`
}
type RequestDataStruct struct {
RequestType string `json:"requestType"`
RequestId string `json:"requestId"`
RequestData any `json:"requestData,omitempty"`
}
type RequestStruct struct {
MainMessage
RequestDataStruct `json:"d"`
}
type RequestStatus struct {
Result bool `json:"result"`
Code int32 `json:"code"`
Comment string `json:"comment,omitempty"`
}
type RequestResponseDataStruc struct {
RequestType string `json:"requestType"`
RequestId string `json:"requestId"`
ResponseData any `json:"responseData,omitempty"`
RequestStatus `json:"requestStatus"`
}
type RequestResponseStruct struct {
MainMessage
RequestResponseDataStruc `json:"d"`
}
type MainMessage struct {
OpCode int8 `json:"op"`
}
func generatePassword(authInfo AuthData) string {
pass := os.Getenv("PASS")
first := pass + authInfo.Salt
base64Secret := hash256AndEncode(first)
second := base64Secret + authInfo.Challenge
base64Secret = hash256AndEncode(second)
return base64Secret
}
func hash256AndEncode(phrase string) string {
hasher := sha256.New()
hasher.Write([]byte(phrase))
return base64.StdEncoding.EncodeToString(hasher.Sum(nil))
}
func identifyMe(question HelloStruct, done chan struct{}, writeChan chan string) {
pass := generatePassword(question.Auth)
identify := IdentifyStruct{MainMessage{Identify}, IdentifyDataStruct{pass, question.RpcVersion}}
output, err := json.Marshal(&identify)
if err != nil {
log.Println("error", err)
close(done)
}
writeChan <- string(output)
}
func tryDecodeJson[t any](message []byte, myVar *t, done chan struct{}) {
err := json.Unmarshal(message, myVar)
if err != nil {
log.Println("Error decoding json:", err)
close(done)
}
}
func decodeMessage(message []byte, done chan struct{}, writeChan chan string, isLogged chan bool) {
var msgObj MainMessage = MainMessage{}
tryDecodeJson(message, &msgObj, done)
switch msgObj.OpCode {
case Hello:
var helloMessage HelloStruct
tryDecodeJson(message, &helloMessage, done)
identifyMe(helloMessage, done, writeChan)
case Identified:
var identifiedMessage IdentifiedStruct
tryDecodeJson(message, &identifiedMessage, done)
if identifiedMessage.IdentifiedDataStruct.RpcVersion > 0 {
isLogged <- true
}
case Event:
var eventMessage EventStruct
tryDecodeJson(message, &eventMessage, done)
log.Println("Evento en obs: ", eventMessage.EventDataStruct)
case RequestResponse:
var response RequestResponseStruct
tryDecodeJson(message, &response, done)
log.Println("Respuesta a solicitud", response.RequestResponseDataStruc.ResponseData)
default:
log.Println(msgObj)
}
}
func closeWebSocket(c *websocket.Conn, myChanel chan struct{}) {
log.Println("interrupt")
err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "chao"))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-myChanel:
case <-time.After(time.Second * 5):
}
}
func askForVersion(done chan struct{}, writeChan chan string) {
requestID := uuid.New().String()
requestMessage := RequestStruct{MainMessage{Request}, RequestDataStruct{"GetStats", requestID, nil}}
output, err := json.Marshal(&requestMessage)
if err != nil {
log.Println("error", err)
close(done)
}
writeChan <- string(output)
}
func askChangeScene(done chan struct{}, writeChan chan string) {
requestID := uuid.New().String()
type Params struct {
SceneName string `json:"sceneName"`
}
newScene := Params{"brb"}
requestMessage := RequestStruct{MainMessage{Request}, RequestDataStruct{"SetCurrentProgramScene", requestID, newScene}}
output, err := json.Marshal(&requestMessage)
if err != nil {
log.Println("error", err)
close(done)
}
writeChan <- string(output)
}
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error:", err)
}
host := os.Getenv("HOST") + ":" + os.Getenv("PORT")
u := url.URL{Scheme: "ws", Host: host, Path: "/"}
log.Printf("connecting to %s", u.String())
c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
done := make(chan struct{})
timeout := make(chan struct{})
dataToSend := make(chan string, 100)
isLogged := make(chan bool, 1)
// We are reading the response for server here
go func() {
defer close(done)
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
log.Printf("recv: %s \n", message)
decodeMessage(message, done, dataToSend, isLogged)
}
}()
go func() {
defer close(timeout)
time.Sleep(10 * time.Second)
}()
for {
select {
case <-done:
return
case <-timeout:
closeWebSocket(c, done)
return
case texto, ok := <-dataToSend:
if !ok {
log.Println("cerrADO")
}
err := c.WriteMessage(websocket.TextMessage, []byte(texto))
if err != nil {
log.Println("write:", err)
return
}
log.Printf("sent: %s \n", texto)
case loggedIn := <-isLogged:
if loggedIn {
log.Println("estoy autenticado")
askForVersion(done, dataToSend)
askChangeScene(done, dataToSend)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment