Skip to content

Commit

Permalink
first commit 🎉
Browse files Browse the repository at this point in the history
  • Loading branch information
gizmo-ds committed Mar 12, 2022
0 parents commit 5c66673
Show file tree
Hide file tree
Showing 15 changed files with 426 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
.vscode
*.exe
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2022 Gizmo

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# vrchat-osc-manager (WIP)

VRChat OSC 管理工具, 通过 WebSocket 与插件通信.
44 changes: 44 additions & 0 deletions app/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package app

import (
"io/ioutil"
"log"
"path/filepath"
"vrchat-osc-manager/internal/config"
"vrchat-osc-manager/internal/plugin"
)

func init() {
config.LoadConfig("./config.toml")
}

func Start() {
go oscServer()
go loadPlugins()
wsServer()
}

func loadPlugins() {
dir, err := ioutil.ReadDir("./plugins/")
if err != nil {
panic(err)
}
for _, info := range dir {
if !info.IsDir() {
continue
}
p, err := plugin.NewPlugin(filepath.Join("./plugins/", info.Name()))
if err != nil {
log.Println(err)
continue
}
if err = p.Init(); err != nil {
log.Println(err)
continue
}
if err = p.Start(); err != nil {
log.Println(err)
continue
}
}
}
22 changes: 22 additions & 0 deletions app/osc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package app

import (
"github.com/hypebeast/go-osc/osc"
"log"
)

var oscClient = osc.NewClient("127.0.0.1", 9000)

func oscServer() {
d := osc.NewStandardDispatcher()
_ = d.AddMsgHandler("/avatar/change", func(msg *osc.Message) {
if len(msg.Arguments) > 0 {
log.Println("AvatarChange:", msg.Arguments[0])
}
})
server := &osc.Server{
Addr: "127.0.0.1:9001",
Dispatcher: d,
}
log.Fatal(server.ListenAndServe())
}
70 changes: 70 additions & 0 deletions app/ws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package app

import (
"encoding/json"
"fmt"
"github.com/gobwas/ws"
"github.com/gobwas/ws/wsutil"
"github.com/hypebeast/go-osc/osc"
"log"
"net/http"
"reflect"
"vrchat-osc-manager/internal/config"
)

type wsMessage struct {
Method string `json:"method"`
Addr string `json:"addr"`
Value interface{} `json:"value"`
}

func wsServer() {
_ = http.ListenAndServe(
fmt.Sprintf("%s:%d", config.C.WebSocket.Hostname, config.C.WebSocket.Port),
http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
conn, _, _, err := ws.UpgradeHTTP(r, w)
if err != nil {
log.Println(err)
return
}
go func() {
defer conn.Close()

for {
msg, _, err := wsutil.ReadClientData(conn)
if err != nil {
log.Println(err)
return
}
//log.Println("[WebSocket Message]", string(msg))
var value wsMessage
if json.Unmarshal(msg, &value) == nil {
messageHandler(value)
}
}
}()
}))
}

func messageHandler(msg wsMessage) {
switch msg.Method {
case "send":
m := osc.NewMessage(msg.Addr)
switch v := msg.Value.(type) {
case float32:
m.Append(v)
case float64:
m.Append(float32(v))
case int:
m.Append(float32(v))
case bool:
m.Append(v)
default:
log.Println("[WebSocket Message]", "Unknown type", reflect.TypeOf(v))
return
}
_ = oscClient.Send(m)
default:
log.Println("[WebSocket Message]", "Unknown method", msg.Method)
}
}
3 changes: 3 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[websocket]
hostname = "localhost"
port = 8787
16 changes: 16 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module vrchat-osc-manager

go 1.17

require (
github.com/BurntSushi/toml v1.0.0
github.com/gobwas/ws v1.1.0
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5
github.com/joho/godotenv v1.4.0
)

