diff --git a/.gitignore b/.gitignore index c5efcd4..380ea08 100644 --- a/.gitignore +++ b/.gitignore @@ -2,8 +2,8 @@ main mumble-discord-bridge dist -bridge *.prof *.out *.test -cert.pem \ No newline at end of file +cert.pem +*.gob \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml index 8d2e246..dc90159 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -7,7 +7,8 @@ before: # you may remove this if you don't need go generate # - go generate ./... builds: - - ldflags: '-s -w -linkmode external -extldflags "-static" -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser' + - main: ./cmd/mumble-discord-bridge + ldflags: '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.builtBy=goreleaser' env: - CGO_ENABLED=1 flags: diff --git a/Dockerfile b/Dockerfile index da01798..2b04d76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,18 +2,25 @@ # Stage 1 -FROM golang:1.15 as builder +FROM golang:1.16 as builder WORKDIR /go/src/app COPY . . RUN curl -sfL https://install.goreleaser.com/github.com/goreleaser/goreleaser.sh | sh +RUN apt update && apt install -y libopus-dev RUN ./bin/goreleaser build --skip-validate # Stage 2 -FROM alpine:latest as static +FROM alpine:latest as final WORKDIR /opt/ RUN apk add opus +RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2 COPY --from=builder /go/src/app/dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge . +# FROM ubuntu:latest as final +# WORKDIR /opt/ +# RUN apt update && apt install -y libopus0 ca-certificates && apt clean +# COPY --from=builder /go/src/app/dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge . + # Entry Point CMD ["/opt/mumble-discord-bridge"] diff --git a/Makefile b/Makefile index 80a6b48..21de543 100644 --- a/Makefile +++ b/Makefile @@ -1,26 +1,29 @@ -GOFILES=main.go mumble.go discord.go bridge.go config.go mumble-handlers.go discord-handlers.go sleepct.go +GOFILES=$(shell find ./ -type f -name '*.go') -mumble-discord-bridge: $(GOFILES) +mumble-discord-bridge: $(GOFILES) .goreleaser.yml goreleaser build --skip-validate --rm-dist -dev: $(GOFILES) +dev: $(GOFILES) .goreleaser.yml goreleaser build --skip-validate --rm-dist && sudo ./dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge -dev-race: $(GOFILES) - go run -race $(GOFILES) +dev-race: $(GOFILES) .goreleaser.yml + go run -race ./cmd/mumble-discord-bridge -dev-profile: $(GOFILES) +dev-profile: $(GOFILES) .goreleaser.yml goreleaser build --skip-validate --rm-dist && sudo ./dist/mumble-discord-bridge_linux_amd64/mumble-discord-bridge -cpuprofile cpu.prof test-chart: SHELL:=/bin/bash test-chart: - go test & - until pidof mumble-discord-bridge.test; do continue; done; + go test ./test & + until pidof test.test; do continue; done; psrecord --plot docs/test-cpu-memory.png $$(pidof mumble-discord-bridge.test) docker-latest: docker build -t stieneee/mumble-discord-bridge:latest . +docker-latest-run: + docker run --env-file .env -it stieneee/mumble-discord-bridge:latest + docker-next: docker build -t stieneee/mumble-discord-bridge:next . docker push stieneee/mumble-discord-bridge:next diff --git a/config.go b/config.go deleted file mode 100644 index bcf4539..0000000 --- a/config.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "os" - "strconv" - - "layeh.com/gumble/gumble" -) - -type bridgeMode int - -const ( - bridgeModeAuto bridgeMode = iota - bridgeModeManual - bridgeModeConstant -) - -//BridgeConfig holds configuration information set at startup -//It should not change during runtime -type BridgeConfig struct { - MumbleConfig *gumble.Config - MumbleAddr string - MumbleInsecure bool - MumbleCertificate string - MumbleChannel []string - mumbleStartStreamCount int - MumbleDisableText bool - Command string - GID string - CID string - DiscordStartStreamingCount int - DiscordDisableText bool -} - -func lookupEnvOrString(key string, defaultVal string) string { - if val, ok := os.LookupEnv(key); ok { - return val - } - return defaultVal -} - -func lookupEnvOrInt(key string, defaultVal int) int { - if val, ok := os.LookupEnv(key); ok { - v, err := strconv.Atoi(val) - if err != nil { - log.Fatalf("LookupEnvOrInt[%s]: %v", key, err) - } - return v - } - return defaultVal -} - -func lookupEnvOrBool(key string, defaultVal bool) bool { - if val, ok := os.LookupEnv(key); ok { - v, err := strconv.ParseBool(val) - if err != nil { - log.Fatalf("LookupEnvOrInt[%s]: %v", key, err) - } - return v - } - return defaultVal -} - -func getConfig(fs *flag.FlagSet) []string { - cfg := make([]string, 0, 10) - fs.VisitAll(func(f *flag.Flag) { - cfg = append(cfg, fmt.Sprintf("%s:%q", f.Name, f.Value.String())) - }) - - return cfg -} diff --git a/go.mod b/go.mod index 37ad23d..001a44e 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,11 @@ module github.com/stieneee/mumble-discord-bridge go 1.15 require ( - github.com/bwmarrin/discordgo v0.23.2 - github.com/golang/protobuf v1.4.3 // indirect - github.com/gorilla/websocket v1.4.2 // indirect + github.com/bwmarrin/discordgo v0.23.3-0.20210512035133-7d7206b01bb5 github.com/joho/godotenv v1.3.0 + github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335 + github.com/stieneee/gumble v0.0.0-20210424210604-732f48b5e0de github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9 golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 // indirect - google.golang.org/protobuf v1.25.0 // indirect - layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa - layeh.com/gumble v0.0.0-20200818122324-146f9205029b ) diff --git a/go.sum b/go.sum index 2e6259b..9eba3fd 100644 --- a/go.sum +++ b/go.sum @@ -1,79 +1,29 @@ -cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM= -github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= -github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4= -github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/bwmarrin/discordgo v0.23.3-0.20210512035133-7d7206b01bb5 h1:VtiZMSjY2N6XpM1luSchBVX76QURpS0HA7BffVuHOCo= +github.com/bwmarrin/discordgo v0.23.3-0.20210512035133-7d7206b01bb5/go.mod h1:OMKxbTmkKofBjBi4/yidO3ItxbJ6PUfEUkjchM4En8c= github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372 h1:tz3KnXWtRZR0RWOfcMNOw+HHezWLQa7vfSOWTtKjchI= github.com/dchote/go-openal v0.0.0-20171116030048-f4a9a141d372/go.mod h1:74z+CYu2/mx4N+mcIS/rsvfAxBPBV9uv8zRAnwyFkdI= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473 h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335 h1:yzwz6AqGKysli5du4CrQ48BMGUCSkrl7V7Kbo9VaG8w= +github.com/stieneee/gopus v0.0.0-20210424193312-6d10f6090335/go.mod h1:tAKYr3fSBJGold7c9DMPlhupn9oy8hTgl3cZ0hoyRQs= +github.com/stieneee/gumble v0.0.0-20210424210604-732f48b5e0de h1:4dWOeXRnba4jHVa3KuWf7i/GOIAlBMR3euVTUXOey2I= +github.com/stieneee/gumble v0.0.0-20210424210604-732f48b5e0de/go.mod h1:hVIsmrlrudlx2HJbsDkIZI4crkv6NHSau0ldEWbQI/Y= github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9 h1:0l2H6Oj6JHMmkqm9xaBMQA5MOGhPT+Nn/thlTUcD9Iw= github.com/stieneee/tickerct v0.0.0-20210420020607-d1b092aa40e9/go.mod h1:54+oZlabriEpT52rPAjAeEWUFgYqv325LrS3hNxHGFE= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4 h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3 h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210108172913-0df2131ae363 h1:wHn06sgWHMO1VsQ8F+KzDJx/JzqfsNLnc+oEi07qD7s= golang.org/x/sys v0.0.0-20210108172913-0df2131ae363/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -81,39 +31,8 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221 h1:/ZHdbVpdR/jk3g30/d4yUL0J golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 h1:5Beo0mZN8dRzgrMMkDp0jc8YXQKx9DiJ2k1dkvGsn5A= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa h1:WNU4LYsgD2UHxgKgB36mL6iMAMOvr127alafSlgBbiA= -layeh.com/gopus v0.0.0-20161224163843-0ebf989153aa/go.mod h1:AOef7vHz0+v4sWwJnr0jSyHiX/1NgsMoaxl+rEPz/I0= -layeh.com/gumble v0.0.0-20200818122324-146f9205029b h1:Kne6wkHqbqrygRsqs5XUNhSs84DFG5TYMeCkCbM56sY= -layeh.com/gumble v0.0.0-20200818122324-146f9205029b/go.mod h1:tWPVA9ZAfImNwabjcd9uDE+Mtz0Hfs7a7G3vxrnrwyc= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= diff --git a/bridge.go b/internal/bridge/bridge.go similarity index 86% rename from bridge.go rename to internal/bridge/bridge.go index 5703a60..7ab4f37 100644 --- a/bridge.go +++ b/internal/bridge/bridge.go @@ -1,4 +1,4 @@ -package main +package bridge import ( "context" @@ -12,15 +12,39 @@ import ( "time" "github.com/bwmarrin/discordgo" - "layeh.com/gumble/gumble" + "github.com/stieneee/gumble/gumble" ) -type discordUser struct { +type DiscordUser struct { username string seen bool dm *discordgo.Channel } +type BridgeMode int + +const ( + BridgeModeAuto BridgeMode = iota + BridgeModeManual + BridgeModeConstant +) + +type BridgeConfig struct { + MumbleConfig *gumble.Config + MumbleAddr string + MumbleInsecure bool + MumbleCertificate string + MumbleChannel []string + MumbleStartStreamCount int + MumbleDisableText bool + Command string + GID string + CID string + DiscordStartStreamingCount int + DiscordDisableText bool + Version string +} + //BridgeState manages dynamic information about the bridge during runtime type BridgeState struct { // The configuration data for this bridge @@ -42,7 +66,7 @@ type BridgeState struct { Connected bool // The bridge mode constant, auto, manual. Default is constant. - Mode bridgeMode + Mode BridgeMode // Discord session. This is created and outside the bridge state DiscordSession *discordgo.Session @@ -54,7 +78,7 @@ type BridgeState struct { MumbleClient *gumble.Client // Map of Discord users tracked by this bridge. - DiscordUsers map[string]discordUser + DiscordUsers map[string]DiscordUser DiscordUsersMutex sync.Mutex // Map of Mumble users tracked by this bridge @@ -80,7 +104,7 @@ type BridgeState struct { } // startBridge established the voice connection -func (b *BridgeState) startBridge() { +func (b *BridgeState) StartBridge() { b.lock.Lock() defer b.lock.Unlock() @@ -214,10 +238,10 @@ func (b *BridgeState) startBridge() { b.MumbleUsersMutex.Lock() b.MumbleUsers = make(map[string]bool) b.MumbleUsersMutex.Unlock() - b.DiscordUsers = make(map[string]discordUser) + b.DiscordUsers = make(map[string]DiscordUser) } -func (b *BridgeState) discordStatusUpdate() { +func (b *BridgeState) DiscordStatusUpdate() { m, _ := time.ParseDuration("30s") for { time.Sleep(3 * time.Second) @@ -254,14 +278,14 @@ func (b *BridgeState) discordStatusUpdate() { // when there is at least one user on both, starts up the bridge // when there are no users on either side, kills the bridge func (b *BridgeState) AutoBridge() { - log.Println("beginning auto mode") + log.Println("Beginning auto mode") ticker := time.NewTicker(3 * time.Second) for { select { case <-ticker.C: case <-b.AutoChanDie: - log.Println("ending automode") + log.Println("Ending automode") return } @@ -270,11 +294,11 @@ func (b *BridgeState) AutoBridge() { b.BridgeMutex.Lock() if !b.Connected && b.MumbleUserCount > 0 && len(b.DiscordUsers) > 0 { - log.Println("users detected in mumble and discord, bridging") - go b.startBridge() + log.Println("Users detected in mumble and discord, bridging") + go b.StartBridge() } if b.Connected && b.MumbleUserCount == 0 && len(b.DiscordUsers) <= 1 { - log.Println("no one online, killing bridge") + log.Println("No one online, killing bridge") b.BridgeDie <- true } diff --git a/discord-handlers.go b/internal/bridge/discord-handlers.go similarity index 88% rename from discord-handlers.go rename to internal/bridge/discord-handlers.go index e0e3083..c4a4fd4 100644 --- a/discord-handlers.go +++ b/internal/bridge/discord-handlers.go @@ -1,4 +1,4 @@ -package main +package bridge import ( "fmt" @@ -15,11 +15,11 @@ type DiscordListener struct { Bridge *BridgeState } -func (l *DiscordListener) guildCreate(s *discordgo.Session, event *discordgo.GuildCreate) { +func (l *DiscordListener) GuildCreate(s *discordgo.Session, event *discordgo.GuildCreate) { log.Println("CREATE event registered") if event.ID != l.Bridge.BridgeConfig.GID { - log.Println("received GuildCreate from a guild not in config") + log.Println("Received GuildCreate from a guild not in config") return } @@ -32,7 +32,7 @@ func (l *DiscordListener) guildCreate(s *discordgo.Session, event *discordgo.Gui u, err := s.User(vs.UserID) if err != nil { - log.Println("error looking up username") + log.Println("Error looking up username") } dm, err := s.UserChannelCreate(u.ID) @@ -41,7 +41,7 @@ func (l *DiscordListener) guildCreate(s *discordgo.Session, event *discordgo.Gui } l.Bridge.DiscordUsersMutex.Lock() - l.Bridge.DiscordUsers[vs.UserID] = discordUser{ + l.Bridge.DiscordUsers[vs.UserID] = DiscordUser{ username: u.Username, seen: true, dm: dm, @@ -61,7 +61,7 @@ func (l *DiscordListener) guildCreate(s *discordgo.Session, event *discordgo.Gui } } -func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { +func (l *DiscordListener) MessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) { // Ignore all messages created by the bot itself if m.Author.ID == s.State.User.ID { @@ -82,7 +82,7 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa } prefix := "!" + l.Bridge.BridgeConfig.Command - if l.Bridge.Mode == bridgeModeConstant && strings.HasPrefix(m.Content, prefix) { + if l.Bridge.Mode == BridgeModeConstant && strings.HasPrefix(m.Content, prefix) { l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Constant mode enabled, manual commands can not be entered") return } @@ -101,7 +101,7 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa if vs.UserID == m.Author.ID { log.Printf("Trying to join GID %v and VID %v\n", g.ID, vs.ChannelID) l.Bridge.DiscordChannelID = vs.ChannelID - go l.Bridge.startBridge() + go l.Bridge.StartBridge() return } } @@ -135,16 +135,16 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa time.Sleep(5 * time.Second) - go l.Bridge.startBridge() + go l.Bridge.StartBridge() return } } } if strings.HasPrefix(m.Content, prefix+" auto") { - if l.Bridge.Mode != bridgeModeAuto { + if l.Bridge.Mode != BridgeModeAuto { l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Auto mode enabled") - l.Bridge.Mode = bridgeModeAuto + l.Bridge.Mode = BridgeModeAuto l.Bridge.DiscordChannelID = l.Bridge.BridgeConfig.CID l.Bridge.AutoChanDie = make(chan bool) go l.Bridge.AutoBridge() @@ -152,12 +152,12 @@ func (l *DiscordListener) messageCreate(s *discordgo.Session, m *discordgo.Messa l.Bridge.DiscordSession.ChannelMessageSend(m.ChannelID, "Auto mode disabled") l.Bridge.DiscordChannelID = "" l.Bridge.AutoChanDie <- true - l.Bridge.Mode = bridgeModeManual + l.Bridge.Mode = BridgeModeManual } } } -func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.VoiceStateUpdate) { +func (l *DiscordListener) VoiceUpdate(s *discordgo.Session, event *discordgo.VoiceStateUpdate) { l.Bridge.DiscordUsersMutex.Lock() defer l.Bridge.DiscordUsersMutex.Unlock() @@ -165,7 +165,7 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi g, err := s.State.Guild(l.Bridge.BridgeConfig.GID) if err != nil { - log.Println("error finding guild") + log.Println("Error finding guild") panic(err) } @@ -187,7 +187,7 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi u, err := s.User(vs.UserID) if err != nil { - log.Println("error looking up username") + log.Println("Error looking up username") continue } @@ -196,7 +196,7 @@ func (l *DiscordListener) voiceUpdate(s *discordgo.Session, event *discordgo.Voi if err != nil { log.Println("Error creating private channel for", u.Username) } - l.Bridge.DiscordUsers[vs.UserID] = discordUser{ + l.Bridge.DiscordUsers[vs.UserID] = DiscordUser{ username: u.Username, seen: true, dm: dm, diff --git a/discord.go b/internal/bridge/discord.go similarity index 75% rename from discord.go rename to internal/bridge/discord.go index 23fe141..b68297b 100644 --- a/discord.go +++ b/internal/bridge/discord.go @@ -1,4 +1,4 @@ -package main +package bridge import ( "context" @@ -8,15 +8,18 @@ import ( "time" "github.com/bwmarrin/discordgo" - "layeh.com/gopus" - "layeh.com/gumble/gumble" - _ "layeh.com/gumble/opus" + "github.com/stieneee/gopus" + "github.com/stieneee/gumble/gumble" + "github.com/stieneee/mumble-discord-bridge/pkg/sleepct" ) type fromDiscord struct { - decoder *gopus.Decoder - pcm chan []int16 - streaming bool + decoder *gopus.Decoder + pcm chan []int16 + receiving bool // is used to to track the assumption that we are streaming a continuos stream form discord + streaming bool // The buffer streaming is streaming out + lastSequence uint16 + lastTimeStamp uint32 } // DiscordDuplex Handle discord voice stream @@ -57,15 +60,9 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, // Generate Opus Silence Frame opusSilence := []byte{0xf8, 0xff, 0xfe} - for i := 3; i < frameSize; i++ { - opusSilence = append(opusSilence, 0x00) - } - // ticker := NewTickerCT(20 * time.Millisecond) - sleepTick := SleepCT{ - d: 20 * time.Millisecond, - t: time.Now(), - } + sleepTick := sleepct.SleepCT{} + sleepTick.Start(20 * time.Millisecond) lastReady := true var readyTimeout *time.Timer @@ -79,7 +76,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, if lastReady { OnError(fmt.Sprintf("Discordgo not ready for opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) readyTimeout = time.AfterFunc(30*time.Second, func() { - log.Println("set ready timeout") + log.Println("Debug: Set ready timeout") cancel() }) lastReady = false @@ -164,7 +161,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro if lastReady { OnError(fmt.Sprintf("Discordgo not to receive opus packets. %+v : %+v", dd.Bridge.DiscordVoice.Ready, dd.Bridge.DiscordVoice.OpusSend), nil) readyTimeout = time.AfterFunc(30*time.Second, func() { - log.Println("set ready timeout") + log.Println("Debug: Set ready timeout") cancel() }) lastReady = false @@ -193,46 +190,79 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro } dd.discordMutex.Lock() + _, ok = dd.fromDiscordMap[p.SSRC] - dd.discordMutex.Unlock() if !ok { newStream := fromDiscord{} newStream.pcm = make(chan []int16, 100) + newStream.receiving = false newStream.streaming = false - newStream.decoder, err = gopus.NewDecoder(48000, 1) + newStream.decoder, err = gopus.NewDecoder(48000, 1) // Decode into mono if err != nil { OnError("error creating opus decoder", err) + dd.discordMutex.Unlock() continue } - dd.discordMutex.Lock() + dd.fromDiscordMap[p.SSRC] = newStream - dd.discordMutex.Unlock() } - dd.discordMutex.Lock() - p.PCM, err = dd.fromDiscordMap[p.SSRC].decoder.Decode(p.Opus, 960, false) + s := dd.fromDiscordMap[p.SSRC] + + deltaT := int(p.Timestamp - s.lastTimeStamp) + if p.Sequence-s.lastSequence != 1 { + s.decoder.ResetState() + } + + // oldReceiving := s.receiving + + if !s.receiving || deltaT < 1 || deltaT > 960*25 { + // First packet assume deltaT + fmt.Println("replacing", deltaT, deltaT, 960) + deltaT = 960 + s.receiving = true + } + + s.lastTimeStamp = p.Timestamp + s.lastSequence = p.Sequence + + dd.fromDiscordMap[p.SSRC] = s dd.discordMutex.Unlock() + + p.PCM, err = s.decoder.Decode(p.Opus, deltaT*5, false) if err != nil { OnError("Error decoding opus data", err) continue } - if len(p.PCM) != 960 { - log.Println("Opus size error") - continue - } + // fmt.Println(p.SSRC, p.Type, deltaT, p.Sequence, p.Sequence-s.lastSequence, oldReceiving, s.streaming, len(p.Opus), len(p.PCM)) + + // Stereo to Mono - Testing + // if len(p.PCM) != 0 { + // mono := make([]int16, len(p.PCM)/2) + // x := 0 + // for i := 0; i < len(p.PCM); i = i + 2 { + // mono[x] = (p.PCM[i] / 2) + (p.PCM[i+1] / 2) + // x++ + // } + // p.PCM = mono[:] + // // fmt.Println("mono resample", len(p.PCM)) + // } else if len(p.PCM) == 0 { + // p.PCM = zeros[:] + // } + + // Push data into pcm channel in 10ms chunks of mono pcm data dd.discordMutex.Lock() - select { - case dd.fromDiscordMap[p.SSRC].pcm <- p.PCM[0:480]: - default: - log.Println("fromDiscordMap buffer full. Dropping packet") - dd.discordMutex.Unlock() - continue - } - select { - case dd.fromDiscordMap[p.SSRC].pcm <- p.PCM[480:960]: - default: - log.Println("fromDiscordMap buffer full. Dropping packet") + for l := 0; l < deltaT; l = l + 480 { + var next []int16 + u := l + 480 + next = p.PCM[l:u] + + select { + case dd.fromDiscordMap[p.SSRC].pcm <- next: + default: + log.Println("From Discord buffer full. Dropping packet") + } } dd.discordMutex.Unlock() } @@ -244,10 +274,10 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou mumbleSilence = append(mumbleSilence, 0x00) } var speakingStart time.Time - sleepTick := SleepCT{ - d: 10 * time.Millisecond, - t: time.Now(), - } + + sleepTick := sleepct.SleepCT{} + sleepTick.Start(10 * time.Millisecond) + sendAudio := false toMumbleStreaming := false wg.Add(1) @@ -271,7 +301,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou for i := range dd.fromDiscordMap { bufferLength := len(dd.fromDiscordMap[i].pcm) isStreaming := dd.fromDiscordMap[i].streaming - if (bufferLength > 0 && isStreaming) || (bufferLength > dd.Bridge.BridgeConfig.mumbleStartStreamCount && !isStreaming) { + if (bufferLength > 0 && isStreaming) || (bufferLength > dd.Bridge.BridgeConfig.MumbleStartStreamCount && !isStreaming) { if !toMumbleStreaming { speakingStart = time.Now() toMumbleStreaming = true @@ -290,6 +320,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou if dd.fromDiscordMap[i].streaming { x := dd.fromDiscordMap[i] x.streaming = false + x.receiving = false // toggle this here is not optimal but there is no better location atm. dd.fromDiscordMap[i] = x } } @@ -307,7 +338,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou select { case toMumble <- outBuf: case <-timeout: - log.Println("toMumble timeout. Dropping packet") + log.Println("To Mumble timeout. Dropping packet") } } @@ -315,8 +346,8 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou // Regular send mixed audio outBuf := make([]int16, 480) - for i := 0; i < len(outBuf); i++ { - for j := 0; j < len(internalMixerArr); j++ { + for j := 0; j < len(internalMixerArr); j++ { + for i := 0; i < len(internalMixerArr[j]); i++ { outBuf[i] += (internalMixerArr[j])[i] } } diff --git a/mumble-handlers.go b/internal/bridge/mumble-handlers.go similarity index 87% rename from mumble-handlers.go rename to internal/bridge/mumble-handlers.go index 3dc58df..05c9a93 100644 --- a/mumble-handlers.go +++ b/internal/bridge/mumble-handlers.go @@ -1,10 +1,10 @@ -package main +package bridge import ( "log" "strings" - "layeh.com/gumble/gumble" + "github.com/stieneee/gumble/gumble" ) // MumbleListener Handle mumble events @@ -12,7 +12,7 @@ type MumbleListener struct { Bridge *BridgeState } -func (l *MumbleListener) mumbleConnect(e *gumble.ConnectEvent) { +func (l *MumbleListener) MumbleConnect(e *gumble.ConnectEvent) { //join specified channel startingChannel := e.Client.Channels.Find(l.Bridge.BridgeConfig.MumbleChannel...) if startingChannel != nil { @@ -20,7 +20,7 @@ func (l *MumbleListener) mumbleConnect(e *gumble.ConnectEvent) { } } -func (l *MumbleListener) mumbleUserChange(e *gumble.UserChangeEvent) { +func (l *MumbleListener) MumbleUserChange(e *gumble.UserChangeEvent) { l.Bridge.MumbleUsersMutex.Lock() if e.Type.Has(gumble.UserChangeConnected) || e.Type.Has(gumble.UserChangeChannel) || e.Type.Has(gumble.UserChangeDisconnected) { l.Bridge.MumbleUsers = make(map[string]bool) @@ -40,7 +40,7 @@ func (l *MumbleListener) mumbleUserChange(e *gumble.UserChangeEvent) { log.Println("User connected to mumble " + e.User.Name) if !l.Bridge.BridgeConfig.MumbleDisableText { - e.User.Send("Mumble-Discord-Bridge v" + version) + e.User.Send("Mumble-Discord-Bridge v" + l.Bridge.BridgeConfig.Version) // Tell the user who is connected to discord l.Bridge.DiscordUsersMutex.Lock() diff --git a/mumble.go b/internal/bridge/mumble.go similarity index 84% rename from mumble.go rename to internal/bridge/mumble.go index c248b57..be3edd8 100644 --- a/mumble.go +++ b/internal/bridge/mumble.go @@ -1,4 +1,4 @@ -package main +package bridge import ( "context" @@ -6,8 +6,9 @@ import ( "sync" "time" - "layeh.com/gumble/gumble" - _ "layeh.com/gumble/opus" + "github.com/stieneee/gumble/gumble" + _ "github.com/stieneee/gumble/opus" + "github.com/stieneee/mumble-discord-bridge/pkg/sleepct" ) var mutex sync.Mutex @@ -30,7 +31,7 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { go func() { name := e.User.Name - log.Println("new mumble audio stream", name) + log.Println("New mumble audio stream", name) for p := range e.C { // log.Println("audio packet", p.Sender.Name, len(p.AudioBuffer)) @@ -39,15 +40,14 @@ func (m MumbleDuplex) OnAudioStream(e *gumble.AudioStreamEvent) { localMumbleArray <- p.AudioBuffer[480*i : 480*(i+1)] } } - log.Println("mumble audio stream ended", name) + log.Println("Mumble audio stream ended", name) }() } func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, toDiscord chan []int16) { - sleepTick := SleepCT{ - d: 10 * time.Millisecond, - t: time.Now(), - } + sleepTick := sleepct.SleepCT{} + sleepTick.Start(10 * time.Millisecond) + sendAudio := false bufferWarning := false @@ -74,7 +74,7 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t sendAudio = true if !mumbleStreamingArr[i] { mumbleStreamingArr[i] = true - // log.Println("mumble starting", i) + // log.Println("Mumble starting", i) } x1 := (<-fromMumbleArr[i]) @@ -82,7 +82,7 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t } else { if mumbleStreamingArr[i] { mumbleStreamingArr[i] = false - // log.Println("mumble stopping", i) + // log.Println("Mumble stopping", i) } } } diff --git a/main.go b/main.go deleted file mode 100644 index 5173200..0000000 --- a/main.go +++ /dev/null @@ -1,234 +0,0 @@ -package main - -import ( - "flag" - "fmt" - "log" - "math" - "os" - "os/signal" - "runtime/pprof" - "strconv" - "strings" - "syscall" - "time" - - "github.com/bwmarrin/discordgo" - "github.com/joho/godotenv" - "layeh.com/gumble/gumble" - "layeh.com/gumble/gumbleutil" - _ "layeh.com/gumble/opus" -) - -var ( - // Build vars - version string - commit string - date string -) - -func main() { - var err error - - fmt.Println("Mumble-Discord-Bridge") - fmt.Println("v" + version + " " + commit + " " + date) - - godotenv.Load() - - mumbleAddr := flag.String("mumble-address", lookupEnvOrString("MUMBLE_ADDRESS", ""), "MUMBLE_ADDRESS, mumble server address, example example.com, required") - mumblePort := flag.Int("mumble-port", lookupEnvOrInt("MUMBLE_PORT", 64738), "MUMBLE_PORT, mumble port, (default 64738)") - mumbleUsername := flag.String("mumble-username", lookupEnvOrString("MUMBLE_USERNAME", "Discord"), "MUMBLE_USERNAME, mumble username, (default: discord)") - mumblePassword := flag.String("mumble-password", lookupEnvOrString("MUMBLE_PASSWORD", ""), "MUMBLE_PASSWORD, mumble password, optional") - mumbleInsecure := flag.Bool("mumble-insecure", lookupEnvOrBool("MUMBLE_INSECURE", false), " MUMBLE_INSECURE, mumble insecure, optional") - mumbleCertificate := flag.String("mumble-certificate", lookupEnvOrString("MUMBLE_CERTIFICATE", ""), "MUMBLE_CERTIFICATE, client certificate to use when connecting to the Mumble server") - mumbleChannel := flag.String("mumble-channel", lookupEnvOrString("MUMBLE_CHANNEL", ""), "MUMBLE_CHANNEL, mumble channel to start in, using '/' to separate nested channels, optional") - mumbleSendBuffer := flag.Int("to-mumble-buffer", lookupEnvOrInt("TO_MUMBLE_BUFFER", 50), "TO_MUMBLE_BUFFER, Jitter buffer from Discord to Mumble to absorb timing issues related to network, OS and hardware quality. (Increments of 10ms)") - mumbleDisableText := flag.Bool("mumble-disable-text", lookupEnvOrBool("MUMBLE_DISABLE_TEXT", false), "MUMBLE_DISABLE_TEXT, disable sending text to mumble, (default false)") - discordToken := flag.String("discord-token", lookupEnvOrString("DISCORD_TOKEN", ""), "DISCORD_TOKEN, discord bot token, required") - discordGID := flag.String("discord-gid", lookupEnvOrString("DISCORD_GID", ""), "DISCORD_GID, discord gid, required") - discordCID := flag.String("discord-cid", lookupEnvOrString("DISCORD_CID", ""), "DISCORD_CID, discord cid, required") - discordSendBuffer := flag.Int("to-discord-buffer", lookupEnvOrInt("TO_DISCORD_BUFFER", 50), "TO_DISCORD_BUFFER, Jitter buffer from Mumble to Discord to absorb timing issues related to network, OS and hardware quality. (Increments of 10ms)") - discordCommand := flag.String("discord-command", lookupEnvOrString("DISCORD_COMMAND", "mumble-discord"), "DISCORD_COMMAND, Discord command string, env alt DISCORD_COMMAND, optional, (defaults mumble-discord)") - discordDisableText := flag.Bool("discord-disable-text", lookupEnvOrBool("DISCORD_DISABLE_TEXT", false), "DISCORD_DISABLE_TEXT, disable sending direct messages to discord, (default false)") - mode := flag.String("mode", lookupEnvOrString("MODE", "constant"), "MODE, [constant, manual, auto] determine which mode the bridge starts in, (default constant)") - nice := flag.Bool("nice", lookupEnvOrBool("NICE", false), "NICE, whether the bridge should automatically try to 'nice' itself, (default false)") - debug := flag.Int("debug-level", lookupEnvOrInt("DEBUG", 1), "DEBUG_LEVEL, Discord debug level, optional, (default 1)") - - cpuprofile := flag.String("cpuprofile", "", "write cpu profile to `file`") - - flag.Parse() - log.Printf("app.config %v\n", getConfig(flag.CommandLine)) - - if *mumbleAddr == "" { - log.Fatalln("missing mumble address") - } - if *mumbleUsername == "" { - log.Fatalln("missing mumble username") - } - - if *discordToken == "" { - log.Fatalln("missing discord bot token") - } - if *discordGID == "" { - log.Fatalln("missing discord gid") - } - if *discordCID == "" { - log.Fatalln("missing discord cid") - } - if *mode == "" { - log.Fatalln("missing mode set") - } - if *nice { - err := syscall.Setpriority(syscall.PRIO_PROCESS, os.Getpid(), -5) - if err != nil { - log.Println("Unable to set priority. ", err) - } - } - - // Optional CPU Profiling - if *cpuprofile != "" { - f, err := os.Create(*cpuprofile) - if err != nil { - log.Fatal("could not create CPU profile: ", err) - } - defer f.Close() // error handling omitted for example - if err := pprof.StartCPUProfile(f); err != nil { - log.Fatal("could not start CPU profile: ", err) - } - defer pprof.StopCPUProfile() - } - - // Buffer Math - if *discordSendBuffer < 10 { - *discordSendBuffer = 10 - } - - if *mumbleSendBuffer < 10 { - *mumbleSendBuffer = 10 - } - - var discordStartStreamingCount int = int(math.Round(float64(*discordSendBuffer) / 10.0)) - log.Println("To Discord Jitter Buffer: ", discordStartStreamingCount*10, " ms") - - var mumbleStartStreamCount int = int(math.Round(float64(*mumbleSendBuffer) / 10.0)) - log.Println("To Mumble Jitter Buffer: ", mumbleStartStreamCount*10, " ms") - - // BRIDGE SETUP - - Bridge := &BridgeState{ - BridgeConfig: &BridgeConfig{ - // MumbleConfig: config, - MumbleAddr: *mumbleAddr + ":" + strconv.Itoa(*mumblePort), - MumbleInsecure: *mumbleInsecure, - MumbleCertificate: *mumbleCertificate, - MumbleChannel: strings.Split(*mumbleChannel, "/"), - mumbleStartStreamCount: mumbleStartStreamCount, - MumbleDisableText: *mumbleDisableText, - Command: *discordCommand, - GID: *discordGID, - CID: *discordCID, - DiscordStartStreamingCount: discordStartStreamingCount, - DiscordDisableText: *discordDisableText, - }, - Connected: false, - DiscordUsers: make(map[string]discordUser), - MumbleUsers: make(map[string]bool), - } - - // MUMBLE SETUP - Bridge.BridgeConfig.MumbleConfig = gumble.NewConfig() - Bridge.BridgeConfig.MumbleConfig.Username = *mumbleUsername - Bridge.BridgeConfig.MumbleConfig.Password = *mumblePassword - Bridge.BridgeConfig.MumbleConfig.AudioInterval = time.Millisecond * 10 - - Bridge.MumbleListener = &MumbleListener{ - Bridge: Bridge, - } - - Bridge.BridgeConfig.MumbleConfig.Attach(gumbleutil.Listener{ - Connect: Bridge.MumbleListener.mumbleConnect, - UserChange: Bridge.MumbleListener.mumbleUserChange, - }) - - // DISCORD SETUP - - //Connect to discord - Bridge.DiscordSession, err = discordgo.New("Bot " + *discordToken) - if err != nil { - log.Println(err) - return - } - - Bridge.DiscordSession.LogLevel = *debug - Bridge.DiscordSession.StateEnabled = true - Bridge.DiscordSession.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAllWithoutPrivileged) - Bridge.DiscordSession.ShouldReconnectOnError = true - // register handlers - Bridge.DiscordListener = &DiscordListener{ - Bridge: Bridge, - } - Bridge.DiscordSession.AddHandler(Bridge.DiscordListener.messageCreate) - Bridge.DiscordSession.AddHandler(Bridge.DiscordListener.guildCreate) - Bridge.DiscordSession.AddHandler(Bridge.DiscordListener.voiceUpdate) - - // Open Discord websocket - err = Bridge.DiscordSession.Open() - if err != nil { - log.Println(err) - return - } - defer Bridge.DiscordSession.Close() - - log.Println("Discord Bot Connected") - log.Printf("Discord bot looking for command !%v", *discordCommand) - - switch *mode { - case "auto": - log.Println("bridge starting in automatic mode") - Bridge.AutoChanDie = make(chan bool) - Bridge.Mode = bridgeModeAuto - Bridge.DiscordChannelID = Bridge.BridgeConfig.CID - go Bridge.AutoBridge() - case "manual": - log.Println("bridge starting in manual mode") - Bridge.Mode = bridgeModeManual - case "constant": - log.Println("bridge starting in constant mode") - Bridge.Mode = bridgeModeConstant - Bridge.DiscordChannelID = Bridge.BridgeConfig.CID - go func() { - defer func() { - if r := recover(); r != nil { - fmt.Println("Bridge paniced", r) - } - }() - for { - Bridge.startBridge() - log.Println("Bridge died") - time.Sleep(5 * time.Second) - log.Println("Restarting") - } - }() - default: - Bridge.DiscordSession.Close() - log.Fatalln("invalid bridge mode set") - } - - go Bridge.discordStatusUpdate() - - // Shutdown on OS signal - sc := make(chan os.Signal, 1) - signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt) - <-sc - - log.Println("OS Signal. Bot shutting down") - - // Wait or the bridge to exit cleanly - Bridge.BridgeMutex.Lock() - if Bridge.Connected { - //TODO BridgeDie occasionally panics on send to closed channel - Bridge.BridgeDie <- true - Bridge.WaitExit.Wait() - } - Bridge.BridgeMutex.Unlock() -} diff --git a/sleepct.go b/pkg/sleepct/sleepct.go similarity index 79% rename from sleepct.go rename to pkg/sleepct/sleepct.go index f5277c7..8a82d0a 100644 --- a/sleepct.go +++ b/pkg/sleepct/sleepct.go @@ -1,4 +1,4 @@ -package main +package sleepct import ( "fmt" @@ -14,6 +14,15 @@ type SleepCT struct { t time.Time // last time target } +func (s *SleepCT) Start(d time.Duration) { + if s.t.IsZero() { + s.d = d + s.t = time.Now() + } else { + panic("SleepCT already started") + } +} + func (s *SleepCT) SleepNextTarget() { s.Lock() diff --git a/timing_test.go b/test/timing_test.go similarity index 97% rename from timing_test.go rename to test/timing_test.go index 8592d90..d3cfdcb 100644 --- a/timing_test.go +++ b/test/timing_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/stieneee/mumble-discord-bridge/pkg/sleepct" "github.com/stieneee/tickerct" ) @@ -107,10 +108,8 @@ func testSleepCT(wg *sync.WaitGroup) { now := time.Now() start := now // start the ticker - s := SleepCT{ - d: interval, - t: time.Now(), - } + s := sleepct.SleepCT{} + s.Start(interval) var i int64 for i = 0; i < testCount; i++ { if i+1 < testCount {