mirror of
https://github.com/xaionaro-go/streamctl.git
synced 2025-11-01 03:12:34 +08:00
Initial commit, pt. 12
This commit is contained in:
3
go.mod
3
go.mod
@@ -29,12 +29,14 @@ require (
|
|||||||
github.com/go-text/typesetting v0.1.0 // indirect
|
github.com/go-text/typesetting v0.1.0 // indirect
|
||||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
|
github.com/huandu/go-tls v0.0.0-20200109070953-6f75fb441850 // indirect
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||||
github.com/stretchr/testify v1.8.4 // indirect
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
github.com/tevino/abool v1.2.0 // indirect
|
github.com/tevino/abool v1.2.0 // indirect
|
||||||
|
github.com/xaionaro-go/spinlock v0.0.0-20200518175509-30e6d1ce68a1 // indirect
|
||||||
github.com/yuin/goldmark v1.5.5 // indirect
|
github.com/yuin/goldmark v1.5.5 // indirect
|
||||||
golang.org/x/image v0.11.0 // indirect
|
golang.org/x/image v0.11.0 // indirect
|
||||||
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
|
golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda // indirect
|
||||||
@@ -68,6 +70,7 @@ require (
|
|||||||
github.com/mattn/go-colorable v0.1.8 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
|
github.com/xaionaro-go/gorex v0.0.0-20200314172213-23bed04bc3e3
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||||
|
|||||||
15
go.sum
15
go.sum
@@ -49,6 +49,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/DataDog/gostackparse v0.6.0 h1:egCGQviIabPwsyoWpGvIBGrEnNWez35aEO7OJ1vBI4o=
|
github.com/DataDog/gostackparse v0.6.0 h1:egCGQviIabPwsyoWpGvIBGrEnNWez35aEO7OJ1vBI4o=
|
||||||
github.com/DataDog/gostackparse v0.6.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
|
github.com/DataDog/gostackparse v0.6.0/go.mod h1:lTfqcJKqS9KnXQGnyQMCugq3u1FP6UZMfWR0aitKFMM=
|
||||||
|
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||||
@@ -243,6 +244,8 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
|||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/huandu/go-tls v0.0.0-20200109070953-6f75fb441850 h1:e6Xuec7psx1wWcYffIzWzhXBBFOJ526073fie9Cc79c=
|
||||||
|
github.com/huandu/go-tls v0.0.0-20200109070953-6f75fb441850/go.mod h1:WeItecBdaIdUBRb7cSMMk+rq41iFKhf6Q9mDRDpbdec=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
@@ -338,6 +341,13 @@ github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXl
|
|||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||||
|
github.com/valyala/fastrand v1.0.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ=
|
||||||
|
github.com/xaionaro-go/gorex v0.0.0-20200314172213-23bed04bc3e3 h1:V9vb07lOoiuOXwF2XviWrLM+VXD7+0YAnlWAW7e1BXs=
|
||||||
|
github.com/xaionaro-go/gorex v0.0.0-20200314172213-23bed04bc3e3/go.mod h1:azYJIFDMpJyMON8WqQL7nz7ZnoM8XDdGyr5kZ0fQxBI=
|
||||||
|
github.com/xaionaro-go/rand v0.0.0-20191005105903-aba1befc54a5/go.mod h1:7GUXx8dYHRMAYntgs4o1Vm1IPZ7EIpGbev68nKIKGMQ=
|
||||||
|
github.com/xaionaro-go/spinlock v0.0.0-20190309154744-55278e21e817/go.mod h1:Nb/15eS0BMty6TMuWgRQM8WCDIUlyPZagcpchHT6c9Y=
|
||||||
|
github.com/xaionaro-go/spinlock v0.0.0-20200518175509-30e6d1ce68a1 h1:1Kqw9dv2LnznIhJoMt3dNzc/ctSj6VHjyGh4YZHjpE4=
|
||||||
|
github.com/xaionaro-go/spinlock v0.0.0-20200518175509-30e6d1ce68a1/go.mod h1:UwmTXX+EpoEYHuy0rSys1Rp5PW+eVTgZSjgMVLJENKg=
|
||||||
github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b h1:eoc4aMdU40lOUIlpKTRXfSMcaE8Z6EFJuFw5ptB83lg=
|
github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b h1:eoc4aMdU40lOUIlpKTRXfSMcaE8Z6EFJuFw5ptB83lg=
|
||||||
github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
github.com/yoelsusanto/go-yaml v0.0.0-20240324162521-2018c1ab915b/go.mod h1:wKnAMd44+9JAAnGQpWVEgBzGt3YuTaQ4uXoHvE4m7WU=
|
||||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
@@ -349,6 +359,7 @@ github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1
|
|||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU=
|
github.com/yuin/goldmark v1.5.5 h1:IJznPe8wOzfIKETmMkd06F8nXkmlhaHqFRM9l1hAGsU=
|
||||||
github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.5.5/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
gitlab.com/NebulousLabs/fastrand v0.0.0-20181126182046-603482d69e40/go.mod h1:rOnSnoRyxMI3fe/7KIbVcsHRGxe30OONv8dEgo+vCfA=
|
||||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||||
@@ -386,6 +397,7 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
|||||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
@@ -516,11 +528,13 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -774,6 +788,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
|
|||||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
|
||||||
|
lukechampine.com/frand v1.1.0/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s=
|
||||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||||
|
|||||||
@@ -82,7 +82,11 @@ func NewCodeReceiver(redirectURL string) (codeCh chan string, err error) {
|
|||||||
codeCh <- code
|
codeCh <- code
|
||||||
listener.Close()
|
listener.Close()
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
fmt.Fprintf(w, "Received code: %v\r\nYou can now safely close this browser window.", code)
|
if code == "" {
|
||||||
|
fmt.Fprintf(w, "No code received :(\r\n\r\nYou can close this browser window.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "Received code: %v\r\n\r\nYou can now safely close this browser window.", code)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
return codeCh, nil
|
return codeCh, nil
|
||||||
|
|||||||
@@ -200,6 +200,16 @@ func GetPlatformConfig[T any, S StreamProfile](
|
|||||||
return ConvertPlatformConfig[T, S](ctx, platCfg)
|
return ConvertPlatformConfig[T, S](ctx, platCfg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToAbstractPlatformConfig[T any, S StreamProfile](
|
||||||
|
ctx context.Context,
|
||||||
|
platCfg *PlatformConfig[T, S],
|
||||||
|
) *AbstractPlatformConfig {
|
||||||
|
return &AbstractPlatformConfig{
|
||||||
|
Config: platCfg.Config,
|
||||||
|
StreamProfiles: ToAbstractStreamProfiles[S](platCfg.StreamProfiles),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func ConvertPlatformConfig[T any, S StreamProfile](
|
func ConvertPlatformConfig[T any, S StreamProfile](
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
platCfg *AbstractPlatformConfig,
|
platCfg *AbstractPlatformConfig,
|
||||||
|
|||||||
@@ -41,21 +41,33 @@ type StreamProfile interface {
|
|||||||
AbstractStreamProfile
|
AbstractStreamProfile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetStreamProfile[T StreamProfile](
|
||||||
|
ctx context.Context,
|
||||||
|
v AbstractStreamProfile,
|
||||||
|
) (*T, error) {
|
||||||
|
var profile T
|
||||||
|
b, err := json.Marshal(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to serialize: %w: %#+v", err, v)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(b, &profile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to deserialize: %w: <%s>", err, b)
|
||||||
|
}
|
||||||
|
logger.Debugf(ctx, "converted %#+v to %#+v", v, profile)
|
||||||
|
return &profile, nil
|
||||||
|
}
|
||||||
|
|
||||||
func ConvertStreamProfiles[T StreamProfile](
|
func ConvertStreamProfiles[T StreamProfile](
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
m map[ProfileName]AbstractStreamProfile,
|
m map[ProfileName]AbstractStreamProfile,
|
||||||
) error {
|
) error {
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
var profile T
|
profile, err := GetStreamProfile[T](ctx, v)
|
||||||
b, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to serialize: %w: %#+v", err, v)
|
return err
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(b, &profile)
|
m[k] = *profile
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("unable to deserialize: %w: <%s>", err, b)
|
|
||||||
}
|
|
||||||
m[k] = profile
|
|
||||||
logger.Debugf(ctx, "converted %#+v to %#+v", v, profile)
|
logger.Debugf(ctx, "converted %#+v to %#+v", v, profile)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package twitch
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||||
@@ -105,8 +106,19 @@ func (t *Twitch) ApplyProfile(
|
|||||||
logger.Errorf(ctx, "unable to get the category ID: %v", err)
|
logger.Errorf(ctx, "unable to get the category ID: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tags := make([]string, 0, len(profile.Tags))
|
||||||
|
for _, tag := range profile.Tags {
|
||||||
|
tag = strings.ReplaceAll(tag, " ", "")
|
||||||
|
tag = strings.ReplaceAll(tag, "-", "")
|
||||||
|
if tag == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tags = append(tags, tag)
|
||||||
|
}
|
||||||
|
|
||||||
params := &helix.EditChannelInformationParams{
|
params := &helix.EditChannelInformationParams{
|
||||||
Tags: profile.Tags,
|
Tags: tags,
|
||||||
}
|
}
|
||||||
if profile.Language != nil {
|
if profile.Language != nil {
|
||||||
params.BroadcasterLanguage = *profile.Language
|
params.BroadcasterLanguage = *profile.Language
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
@@ -22,6 +23,7 @@ import (
|
|||||||
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
"github.com/facebookincubator/go-belt/tool/experimental/errmon"
|
||||||
"github.com/facebookincubator/go-belt/tool/logger"
|
"github.com/facebookincubator/go-belt/tool/logger"
|
||||||
"github.com/go-ng/xmath"
|
"github.com/go-ng/xmath"
|
||||||
|
"github.com/xaionaro-go/gorex"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
|
"github.com/xaionaro-go/streamctl/pkg/oauthhandler"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol"
|
||||||
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
"github.com/xaionaro-go/streamctl/pkg/streamcontrol/twitch"
|
||||||
@@ -40,7 +42,7 @@ type Panel struct {
|
|||||||
|
|
||||||
app fyne.App
|
app fyne.App
|
||||||
config config
|
config config
|
||||||
startStopMutex sync.Mutex
|
startStopMutex gorex.Mutex
|
||||||
updateTimerHandler *updateTimerHandler
|
updateTimerHandler *updateTimerHandler
|
||||||
streamControllers struct {
|
streamControllers struct {
|
||||||
Twitch *twitch.Twitch
|
Twitch *twitch.Twitch
|
||||||
@@ -59,6 +61,9 @@ type Panel struct {
|
|||||||
|
|
||||||
dataPath string
|
dataPath string
|
||||||
filterValue string
|
filterValue string
|
||||||
|
|
||||||
|
youtubeCheck *widget.Check
|
||||||
|
twitchCheck *widget.Check
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(
|
func New(
|
||||||
@@ -75,6 +80,10 @@ func (p *Panel) Loop(ctx context.Context) error {
|
|||||||
if p.defaultContext != nil {
|
if p.defaultContext != nil {
|
||||||
return fmt.Errorf("Loop was already used, and cannot be used the second time")
|
return fmt.Errorf("Loop was already used, and cannot be used the second time")
|
||||||
}
|
}
|
||||||
|
p.startStopMutex = gorex.Mutex{
|
||||||
|
InfiniteContext: ctx,
|
||||||
|
}
|
||||||
|
|
||||||
p.defaultContext = ctx
|
p.defaultContext = ctx
|
||||||
logger.Debug(ctx, "config", p.config)
|
logger.Debug(ctx, "config", p.config)
|
||||||
|
|
||||||
@@ -84,19 +93,36 @@ func (p *Panel) Loop(ctx context.Context) error {
|
|||||||
|
|
||||||
p.app = fyneapp.New()
|
p.app = fyneapp.New()
|
||||||
|
|
||||||
|
go func() {
|
||||||
if err := p.initStreamControllers(ctx); err != nil {
|
if err := p.initStreamControllers(ctx); err != nil {
|
||||||
return fmt.Errorf("unable to initialize stream controllers: %w", err)
|
err = fmt.Errorf("unable to initialize stream controllers: %w", err)
|
||||||
|
p.displayError(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
p.initTwitchData()
|
p.initTwitchData()
|
||||||
p.normalizeTwitchData()
|
p.normalizeTwitchData()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
p.initYoutubeData()
|
p.initYoutubeData()
|
||||||
p.normalizeYoutubeData()
|
p.normalizeYoutubeData()
|
||||||
|
}()
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
p.initMainWindow(ctx)
|
p.initMainWindow(ctx)
|
||||||
p.rearrangeProfiles(ctx)
|
p.rearrangeProfiles(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
p.mainWindow.ShowAndRun()
|
p.app.Run()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,7 +293,11 @@ func (p *Panel) openBrowser(authURL string) error {
|
|||||||
case "darwin":
|
case "darwin":
|
||||||
browserCmd = "open"
|
browserCmd = "open"
|
||||||
case "linux":
|
case "linux":
|
||||||
|
if envBrowser := os.Getenv("BROWSER"); envBrowser != "" {
|
||||||
|
browserCmd = envBrowser
|
||||||
|
} else {
|
||||||
browserCmd = "xdg-open"
|
browserCmd = "xdg-open"
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return oauthhandler.LaunchBrowser(authURL)
|
return oauthhandler.LaunchBrowser(authURL)
|
||||||
}
|
}
|
||||||
@@ -275,6 +305,8 @@ func (p *Panel) openBrowser(authURL string) error {
|
|||||||
waitCh := make(chan struct{})
|
waitCh := make(chan struct{})
|
||||||
|
|
||||||
w := p.app.NewWindow("Browser selection window")
|
w := p.app.NewWindow("Browser selection window")
|
||||||
|
promptText := widget.NewRichTextWithText("It is required to confirm access in Twitch/YouTube using browser. Select a browser for that (or leave the field empty for auto-selection):")
|
||||||
|
promptText.Wrapping = fyne.TextWrapWord
|
||||||
browserField := widget.NewEntry()
|
browserField := widget.NewEntry()
|
||||||
browserField.PlaceHolder = "command to execute the browser"
|
browserField.PlaceHolder = "command to execute the browser"
|
||||||
browserField.OnSubmitted = func(s string) {
|
browserField.OnSubmitted = func(s string) {
|
||||||
@@ -284,16 +316,19 @@ func (p *Panel) openBrowser(authURL string) error {
|
|||||||
close(waitCh)
|
close(waitCh)
|
||||||
})
|
})
|
||||||
w.SetContent(container.NewBorder(
|
w.SetContent(container.NewBorder(
|
||||||
|
container.NewVBox(
|
||||||
|
promptText,
|
||||||
browserField,
|
browserField,
|
||||||
|
),
|
||||||
okButton,
|
okButton,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
))
|
))
|
||||||
|
|
||||||
go w.ShowAndRun()
|
w.Show()
|
||||||
<-waitCh
|
<-waitCh
|
||||||
w.Close()
|
w.Hide()
|
||||||
|
|
||||||
if browserField.Text != "" {
|
if browserField.Text != "" {
|
||||||
browserCmd = browserField.Text
|
browserCmd = browserField.Text
|
||||||
@@ -302,18 +337,136 @@ func (p *Panel) openBrowser(authURL string) error {
|
|||||||
return exec.Command(browserCmd, authURL).Start()
|
return exec.Command(browserCmd, authURL).Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var twitchAppsCreateLink, _ = url.Parse("https://dev.twitch.tv/console/apps/create")
|
||||||
|
|
||||||
|
func (p *Panel) inputTwitchUserInfo(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile],
|
||||||
|
) error {
|
||||||
|
w := p.app.NewWindow("Input Twitch user info")
|
||||||
|
w.Resize(fyne.NewSize(600, 200))
|
||||||
|
|
||||||
|
channelField := widget.NewEntry()
|
||||||
|
channelField.SetPlaceHolder("channel ID (copy&paste it from the browser: https://www.twitch.tv/<the channel ID is here>)")
|
||||||
|
clientIDField := widget.NewEntry()
|
||||||
|
clientIDField.SetPlaceHolder("client ID")
|
||||||
|
clientSecretField := widget.NewEntry()
|
||||||
|
clientSecretField.SetPlaceHolder("client secret")
|
||||||
|
instructionText := widget.NewRichText(
|
||||||
|
&widget.TextSegment{Text: "Go to\n", Style: widget.RichTextStyle{Inline: true}},
|
||||||
|
&widget.HyperlinkSegment{Text: twitchAppsCreateLink.String(), URL: twitchAppsCreateLink},
|
||||||
|
&widget.TextSegment{Text: `,` + "\n" + `create an application (enter "http://localhost:8091/" as the "OAuth Redirect URLs" value), then click "Manage" then "New Secret", and copy&paste client ID and client secret.`, Style: widget.RichTextStyle{Inline: true}},
|
||||||
|
)
|
||||||
|
instructionText.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
|
waitCh := make(chan struct{})
|
||||||
|
okButton := widget.NewButtonWithIcon("OK", theme.ConfirmIcon(), func() {
|
||||||
|
close(waitCh)
|
||||||
|
})
|
||||||
|
|
||||||
|
w.SetContent(container.NewBorder(
|
||||||
|
widget.NewRichTextWithText("Enter Twitch user info:"),
|
||||||
|
okButton,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
container.NewVBox(
|
||||||
|
channelField,
|
||||||
|
clientIDField,
|
||||||
|
clientSecretField,
|
||||||
|
instructionText,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
w.Show()
|
||||||
|
<-waitCh
|
||||||
|
w.Hide()
|
||||||
|
|
||||||
|
cfg.Config.AuthType = "user"
|
||||||
|
cfg.Config.Channel = channelField.Text
|
||||||
|
cfg.Config.ClientID = clientIDField.Text
|
||||||
|
cfg.Config.ClientSecret = clientSecretField.Text
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var youtubeCredentialsCreateLink, _ = url.Parse("https://console.cloud.google.com/apis/credentials/oauthclient")
|
||||||
|
|
||||||
|
func (p *Panel) inputYouTubeUserInfo(
|
||||||
|
ctx context.Context,
|
||||||
|
cfg *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile],
|
||||||
|
) error {
|
||||||
|
w := p.app.NewWindow("Input YouTube user info")
|
||||||
|
w.Resize(fyne.NewSize(600, 200))
|
||||||
|
|
||||||
|
clientIDField := widget.NewEntry()
|
||||||
|
clientIDField.SetPlaceHolder("client ID")
|
||||||
|
clientSecretField := widget.NewEntry()
|
||||||
|
clientSecretField.SetPlaceHolder("client secret")
|
||||||
|
instructionText := widget.NewRichText(
|
||||||
|
&widget.TextSegment{Text: "Go to\n", Style: widget.RichTextStyle{Inline: true}},
|
||||||
|
&widget.HyperlinkSegment{Text: youtubeCredentialsCreateLink.String(), URL: youtubeCredentialsCreateLink},
|
||||||
|
&widget.TextSegment{Text: `,` + "\n" + `configure "consent screen" (note: you may add yourself into Test Users to avoid problems further on, and don't forget to add "YouTube Data API v3" scopes) and go back to` + "\n", Style: widget.RichTextStyle{Inline: true}},
|
||||||
|
&widget.HyperlinkSegment{Text: youtubeCredentialsCreateLink.String(), URL: youtubeCredentialsCreateLink},
|
||||||
|
&widget.TextSegment{Text: `,` + "\n" + `choose "Desktop app", confirm and copy&paste client ID and client secret.`, Style: widget.RichTextStyle{Inline: true}},
|
||||||
|
)
|
||||||
|
instructionText.Wrapping = fyne.TextWrapWord
|
||||||
|
|
||||||
|
waitCh := make(chan struct{})
|
||||||
|
okButton := widget.NewButtonWithIcon("OK", theme.ConfirmIcon(), func() {
|
||||||
|
close(waitCh)
|
||||||
|
})
|
||||||
|
|
||||||
|
w.SetContent(container.NewBorder(
|
||||||
|
widget.NewRichTextWithText("Enter YouTube user info:"),
|
||||||
|
okButton,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
container.NewVBox(
|
||||||
|
clientIDField,
|
||||||
|
clientSecretField,
|
||||||
|
instructionText,
|
||||||
|
),
|
||||||
|
))
|
||||||
|
w.Show()
|
||||||
|
<-waitCh
|
||||||
|
w.Hide()
|
||||||
|
|
||||||
|
cfg.Config.ClientID = clientIDField.Text
|
||||||
|
cfg.Config.ClientSecret = clientSecretField.Text
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Panel) initStreamControllers(ctx context.Context) error {
|
func (p *Panel) initStreamControllers(ctx context.Context) error {
|
||||||
for platName, cfg := range p.data.Backends {
|
platNames := make([]streamcontrol.PlatformName, 0, len(p.data.Backends))
|
||||||
|
for platName := range p.data.Backends {
|
||||||
|
platNames = append(platNames, platName)
|
||||||
|
}
|
||||||
|
sort.Slice(platNames, func(i, j int) bool {
|
||||||
|
return platNames[i] < platNames[j]
|
||||||
|
})
|
||||||
|
for _, platName := range platNames {
|
||||||
|
cfg := p.data.Backends[platName]
|
||||||
var err error
|
var err error
|
||||||
switch strings.ToLower(string(platName)) {
|
switch strings.ToLower(string(platName)) {
|
||||||
case strings.ToLower(string(twitch.ID)):
|
case strings.ToLower(string(twitch.ID)):
|
||||||
p.streamControllers.Twitch, err = newTwitch(ctx, cfg, func(cfg *streamcontrol.AbstractPlatformConfig) error {
|
p.streamControllers.Twitch, err = newTwitch(
|
||||||
|
ctx,
|
||||||
|
cfg,
|
||||||
|
p.inputTwitchUserInfo,
|
||||||
|
func(cfg *streamcontrol.AbstractPlatformConfig) error {
|
||||||
return p.savePlatformConfig(ctx, twitch.ID, cfg)
|
return p.savePlatformConfig(ctx, twitch.ID, cfg)
|
||||||
}, p.oauthHandlerTwitch)
|
},
|
||||||
|
p.oauthHandlerTwitch)
|
||||||
case strings.ToLower(string(youtube.ID)):
|
case strings.ToLower(string(youtube.ID)):
|
||||||
p.streamControllers.YouTube, err = newYouTube(ctx, cfg, func(cfg *streamcontrol.AbstractPlatformConfig) error {
|
p.streamControllers.YouTube, err = newYouTube(
|
||||||
|
ctx,
|
||||||
|
cfg,
|
||||||
|
p.inputYouTubeUserInfo,
|
||||||
|
func(cfg *streamcontrol.AbstractPlatformConfig) error {
|
||||||
return p.savePlatformConfig(ctx, youtube.ID, cfg)
|
return p.savePlatformConfig(ctx, youtube.ID, cfg)
|
||||||
}, p.oauthHandlerYouTube)
|
},
|
||||||
|
p.oauthHandlerYouTube,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to initialize '%s': %w", platName, err)
|
return fmt.Errorf("unable to initialize '%s': %w", platName, err)
|
||||||
@@ -550,6 +703,7 @@ func (p *Panel) setFilter(ctx context.Context, filter string) {
|
|||||||
|
|
||||||
func (p *Panel) initMainWindow(ctx context.Context) {
|
func (p *Panel) initMainWindow(ctx context.Context) {
|
||||||
w := p.app.NewWindow("StreamPanel")
|
w := p.app.NewWindow("StreamPanel")
|
||||||
|
w.Resize(fyne.NewSize(400, 600))
|
||||||
|
|
||||||
profileFilter := widget.NewEntry()
|
profileFilter := widget.NewEntry()
|
||||||
profileFilter.SetPlaceHolder("filter")
|
profileFilter.SetPlaceHolder("filter")
|
||||||
@@ -625,11 +779,21 @@ func (p *Panel) initMainWindow(ctx context.Context) {
|
|||||||
p.startStopButton.OnTapped()
|
p.startStopButton.OnTapped()
|
||||||
}
|
}
|
||||||
|
|
||||||
bottomPanel := container.NewAdaptiveGrid(
|
p.twitchCheck = widget.NewCheck("Twitch", nil)
|
||||||
1,
|
p.twitchCheck.SetChecked(true)
|
||||||
|
p.youtubeCheck = widget.NewCheck("YouTube", nil)
|
||||||
|
p.youtubeCheck.SetChecked(true)
|
||||||
|
|
||||||
|
bottomPanel := container.NewVBox(
|
||||||
p.streamTitleField,
|
p.streamTitleField,
|
||||||
p.streamDescriptionField,
|
p.streamDescriptionField,
|
||||||
|
container.NewBorder(
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
container.NewHBox(p.twitchCheck, p.youtubeCheck),
|
||||||
|
nil,
|
||||||
p.startStopButton,
|
p.startStopButton,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
w.SetContent(container.NewBorder(
|
w.SetContent(container.NewBorder(
|
||||||
topPanel,
|
topPanel,
|
||||||
@@ -644,24 +808,118 @@ func (p *Panel) initMainWindow(ctx context.Context) {
|
|||||||
p.profilesListWidget = profilesList
|
p.profilesListWidget = profilesList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Panel) getSelectedProfile() Profile {
|
||||||
|
return p.getProfile(*p.selectedProfileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Panel) startStream() {
|
||||||
|
p.startStopMutex.Lock()
|
||||||
|
defer p.startStopMutex.Unlock()
|
||||||
|
|
||||||
|
if p.startStopButton.Disabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.startStopButton.Disable()
|
||||||
|
defer p.startStopButton.Enable()
|
||||||
|
|
||||||
|
p.twitchCheck.Disable()
|
||||||
|
p.youtubeCheck.Disable()
|
||||||
|
|
||||||
|
p.startStopButton.SetText("Stop stream")
|
||||||
|
p.startStopButton.Icon = theme.MediaStopIcon()
|
||||||
|
p.startStopButton.Importance = widget.DangerImportance
|
||||||
|
if p.updateTimerHandler != nil {
|
||||||
|
p.updateTimerHandler.Stop()
|
||||||
|
}
|
||||||
|
p.updateTimerHandler = newUpdateTimerHandler(p.startStopButton)
|
||||||
|
profile := p.getSelectedProfile()
|
||||||
|
|
||||||
|
var twitchProfile *twitch.StreamProfile
|
||||||
|
if p.twitchCheck.Checked && p.streamControllers.Twitch != nil {
|
||||||
|
var err error
|
||||||
|
twitchProfile, err = streamcontrol.GetStreamProfile[twitch.StreamProfile](p.defaultContext, profile.PerPlatform[twitch.ID])
|
||||||
|
if err != nil {
|
||||||
|
p.displayError(fmt.Errorf("unable to get the streaming profile for Twitch: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var youtubeProfile *youtube.StreamProfile
|
||||||
|
if p.youtubeCheck.Checked && p.streamControllers.YouTube != nil {
|
||||||
|
var err error
|
||||||
|
youtubeProfile, err = streamcontrol.GetStreamProfile[youtube.StreamProfile](p.defaultContext, profile.PerPlatform[youtube.ID])
|
||||||
|
if err != nil {
|
||||||
|
p.displayError(fmt.Errorf("unable to get the streaming profile for YouTube: %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.twitchCheck.Checked && p.streamControllers.Twitch != nil {
|
||||||
|
err := p.streamControllers.Twitch.StartStream(
|
||||||
|
p.defaultContext,
|
||||||
|
p.streamTitleField.Text,
|
||||||
|
p.streamDescriptionField.Text,
|
||||||
|
*twitchProfile,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
p.displayError(fmt.Errorf("unable to setup the stream on Twitch: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.youtubeCheck.Checked && p.streamControllers.YouTube != nil {
|
||||||
|
err := p.streamControllers.YouTube.StartStream(
|
||||||
|
p.defaultContext,
|
||||||
|
p.streamTitleField.Text,
|
||||||
|
p.streamDescriptionField.Text,
|
||||||
|
*youtubeProfile,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
p.displayError(fmt.Errorf("unable to start the stream on YouTube: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Panel) stopStream() {
|
||||||
|
p.startStopMutex.Lock()
|
||||||
|
defer p.startStopMutex.Unlock()
|
||||||
|
|
||||||
|
p.twitchCheck.Enable()
|
||||||
|
p.youtubeCheck.Enable()
|
||||||
|
|
||||||
|
p.startStopButton.SetText("Start stream")
|
||||||
|
p.startStopButton.Icon = theme.MediaRecordIcon()
|
||||||
|
p.startStopButton.Importance = widget.SuccessImportance
|
||||||
|
|
||||||
|
p.updateTimerHandler.Stop()
|
||||||
|
if p.updateTimerHandler != nil {
|
||||||
|
p.updateTimerHandler.Stop()
|
||||||
|
}
|
||||||
|
p.updateTimerHandler = nil
|
||||||
|
p.startStopButton.SetText("")
|
||||||
|
|
||||||
|
if p.streamControllers.Twitch != nil {
|
||||||
|
err := p.streamControllers.Twitch.EndStream(p.defaultContext)
|
||||||
|
if err != nil {
|
||||||
|
p.displayError(fmt.Errorf("unable to stop the stream on Twitch: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.streamControllers.YouTube != nil {
|
||||||
|
err := p.streamControllers.YouTube.EndStream(p.defaultContext)
|
||||||
|
if err != nil {
|
||||||
|
p.displayError(fmt.Errorf("unable to stop the stream on YouTube: %w", err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *Panel) onStartStopButton() {
|
func (p *Panel) onStartStopButton() {
|
||||||
p.startStopMutex.Lock()
|
p.startStopMutex.Lock()
|
||||||
defer p.startStopMutex.Unlock()
|
defer p.startStopMutex.Unlock()
|
||||||
|
|
||||||
if p.updateTimerHandler != nil {
|
if p.updateTimerHandler != nil {
|
||||||
p.startStopButton.SetText("Start stream")
|
p.stopStream()
|
||||||
p.startStopButton.Icon = theme.MediaRecordIcon()
|
|
||||||
p.startStopButton.Importance = widget.SuccessImportance
|
|
||||||
p.updateTimerHandler.Stop()
|
|
||||||
panic("stream stopping is not implemented")
|
|
||||||
p.updateTimerHandler = nil
|
|
||||||
p.startStopButton.SetText("")
|
|
||||||
} else {
|
} else {
|
||||||
p.startStopButton.SetText("Stop stream")
|
p.startStream()
|
||||||
p.startStopButton.Icon = theme.MediaStopIcon()
|
|
||||||
p.startStopButton.Importance = widget.DangerImportance
|
|
||||||
p.updateTimerHandler = newUpdateTimerHandler(p.startStopButton)
|
|
||||||
panic("stream starting is not implemented")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p.startStopButton.Refresh()
|
p.startStopButton.Refresh()
|
||||||
@@ -750,8 +1008,15 @@ func (p *Panel) newProfileWindow(ctx context.Context) fyne.Window {
|
|||||||
"Create a profile",
|
"Create a profile",
|
||||||
Profile{},
|
Profile{},
|
||||||
func(ctx context.Context, profile Profile) error {
|
func(ctx context.Context, profile Profile) error {
|
||||||
oldProfile := p.getProfile(profile.Name)
|
found := false
|
||||||
if oldProfile.Name == profile.Name {
|
for _, platCfg := range p.data.Backends {
|
||||||
|
_, ok := platCfg.GetStreamProfile(profile.Name)
|
||||||
|
if ok {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if found {
|
||||||
return fmt.Errorf("profile with name '%s' already exists", profile.Name)
|
return fmt.Errorf("profile with name '%s' already exists", profile.Name)
|
||||||
}
|
}
|
||||||
if err := p.profileCreateOrUpdate(ctx, profile); err != nil {
|
if err := p.profileCreateOrUpdate(ctx, profile); err != nil {
|
||||||
@@ -1002,6 +1267,9 @@ func (p *Panel) profileWindow(
|
|||||||
}
|
}
|
||||||
_tags := make([]string, len(tags))
|
_tags := make([]string, len(tags))
|
||||||
for k := range tags {
|
for k := range tags {
|
||||||
|
if k == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
_tags = append(_tags, k)
|
_tags = append(_tags, k)
|
||||||
}
|
}
|
||||||
profile := Profile{
|
profile := Profile{
|
||||||
@@ -1050,6 +1318,13 @@ func (p *Panel) displayError(err error) {
|
|||||||
w := p.app.NewWindow("Got an error: " + err.Error())
|
w := p.app.NewWindow("Got an error: " + err.Error())
|
||||||
errorMessage := fmt.Sprintf("Error: %v\n\nstack trace:\n%s", err, debug.Stack())
|
errorMessage := fmt.Sprintf("Error: %v\n\nstack trace:\n%s", err, debug.Stack())
|
||||||
w.Resize(fyne.NewSize(400, 300))
|
w.Resize(fyne.NewSize(400, 300))
|
||||||
w.SetContent(widget.NewLabelWithStyle(errorMessage, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}))
|
textWidget := widget.NewMultiLineEntry()
|
||||||
|
textWidget.SetText(errorMessage)
|
||||||
|
textWidget.Wrapping = fyne.TextWrapWord
|
||||||
|
textWidget.TextStyle = fyne.TextStyle{
|
||||||
|
Bold: true,
|
||||||
|
Monospace: true,
|
||||||
|
}
|
||||||
|
w.SetContent(textWidget)
|
||||||
w.Show()
|
w.Show()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
func newTwitch(
|
func newTwitch(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.AbstractPlatformConfig,
|
cfg *streamcontrol.AbstractPlatformConfig,
|
||||||
|
setUserData func(context.Context, *streamcontrol.PlatformConfig[twitch.PlatformSpecificConfig, twitch.StreamProfile]) error,
|
||||||
saveCfgFunc func(*streamcontrol.AbstractPlatformConfig) error,
|
saveCfgFunc func(*streamcontrol.AbstractPlatformConfig) error,
|
||||||
customOAuthHandler twitch.OAuthHandler,
|
customOAuthHandler twitch.OAuthHandler,
|
||||||
) (
|
) (
|
||||||
@@ -26,9 +27,18 @@ func newTwitch(
|
|||||||
return nil, fmt.Errorf("twitch config was not found")
|
return nil, fmt.Errorf("twitch config was not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hadSetNewUserData := false
|
||||||
|
if platCfg.Config.Channel == "" || platCfg.Config.ClientID == "" || platCfg.Config.ClientSecret == "" {
|
||||||
|
if err := setUserData(ctx, platCfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to set user info: %w", err)
|
||||||
|
}
|
||||||
|
hadSetNewUserData = true
|
||||||
|
}
|
||||||
|
|
||||||
logger.Debugf(ctx, "twitch config: %#+v", platCfg)
|
logger.Debugf(ctx, "twitch config: %#+v", platCfg)
|
||||||
platCfg.Config.CustomOAuthHandler = customOAuthHandler
|
platCfg.Config.CustomOAuthHandler = customOAuthHandler
|
||||||
return twitch.New(ctx, *platCfg,
|
cfg = streamcontrol.ToAbstractPlatformConfig(ctx, platCfg)
|
||||||
|
twitch, err := twitch.New(ctx, *platCfg,
|
||||||
func(c twitch.Config) error {
|
func(c twitch.Config) error {
|
||||||
return saveCfgFunc(&streamcontrol.AbstractPlatformConfig{
|
return saveCfgFunc(&streamcontrol.AbstractPlatformConfig{
|
||||||
Config: c.Config,
|
Config: c.Config,
|
||||||
@@ -36,11 +46,22 @@ func newTwitch(
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize Twitch client: %w", err)
|
||||||
|
}
|
||||||
|
if hadSetNewUserData {
|
||||||
|
logger.Debugf(ctx, "confirmed new twitch user data, saving it")
|
||||||
|
if err := saveCfgFunc(cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to save the configuration: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return twitch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newYouTube(
|
func newYouTube(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
cfg *streamcontrol.AbstractPlatformConfig,
|
cfg *streamcontrol.AbstractPlatformConfig,
|
||||||
|
setUserData func(context.Context, *streamcontrol.PlatformConfig[youtube.PlatformSpecificConfig, youtube.StreamProfile]) error,
|
||||||
saveCfgFunc func(*streamcontrol.AbstractPlatformConfig) error,
|
saveCfgFunc func(*streamcontrol.AbstractPlatformConfig) error,
|
||||||
customOAuthHandler youtube.OAuthHandler,
|
customOAuthHandler youtube.OAuthHandler,
|
||||||
) (
|
) (
|
||||||
@@ -54,9 +75,18 @@ func newYouTube(
|
|||||||
return nil, fmt.Errorf("youtube config was not found")
|
return nil, fmt.Errorf("youtube config was not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hadSetNewUserData := false
|
||||||
|
if platCfg.Config.ClientID == "" || platCfg.Config.ClientSecret == "" {
|
||||||
|
if err := setUserData(ctx, platCfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to set user info: %w", err)
|
||||||
|
}
|
||||||
|
hadSetNewUserData = true
|
||||||
|
}
|
||||||
|
|
||||||
logger.Debugf(ctx, "youtube config: %#+v", platCfg)
|
logger.Debugf(ctx, "youtube config: %#+v", platCfg)
|
||||||
platCfg.Config.CustomOAuthHandler = customOAuthHandler
|
platCfg.Config.CustomOAuthHandler = customOAuthHandler
|
||||||
return youtube.New(ctx, *platCfg,
|
cfg = streamcontrol.ToAbstractPlatformConfig(ctx, platCfg)
|
||||||
|
yt, err := youtube.New(ctx, *platCfg,
|
||||||
func(c youtube.Config) error {
|
func(c youtube.Config) error {
|
||||||
return saveCfgFunc(&streamcontrol.AbstractPlatformConfig{
|
return saveCfgFunc(&streamcontrol.AbstractPlatformConfig{
|
||||||
Config: c.Config,
|
Config: c.Config,
|
||||||
@@ -64,4 +94,14 @@ func newYouTube(
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to initialize YouTube client: %w", err)
|
||||||
|
}
|
||||||
|
if hadSetNewUserData {
|
||||||
|
logger.Debugf(ctx, "confirmed new youtube user data, saving it")
|
||||||
|
if err := saveCfgFunc(cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to save the configuration: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return yt, nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user