diff --git a/.old/.gitignore b/.old/.gitignore deleted file mode 100644 index 2cf0a69..0000000 --- a/.old/.gitignore +++ /dev/null @@ -1 +0,0 @@ -./video \ No newline at end of file diff --git a/.old/DEV.md b/.old/DEV.md deleted file mode 100644 index 5da6fb2..0000000 --- a/.old/DEV.md +++ /dev/null @@ -1,7 +0,0 @@ -GOOS=windows GOARCH=amd64 go build -o bin/windows/chatubrate-dvr.exe && -GOOS=darwin GOARCH=amd64 go build -o bin/darwin/chatubrate-dvr && -GOOS=linux GOARCH=amd64 go build -o bin/linux/chatubrate-dvr - -GOOS=windows GOARCH=arm64 go build -o bin/arm64/windows/chatubrate-dvr.exe && -GOOS=darwin GOARCH=arm64 go build -o bin/arm64/darwin/chatubrate-dvr && -GOOS=linux GOARCH=arm64 go build -o bin/arm64/linux/chatubrate-dvr diff --git a/.old/Dockerfile b/.old/Dockerfile deleted file mode 100644 index e6dee2b..0000000 --- a/.old/Dockerfile +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:latest - -WORKDIR /usr/src/app - -COPY go.mod go.sum ./ -RUN go mod download && go mod verify - -COPY . . -RUN go build - -CMD [ "sh", "-c", "./chaturbate-dvr -u $USERNAME" ] \ No newline at end of file diff --git a/.old/LICENSE b/.old/LICENSE deleted file mode 100644 index b6a3517..0000000 --- a/.old/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2022 TeaCat - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/.old/README-tw.md b/.old/README-tw.md deleted file mode 100644 index b260b5d..0000000 --- a/.old/README-tw.md +++ /dev/null @@ -1,90 +0,0 @@ -# Chaturbate 重新錄播 (Alpha) - -這個程式能夠監聽指定的 Chaturbate 頻道,並且在該頻道開始直播時自動儲存影片至本機。這樣你就不會錯過任何精彩的事情 - -**警告**:在 Chaturbate 上的直播內容都有版權,你不應該複製、分享、散播這些內容。(想閱讀更多,請參閱 [DMCA](https://www.dmca.com/)) - -**免責聲明**:因為這還在早期開發階段,錄播內容可能會有幀數遺失(3 小時直播遺失 20 秒內容),這仍然需要測試。 - -## 使用方式 - -這個程式能夠在 64 位元的 macOS、Linux、Windows(懶得編譯 32 位元的版本)上正常運作。你只需要進入 `/bin` 資料夾找到對應你的系統,然後在終端機執行該檔案即可。 - -```bash -$ chaturbate-dvr -u 好棒棒頻道名稱 - - .o88b. db db .d8b. d888888b db db d8888b. d8888b. .d8b. d888888b d88888b -d8P Y8 88 88 d8' `8b `~~88~~' 88 88 88 `8D 88 `8D d8' `8b `~~88~~' 88' -8P 88ooo88 88ooo88 88 88 88 88oobY' 88oooY' 88ooo88 88 88ooooo -8b 88~~~88 88~~~88 88 88 88 88`8b 88~~~b. 88~~~88 88 88~~~~~ -Y8b d8 88 88 88 88 88 88b d88 88 `88. 88 8D 88 88 88 88. - `Y88P' YP YP YP YP YP ~Y8888P' 88 YD Y8888P' YP YP YP Y88888P -d8888b. db db d8888b. -88 `8D 88 88 88 `8D -88 88 Y8 8P 88oobY' -88 88 `8b d8' 88`8b -88 .8D `8bd8' 88 `88. -Y8888D' YP 88 YD ---- -2020/02/13 18:05:22 好棒棒頻道名稱 is online! fetching... -2020/02/13 18:05:24 the video will be saved as "2020-02-13_22-16-27.ts". -2020/02/13 18:05:28 fetching media_w402018999_b5128000_t64RlBTOjI5Ljk3_9134.ts (size: 936428) -2020/02/13 19:07:06 failed to fetch the video segments, will try again. (1/2) -2020/02/13 19:07:06 failed to fetch the video segments, will try again. (2/2) -2020/02/13 19:07:11 failed to fetch the video segments after retried, 好棒棒頻道名稱 might went offline. -2020/02/13 19:07:11 好棒棒頻道名稱 is not online, check again after 3 minute(s)... -``` - -## 說明 - -```bash -NAME: - chaturbate-dvr - watching a specified chaturbate channel and auto saves the stream as local file - -USAGE: - main [global options] command [command options] [arguments...] - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --username value, -u value channel username to watching - --interval value, -i value minutes to check if a channel goes online or not (default: 1) - --strip value, -s value MB sizes to split the video into chunks (default: 0) - --resolution 240, -r 240 Video resolution, could be 240, `480`, `540`, `720`, `1080` (default: "1080") - --resolution-fallback up, --rf up Looking for larger or smaller resolution (up for larger, `down` for smaller) if a specified resolution was not found (default: "down") - --fps value, -f value Preferred framerate, only works if streaming source supports it, otherwise it will always be 30 FPS (default: "60") - --help, -h show help (default: false) - --version, -v print the version (default: false) -``` - -## 中文對應 - -``` -XXX is online! fetching... -XXX 正在線上!開始撈取實況內容… - -the video will be saved as "XXX". -影片將會被保存為「XXX」。 - -fetching XXX.ts (size: XXX) -正在擷取 XXX.ts 片段(大小:XXX) - -failed to fetch the video segments, will try again. (1/2) -無法取得影片段落,稍後會重新嘗試。(1/2) - -failed to fetch the video segments after retried, XXX might went offline. -無法取得影片段落,XXX 可能已經結束直播了。 - -cannot find segment XXX, will try again. (1/5) -無法找到影片段落,燒後會重新嘗試。(1/5) - -inserting XXX segment to the master file. (total: XXX) -正在插入片段 XXX 至主要影片檔案。(總共:XXX) - -skipped XXX due to the empty body! -跳過 XXX 片段因為其為空白內容! - -exceeded the specified stripping limit, creating new video file. (file: XXX) -達到影片分割上限,建立新的影片檔案(檔名:XXX) -``` diff --git a/.old/README.md b/.old/README.md deleted file mode 100644 index 228b98b..0000000 --- a/.old/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Chaturbate DVR (Alpha) - -[[正體中文翻譯點此]](README-tw.md) - -The program watches a specified Chaturbate channel and save the stream in real-time when the channel goes online, so you won't miss anything. - -**Warning**: The streaming content on Chaturbate is copyrighted, you should not copy, share, distribute the content. (for more information, check [DMCA](https://www.dmca.com/)) - -**Disclaimer**: Due to early development, might have frames dropped (20s gone in a 3hrs long stream), still requires more tests. - -## Usage - -The program works for 64-bit macOS, Linux, Windows (too lazy to compile for 32-bit). Just get in the `/bin` folder and find your operating system then execute the program in terminal. - -```bash -$ chaturbate-dvr -u my_lovely_channel_name - - .o88b. db db .d8b. d888888b db db d8888b. d8888b. .d8b. d888888b d88888b -d8P Y8 88 88 d8' `8b `~~88~~' 88 88 88 `8D 88 `8D d8' `8b `~~88~~' 88' -8P 88ooo88 88ooo88 88 88 88 88oobY' 88oooY' 88ooo88 88 88ooooo -8b 88~~~88 88~~~88 88 88 88 88`8b 88~~~b. 88~~~88 88 88~~~~~ -Y8b d8 88 88 88 88 88 88b d88 88 `88. 88 8D 88 88 88 88. - `Y88P' YP YP YP YP YP ~Y8888P' 88 YD Y8888P' YP YP YP Y88888P -d8888b. db db d8888b. -88 `8D 88 88 88 `8D -88 88 Y8 8P 88oobY' -88 88 `8b d8' 88`8b -88 .8D `8bd8' 88 `88. -Y8888D' YP 88 YD ---- -2020/02/13 18:05:22 my_lovely_channel_name is online! fetching... -2020/02/13 18:05:24 the video will be saved as "2020-02-13_22-16-27.ts". -2020/02/13 18:05:28 fetching media_w402018999_b5128000_t64RlBTOjI5Ljk3_9134.ts (size: 936428) -2020/02/13 19:07:06 failed to fetch the video segments, will try again. (1/2) -2020/02/13 19:07:06 failed to fetch the video segments, will try again. (2/2) -2020/02/13 19:07:11 failed to fetch the video segments after retried, my_lovely_channel_name might went offline. -2020/02/13 19:07:11 my_lovely_channel_name is not online, check again after 3 minute(s)... -``` - -## Help - -```bash -NAME: - chaturbate-dvr - watching a specified chaturbate channel and auto saves the stream as local file - -USAGE: - main [global options] command [command options] [arguments...] - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS: - --username value, -u value channel username to watching - --interval value, -i value minutes to check if a channel goes online or not (default: 1) - --strip value, -s value MB sizes to split the video into chunks (default: 0) - --resolution 240, -r 240 Video resolution, could be 240, `480`, `540`, `720`, `1080` (default: "1080") - --resolution-fallback up, --rf up Looking for larger or smaller resolution (up for larger, `down` for smaller) if a specified resolution was not found (default: "down") - --fps value, -f value Preferred framerate, only works if streaming source supports it, otherwise it will always be 30 FPS (default: "60") - --help, -h show help (default: false) - --version, -v print the version (default: false) -``` diff --git a/.old/bin/arm64/darwin/chatubrate-dvr b/.old/bin/arm64/darwin/chatubrate-dvr deleted file mode 100644 index 7db040f..0000000 Binary files a/.old/bin/arm64/darwin/chatubrate-dvr and /dev/null differ diff --git a/.old/bin/arm64/linux/chatubrate-dvr b/.old/bin/arm64/linux/chatubrate-dvr deleted file mode 100644 index 23f7a62..0000000 Binary files a/.old/bin/arm64/linux/chatubrate-dvr and /dev/null differ diff --git a/.old/bin/arm64/windows/chatubrate-dvr.exe b/.old/bin/arm64/windows/chatubrate-dvr.exe deleted file mode 100644 index 4c0dde6..0000000 Binary files a/.old/bin/arm64/windows/chatubrate-dvr.exe and /dev/null differ diff --git a/.old/bin/darwin/chatubrate-dvr b/.old/bin/darwin/chatubrate-dvr deleted file mode 100644 index 6911638..0000000 Binary files a/.old/bin/darwin/chatubrate-dvr and /dev/null differ diff --git a/.old/bin/linux/chatubrate-dvr b/.old/bin/linux/chatubrate-dvr deleted file mode 100644 index af00050..0000000 Binary files a/.old/bin/linux/chatubrate-dvr and /dev/null differ diff --git a/.old/bin/windows/chatubrate-dvr.exe b/.old/bin/windows/chatubrate-dvr.exe deleted file mode 100644 index 5769c0b..0000000 Binary files a/.old/bin/windows/chatubrate-dvr.exe and /dev/null differ diff --git a/.old/chaturbate-dvr b/.old/chaturbate-dvr deleted file mode 100644 index 4619eec..0000000 Binary files a/.old/chaturbate-dvr and /dev/null differ diff --git a/.old/docker-compose.yml b/.old/docker-compose.yml deleted file mode 100644 index 721b562..0000000 --- a/.old/docker-compose.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: "3.0" - -services: - chaturbate-dvr: - build: . - environment: - - USERNAME=my_lovely_channel_name - volumes: - - ./video/my_lovely_channel_name:/usr/src/app/video \ No newline at end of file diff --git a/.old/go.mod b/.old/go.mod deleted file mode 100644 index 331c3a7..0000000 --- a/.old/go.mod +++ /dev/null @@ -1,23 +0,0 @@ -module github.com/YamiOdymel/chaturbate-dvr - -go 1.19 - -require ( - github.com/TwiN/go-color v1.1.0 - github.com/grafov/m3u8 v0.11.1 - github.com/parnurzeal/gorequest v0.2.16 - github.com/urfave/cli/v2 v2.3.0 -) - -require ( - github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect - github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/russross/blackfriday/v2 v2.0.1 // indirect - github.com/samber/lo v1.38.1 // indirect - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - github.com/smartystreets/goconvey v1.7.2 // indirect - golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 // indirect - moul.io/http2curl v1.0.0 // indirect -) diff --git a/.old/go.sum b/.old/go.sum deleted file mode 100644 index 1d0535b..0000000 --- a/.old/go.sum +++ /dev/null @@ -1,46 +0,0 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/TwiN/go-color v1.1.0 h1:yhLAHgjp2iAxmNjDiVb6Z073NE65yoaPlcki1Q22yyQ= -github.com/TwiN/go-color v1.1.0/go.mod h1:aKVf4e1mD4ai2FtPifkDPP5iyoCwiK08YGzGwerjKo0= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4 h1:lS3P5Nw3oPO05Lk2gFiYUOL3QPaH+fRoI1wFOc4G1UY= -github.com/elazarl/goproxy v0.0.0-20210801061803-8e322dfb79c4/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/grafov/m3u8 v0.11.1 h1:igZ7EBIB2IAsPPazKwRKdbhxcoBKO3lO1UY57PZDeNA= -github.com/grafov/m3u8 v0.11.1/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= -github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/parnurzeal/gorequest v0.2.16 h1:T/5x+/4BT+nj+3eSknXmCTnEVGSzFzPGdpqmUVVZXHQ= -github.com/parnurzeal/gorequest v0.2.16/go.mod h1:3Kh2QUMJoqw3icWAecsyzkpY7UzRfDhbRdTjtNwNiUE= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= -github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= -github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= -github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= -github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= -github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20211109214657-ef0fda0de508 h1:v3NKo+t/Kc3EASxaKZ82lwK6mCf4ZeObQBduYFZHo7c= -golang.org/x/net v0.0.0-20211109214657-ef0fda0de508/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -moul.io/http2curl v1.0.0 h1:6XwpyZOYsgZJrU8exnG87ncVkU1FVCcTRpwzOkTDUi8= -moul.io/http2curl v1.0.0/go.mod h1:f6cULg+e4Md/oW1cYmwW4IWQOVl2lGbmCNGOHvzX2kE= diff --git a/.old/main.go b/.old/main.go deleted file mode 100644 index 9052e5c..0000000 --- a/.old/main.go +++ /dev/null @@ -1,510 +0,0 @@ -package main - -import ( - "crypto/tls" - "encoding/json" - "errors" - "fmt" - "log" - "net/http" - "os" - "regexp" - "strconv" - "strings" - "sync" - "time" - - "github.com/TwiN/go-color" - "github.com/samber/lo" - - "github.com/grafov/m3u8" - "github.com/parnurzeal/gorequest" - "github.com/urfave/cli/v2" -) - -// chaturbateURL is the base url of the website. -const chaturbateURL = "https://chaturbate.com/" - -// retriesAfterOnlined tells the retries for stream when disconnected but not really offlined. -var retriesAfterOnlined = 0 - -// temp stores the used segment to prevent fetched the duplicates. -var temp []string - -// segmentIndex is current stored segment index. -var segmentIndex int - -// segmentMap is the map stores temporary video segments, it will be merged into master video file then got deleted. -var segmentMap map[string][]byte = make(map[string][]byte) - -var segmentMapLock sync.Mutex - -// stripLimit reprsents the maximum Bytes sizes to split the video into chunks. -var stripLimit int - -// stripQuota represents how many Bytes left til the next video chunk stripping. -var stripQuota int - -// preferredFPS represents the preferred framerate. -var preferredFPS string - -// preferredResolution represents the preferred resolution, e.g. `240`, `480`, `540`, `720`, `1080`. -var preferredResolution string - -// preferredResolutionFallback represents the preferred resolution fallback, `up`, `down` or `no`. -var preferredResolutionFallback string - -// path save video -const savePath = "video" - -// error/message handler -var ( - errInternal = errors.New("err") - errNoUsername = errors.New("recording: channel username required `-u [USERNAME]` option") - errSegRetFail = color.Colorize(color.Red, ("[FAILED] to fetch the video segments after retried, %s might went offline or is in ticket/privat show.")) - errSegRetFailOnline = color.Colorize(color.Red, ("[FAILED] to fetch the video segments, will try again. [%d/10]")) - infoIsOnline = color.Colorize(color.Green, ("[RECORDING] %s is online! start fetching..")) - infoBackOnline = color.Colorize(color.Green, ("[INFO] %s is back online!")) - infoMergeSegment = color.Colorize(color.Green, ("[INFO] inserting %d segment to the master file. [total: %d]")) - infoSkipped = color.Colorize(color.Blue, ("[INFO] skipped %s due to the empty body!\n")) - infoNotOnline = color.Colorize(color.Gray, ("[INFO] %s is not online, check again in %d minute(s)")) - warningSegment = color.Colorize(color.Yellow, ("[WARNING] cannot find segment %d, will try again. [%d/5]")) -) - -// roomDossier is the struct to parse the HLS source from the content body. -type roomDossier struct { - HLSSource string `json:"hls_source"` -} - -// unescapeUnicode escapes the unicode from the content body. -func unescapeUnicode(raw string) string { - str, err := strconv.Unquote(strings.Replace(strconv.Quote(string(raw)), `\\u`, `\u`, -1)) - if err != nil { - panic(err) - } - return str -} - -// getChannelURL returns the full channel url to the specified user. -func getChannelURL(username string) string { - return fmt.Sprintf("%s%s", chaturbateURL, username) -} - -// getBody gets the channel page content body. -func getBody(username string) string { - resp, body, errs := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(getChannelURL(username)).End() - if len(errs) > 0 { - log.Println(color.Colorize(color.Red, errs[0].Error())) - } - if resp == nil || resp.StatusCode != 200 { - return "" - } - return body -} - -// getOnlineStatus check if the user is currently online by checking the playlist exists in the content body or not. -func getOnlineStatus(username string) bool { - return strings.Contains(getBody(username), "playlist.m3u8") -} - -// getHLSSource extracts the playlist url from the room detail page body. -func getHLSSource(body string) (string, string) { - // Get the room data from the page body. - r := regexp.MustCompile(`window\.initialRoomDossier = "(.*?)"`) - matches := r.FindAllStringSubmatch(body, -1) - - // Extract the data and get the HLS source URL. - var roomData roomDossier - data := unescapeUnicode(matches[0][1]) - err := json.Unmarshal([]byte(data), &roomData) - if err != nil { - panic(err) - } - - return roomData.HLSSource, strings.TrimSuffix(roomData.HLSSource, "playlist.m3u8") -} - -// parseHLSSource parses the HLS table and return the maximum resolution m3u8 source. -func parseHLSSource(url string, baseURL string) string { - resp, body, errs := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(url).End() - if len(errs) > 0 { - log.Println(color.Colorize(color.Red, errs[0].Error())) - } - if resp == nil || resp.StatusCode == 403 { - return "" - } - p, _, _ := m3u8.DecodeFrom(strings.NewReader(body), true) - master, ok := p.(*m3u8.MasterPlaylist) - if !ok { - return "" - } - - resolutions := make(map[string][]string) - resolutionInts := []string{} - - for _, v := range master.Variants { - resStr := strings.Split(v.Resolution, "x") - resolutionInts = append(resolutionInts, resStr[1]) - // If the resolution exists in local, it might be a higher framerate source, store it for later use - if _, ok := resolutions[resStr[1]]; ok { - resolutions[resStr[1]] = append(resolutions[resStr[1]], v.URI) - continue - } - if strings.Contains(v.Name, "FPS:60.0") { - if _, ok := resolutions[resStr[1]]; !ok { - resolutions[resStr[1]] = []string{"", v.URI} // The video has no 30 FPS, we fill it with an empty URI - } else { - resolutions[resStr[1]] = []string{v.URI} - } - } else { - resolutions[resStr[1]] = []string{v.URI} - } - } - - log.Printf("Found available resolutions: %s", strings.TrimPrefix(lo.Reduce(resolutionInts, func(prev string, cur string, _ int) string { - return fmt.Sprintf("%s, %s", prev, cur) - }, ""), ", ")) - - pickedResolution, ok := resolutions[preferredResolution] - if !ok { - var comparison []string - if preferredResolutionFallback == "down" { - comparison = lo.Reverse(lo.Map(resolutionInts, func(v string, _ int) string { return v })) - } else { - comparison = resolutionInts - } - fallbackResolution, ok := lo.Find(comparison, func(v string) bool { - sizeInt, _ := strconv.Atoi(v) - prefInt, _ := strconv.Atoi(preferredResolution) - // - if preferredResolutionFallback == "down" { - return sizeInt < prefInt - } else { - return sizeInt > prefInt - } - }) - if ok { - pickedResolution = resolutions[fallbackResolution] - log.Printf("Preferred video resolution %sp not found, use %sp instead.", preferredResolution, fallbackResolution) - } else { - if preferredResolutionFallback == "down" { - pickedResolution = resolutions[resolutionInts[0]] - log.Printf("No fallback video resolution was found, use worse quality %sp instead.", resolutionInts[0]) - } else { - pickedResolution = resolutions[resolutionInts[len(resolutionInts)-1]] - log.Printf("No fallback video resolution was found, use best quality %sp instead.", resolutionInts[len(resolutionInts)-1]) - } - } - } else { - log.Printf("Fetching video resolution in %sp.", preferredResolution) - } - - var uri string - - if preferredFPS == "60" && len(pickedResolution) > 1 { - log.Printf("Fetching video in 60 FPS.") - uri = pickedResolution[1] - } else { - log.Printf("Fetching video in 30 FPS.") - uri = pickedResolution[0] - - if uri == "" { - log.Printf("The video has no 30 FPS, use 60 FPS instead.") - uri = pickedResolution[1] - } - }1 - - return fmt.Sprintf("%s%s", baseURL, uri) -} - -// parseM3U8Source gets the current segment list, the channel might goes offline if 403 was returned. -func parseM3U8Source(url string) (chunks []*m3u8.MediaSegment, wait float64, err error) { - resp, body, errs := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(url).End() - if len(errs) > 0 { - log.Println(color.Colorize(color.Red, errs[0].Error())) - } - // Retry after 3 seconds if the connection lost or status code returns 403 (the channel might went offline). - if len(errs) > 0 || resp == nil || resp.StatusCode == http.StatusForbidden { - return nil, 3, errInternal - } - - // Decode the segment table. - p, _, err := m3u8.DecodeFrom(strings.NewReader(body), true) - if err != nil { - log.Println(color.Colorize(color.Red, err.Error())) - } - media, ok := p.(*m3u8.MediaPlaylist) - if !ok { - return nil, 3, errInternal - } - wait = media.TargetDuration / 1.5 - - // Ignore the empty segments. - for _, v := range media.Segments { - if v != nil { - chunks = append(chunks, v) - } - } - return -} - -// capture captures the specified channel streaming. -func capture(username string) { - // Define the video filename by current time //04.09.22 added username into filename mK33y. - filename := username + "_" + time.Now().Format("2006-01-02_15-04-05") - var m3u8Source, baseURL, hlsSource string - var tried int - for { - tried++ - // - if tried > 10 { - panic(errors.New("cannot fetch the Playlist correctly after 10 tries")) - } - // Get the channel page content body. - body := getBody(username) - // - if body == "" { - continue - } - // Get the master playlist URL from extracting the channel body. - hlsSource, baseURL = getHLSSource(body) - // Get the best resolution m3u8 by parsing the HLS source table. - m3u8Source = parseHLSSource(hlsSource, baseURL) - // - if m3u8Source != "" { - break - } - <-time.After(time.Millisecond * 500) - } - // Create the master video file. - masterFile, err := os.OpenFile("./"+savePath+"/"+filename+".ts", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) - if err != nil { - log.Println(color.Colorize(color.Red, err.Error())) - } - // - log.Printf("the video will be saved as \"./"+savePath+"/%s\".", filename+".ts") - - go combineSegment(masterFile, filename) - watchStream(m3u8Source, username, masterFile, filename, baseURL) -} - -// watchStream watches the stream and ends if the channel went offline. -func watchStream(m3u8Source string, username string, masterFile *os.File, filename string, baseURL string) { - // Keep fetching the stream chunks until the playlist cannot be accessed after retried x times. - for { - // Get the chunks. - chunks, wait, err := parseM3U8Source(m3u8Source) - // Exit the fetching loop if the channel went offline. - if err != nil { - if retriesAfterOnlined > 10 { - log.Printf(errSegRetFail, username) - break - } else { - log.Printf(errSegRetFailOnline, retriesAfterOnlined) - retriesAfterOnlined++ - // Wait to fetch the next playlist. - <-time.After(time.Duration(wait*1000) * time.Millisecond) - continue - } - } - if retriesAfterOnlined != 0 { - log.Printf(infoBackOnline, username) - retriesAfterOnlined = 0 - } - for _, v := range chunks { - // Ignore the duplicated chunks. - if isDuplicateSegment(v.URI) { - continue - } - segmentIndex++ - go fetchSegment(masterFile, v, baseURL, filename, segmentIndex) - } - <-time.After(time.Duration(wait*1000) * time.Millisecond) - } -} - -// isDuplicateSegment returns true if the segment is already been fetched. -func isDuplicateSegment(URI string) bool { - for _, v := range temp { - if URI[len(URI)-10:] == v { - return true - } - } - temp = append(temp, URI[len(URI)-10:]) - return false -} - -// combineSegment combines the segments to the master video file in the background. -// fixed segment problems mK33y. -// still needs some attention here -func combineSegment(master *os.File, filename string) { - index := 1 - stripIndex := 1 - var retry int - <-time.After(4 * time.Second) - - for { - <-time.After(300 * time.Millisecond) - - if index >= segmentIndex { - <-time.After(1 * time.Second) - continue - } - - if _, ok := segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)]; !ok { - if retry >= 5 { - index++ - retry = 0 - continue - } - if retry != 0 { - log.Printf(warningSegment, index, retry) - } - retry++ - <-time.After(time.Duration(1*retry) * time.Second) - continue - } - if retry != 0 { - retry = 0 - } - // - b := segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)] - // - var err error - if stripLimit != 0 && stripQuota <= 0 { - newMasterFilename := "./" + savePath + "/" + filename + "_" + strconv.Itoa(stripIndex) + ".ts" - master, err = os.OpenFile(newMasterFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) - if err != nil { - log.Println(color.Colorize(color.Red, err.Error())) - } - log.Printf("exceeded the specified stripping limit, creating new video file. (file: %s)", newMasterFilename) - stripQuota = stripLimit - stripIndex++ - } - master.Write(b) - // - log.Printf(infoMergeSegment, index, segmentIndex) - segmentMapLock.Lock() - delete(segmentMap, fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)) - segmentMapLock.Unlock() - index++ - } -} - -// fetchSegment fetches the segment and append to the master file. -func fetchSegment(master *os.File, segment *m3u8.MediaSegment, baseURL string, filename string, index int) { - _, body, _ := gorequest.New().TLSClientConfig(&tls.Config{InsecureSkipVerify: true}).Get(fmt.Sprintf("%s%s", baseURL, segment.URI)).EndBytes() - log.Printf("fetching %s (size: %d)\n", segment.URI, len(body)) - if len(body) == 0 { - log.Printf(infoSkipped, segment.URI) - return - } - stripQuota -= len(body) - segmentMapLock.Lock() - segmentMap[fmt.Sprintf("./%s/%s~%d.ts", savePath, filename, index)] = body - segmentMapLock.Unlock() -} - -// endpoint implements the application main function endpoint. -func endpoint(c *cli.Context) error { - if c.String("username") == "" { - log.Fatal(errNoUsername) - } - // Converts `strip` from MiB to Bytes - stripLimit = c.Int("strip") * 1024 * 1024 - stripQuota = c.Int("strip") * 1024 * 1024 - // - preferredFPS = c.String("fps") - preferredResolution = c.String("resolution") - preferredResolutionFallback = c.String("resolution-fallback") - // - - fmt.Println(" .o88b. db db .d8b. d888888b db db d8888b. d8888b. .d8b. d888888b d88888b") - fmt.Println("d8P Y8 88 88 d8' `8b `~~88~~' 88 88 88 `8D 88 `8D d8' `8b `~~88~~' 88'") - fmt.Println("8P 88ooo88 88ooo88 88 88 88 88oobY' 88oooY' 88ooo88 88 88ooooo") - fmt.Println("8b 88~~~88 88~~~88 88 88 88 88`8b 88~~~b. 88~~~88 88 88~~~~~") - fmt.Println("Y8b d8 88 88 88 88 88 88b d88 88 `88. 88 8D 88 88 88 88.") - fmt.Println(" `Y88P' YP YP YP YP YP ~Y8888P' 88 YD Y8888P' YP YP YP Y88888P") - fmt.Println("d8888b. db db d8888b.") - fmt.Println("88 `8D 88 88 88 `8D") - fmt.Println("88 88 Y8 8P 88oobY'") - fmt.Println("88 88 `8b d8' 88`8b") - fmt.Println("88 .8D `8bd8' 88 `88.") - fmt.Println("Y8888D' YP 88 YD") - fmt.Println("---") - - // Mkdir video folder - if _, err := os.Stat("./" + savePath); os.IsNotExist(err) { - os.Mkdir("./"+savePath, 0777) - } - // - if c.Int("strip") != 0 { - log.Printf("specifying stripping limit as %d MiB(s)", c.Int("strip")) - } - - for { - // Capture the stream if the user is currently online. - if getOnlineStatus(c.String("username")) { - log.Printf(infoIsOnline, c.String("username")) - capture(c.String("username")) - segmentIndex = 0 - temp = []string{} - retriesAfterOnlined = 0 - continue - } - // Otherwise we keep checking the channel status until the user is online. - log.Printf(infoNotOnline, c.String("username"), c.Int("interval")) - <-time.After(time.Minute * time.Duration(c.Int("interval"))) - } -} - -func main() { - app := &cli.App{ - Version: "0.94 Alpha", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "username", - Aliases: []string{"u"}, - Value: "", - Usage: "channel username to watching", - }, - &cli.IntFlag{ - Name: "interval", - Aliases: []string{"i"}, - Value: 1, - Usage: "minutes to check if a channel goes online or not", - }, - &cli.IntFlag{ - Name: "strip", - Aliases: []string{"s"}, - Value: 0, - Usage: "MB sizes to split the video into chunks", - }, - &cli.StringFlag{ - Name: "resolution", - Aliases: []string{"r"}, - Value: "1080", - Usage: "Video resolution, could be `240`, `480`, `540`, `720`, `1080`", - }, - &cli.StringFlag{ - Name: "resolution-fallback", - Aliases: []string{"rf"}, - Value: "down", - Usage: "Looking for larger or smaller resolution (`up` for larger, `down` for smaller) if a specified resolution was not found", - }, - &cli.StringFlag{ - Name: "fps", - Aliases: []string{"f"}, - Value: "60", - Usage: "Preferred framerate, only works if streaming source supports it, otherwise it will always be 30 FPS", - }, - }, - Name: "chaturbate-dvr", - Usage: "watching a specified chaturbate channel and auto saves the stream as local file", - Action: endpoint, - } - err := app.Run(os.Args) - if err != nil { - log.Fatal(err) - } -} diff --git a/Dockerfile b/Dockerfile index 3e61240..b46f476 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,4 +8,4 @@ RUN go mod download && go mod verify COPY . . RUN go build -CMD [ "sh", "-c", "./chaturbate-dvr -u $USERNAME -ui no start" ] \ No newline at end of file +CMD [ "sh", "-c", "./chaturbate-dvr -u $USERNAME start" ] \ No newline at end of file diff --git a/README.md b/README.md index d666eed..04233ee 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,54 @@ # Chaturbate DVR -The program records Chaturbate stream, supports 32-bit/64-bit Windows, macOS, Linux (or ARM), comes with a Web UI. +The program can records **multiple** Chaturbate streams, supports 32-bit/64-bit/ARM macOS, Windows, Linux, can be run on Docker. + +For Chaturbate-**only**, private/ticket stream is **unsupported**. ※ **[DMCA WARNING](https://www.dmca.com/)**: Contents on Chaturbate are copyrighted, you should not copy, share, distribute the content. -## Hello +  -![image](./assets/image_1.png) -![image](./assets/image_2.png) +## Usage + +1. Download **`source code (zip)`** from **[Release](https://github.com/teacat/chaturbate-dvr/releases)** page. +2. Unzip **`/bin`** folder and look up for executable that **fits your system**. + +  + +**🌐 Start the program with the Web UI** + +Visit [`http://localhost:8080`](http://localhost:8080) to use the Web UI. + +```yaml +# Windows (or double-click `chaturbate-dvr.exe` to open) +$ chaturbate-dvr.exe + +# macOS or Linux +$ chaturbate-dvr +``` + +  + +**💻 or... Run as a command-line tool** + +Run the program with a channel name (`-u CHANNEL_USERNAME`) records the channel immediately, and the Web UI will be disabled. + +```yaml +# Windows +$ chaturbate-dvr.exe -u CHANNEL_USERNAME + +# macOS or Linux +$ chaturbate-dvr -u CHANNEL_USERNAME +``` + +  + +## Preview + +![image_1](https://github.com/teacat/chaturbate-dvr/assets/7308718/c6d17ffe-eba7-4296-9315-f501489d85f3) +![image_2](https://github.com/teacat/chaturbate-dvr/assets/7308718/d02923e0-574d-4a15-a373-8b0599101e3f) + +**or... Command-line tool** ``` $ ./chaturbate-dvr -u emillybrowm start @@ -36,30 +77,6 @@ $ ./chaturbate-dvr -u emillybrowm start   -## Usage - -Start the program also enables the Web UI. Visit [`http://localhost:8080`](http://localhost:8080) to use the Web UI to manage channels. - -```yaml -# Windows -$ chaturbate-dvr.exe start - -# macOS or Linux -$ chaturbate-dvr start -``` - -  - -**💻 or... Run as a command-line tool** - -Run the program with a channel name (`-u CHANNEL_USERNAME`) records the channel immediately, and the Web UI will be disabled. - -```yaml -$ chaturbate-dvr -u CHANNEL_USERNAME start -``` - -  - ## Help ```bash @@ -75,7 +92,6 @@ VERSION: 1.0.0 COMMANDS: - start help, h Shows a list of commands or help for one command GLOBAL OPTIONS: @@ -96,25 +112,25 @@ GLOBAL OPTIONS: ```yaml # Records in 720p/60fps -$ chaturbate-dvr -u yamiodymel -f 60 -r 720 start +$ chaturbate-dvr -u yamiodymel -r 720 -f 60 # Split the video every 30 minutes -$ chaturbate-dvr -u yamiodymel -sd 30 start +$ chaturbate-dvr -u yamiodymel -sd 30 # Split the video every 1024 MB -$ chaturbate-dvr -u yamiodymel -sf 1024 start +$ chaturbate-dvr -u yamiodymel -sf 1024 # Change output filename pattern -$ chaturbate-dvr -u yamiodymel -fp video/{{.Username}}/{{.Year}}-{{.Month}}-{{.Day}}_{{.Hour}}-{{.Minute}}-{{.Second}}_{{.Sequence}} start +$ chaturbate-dvr -u yamiodymel -fp video/{{.Username}}/{{.Year}}-{{.Month}}-{{.Day}}_{{.Hour}}-{{.Minute}}-{{.Second}}_{{.Sequence}} ``` -※ If the `-u CHANNEL_NAME` flag was not specified, the settings will be default settings for Web UI to create channels. +※ When runs in Web UI mode, the settings will be default settings for Web UI to create channels.   ## 📺 Framerate & Resolution / Fallback -Fallback indicates what to do when there's no expected target resolution, imagine the situation: +Fallback indicates what to do when there's no expected target resolution, situation: ``` Availables: 1080p, 720p, 240p @@ -160,3 +176,23 @@ Pattern: video/{{.Username}}/{{.Year}}-{{.Month}}-{{.Day}}_{{.Hour}}-{{.Minute}} ``` ※ The file will be saved as `.ts` format and it's not configurable. + +  + +## 💬 Verbose Log + +Change `-log-level` to `DEBUG` to see more details in terminal, like Duration and Size. + +```yaml +# Availables: DEBUG, INFO, WARN, ERROR +$ chaturbate-dvr -u hepbugbear -log-level DEBUG +[2024-01-24 01:18:11] [INFO] [hepbugbear] segment #0 written +[2024-01-24 01:18:11] [DEBUG] [hepbugbear] duration: 00:00:06, size: 0.00 MiB +[2024-01-24 01:18:11] [INFO] [hepbugbear] segment #1 written +[2024-01-24 01:18:11] [DEBUG] [hepbugbear] duration: 00:00:06, size: 1.36 MiB +[2024-01-24 01:18:11] [INFO] [hepbugbear] segment #2 written +[2024-01-24 01:18:11] [DEBUG] [hepbugbear] duration: 00:00:06, size: 2.72 MiB +[2024-01-24 01:18:12] [DEBUG] [hepbugbear] segment #3 fetched +[2024-01-24 01:18:13] [INFO] [hepbugbear] segment #3 written +[2024-01-24 01:18:13] [DEBUG] [hepbugbear] duration: 00:00:10, size: 4.08 MiB +``` diff --git a/README_DEV.md b/README_DEV.md new file mode 100644 index 0000000..0ba47af --- /dev/null +++ b/README_DEV.md @@ -0,0 +1,26 @@ +Compile for 64-bit Windows, macOS, Linux: + +``` +GOOS=windows GOARCH=amd64 go build -o bin/windows/chatubrate-dvr.exe && +GOOS=darwin GOARCH=amd64 go build -o bin/darwin/chatubrate-dvr && +GOOS=linux GOARCH=amd64 go build -o bin/linux/chatubrate-dvr +``` + +Compile for arm64 Windows, macOS, Linux: + +``` +GOOS=windows GOARCH=arm64 go build -o bin/arm64/windows/chatubrate-dvr.exe && +GOOS=darwin GOARCH=arm64 go build -o bin/arm64/darwin/chatubrate-dvr && +GOOS=linux GOARCH=arm64 go build -o bin/arm64/linux/chatubrate-dvr +``` + +or Compile All at once: + +``` +GOOS=windows GOARCH=amd64 go build -o bin/windows/chatubrate-dvr.exe && +GOOS=darwin GOARCH=amd64 go build -o bin/darwin/chatubrate-dvr && +GOOS=linux GOARCH=amd64 go build -o bin/linux/chatubrate-dvr && +GOOS=windows GOARCH=arm64 go build -o bin/arm64/windows/chatubrate-dvr.exe && +GOOS=darwin GOARCH=arm64 go build -o bin/arm64/darwin/chatubrate-dvr && +GOOS=linux GOARCH=arm64 go build -o bin/arm64/linux/chatubrate-dvr +``` diff --git a/assets/image_1.png b/assets/image_1.png deleted file mode 100644 index 22d146e..0000000 Binary files a/assets/image_1.png and /dev/null differ diff --git a/assets/image_2.png b/assets/image_2.png deleted file mode 100644 index aabe675..0000000 Binary files a/assets/image_2.png and /dev/null differ diff --git a/bin/arm64/darwin/chatubrate-dvr b/bin/arm64/darwin/chatubrate-dvr new file mode 100644 index 0000000..bb6e3c6 Binary files /dev/null and b/bin/arm64/darwin/chatubrate-dvr differ diff --git a/bin/arm64/linux/chatubrate-dvr b/bin/arm64/linux/chatubrate-dvr new file mode 100644 index 0000000..1262f19 Binary files /dev/null and b/bin/arm64/linux/chatubrate-dvr differ diff --git a/bin/arm64/windows/chatubrate-dvr.exe b/bin/arm64/windows/chatubrate-dvr.exe new file mode 100644 index 0000000..c1ddf06 Binary files /dev/null and b/bin/arm64/windows/chatubrate-dvr.exe differ diff --git a/bin/darwin/chatubrate-dvr b/bin/darwin/chatubrate-dvr new file mode 100644 index 0000000..71554e8 Binary files /dev/null and b/bin/darwin/chatubrate-dvr differ diff --git a/bin/linux/chatubrate-dvr b/bin/linux/chatubrate-dvr new file mode 100644 index 0000000..801e78c Binary files /dev/null and b/bin/linux/chatubrate-dvr differ diff --git a/bin/windows/chatubrate-dvr.exe b/bin/windows/chatubrate-dvr.exe new file mode 100644 index 0000000..0163f80 Binary files /dev/null and b/bin/windows/chatubrate-dvr.exe differ diff --git a/chaturbate-dvr b/chaturbate-dvr index d21e399..801e78c 100644 Binary files a/chaturbate-dvr and b/chaturbate-dvr differ diff --git a/docker-compose.yml b/docker-compose.yml index bcdc9fb..0ae7cae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,6 @@ services: chaturbate-dvr: build: . environment: - - USERNAME=my_lovely_channel_name + - USERNAME=CHANNEL_USERNAME volumes: - - ./video/my_lovely_channel_name:/usr/src/app/video + - ./videos:/usr/src/app/videos diff --git a/go.mod b/go.mod index 61ac934..2da5932 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/chenzhuoyu/iasm v0.9.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/cors v1.5.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -24,11 +23,13 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.5 // indirect + github.com/kr/pretty v0.3.0 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mattn/go-isatty v0.0.19 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect @@ -40,5 +41,6 @@ require ( golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index a4d2750..1b95e15 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc= github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0= github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= @@ -13,13 +10,12 @@ github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/gin-contrib/cors v1.5.0 h1:DgGKV7DDoOn36DFkNtbHrjoRiT5ExCe+PC9/xp7aKvk= -github.com/gin-contrib/cors v1.5.0/go.mod h1:TvU7MAZ3EwrPLI2ztzTt3tqgvBCq+wn8WpZmfADjupI= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= @@ -30,8 +26,6 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24= github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -47,11 +41,17 @@ github.com/grafov/m3u8 v0.12.0/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg= github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= @@ -61,12 +61,14 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= @@ -80,8 +82,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -92,41 +93,31 @@ github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6S github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y= golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index 08c4322..ef52ef8 100644 --- a/main.go +++ b/main.go @@ -93,12 +93,6 @@ func main() { //}, }, Action: start, - Commands: []*cli.Command{ - { - Name: "start", - Action: start, - }, - }, } if err := app.Run(os.Args); err != nil { log.Fatal(err) @@ -156,5 +150,7 @@ func startWeb(c *cli.Context) error { r.POST("/api/get_settings", handler.NewGetSettingsHandler(c).Handle) r.POST("/api/terminate_program", handler.NewTerminateProgramHandler(c).Handle) + fmt.Printf("👋 Visit http://localhost:%s to use the Web UI\n", c.String("port")) + return r.Run(fmt.Sprintf(":%s", c.String("port"))) }