diff --git a/.gitignore b/.gitignore index 66fd13c..0b5248f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# Binaries for programs and plugins -*.exe +# Binaries +devmode-extender_* *.exe~ *.dll *.so @@ -12,4 +12,7 @@ *.out # Dependency directories (remove the comment below to include it) -# vendor/ +vendor/ + +# IDE files. +.idea diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5ff9e3a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM debian:stable-slim + +RUN apt-get update && \ + apt-get -y --no-install-recommends install make binutils upx-ucl wget gcc-arm-linux-gnueabihf software-properties-common && \ + add-apt-repository "deb http://httpredir.debian.org/debian sid main" && \ + apt-get update && \ + apt-get -t sid install -y --no-install-recommends golang-1.17-go && \ + apt clean && \ + rm -rf /var/lib/apt && \ + rm -rf /var/lib/dpkg + +RUN wget https://github.com/tinygo-org/tinygo/releases/download/v0.21.0/tinygo_0.21.0_amd64.deb \ + && dpkg -i tinygo_0.21.0_amd64.deb \ + && rm tinygo_0.21.0_amd64.deb + +ENV PATH="/usr/lib/go-1.17/bin/:$PATH" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..20e76aa --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +SHELL = /bin/bash -o pipefail +export PATH := $(shell go env GOPATH)/bin:$(PATH) + +ROOT_DIR=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +BIN=$(ROOT_DIR)/devmode-extender + +all: + @docker-compose run --rm app + +build: deps fmt + @echo "> Building" + # TODO: TinyGo has problems with the arm crosscompilation. See this: https://github.com/tinygo-org/tinygo/issues/1906 + #@CGO_ENABLED=0 GOOS=linux GOARCH=arm tinygo build -o $(BIN)_arm . && strip $(BIN)_arm && upx -9 $(BIN)_arm + @CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags="-s -w" -o $(BIN)_arm . && upx -9 $(BIN)_arm + @echo $(BIN)_arm + # TODO: TinyGo has problems with the arm crosscompilation. See this: https://github.com/tinygo-org/tinygo/issues/1906 + #@CGO_ENABLED=0 GOOS=linux GOARCH=arm64 tinygo build -o $(BIN)_arm64 . && strip $(BIN)_arm64 && upx -9 $(BIN)_arm64 + @CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="-s -w" -o $(BIN)_arm64 . && upx -9 $(BIN)_arm64 + @echo $(BIN)_arm64 + @CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags="-s -w" -o $(BIN)_386.exe . && upx -9 $(BIN)_386.exe + @echo $(BIN)_386.exe + @CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w" -o $(BIN)_amd64.exe . && upx -9 $(BIN)_amd64.exe + @echo $(BIN)_amd64.exe + +fmt: + @echo "> gofmt" + @gofmt -l -s -w `go list -f '{{.Dir}}' ${ROOT_DIR}/... | grep -v /vendor/` + +deps: + @echo "> Dependencies" + @go mod tidy + @go mod vendor diff --git a/README.md b/README.md index ba70a60..f311fdb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,12 @@ -# lg-webos-devmode-timer-extender +# LG WebOS Developer Mode Extender This app can extend developer mode timer for the LG TV (you'll need the session token for that) + +You can obtain the developer mode session token by logging into your TV and executing this command: + +```shell +cat /var/luna/preferences/devmode_enabled +``` + +## Building + +Run `make all`. You will need Docker for that. diff --git a/api/client.go b/api/client.go new file mode 100644 index 0000000..4047829 --- /dev/null +++ b/api/client.go @@ -0,0 +1,63 @@ +package api + +import ( + "encoding/json" + "fmt" + "net/http" + "time" +) + +const BaseURL = "https://developer.lge.com/secure" + +type Client struct { + client *http.Client + token string +} + +func New(token string) *Client { + return &Client{ + client: &http.Client{Timeout: time.Second * 10}, + token: token, + } +} + +func (c *Client) call(method string, target interface{}) (err error) { + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/%s.dev?sessionToken=%s", BaseURL, method, c.token), nil) + if err != nil { + return err + } + + res, err := c.client.Do(req) + if err != nil { + return err + } + + defer res.Body.Close() + + err = json.NewDecoder(res.Body).Decode(target) + return +} + +func (c *Client) Query() (timeLeft string, err error) { + var res Response + if err = c.call("CheckDevModeSession", &res); err != nil { + return + } + if res.Failed() { + err = res.Error() + return + } + return res.ErrorMsg, nil +} + +func (c *Client) Extend() (err error) { + var res Response + if err = c.call("ResetDevModeSession", &res); err != nil { + return + } + if res.Failed() || !res.ResetIsSuccessful() { + err = res.Error() + return + } + return +} diff --git a/api/response.go b/api/response.go new file mode 100644 index 0000000..f232a13 --- /dev/null +++ b/api/response.go @@ -0,0 +1,31 @@ +package api + +import ( + "errors" + "fmt" +) + +type Response struct { + Result string `json:"result,omitempty"` + ErrorCode string `json:"errorCode,omitempty"` + ErrorMsg string `json:"errorMsg,omitempty"` +} + +func (b Response) Failed() bool { + return b.Result == "fail" +} + +func (b Response) Succeeded() bool { + return b.Result == "success" && b.ErrorCode == "200" +} + +func (b Response) Error() error { + if b.Failed() { + return errors.New(fmt.Sprintf("%s: %s", b.ErrorCode, b.ErrorMsg)) + } + return nil +} + +func (b Response) ResetIsSuccessful() bool { + return b.Succeeded() && b.ErrorMsg == "GNL" +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e436a5c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,11 @@ +version: "3" + +services: + app: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./:/app + working_dir: "/app" + command: "make build" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f1d03d5 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/Neur0toxine/lg-webos-devmode-timer-extender + +go 1.17 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/main.go b/main.go new file mode 100644 index 0000000..b8ad6d0 --- /dev/null +++ b/main.go @@ -0,0 +1,51 @@ +package main + +import ( + "log" + "os" + "strings" + + "github.com/Neur0toxine/lg-webos-devmode-timer-extender/api" +) + +const TokenParam = "--token=" + +func main() { + token := getTokenArg() + if token == "" { + log.Println("Usage:") + log.Printf("$ %s %stoken\n", os.Args[0], TokenParam) + os.Exit(1) + } + + client := api.New(token) + timeLeft, err := client.Query() + if err != nil { + log.Fatalln("Cannot query devmode status:", err) + } + + log.Println("Time left before resetting:", timeLeft) + + if err := client.Extend(); err != nil { + log.Fatalln("Cannot extend devmode timer:", err) + } + + timeLeft, err = client.Query() + if err != nil { + log.Fatalln("Cannot query devmode status again:", err) + } + + log.Println("Time left after resetting:", timeLeft) +} + +func getTokenArg() string { + if len(os.Args) != 2 { + return "" + } + + if !strings.HasPrefix(os.Args[1], TokenParam) { + return "" + } + + return strings.TrimSpace(os.Args[1][len(TokenParam):]) +}