require (
github.com/gobwas/httphead v0.1.0 // indirect
github.com/gobwas/pool v0.2.1 // indirect
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5 h1:fqwINudmUrvGCuw+e3tedZ2UJ0hklSw6t8UPomctKyQ=
github.com/hypebeast/go-osc v0.0.0-20220308234300-cec5a8a1e5f5/go.mod h1:lqMjoCs0y0GoRRujSPZRBaGb4c5ER6TfkFKSClxkMbY=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d h1:MiWWjyhUzZ+jvhZvloX6ZrUsdEghn8a64Upd8EMHglE=
golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
19 changes: 19 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package config

import "github.com/BurntSushi/toml"

type Config struct {
WebSocket struct {
Hostname string `toml:"hostname"`
Port int `toml:"port"`
}
}

var C Config

func LoadConfig(path string) {
_, err := toml.DecodeFile(path, &C)
if err != nil {
panic(err)
}
}
14 changes: 14 additions & 0 deletions internal/httputil2/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package httputil2

import "net"

func PickPort() int {
listener, err := net.Listen("tcp4", "127.0.0.1:0")
if err != nil {
return -1
}
defer listener.Close()

addr := listener.Addr().(*net.TCPAddr)
return addr.Port
}
76 changes: 76 additions & 0 deletions internal/plugin/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package plugin

import (
"encoding/json"
"errors"
"github.com/BurntSushi/toml"
"io/ioutil"
"log"
"os"
"path/filepath"
)

func NewPlugin(dir string) (*Plugin, error) {
p := &Plugin{dir: dir}
if err := p.load(); err != nil {
return nil, err
}
return p, nil
}

func (p *Plugin) load() error {
_, err := toml.DecodeFile(filepath.Join(p.dir, "manifest.toml"), p)
if err != nil {
f, err := ioutil.ReadFile(filepath.Join(p.dir, "manifest.json"))
if err != nil {
return errors.New("can not open manifest file: " + p.dir)
}
if err = json.Unmarshal(f, p); err != nil {
return err
}
}
p.Entrypoint.dir = p.dir
p.Entrypoint.name = p.Name
if p.Install != nil {
p.Install.dir = p.dir
p.Install.name = p.Name
}
return nil
}

func (p *Plugin) Init() (err error) {
if err = p.Entrypoint.checkExecutable(); err != nil {
return err
}
if p.Install != nil {
// check if the plugin is already installed
if _, err = os.Stat(filepath.Join(p.Install.dir, ".installed")); err != nil {
if err = p.Install.checkExecutable(); err != nil {
return err
}
if err = p.Install.Start(); err != nil {
return err
}
if err := p.Install.Wait(); err != nil {
log.Println("[plugin]", p.Name, "installation failed:", err)
return err
}
_ = ioutil.WriteFile(filepath.Join(p.dir, ".installed"), nil, 0644)
}
}
return
}

func (p *Plugin) Start() (err error) {
if err = p.Entrypoint.Start(); err != nil {
return err
}
return nil
}

func (p *Plugin) Stop() (err error) {
if err = p.Entrypoint.Stop(); err != nil {
return err
}
return nil
}
38 changes: 38 additions & 0 deletions internal/plugin/plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package plugin

import (
"testing"
"time"
)

func TestPlugin_Init(t *testing.T) {
p, err := NewPlugin("plugins/pulsoid-bridge")
if err != nil {
t.Error(err)
return
}
if err = p.Init(); err != nil {
t.Fatal(err)
}
}

func TestPlugin_Start(t *testing.T) {
p, err := NewPlugin("plugins/pulsoid-bridge")
if err != nil {
t.Error(err)
return
}
if err = p.Init(); err != nil {
t.Fatal(err)
return
}
if err = p.Start(); err != nil {
t.Fatal(err)
return
}
time.Sleep(time.Second * 10)
if err = p.Stop(); err != nil {
t.Fatal(err)
return
}
}
Loading

0 comments on commit 5c66673

Please sign in to comment.