diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index b41ea7c..0000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,15 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: circleci/golang:1 - - working_directory: /go/src/github.com/tellytv/telly - steps: - - checkout - - run: curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh - - run: go get -u github.com/alecthomas/gometalinter - - run: gometalinter --install - - run: dep ensure -vendor-only - - run: go test -v ./... - - run: gometalinter --config=.gometalinter.json ./... diff --git a/.github/workflows/go-2.yml b/.github/workflows/go-2.yml new file mode 100644 index 0000000..e3a2348 --- /dev/null +++ b/.github/workflows/go-2.yml @@ -0,0 +1,117 @@ +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +name: Latest Release + +defaults: + run: + shell: bash + +jobs: + lint: + name: Lint files + runs-on: 'ubuntu-latest' + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: '1.20' + - name: golangci-lint + uses: golangci/golangci-lint-action@v2.5.2 + with: + version: latest + test: + name: Run tests + runs-on: 'ubuntu-latest' + needs: lint + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: '1.20' + - run: go test -v -cover + release: + name: Create Release + runs-on: 'ubuntu-latest' + needs: test + strategy: + matrix: + goosarch: + # - 'aix/ppc64' + # - 'android/386' + # - 'android/amd64' + # - 'android/arm' + # - 'android/arm64' + - 'darwin/amd64' + - 'darwin/arm64' + # - 'dragonfly/amd64' + # - 'freebsd/386' + # - 'freebsd/amd64' + # - 'freebsd/arm' + # - 'freebsd/arm64' + # - 'illumos/amd64' + # - 'ios/amd64' + # - 'ios/arm64' + # - 'js/wasm' + # - 'linux/386' + - 'linux/amd64' + # - 'linux/arm' + - 'linux/arm64' + # - 'linux/mips' + # - 'linux/mips64' + # - 'linux/mips64le' + # - 'linux/mipsle' + # - 'linux/ppc64' + # - 'linux/ppc64le' + # - 'linux/riscv64' + # - 'linux/s390x' + # - 'netbsd/386' + # - 'netbsd/amd64' + # - 'netbsd/arm' + # - 'netbsd/arm64' + # - 'openbsd/386' + # - 'openbsd/amd64' + # - 'openbsd/arm' + # - 'openbsd/arm64' + # - 'openbsd/mips64' + # - 'plan9/386' + # - 'plan9/amd64' + # - 'plan9/arm' + # - 'solaris/amd64' + # - 'windows/386' + - 'windows/amd64' + - 'windows/arm' + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: actions/setup-go@v2 + with: + go-version: '1.20' + - name: Get OS and arch info + run: | + GOOSARCH=${{matrix.goosarch}} + GOOS=${GOOSARCH%/*} + GOARCH=${GOOSARCH#*/} + BINARY_NAME=${{github.repository}}-$GOOS-$GOARCH + echo "BINARY_NAME=$BINARY_NAME" >> $GITHUB_ENV + echo "GOOS=$GOOS" >> $GITHUB_ENV + echo "GOARCH=$GOARCH" >> $GITHUB_ENV + - name: Build + run: | + go build -o "$BINARY_NAME" -v + - name: Release Notes + run: + git log $(git describe HEAD~ --tags --abbrev=0)..HEAD --pretty='format:* %h %s%n * %an <%ae>' --no-merges >> ".github/RELEASE-TEMPLATE.md" + - name: Release with Notes + uses: softprops/action-gh-release@v1 + with: + body_path: ".github/RELEASE-TEMPLATE.md" + draft: true + files: ${{env.BINARY_NAME}} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..396d617 --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,28 @@ +# This workflow will build a golang project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go + +name: Build Telly + +on: + push: + branches: [ "dev" ] + pull_request: + branches: [ "dev" ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: '1.20' + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... diff --git a/.gitignore b/.gitignore index a22990c..4c0126a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,13 @@ telly .DS_Store /.GOPATH /bin -*.xml +/*.xml vendor/ /.build /.release /.tarballs *.tar.gz +telly.config.* +.idea/* +telly.db +a_main-packr.go \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..c601d1c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "frontend"] + path = frontend + url = https://github.com/tellytv/frontend.git diff --git a/.promu.yml b/.promu.yml index e3986e5..ee16bbb 100644 --- a/.promu.yml +++ b/.promu.yml @@ -1,14 +1,25 @@ repository: - path: github.com/tellytv/telly + path: github.com/tellytv/telly build: - flags: -a -tags netgo - ldflags: | - -X {{repoPath}}/vendor/github.com/prometheus/common/version.Version={{.Version}} - -X {{repoPath}}/vendor/github.com/prometheus/common/version.Revision={{.Revision}} - -X {{repoPath}}/vendor/github.com/prometheus/common/version.Branch={{.Branch}} - -X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildUser={{user}}@{{host}} - -X {{repoPath}}/vendor/github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} + flags: -a -tags netgo + ldflags: | + -X github.com/prometheus/common/version.Version={{.Version}} + -X github.com/prometheus/common/version.Revision={{.Revision}} + -X github.com/prometheus/common/version.Branch={{.Branch}} + -X github.com/prometheus/common/version.BuildUser={{user}}@{{host}} + -X github.com/prometheus/common/version.BuildDate={{date "20060102-15:04:05"}} tarball: - files: - - LICENSE - - NOTICE + files: + - LICENSE + - NOTICE +crossbuild: + platforms: + - linux/386 + - linux/amd64 + - linux/arm + - linux/arm64 + - darwin/amd64 + - windows/amd64 + - windows/386 + - freebsd/amd64 + - freebsd/386 diff --git a/Dockerfile b/Dockerfile index fb5a489..7284ee9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,26 +1,6 @@ -FROM golang:alpine as builder - -# Download and install the latest release of dep -ADD https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64 /usr/bin/dep -RUN chmod +x /usr/bin/dep - -# Install git because gin/yaml needs it -RUN apk update && apk upgrade && apk add git - -# Copy the code from the host and compile it -WORKDIR $GOPATH/src/github.com/tellytv/telly -COPY Gopkg.toml Gopkg.lock ./ -RUN dep ensure --vendor-only -COPY . ./ -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix nocgo -o /app . - -# install ca root certificates + listen on 0.0.0.0 + build -RUN apk add --no-cache ca-certificates \ - && find . -type f -print0 | xargs -0 sed -i 's/"listen", "localhost/"listen", "0.0.0.0/g' \ - && CGO_ENABLED=0 GOOS=linux go install -ldflags '-w -s -extldflags "-static"' - FROM scratch -COPY --from=builder /app ./ -COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs/ +COPY .build/linux-amd64/telly ./app EXPOSE 6077 ENTRYPOINT ["./app"] + + diff --git a/Dockerfile.ffmpeg b/Dockerfile.ffmpeg new file mode 100644 index 0000000..ec62afe --- /dev/null +++ b/Dockerfile.ffmpeg @@ -0,0 +1,4 @@ +FROM jrottenberg/ffmpeg:4.0-alpine +COPY --from=tellytv/telly:dev /app /app +EXPOSE 6077 +ENTRYPOINT ["/app"] diff --git a/Gopkg.lock b/Gopkg.lock deleted file mode 100644 index 6544274..0000000 --- a/Gopkg.lock +++ /dev/null @@ -1,227 +0,0 @@ -# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. - - -[[projects]] - branch = "master" - digest = "1:315c5f2f60c76d89b871c73f9bd5fe689cad96597afd50fb9992228ef80bdd34" - name = "github.com/alecthomas/template" - packages = [ - ".", - "parse", - ] - pruneopts = "UT" - revision = "a0175ee3bccc567396460bf5acd36800cb10c49c" - -[[projects]] - branch = "master" - digest = "1:c198fdc381e898e8fb62b8eb62758195091c313ad18e52a3067366e1dda2fb3c" - name = "github.com/alecthomas/units" - packages = ["."] - pruneopts = "UT" - revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a" - -[[projects]] - branch = "master" - digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" - name = "github.com/beorn7/perks" - packages = ["quantile"] - pruneopts = "UT" - revision = "3a771d992973f24aa725d07868b467d1ddfceafb" - -[[projects]] - branch = "master" - digest = "1:36fe9527deed01d2a317617e59304eb2c4ce9f8a24115bcc5c2e37b3aee5bae4" - name = "github.com/gin-contrib/sse" - packages = ["."] - pruneopts = "UT" - revision = "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae" - -[[projects]] - digest = "1:489e108f21464371ebf9cb5c30b1eceb07c6dd772dff073919267493dd9d04ea" - name = "github.com/gin-gonic/gin" - packages = [ - ".", - "binding", - "render", - ] - pruneopts = "UT" - revision = "d459835d2b077e44f7c9b453505ee29881d5d12d" - version = "v1.2" - -[[projects]] - digest = "1:15042ad3498153684d09f393bbaec6b216c8eec6d61f63dff711de7d64ed8861" - name = "github.com/golang/protobuf" - packages = ["proto"] - pruneopts = "UT" - revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" - version = "v1.1.0" - -[[projects]] - branch = "master" - digest = "1:8f57afa9ef1d9205094e9d89b9cb4ecb3123f342c4eb0053d7631181b511e6e4" - name = "github.com/koron/go-ssdp" - packages = ["."] - pruneopts = "UT" - revision = "4a0ed625a78b6858dc8d3a55fb7728968b712122" - -[[projects]] - digest = "1:fa610f9fe6a93f4a75e64c83673dfff9bf1a34bbb21e6102021b6bc7850834a3" - name = "github.com/mattn/go-isatty" - packages = ["."] - pruneopts = "UT" - revision = "57fdcb988a5c543893cc61bce354a6e24ab70022" - -[[projects]] - digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc" - name = "github.com/matttproud/golang_protobuf_extensions" - packages = ["pbutil"] - pruneopts = "UT" - revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" - version = "v1.0.1" - -[[projects]] - branch = "master" - digest = "1:5ab79470a1d0fb19b041a624415612f8236b3c06070161a910562f2b2d064355" - name = "github.com/mitchellh/mapstructure" - packages = ["."] - pruneopts = "UT" - revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac" - -[[projects]] - digest = "1:d14a5f4bfecf017cb780bdde1b6483e5deb87e12c332544d2c430eda58734bcb" - name = "github.com/prometheus/client_golang" - packages = [ - "prometheus", - "prometheus/promhttp", - ] - pruneopts = "UT" - revision = "c5b7fccd204277076155f10851dad72b76a49317" - version = "v0.8.0" - -[[projects]] - branch = "master" - digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4" - name = "github.com/prometheus/client_model" - packages = ["go"] - pruneopts = "UT" - revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f" - -[[projects]] - branch = "master" - digest = "1:9b2b68310a7555601c28980840f4d6966f8ff5443e11f4f78d227dbf73205132" - name = "github.com/prometheus/common" - packages = [ - "expfmt", - "internal/bitbucket.org/ww/goautoneg", - "model", - "version", - ] - pruneopts = "UT" - revision = "c7de2306084e37d54b8be01f3541a8464345e9a5" - -[[projects]] - branch = "master" - digest = "1:8c49953a1414305f2ff5465147ee576dd705487c35b15918fcd4efdc0cb7a290" - name = "github.com/prometheus/procfs" - packages = [ - ".", - "internal/util", - "nfs", - "xfs", - ] - pruneopts = "UT" - revision = "05ee40e3a273f7245e8777337fc7b46e533a9a92" - -[[projects]] - digest = "1:d867dfa6751c8d7a435821ad3b736310c2ed68945d05b50fb9d23aee0540c8cc" - name = "github.com/sirupsen/logrus" - packages = ["."] - pruneopts = "UT" - revision = "3e01752db0189b9157070a0e1668a620f9a85da2" - version = "v1.0.6" - -[[projects]] - digest = "1:c268acaa4a4d94a467980e5e91452eb61c460145765293dc0aed48e5e9919cc6" - name = "github.com/ugorji/go" - packages = ["codec"] - pruneopts = "UT" - revision = "c88ee250d0221a57af388746f5cf03768c21d6e2" - -[[projects]] - branch = "master" - digest = "1:7e4543a28ce437be9d263089699c5fd6cefc0f02a63592f7f85c0c4e21245e0a" - name = "github.com/zsais/go-gin-prometheus" - packages = ["."] - pruneopts = "UT" - revision = "3f93884fa240fd102425d65ce9781e561ba40496" - -[[projects]] - branch = "master" - digest = "1:3f3a05ae0b95893d90b9b3b5afdb79a9b3d96e4e36e099d841ae602e4aca0da8" - name = "golang.org/x/crypto" - packages = ["ssh/terminal"] - pruneopts = "UT" - revision = "de0752318171da717af4ce24d0a2e8626afaeb11" - -[[projects]] - branch = "master" - digest = "1:937d8f64b118c494c48b0cc9c990f2163c7483e6c70b5828f20006d81c61412f" - name = "golang.org/x/net" - packages = [ - "bpf", - "internal/iana", - "internal/socket", - "ipv4", - ] - pruneopts = "UT" - revision = "c39426892332e1bb5ec0a434a079bf82f5d30c54" - -[[projects]] - branch = "master" - digest = "1:a60cae5be8993938498243605b120290533a5208fd5cac81c932afbad3642fb0" - name = "golang.org/x/sys" - packages = [ - "unix", - "windows", - ] - pruneopts = "UT" - revision = "98c5dad5d1a0e8a73845ecc8897d0bd56586511d" - -[[projects]] - digest = "1:c06d9e11d955af78ac3bbb26bd02e01d2f61f689e1a3bce2ef6fb683ef8a7f2d" - name = "gopkg.in/alecthomas/kingpin.v2" - packages = ["."] - pruneopts = "UT" - revision = "947dcec5ba9c011838740e680966fd7087a71d0d" - version = "v2.2.6" - -[[projects]] - digest = "1:1b4724d3c8125f6044925f02b485b74bfec9905cbf579d95aafd1a6c8f8447d3" - name = "gopkg.in/go-playground/validator.v8" - packages = ["."] - pruneopts = "UT" - revision = "5f57d2222ad794d0dffb07e664ea05e2ee07d60c" - version = "v8.18.1" - -[[projects]] - digest = "1:cacb98d52c60c337c2ce95a7af83ba0313a93ce5e73fa9e99a96aff70776b9d3" - name = "gopkg.in/yaml.v2" - packages = ["."] - pruneopts = "UT" - revision = "a5b47d31c556af34a302ce5d659e6fea44d90de0" - -[solve-meta] - analyzer-name = "dep" - analyzer-version = 1 - input-imports = [ - "github.com/gin-gonic/gin", - "github.com/koron/go-ssdp", - "github.com/mitchellh/mapstructure", - "github.com/prometheus/client_golang/prometheus", - "github.com/prometheus/common/version", - "github.com/sirupsen/logrus", - "github.com/zsais/go-gin-prometheus", - "gopkg.in/alecthomas/kingpin.v2", - ] - solver-name = "gps-cdcl" - solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml deleted file mode 100644 index 31ebde3..0000000 --- a/Gopkg.toml +++ /dev/null @@ -1,62 +0,0 @@ -# Gopkg.toml example -# -# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html -# for detailed Gopkg.toml documentation. -# -# required = ["github.com/user/thing/cmd/thing"] -# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] -# -# [[constraint]] -# name = "github.com/user/project" -# version = "1.0.0" -# -# [[constraint]] -# name = "github.com/user/project2" -# branch = "dev" -# source = "github.com/myfork/project2" -# -# [[override]] -# name = "github.com/x/y" -# version = "2.4.0" -# -# [prune] -# non-go = false -# go-tests = true -# unused-packages = true - - -[[constraint]] - name = "github.com/gin-gonic/gin" - version = "1.2.0" - -[[constraint]] - name = "github.com/koron/go-ssdp" - branch = "master" - -[[constraint]] - branch = "master" - name = "github.com/mitchellh/mapstructure" - -[[constraint]] - name = "github.com/prometheus/client_golang" - version = "0.8.0" - -[[constraint]] - branch = "master" - name = "github.com/prometheus/common" - -[[constraint]] - name = "github.com/sirupsen/logrus" - version = "1.0.6" - -[[constraint]] - branch = "master" - name = "github.com/zsais/go-gin-prometheus" - -[[constraint]] - name = "gopkg.in/alecthomas/kingpin.v2" - version = "2.2.6" - -[prune] - go-tests = true - unused-packages = true diff --git a/Makefile b/Makefile index 437f729..885a4b7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ -GO := GO15VENDOREXPERIMENT=1 go +GO := go +GOPATH ?= $(HOME)/go PROMU := $(GOPATH)/bin/promu -pkgs = $(shell $(GO) list ./... | grep -v /vendor/) PREFIX ?= $(shell pwd) BIN_DIR ?= $(shell pwd) @@ -16,15 +16,23 @@ style: test: @echo ">> running tests" - @$(GO) test -short $(pkgs) + @$(GO) test -short ./... format: @echo ">> formatting code" - @$(GO) fmt $(pkgs) + @$(GO) fmt ./... vet: @echo ">> vetting code" - @$(GO) vet $(pkgs) + @$(GO) vet ./... + +cross: promu + @echo ">> crossbuilding binaries" + @$(PROMU) crossbuild + +tarballs: promu cross + @echo ">> creating release tarballs" + @$(PROMU) crossbuild tarballs build: promu @echo ">> building binaries" @@ -32,14 +40,15 @@ build: promu tarball: promu @echo ">> building release tarball" - @$(PROMU) tarball --prefix $(PREFIX) $(BIN_DIR) + @$(PROMU) tarball $(BIN_DIR) -docker: +docker: cross @echo ">> building docker image" @docker build -t "$(DOCKER_IMAGE_NAME):$(DOCKER_IMAGE_TAG)" . promu: - @GOOS=$(shell uname -s | tr A-Z a-z) \ + @GO111MODULE=off \ + GOOS=$(shell uname -s | tr A-Z a-z) \ GOARCH=$(subst x86_64,amd64,$(patsubst i%86,386,$(shell uname -m))) \ $(GO) get -u github.com/prometheus/promu diff --git a/README.md b/README.md index 98de502..fb1a9b5 100644 --- a/README.md +++ b/README.md @@ -2,98 +2,139 @@ IPTV proxy for Plex Live written in Golang -## Please refer to the [Wiki](https://github.com/tellytv/telly/wiki) for the most current documentation. +Please refer to the [Wiki](https://github.com/tellytv/telly/wiki) for the most current documentation. -Seriously, if you have problems the first thing we're going to ask is, "Have you gone through the walkthrough in the wiki?" - -# Setup -## This readme refers to version ![#0eaf29](https://placehold.it/15/0eaf29/000000?text=+) 1.0.x ![#0eaf29](https://placehold.it/15/0eaf29/000000?text=+). It does not apply to versions other than that. - -## Most users should use version 1.1 from the dev branch. - -> **If you are looking for information about the new config-file based ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) PRERELEASE BETA 1.1 ![#f03c15](https://placehold.it/15/f03c15/000000?text=+), go to the [dev branch](https://github.com/tellytv/telly/tree/dev)** - -> **If you are looking for information about the web-based ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) UNSUPPORTED ALPHA 1.5 ![#f03c15](https://placehold.it/15/f03c15/000000?text=+), go to the [telly Discord](https://discord.gg/bnNC8qX); there is no 1.5 documentation on github as yet.** - -> **See end of setup section for an important note about channel filtering** +## This readme refers to version 1.1.x . It does not apply to versions other than that. The [Wiki](https://github.com/tellytv/telly/wiki) includes walkthroughs for most platforms that go into more detail than listed below: -Go to the [Wiki](https://github.com/tellytv/telly/wiki). +## THIS IS A DEVELOPMENT BRANCH + +It is under active development and things may change quickly and dramatically. Please join the discord server if you use this branch and be prepared for some tinkering and breakage. + +# Configuration + +Here's an example configuration file. **You will need to create this file.** It should be placed in `/etc/telly/telly.config.toml` or `$HOME/.telly/telly.config.toml` or `telly.config.toml` in the directory that telly is running from. + +> NOTE "the directory telly is running from" is your CURRENT WORKING DIRECTORY. For example, if telly and its config file file are in `/opt/telly/` and you run telly from your home directory, telly will not find its config file because it will be looking for it in your home directory. If this makes little sense to you, use one of the other two locations OR cd into the directory where telly is located before running it from the command line. + +> ATTENTION Windows users: be sure that there isn’t a hidden extension on the file. Telly can't read its config file if it's named something like `telly.config.toml.txt`. + +```toml +# THIS SECTION IS REQUIRED ######################################################################## +[Discovery] # most likely you won't need to change anything here + Device-Auth = "telly123" # These settings are all related to how telly identifies + Device-ID = "12345678" # itself to Plex. + Device-UUID = "" + Device-Firmware-Name = "hdhomeruntc_atsc" + Device-Firmware-Version = "20150826" + Device-Friendly-Name = "telly" + Device-Manufacturer = "Silicondust" + Device-Model-Number = "HDTC-2US" + SSDP = true + +# Note on running multiple instances of telly +# There are three things that make up a "key" for a given Telly Virtual DVR: +# Device-ID [required], Device-UUID [optional], and port [required] +# When you configure your additional telly instances, change: +# the Device-ID [above] AND +# the Device-UUID [above, if you're entering one] AND +# the port [below in the "Web" section] + +# THIS SECTION IS REQUIRED ######################################################################## +[IPTV] + Streams = 1 # number of simultaneous streams that the telly virtual DVR will provide + # This is often 1, but is set by your iptv provider; for example, + # Vaders provides 5 + Starting-Channel = 10000 # When telly assigns channel numbers it will start here + XMLTV-Channels = true # if true, any channel numbers specified in your M3U file will be used. +# FFMpeg = true # if this is uncommented, streams are buffered through ffmpeg; + # ffmpeg must be installed and on your $PATH + # if you want to use this with Docker, be sure you use the correct docker image +# if you DO NOT WANT TO USE FFMPEG leave this commented; DO NOT SET IT TO FALSE + +# THIS SECTION IS REQUIRED ######################################################################## +[Log] + Level = "info" # Only log messages at or above the given level. [debug, info, warn, error, fatal] + Requests = true # Log HTTP requests made to telly + +# THIS SECTION IS REQUIRED ######################################################################## +[Web] + Base-Address = "0.0.0.0:6077" # Set this to the IP address of the machine telly runs on + Listen-Address = "0.0.0.0:6077" # this can stay as-is + +# THIS SECTION IS NOT USEFUL ====================================================================== +#[SchedulesDirect] # If you have a Schedules Direct account, fill in details and then + # UNCOMMENT THIS SECTION +# Username = "" # This is under construction; no provider +# Password = "" # works with it at this time + +# AT LEAST ONE SOURCE IS REQUIRED ################################################################# +# NONE OF THESE EXAMPLES WORK AS-IS; IF YOU DON'T CHANGE IT, DELETE IT ############################ +[[Source]] + Name = "" # Name is optional and is used mostly for logging purposes + Provider = "Custom" # DO NOT CHANGE THIS IF YOU ARE ENTERING URLS OR FILE PATHS + # "Custom" is telly's internal identifier for this 'Provider' + # If you change it to "NAMEOFPROVIDER" telly's reaction will be + # "I don't recognize a provider called 'NAMEOFPROVIDER'." + M3U = "http://myprovider.com/playlist.m3u" # These can be either URLs or fully-qualified paths. + EPG = "http://myprovider.com/epg.xml" + # THE FOLLOWING KEYS ARE OPTIONAL IN THEORY, REQUIRED IN PRACTICE + Filter = "Sports|Premium Movies|United States.*|USA" + FilterKey = "group-title" # FilterKey normally defaults to whatever the provider file says is best, + # otherwise you must set this. + FilterRaw = false # FilterRaw will run your regex on the entire line instead of just specific keys. + Sort = "group-title" # Sort will alphabetically sort your channels by the M3U key provided +# END TELLY CONFIG ############################################################################### +``` -## Quickstart: +# FFMpeg -Please read through to the end before trying to run telly. With 1.0, you will need at minimum two parameters, a playlist and a filter. +Telly can buffer the streams to Plex through ffmpeg. This has the potential for several benefits, but today it primarily: -0) Go to the [Wiki](https://github.com/tellytv/telly/wiki). +1. Allows support for stream formats that may cause problems for Plex directly. +1. Eliminates the use of redirects and makes it possible for telly to report exactly why a given stream failed. -1) Go to the releases page and download the correct version for your operating system -2) Mark the file as executable for non-windows platforms `chmod a+x ` -3) Rename the file to "telly" if desired; note that from here this readme will refer to "telly"; the file you downloaded is probably called "telly-linux-amd64.dms" or something like that. -**If you do not rename the file, then substitute references here to "telly" with the name of the file you've downloaded.** -**Under Windows, don't forget the `.exe`; i.e. `telly.exe`.** -4) Have the .m3u file on hand from your IPTV provider of choice -**Any command arguments can also be supplied as environment variables, for example --iptv.playlist can also be provided as the TELLY_IPTV_PLAYLIST environment variable** -5) Run `telly` with the `--iptv.playlist` commandline argument pointing to your .m3u file. (This can be a local file or a URL) For example: `./telly --iptv.playlist=/home/github/myiptv.m3u` -6) If you would like multiple streams/tuners use the `--iptv.streams` commandline option. Default is 1. When setting or changing this option, `plexmediaserver` will need to be completely **restarted**. -7) If you would like `telly` to attempt to the filter the m3u a bit, add the `--filter.regex` commandline option. If you would like to use your own regex, run `telly` with `--filter.regex=""`, for example `--filter.regex=".*UK.*"` Regex behavior is by default a blacklist; telly will EXCLUDE channels that match your regex [and if unspecified the filter matches ALL channels]; to reverse this and INCLUDE channels that match your regex, add `--filter.regex-inclusive` to the command line. -8) If `telly` tells you `[telly] [info] listening on ...` - great! Your .m3u file was successfully parsed and `telly` is running. Check below for how to add it into Plex. -9) If `telly` fails to run, check the error. If it's self explanatory, great. If you don't understand, feel free to open an issue and we'll help you out. -10) For your IPTV provider m3u, try using option `type=m3u_plus` and `output=ts`. +To take advantage of this, ffmpeg must be installed and available in your path. -> **Regex handling changed in 1.0. `filter.regex` has become a blacklist which defaults to blocking everything. If you are not using a regex to filter your M3U file, you will need to add at a minimum `--filter.regex-inclusive` to the command line. If you do not add this, telly will by default EXCLUDE everything in your M3U. The symptom here is typically telly seeming to start up just fine but reporting 0 channels.** +# Docker -# Adding it into Plex +There are two different docker images available: -1) Once `telly` is running, you can add it to Plex. **Plex Live requires Plex Pass at the time of writing** -2) Navigate to `app.plex.tv` and make sure you're logged in. Go to Settings -> Server -> Live TV & DVR -3) Click 'Setup' or 'Add'. The Telly virtual DVR should show up automatically. If it doesn't, press the text to add it manually - input `THE_IP_WHERE_TELLY_IS:6077` (or whatever port you're using - you can change it using the `-listen` commandline argument, i.e. `-listen THE_IP_WHERE_TELLY_IS:12345`) -4) Plex will find your device (in some cases it continues to load but the continue button becomes orange, i.e. clickable. Click it) - select the country in the bottom left and ensure Plex has found the channels. Proceed. -5) Once you get to the channel listing, `telly` currently __doesn't__ have any idea of EPG data so it __starts the channel numbers at 10000 to avoid complications__ with selecting channels at this stage. EPG APIs will come in the future, but for now you'll have to manually match up what `telly` is telling Plex to the actual channel numbers. For UK folk, `Sky HD` is the best option I've found. -6) Once you've matched up all the channels, hit next and Plex will start downloading necessary EPG data. -7) Once that is done, you might need to restart Plex so the telly tuner is not marked as dead. -8) You're done! Enjoy using `telly`. :-) +## tellytv/telly:dev +The standard docker image for the dev branch -# Docker - -Go to the [Wiki](https://github.com/tellytv/telly/wiki). +## tellytv/telly:dev-ffmpeg +This docker image has ffmpeg preinstalled. If you want to use the ffmpeg feature, use this image. It may be safest to use this image generally, since it is not much larger than the standard image and allows you to turn the ffmpeg features on and off without requiring changes to your docker run command. The examples below use this image. ## `docker run` ``` docker run -d \ --name='telly' \ --net='bridge' \ - -e TZ="Europe/Amsterdam" \ - -e 'TELLY_IPTV_PLAYLIST'='/home/github/myiptv.m3u' \ - -e TELLY_IPTV_STREAMS=1 \ - -e TELLY_FILTER_REGEX='.*UK.*' \ + -e TZ="America/Chicago" \ -p '6077:6077/tcp' \ - -v '/tmp/telly':'/tmp':'rw' \ - tellytv/telly --web.base-address=localhost:6077 + -v /host/path/to/telly.config.toml:/etc/telly/telly.config.toml \ + --restart unless-stopped \ + tellytv/telly:dev-ffmpeg ``` ## docker-compose ``` telly: - image: tellytv/telly + image: tellytv/telly:dev-ffmpeg ports: - "6077:6077" environment: - TZ=Europe/Amsterdam - - TELLY_IPTV_PLAYLIST=/home/github/myiptv.m3u - - TELLY_FILTER_REGEX='.*UK.*' - - TELLY_WEB_LISTEN_ADDRESS=telly:6077 - - TELLY_IPTV_STREAMS=1 - - TELLY_DISCOVERY_FRIENDLYNAME=Tuner1 - - TELLY_DISCOVERY_DEVICEID=12345678 - command: -base=telly:6077 + volumes: + - /host/path/to/telly.config.toml:/etc/telly/telly.config.toml restart: unless-stopped ``` - # Troubleshooting -Please free to open an issue if you run into any issues at all, I'll be more than happy to help. +Please free to [open an issue](https://github.com/tellytv/telly/issues) if you run into any problems at all, we'll be more than happy to help. # Social diff --git a/VERSION b/VERSION index 21e8796..8ad4501 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.3 +1.1.0.8 diff --git a/a_main-packr.go b/a_main-packr.go new file mode 100644 index 0000000..3d00397 --- /dev/null +++ b/a_main-packr.go @@ -0,0 +1,18 @@ +// Code generated by github.com/gobuffalo/packr. DO NOT EDIT. + +package main + +import "github.com/gobuffalo/packr" + +// You can use the "packr clean" command to clean up this, +// and any other packr generated files. +func init() { + packr.PackJSONBytes("./frontend/dist/telly-fe", "3rdpartylicenses.txt", "\"H4sIAAAAAAAA/+xU3W7bNhi951Mc5KoFVC8Ntq7YVRmLtohJpEDRzXwpS7TFTCYNkW6QPf1A2WmR7RGaK0Hf3/nO+YjT+cl8eAxf7ha/LX4nFddk6U/Pkz0MEe+697i7/fjrh7vbj5+RG2cD6nMY/m4n842Q2kxHG4L1DjZgMJPZPeMwtS6aPsN+MgZ+j25op4PJED1a94yTmYJ38LvYWmfdAS06f3omfo842IDg9/GpnQxa16MNwXe2jaZH77vz0bjYxoS3t6MJeBcHg5vm2nHzfgbpTTsS65ByLyk82Tj4c8RkQpxsl2ZksK4bz33a4SU92qO9IqT2WYVAosc5mGzeM8PR93afvmamdTrvRhuGDL1No3fnaDKEFOyMS12t63/xE4IZR9L5kzUBM9cf2801afVTEjReJQop8jT442smNpD9eXI2DGbu6T2CnxEfTRdTJJXv/Tj6p0St8663iVH4gxA9GLQ7/83MXC5Hdj7a7iL3fIDTj6teU2FoxxE7cxXM9LCOpNALnSnBh9i6aNsRJz/NeP+luSBEFwyNXOkHqhh4g1rJrzxnOW5oA97cZHjgupAbjQeqFBV6C7kCFVv8yUWegf1VK9Y0kIrwqi45yzNwsSw3ORdr3G80hNQoecU1y6ElEuB1FGdNGlYxtSyo0PSel1xvM7LiWqSZK6lAUVOl+XJTUoV6o2rZMFCRQ0jBxUpxsWYVE3oBLiAk2FcmNJqClmWCInSjC6nSfljKeqv4utAoZJkz1eCeoeT0vmQXKLHFsqS8ypDTiq7Z3CV1wRRJZZft8FCwFEp4VIAuNZci0VhKoRVd6gxaKv299YE3LANVvEmCrJSsMpLklKtUwkXqE+wyJUmNVxeRav7fNOz7QOSMllysG3Dx6nwLQv7xziwew5fbxefF3afZO9IDq7hGeXn95P9m8uliJmvvD6PJwF23eHOSNyd5c5Kf10n+DQAA//82zaErgwgAAA==\"") + packr.PackJSONBytes("./frontend/dist/telly-fe", "assets/logo.svg", "\"H4sIAAAAAAAA/yyQT4/jIAzFv4rluxMwkGFWpYedSy97nTui+YOUJhVkSDWffkUayYKnn20eepdcRvApepri/d4vDrf00yPc/ebpmfohvhwOPp8khrWOFIQw+5wd5jJSXOa49ESDh8HTVuq5EwuEtM69w/gYEV6PeckOp217/mnbfd+bXTVrGlsWQrS5jAgl9vvf9eVQgIBOCzCS8Xp5+m2CIc6zw/CTUr9sX+u8JoS7w3/mk0HctP1i2Rioi6fQtigWQQB3jXlDbd81sTaNLIonkp0IJD+aDxCkGKRuVL0V5yrhkKB4UlaH9xgoprNHijOdK+86Hiyk+GY+ORzO9Sd02pO239oGQbVzQDogafv7INmBMnzr9HenJyO5sLW/eL20NYDrpWZ0/R8AAP//F4XLEq8BAAA=\"") + packr.PackJSONBytes("./frontend/dist/telly-fe", "favicon.ico", "\"H4sIAAAAAAAA/+ybT0gcVxzHP/4p1qLt4qEUW90VqrWnSileWtmlx55KDx4KtaUt1UKp5JCboIeQYyDkzyYecsohkEPwFA+CQXIIuQQSBE/GRBOEgAbC6kY3O+HN/pY8htk4Mzu7bxLfF748dpj3vt837+2b9+c30EIbIyMqzXBtEEaBTEZ+p2B9EFKpyu+pdjg9CsPACPAHlesuvsTCwiIcPgK+BXJAtsn8Gmivw/vHQB7YBfYM8ClwAmiL6P9noAg4BrkJfBXR/5+GvTvS9t9F9D8EPDDs/4b046hQr8FTwMWAvABcBV749IPLIcpRPAn01eE9KgaAxx7/S8CHDdTskrqm66Qq4wcZO3T/t6U/9sdQfpfm+wNgHFgG1oGNGLgFlDz+94FHMZS9Ll7HxftfwE4Cxpmw3BHvawnwEpVrPu38LtHPu3pfrEgfSxJXxNtRdVL39QCdCWOPeDvK/7LcnzR0ijfr3wysf7Ow/s3C+jcL698srH+zsP7Nwvo3C+vfLI6L/yfAfMh91mZwXrx5/ZYD1CmpLMtepWkfUam8zybgjCgKi+K9G5gBHgIFQ+d0YVgQrzPiHTnfGwDGDJ2TBmVOPA7UcSZpYfHewHERIKUyarnprE86By3u/aW0o6W3qPzT5nADkWqiQ8lsJD+thWr9qvUN+lyHwY3OyulxWqn42vcYoCPmc5ow74VfgJtUmj0ungc+C6CtesndBs1N/g6gr57V9QZo7wI/BXz+XwC/AVPApA9VPa5oZat5yzTwT437FX+Uc3U/dHniD/qAz4HeGvwU+F9bI9yTWKJa9/dKeVWNfi1WQc1fFiPECzzT6v9S4kLC5F8U7fsG58pK+5Vn7tzoea6+pvBqT2tz3UZwTDT81jV70h6NRla0/PRzTdDPWX2rb/WtvtVP1Ph/AJyRudK/DeKkaBxouocG5x+HEj9rSn9JYl/Pyh56nOuNt3FZNIekT7QaiJtrbUJfTzwcDSVIF4VOKes45ay7/VK9N+04xSj8xHGeV8twt3M0DMuCM6PvU9TzZcrRSMv6TR8XpoTe8cLv+4YJieEvaCzJWmxPu6bm2Oe03aluWdd9A/wK/C5lqfQOsA38J+vOCeH3kkflXZA11pbEe+ssyFiy6bm+KXkW5P++Kt/tXPIwL2dRftdXtfEiH+F5523+2PIvaOcNQZjV2n5b+ul+SJYk71jEd/Ok5K0Llb1N2GiD8uyb69lSber5XgcAAP//wTJ26O46AAA=\"") + packr.PackJSONBytes("./frontend/dist/telly-fe", "index.html", "\"H4sIAAAAAAAA/5RRwXLrIAy85yt4Oj+HaU89gPsT/QEFy7FSGTygOPHfdzBtM9NTe4JdsbvsyP0bUtBtITPpLP3B1cMIxrMHilAJwqE/GONmUjRhwlxIPVx17F5gHyirUP9GIpuzDVT6hIXMlGn0YGeMeCYLD6OIM3lYmW5LygompKgU1cONB538QCsH6nbw33BkZZSuBBTyT81GOL6bTOKBQ4pgagsPPNege9e4Fj/iWuGRQ3pd/XNt9RAX3YTKRKRfzxtzDKVA72zr705p2PZYXJYup6S9s9/Xgysh86Kff1C6q73gio0FU3LwkK9ReabjZbdto/43wiXJNrJI+bt0Ro4/VLY1cbYt/CMAAP//mAHIYQECAAA=\"") + packr.PackJSONBytes("./frontend/dist/telly-fe", "main.js", "\"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\"") + packr.PackJSONBytes("./frontend/dist/telly-fe", "polyfills.js", "\"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\"") + packr.PackJSONBytes("./frontend/dist/telly-fe", "runtime.js", "\"H4sIAAAAAAAA/4xTTW/bMAz9K0kOhgSzgrNjXGL3AUN3F4RCUejGmyoJstSscPzfBzlxsgIdsBv18R7Jx8d1l51JvXcs8nGJV8SIj52P7E3HVYIABkk2CjSS3CrokOQXBRYbGFCq1j4aYcm9pGNr65p7GdBIq1RVDSLk4ci8DEo2ikMJsGkLdVr1bqX50/4nmSRC9Mmn90DiqIenk/sRfaCY3oXR1jINiVcVizIp1DIpPjP0VdUz4u2wJOeDGI59lxhnvI2UcnSrPFcgdAj2nWXozmepODjGp1u3jt2bjUDYtPSYF06q6/urwyxJQcJ1Awa3rXl0yz9T17xZI3rppCmts4TrLW9TVbEshmB7Q4weHmDLIWJgQQzoiiZ8ulYap1ltHCfwODa7ZoJc1L3VGcpU+o4lSYpfQSUW9Dv4mIb2UmK5wrHfEdjdegvXx904TYsmsYBmYd2CBQf3OHBwwpYmb3dTEK8YIQiDCYI44N03QOD4GIQvIT+frxM9UNc7WuY4fxuNd13/kqPeW5prc/mVrqcGXijt3MQnCCLi3778F+Pm+ZmG7/6QLW1gfNM2F54Lg/vAUJQhjFUVxR3z9faDj4syJYnONk27Tx5vAgZxYAQbvQHiQCWd/yDIDfJf7i6AwhFws5lnaPDUu4M/iRPtgza/vg3ehc/uiplBo7l4fN+7AzO8vRyRwKARw2y8y8IU7q6s331fu7rmxIzsFJ9T96jbshxMKt7+CQAA//9/VcMtHQQAAA==\"") + packr.PackJSONBytes("./frontend/dist/telly-fe", "styles.css", "\"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\"") +} diff --git a/frontend b/frontend new file mode 160000 index 0000000..05a69f4 --- /dev/null +++ b/frontend @@ -0,0 +1 @@ +Subproject commit 05a69f4c0f800f807f3b8c28557f401844655fb9 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4bbf884 --- /dev/null +++ b/go.mod @@ -0,0 +1,53 @@ +module github.com/tellytv/telly + +go 1.12 + +require ( + github.com/beorn7/perks v1.0.0 + github.com/fsnotify/fsnotify v1.4.7 + github.com/gin-contrib/cors v0.0.0-20170318125340-cf4846e6a636 + github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 + github.com/gin-gonic/gin v0.0.0-20170702092826-d459835d2b07 + github.com/go-logfmt/logfmt v0.4.0 // indirect + github.com/gobuffalo/depgen v0.1.1 // indirect + github.com/gobuffalo/genny v0.1.1 // indirect + github.com/gobuffalo/gogen v0.1.1 // indirect + github.com/gobuffalo/packr v1.25.0 + github.com/gogo/protobuf v1.2.1 // indirect + github.com/golang/protobuf v1.3.1 + github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce + github.com/karrick/godirwalk v1.10.0 // indirect + github.com/kisielk/errcheck v1.2.0 // indirect + github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b + github.com/kr/pretty v0.1.0 + github.com/kr/pty v1.1.4 // indirect + github.com/kr/text v0.1.0 + github.com/magiconair/properties v1.8.0 + github.com/markbates/grift v1.0.1 // indirect + github.com/mattn/go-isatty v0.0.0-20170307163044-57fdcb988a5c + github.com/matttproud/golang_protobuf_extensions v1.0.1 + github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 + github.com/pelletier/go-toml v1.2.0 + github.com/pkg/errors v0.8.1 + github.com/prometheus/client_golang v0.9.2 + github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 + github.com/prometheus/common v0.3.0 + github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 + github.com/prometheus/promu v0.3.0 // indirect + github.com/sirupsen/logrus v1.4.1 + github.com/spf13/afero v1.1.1 + github.com/spf13/cast v1.2.0 + github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 + github.com/spf13/pflag v1.0.3 + github.com/spf13/viper v1.1.0 + github.com/stretchr/objx v0.2.0 // indirect + github.com/tellytv/go.schedulesdirect v0.0.0-20180828235349-49735fc3ed77 + github.com/ugorji/go v0.0.0-20170215201144-c88ee250d022 + golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 + golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c + golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 + golang.org/x/text v0.3.2 + golang.org/x/tools v0.0.0-20190509014725-d996b19ee77c // indirect + gopkg.in/go-playground/validator.v8 v8.18.1 + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..568feb3 --- /dev/null +++ b/go.sum @@ -0,0 +1,228 @@ +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc h1:cAKDfWh5VpdgMhJosfJnn5/FoN2SRZ4p7fJNX58YPaU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gin-contrib/cors v0.0.0-20170318125340-cf4846e6a636 h1:oGgJA7DJphAc81EMHZ+2G7Ai2xyg5eoq7bbqzCsiWFc= +github.com/gin-contrib/cors v0.0.0-20170318125340-cf4846e6a636/go.mod h1:cw+u9IsAkC16e42NtYYVCLsHYXE98nB3M7Dr9mLSeH4= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY= +github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v0.0.0-20170702092826-d459835d2b07 h1:cZPJWzd2oNeoS0oJM2TlN9rl0OnCgUr10gC8Q4mH+6M= +github.com/gin-gonic/gin v0.0.0-20170702092826-d459835d2b07/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= +github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= +github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= +github.com/gobuffalo/depgen v0.1.1/go.mod h1:65EOv3g7CMe4kc8J1Ds+l2bjcwrWKGXkE4/vpRRLPWY= +github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= +github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= +github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= +github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= +github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= +github.com/gobuffalo/genny v0.1.1 h1:iQ0D6SpNXIxu52WESsD+KoQ7af2e3nCfnSBoSF/hKe0= +github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= +github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= +github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= +github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= +github.com/gobuffalo/gogen v0.1.1 h1:dLg+zb+uOyd/mKeQUYIbwbNmfRsr9hd/WtYWepmayhI= +github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2 h1:8thhT+kUJMTMy3HlX4+y9Da+BNJck+p109tqqKp7WDs= +github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= +github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/mapi v1.0.2 h1:fq9WcL1BYrm36SzK6+aAnZ8hcp+SrmnDyAxhNx8dvJk= +github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= +github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packd v0.1.0 h1:4sGKOD8yaYJ+dek1FDkwcxCHA40M4kfKgFHx8N2kwbU= +github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= +github.com/gobuffalo/packr v1.13.2 h1:fQmeSiOMhl+4U+da7VmX2AjdcCaSOi5IvnqsSXdKYmQ= +github.com/gobuffalo/packr v1.13.2/go.mod h1:qdqw8AgJyKw60qj56fnEBiS9fIqqCaP/vWJQvR4Jcss= +github.com/gobuffalo/packr v1.25.0 h1:NtPK45yOKFdTKHTvRGKL+UIKAKmJVWIVJOZBDI/qEdY= +github.com/gobuffalo/packr v1.25.0/go.mod h1:NqsGg8CSB2ZD+6RBIRs18G7aZqdYDlYNNvsSqP6T4/U= +github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= +github.com/gobuffalo/packr/v2 v2.1.0/go.mod h1:n90ZuXIc2KN2vFAOQascnPItp9A2g9QYSvYvS3AjQEM= +github.com/gobuffalo/packr/v2 v2.2.0 h1:Ir9W9XIm9j7bhhkKE9cokvtTl1vBm62A/fene/ZCj6A= +github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754 h1:tpom+2CJmpzAWj5/VEHync2rJGi+epHNIeRSWjzGA+4= +github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= +github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= +github.com/karrick/godirwalk v1.10.0 h1:fb2G3xs9hsG0CmH6fnx6sxTsvNeDQtcsIegljcXRQGU= +github.com/karrick/godirwalk v1.10.0/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b h1:wxtKgYHEncAU00muMD06dzLiahtGM1eouRNOzVV7tdQ= +github.com/koron/go-ssdp v0.0.0-20180514024734-4a0ed625a78b/go.mod h1:5Ky9EC2xfoUKUor0Hjgi2BJhCSXJfMOFlmyYrVKGQMk= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/markbates/grift v1.0.1/go.mod h1:aC7s7OfCOzc2WCafmTm7wI3cfGFA/8opYhdTGlIAmmo= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2 h1:JgVTCPf0uBVcUSWpyXmGpgOc62nK5HWUBKAGc3Qqa5k= +github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= +github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= +github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-isatty v0.0.0-20170307163044-57fdcb988a5c h1:AHfQR/s6GNi92TOh+kfGworqDvTxj2rMsS+Hca87nck= +github.com/mattn/go-isatty v0.0.0-20170307163044-57fdcb988a5c/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun9knAVAR8tg/aHJEr5DgtcbqyvzacK+CDCaI= +github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8= +github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= +github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.3.0 h1:taZ4h8Tkxv2kNyoSctBvfXEHmBmxrwmIidZTIaHons4= +github.com/prometheus/common v0.3.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= +github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190209105433-f8d8b3f739bd/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/promu v0.3.0 h1:ecIZ1FIjQ+PAneA6g0KpUa7FDimozQtDjzI2rW0Pmh0= +github.com/prometheus/promu v0.3.0/go.mod h1:+NXvSS3J95z3ZmFZP0DXUt+g/I6zyK1CQoBJKkjzX4k= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I= +github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= +github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= +github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 h1:kJI9pPzfsULT/72wy7mxkRQZPtKWgFdCA2RTGZ4v8/E= +github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= +github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.1.0 h1:V7OZpY8i3C1x/pDmU0zNNlfVoDz112fSYvtWMjjS3f4= +github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tellytv/go.schedulesdirect v0.0.0-20180828235349-49735fc3ed77 h1:eZWUcYXkpSpcwKyc/GXRMv+l4pGf47wQp5QCplO/66o= +github.com/tellytv/go.schedulesdirect v0.0.0-20180828235349-49735fc3ed77/go.mod h1:pBZcxidsU285nwpDZ3NQIONgAyOo4wiUoOutTMu7KU4= +github.com/ugorji/go v0.0.0-20170215201144-c88ee250d022 h1:wIYK3i9zY6ZBcWw4GFvoPVwtb45iEm8KyOVmDhSLvsE= +github.com/ugorji/go v0.0.0-20170215201144-c88ee250d022/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= +golang.org/x/crypto v0.0.0-20180808211826-de0752318171 h1:vYogbvSFj2YXcjQxFHu/rASSOt9sLytpCaSkiwQ135I= +golang.org/x/crypto v0.0.0-20180808211826-de0752318171/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284 h1:rlLehGeYg6jfoyz/eDqDU1iRXLKfR42nnNh57ytKEWo= +golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20180811021610-c39426892332 h1:efGso+ep0DjyCBJPjvoz0HI6UldX4Md2F1rZFe1ir0E= +golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c h1:uOCk1iQW6Vc18bnC13MfzScl+wdKBmM9Y9kU7Z83/lw= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0 h1:8H8QZJ30plJyIVj60H3lr8TZGIq2Fh3Cyrs/ZNg1foU= +golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= +golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190404132500-923d25813098/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190509014725-d996b19ee77c h1:FsgttePhaNW32agh7vOjhKj0IuEmI/TmGumOc4z9yEs= +golang.org/x/tools v0.0.0-20190509014725-d996b19ee77c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/validator.v8 v8.18.1 h1:F8SLY5Vqesjs1nI1EL4qmF1PQZ1sitsmq0rPYXLyfGU= +gopkg.in/go-playground/validator.v8 v8.18.1/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/yaml.v2 v2.0.0-20160928153709-a5b47d31c556 h1:hKXbLW5oaJoQgs8KrzTLdF4PoHi+0oQPgea9TNtvE3E= +gopkg.in/yaml.v2 v2.0.0-20160928153709-a5b47d31c556/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/go-gin-prometheus/middleware.go b/internal/go-gin-prometheus/middleware.go new file mode 100644 index 0000000..f3d7477 --- /dev/null +++ b/internal/go-gin-prometheus/middleware.go @@ -0,0 +1,402 @@ +// Package ginprometheus provides a Logrus logger for Gin requests. Slightly modified to remove spammy logs. +// For more info see https://github.com/zsais/go-gin-prometheus/pull/22. +package ginprometheus + +import ( + "bytes" + "io/ioutil" + "net/http" + "os" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + log "github.com/sirupsen/logrus" +) + +var defaultMetricPath = "/metrics" + +// Standard default metrics +// counter, counter_vec, gauge, gauge_vec, +// histogram, histogram_vec, summary, summary_vec +var reqCnt = &Metric{ + ID: "reqCnt", + Name: "requests_total", + Description: "How many HTTP requests processed, partitioned by status code and HTTP method.", + Type: "counter_vec", + Args: []string{"code", "method", "handler", "host", "url"}} + +var reqDur = &Metric{ + ID: "reqDur", + Name: "request_duration_seconds", + Description: "The HTTP request latencies in seconds.", + Type: "summary"} + +var resSz = &Metric{ + ID: "resSz", + Name: "response_size_bytes", + Description: "The HTTP response sizes in bytes.", + Type: "summary"} + +var reqSz = &Metric{ + ID: "reqSz", + Name: "request_size_bytes", + Description: "The HTTP request sizes in bytes.", + Type: "summary"} + +var standardMetrics = []*Metric{ + reqCnt, + reqDur, + resSz, + reqSz, +} + +/* +RequestCounterURLLabelMappingFn is a function which can be supplied to the middleware to control +the cardinality of the request counter's "url" label, which might be required in some contexts. +For instance, if for a "/customer/:name" route you don't want to generate a time series for every +possible customer name, you could use this function: +func(c *gin.Context) string { + url := c.Request.URL.String() + for _, p := range c.Params { + if p.Key == "name" { + url = strings.Replace(url, p.Value, ":name", 1) + break + } + } + return url +} +which would map "/customer/alice" and "/customer/bob" to their template "/customer/:name". +*/ +type RequestCounterURLLabelMappingFn func(c *gin.Context) string + +// Metric is a definition for the name, description, type, ID, and +// prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric +type Metric struct { + MetricCollector prometheus.Collector + ID string + Name string + Description string + Type string + Args []string +} + +// Prometheus contains the metrics gathered by the instance and its path +type Prometheus struct { + reqCnt *prometheus.CounterVec + reqDur, reqSz, resSz prometheus.Summary + router *gin.Engine + listenAddress string + Ppg PrometheusPushGateway + + MetricsList []*Metric + MetricsPath string + + ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn +} + +// PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional) +type PrometheusPushGateway struct { + + // Push interval in seconds + PushIntervalSeconds time.Duration + + // Push Gateway URL in format http://domain:port + // where JOBNAME can be any string of your choice + PushGatewayURL string + + // Local metrics URL where metrics are fetched from, this could be ommited in the future + // if implemented using prometheus common/expfmt instead + MetricsURL string + + // pushgateway job name, defaults to "gin" + Job string +} + +// NewPrometheus generates a new set of metrics with a certain subsystem name +func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus { + + var metricsList []*Metric + + if len(customMetricsList) > 1 { + panic("Too many args. NewPrometheus( string, ).") + } else if len(customMetricsList) == 1 { + metricsList = customMetricsList[0] + } + + for _, metric := range standardMetrics { + metricsList = append(metricsList, metric) + } + + p := &Prometheus{ + MetricsList: metricsList, + MetricsPath: defaultMetricPath, + ReqCntURLLabelMappingFn: func(c *gin.Context) string { + return c.Request.URL.String() // i.e. by default do nothing, i.e. return URL as is + }, + } + + p.registerMetrics(subsystem) + + return p +} + +// SetPushGateway sends metrics to a remote pushgateway exposed on pushGatewayURL +// every pushIntervalSeconds. Metrics are fetched from metricsURL +func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushIntervalSeconds time.Duration) { + p.Ppg.PushGatewayURL = pushGatewayURL + p.Ppg.MetricsURL = metricsURL + p.Ppg.PushIntervalSeconds = pushIntervalSeconds + p.startPushTicker() +} + +// SetPushGatewayJob job name, defaults to "gin" +func (p *Prometheus) SetPushGatewayJob(j string) { + p.Ppg.Job = j +} + +// SetListenAddress for exposing metrics on address. If not set, it will be exposed at the +// same address of the gin engine that is being used +func (p *Prometheus) SetListenAddress(address string) { + p.listenAddress = address + if p.listenAddress != "" { + p.router = gin.Default() + } +} + +// SetListenAddressWithRouter for using a separate router to expose metrics. (this keeps things like GET /metrics out of +// your content's access log). +func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *gin.Engine) { + p.listenAddress = listenAddress + if len(p.listenAddress) > 0 { + p.router = r + } +} + +func (p *Prometheus) setMetricsPath(e *gin.Engine) { + + if p.listenAddress != "" { + p.router.GET(p.MetricsPath, prometheusHandler()) + p.runServer() + } else { + e.GET(p.MetricsPath, prometheusHandler()) + } +} + +func (p *Prometheus) setMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts) { + + if p.listenAddress != "" { + p.router.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler()) + p.runServer() + } else { + e.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler()) + } + +} + +func (p *Prometheus) runServer() { + if p.listenAddress != "" { + go p.router.Run(p.listenAddress) + } +} + +func (p *Prometheus) getMetrics() []byte { + response, _ := http.Get(p.Ppg.MetricsURL) + + defer response.Body.Close() + body, _ := ioutil.ReadAll(response.Body) + + return body +} + +func (p *Prometheus) getPushGatewayURL() string { + h, _ := os.Hostname() + if p.Ppg.Job == "" { + p.Ppg.Job = "gin" + } + return p.Ppg.PushGatewayURL + "/metrics/job/" + p.Ppg.Job + "/instance/" + h +} + +func (p *Prometheus) sendMetricsToPushGateway(metrics []byte) { + req, err := http.NewRequest("POST", p.getPushGatewayURL(), bytes.NewBuffer(metrics)) + client := &http.Client{} + if _, err = client.Do(req); err != nil { + log.WithError(err).Errorln("Error sending to push gateway") + } +} + +func (p *Prometheus) startPushTicker() { + ticker := time.NewTicker(time.Second * p.Ppg.PushIntervalSeconds) + go func() { + for range ticker.C { + p.sendMetricsToPushGateway(p.getMetrics()) + } + }() +} + +// NewMetric associates prometheus.Collector based on Metric.Type +func NewMetric(m *Metric, subsystem string) prometheus.Collector { + var metric prometheus.Collector + switch m.Type { + case "counter_vec": + metric = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + m.Args, + ) + case "counter": + metric = prometheus.NewCounter( + prometheus.CounterOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + ) + case "gauge_vec": + metric = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + m.Args, + ) + case "gauge": + metric = prometheus.NewGauge( + prometheus.GaugeOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + ) + case "histogram_vec": + metric = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + m.Args, + ) + case "histogram": + metric = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + ) + case "summary_vec": + metric = prometheus.NewSummaryVec( + prometheus.SummaryOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + m.Args, + ) + case "summary": + metric = prometheus.NewSummary( + prometheus.SummaryOpts{ + Subsystem: subsystem, + Name: m.Name, + Help: m.Description, + }, + ) + } + return metric +} + +func (p *Prometheus) registerMetrics(subsystem string) { + + for _, metricDef := range p.MetricsList { + metric := NewMetric(metricDef, subsystem) + if err := prometheus.Register(metric); err != nil { + log.WithError(err).Errorf("%s could not be registered in Prometheus", metricDef.Name) + } + switch metricDef { + case reqCnt: + p.reqCnt = metric.(*prometheus.CounterVec) + case reqDur: + p.reqDur = metric.(prometheus.Summary) + case resSz: + p.resSz = metric.(prometheus.Summary) + case reqSz: + p.reqSz = metric.(prometheus.Summary) + } + metricDef.MetricCollector = metric + } +} + +// Use adds the middleware to a gin engine. +func (p *Prometheus) Use(e *gin.Engine) { + e.Use(p.handlerFunc()) + p.setMetricsPath(e) +} + +// UseWithAuth adds the middleware to a gin engine with BasicAuth. +func (p *Prometheus) UseWithAuth(e *gin.Engine, accounts gin.Accounts) { + e.Use(p.handlerFunc()) + p.setMetricsPathWithAuth(e, accounts) +} + +func (p *Prometheus) handlerFunc() gin.HandlerFunc { + return func(c *gin.Context) { + if c.Request.URL.String() == p.MetricsPath { + c.Next() + return + } + + start := time.Now() + reqSz := computeApproximateRequestSize(c.Request) + + c.Next() + + status := strconv.Itoa(c.Writer.Status()) + elapsed := float64(time.Since(start)) / float64(time.Second) + resSz := float64(c.Writer.Size()) + + p.reqDur.Observe(elapsed) + url := p.ReqCntURLLabelMappingFn(c) + p.reqCnt.WithLabelValues(status, c.Request.Method, c.HandlerName(), c.Request.Host, url).Inc() + p.reqSz.Observe(float64(reqSz)) + p.resSz.Observe(resSz) + } +} + +func prometheusHandler() gin.HandlerFunc { + h := promhttp.Handler() + return func(c *gin.Context) { + h.ServeHTTP(c.Writer, c.Request) + } +} + +// From https://github.com/DanielHeckrath/gin-prometheus/blob/master/gin_prometheus.go +func computeApproximateRequestSize(r *http.Request) int { + s := 0 + if r.URL != nil { + s = len(r.URL.String()) + } + + s += len(r.Method) + s += len(r.Proto) + for name, values := range r.Header { + s += len(name) + for _, value := range values { + s += len(value) + } + } + s += len(r.Host) + + // N.B. r.Form and r.MultipartForm are assumed to be included in r.URL. + + if r.ContentLength != -1 { + s += int(r.ContentLength) + } + return s +} diff --git a/m3u/main.go b/internal/m3uplus/main.go similarity index 76% rename from m3u/main.go rename to internal/m3uplus/main.go index ee33ce7..b6abc12 100644 --- a/m3u/main.go +++ b/internal/m3uplus/main.go @@ -1,9 +1,11 @@ -package m3u +// Package m3uplus provides a M3U Plus parser. +package m3uplus import ( "bytes" "fmt" "io" + "net/url" "regexp" "strconv" "strings" @@ -13,15 +15,17 @@ import ( // Playlist is a type that represents an m3u playlist containing 0 or more tracks type Playlist struct { - Tracks []*Track + Tracks []Track } // Track represents an m3u track type Track struct { - Name string - Length float64 - URI string - Tags map[string]string + Name string + Length float64 + URI *url.URL + Tags map[string]string + Raw string + LineNumber int } // UnmarshalTags will decode the Tags map into a struct containing fields with `m3u` tags matching map keys. @@ -72,31 +76,41 @@ func decode(playlist *Playlist, buf *bytes.Buffer) error { return fmt.Errorf("malformed M3U provided") } - if err = decodeLine(playlist, line); err != nil { + if err = decodeLine(playlist, line, lineNum); err != nil { return err } } return nil } -func decodeLine(playlist *Playlist, line string) error { +func decodeLine(playlist *Playlist, line string, lineNumber int) error { line = strings.TrimSpace(line) switch { case strings.HasPrefix(line, "#EXTINF:"): - track := new(Track) + track := Track{ + Raw: line, + LineNumber: lineNumber, + } track.Length, track.Name, track.Tags = decodeInfoLine(line) playlist.Tracks = append(playlist.Tracks, track) - case strings.HasPrefix(line, "http"): - playlist.Tracks[len(playlist.Tracks)-1].URI = line + case IsUrl(line): + uri, _ := url.Parse(line) + playlist.Tracks[len(playlist.Tracks)-1].URI = uri } return nil } +// From https://stackoverflow.com/questions/25747580/ensure-a-uri-is-valid/25747925#25747925 +func IsUrl(str string) bool { + u, err := url.Parse(str) + return err == nil && u.Scheme != "" && u.Host != "" +} + var infoRegex = regexp.MustCompile(`([^\s="]+)=(?:"(.*?)"|(\d+))(?:,([.*^,]))?|#EXTINF:(-?\d*\s*)|,(.*)`) func decodeInfoLine(line string) (float64, string, map[string]string) { @@ -120,7 +134,7 @@ func decodeInfoLine(line string) (float64, string, map[string]string) { if val == "" { // If empty string find a number in [3] val = match[3] } - keyMap[match[1]] = val + keyMap[strings.ToLower(match[1])] = val } return durationFloat, title, keyMap diff --git a/internal/providers/area51.go b/internal/providers/area51.go new file mode 100644 index 0000000..5d8a5a4 --- /dev/null +++ b/internal/providers/area51.go @@ -0,0 +1,81 @@ +package providers + +import ( + "fmt" + "strings" + + m3u "github.com/tellytv/telly/internal/m3uplus" + "github.com/tellytv/telly/internal/xmltv" +) + +// http://iptv-area-51.tv:2095/get.php?username=username&password=password&type=m3uplus&output=ts +// http://iptv-area-51.tv:2095/xmltv.php?username=username&password=password + +type area51 struct { + BaseConfig Configuration +} + +func newArea51(config *Configuration) (Provider, error) { + return &area51{*config}, nil +} + +func (i *area51) Name() string { + return "Area51" +} + +func (i *area51) PlaylistURL() string { + return fmt.Sprintf("http://iptv-area-51.tv:2095/get.php?username=%s&password=%s&type=m3u_plus&output=ts", i.BaseConfig.Username, i.BaseConfig.Password) +} + +func (i *area51) EPGURL() string { + return fmt.Sprintf("http://iptv-area-51.tv:2095/xmltv.php?username=%s&password=%s", i.BaseConfig.Username, i.BaseConfig.Password) +} + +// ParseTrack matches the provided M3U track an XMLTV channel and returns a ProviderChannel. +func (i *area51) ParseTrack(track m3u.Track, channelMap map[string]xmltv.Channel) (*ProviderChannel, error) { + nameVal := track.Name + if i.BaseConfig.NameKey != "" { + nameVal = track.Tags[i.BaseConfig.NameKey] + } + + logoVal := track.Tags["tvg-logo"] + if i.BaseConfig.LogoKey != "" { + logoVal = track.Tags[i.BaseConfig.LogoKey] + } + + pChannel := &ProviderChannel{ + Name: nameVal, + Logo: logoVal, + Number: 0, + StreamURL: track.URI.String(), + StreamID: 0, + HD: strings.Contains(strings.ToLower(track.Name), "hd"), + StreamFormat: "Unknown", + Track: track, + OnDemand: false, + } + + epgVal := track.Tags["tvg-id"] + if i.BaseConfig.EPGMatchKey != "" { + epgVal = track.Tags[i.BaseConfig.EPGMatchKey] + } + + if xmlChan, ok := channelMap[epgVal]; ok { + pChannel.EPGMatch = epgVal + pChannel.EPGChannel = &xmlChan + } + + return pChannel, nil +} + +func (i *area51) ProcessProgramme(programme xmltv.Programme) *xmltv.Programme { + return &programme +} + +func (i *area51) Configuration() Configuration { + return i.BaseConfig +} + +func (i *area51) RegexKey() string { + return "group-title" +} diff --git a/internal/providers/custom.go b/internal/providers/custom.go new file mode 100644 index 0000000..ebf0d31 --- /dev/null +++ b/internal/providers/custom.go @@ -0,0 +1,106 @@ +package providers + +import ( + "fmt" + "net" + "net/url" + "strconv" + "strings" + + log "github.com/sirupsen/logrus" + m3u "github.com/tellytv/telly/internal/m3uplus" + "github.com/tellytv/telly/internal/xmltv" +) + +type customProvider struct { + BaseConfig Configuration +} + +func newCustomProvider(config *Configuration) (Provider, error) { + return &customProvider{*config}, nil +} + +func (i *customProvider) Name() string { + return i.BaseConfig.Name +} + +func (i *customProvider) PlaylistURL() string { + return i.BaseConfig.M3U +} + +func (i *customProvider) EPGURL() string { + return i.BaseConfig.EPG +} + +// ParseTrack matches the provided M3U track an XMLTV channel and returns a ProviderChannel. +func (i *customProvider) ParseTrack(track m3u.Track, channelMap map[string]xmltv.Channel) (*ProviderChannel, error) { + channelVal := track.Tags["tvg-chno"] + if i.BaseConfig.ChannelNumberKey != "" { + channelVal = track.Tags[i.BaseConfig.ChannelNumberKey] + } + + chanNum := 0 + + if channelNumber, channelNumberErr := strconv.Atoi(channelVal); channelNumberErr == nil { + chanNum = channelNumber + } + + nameVal := track.Name + if i.BaseConfig.NameKey != "" { + nameVal = track.Tags[i.BaseConfig.NameKey] + } + + logoVal := track.Tags["tvg-logo"] + if i.BaseConfig.LogoKey != "" { + logoVal = track.Tags[i.BaseConfig.LogoKey] + } + + pChannel := &ProviderChannel{ + Name: nameVal, + Logo: logoVal, + Number: chanNum, + StreamURL: track.URI.String(), + StreamID: chanNum, + HD: strings.Contains(strings.ToLower(track.Name), "hd"), + StreamFormat: "Unknown", + Track: track, + OnDemand: false, + } + + // If Udpxy is set in the provider configuration and StreamURL is a multicast stream, + // rewrite the URL to point to the Udpxy instance. + if i.BaseConfig.Udpxy != "" { + trackURI, err := url.Parse(pChannel.StreamURL) + if err != nil { + return nil, err + } + if IP := net.ParseIP(trackURI.Hostname()); IP != nil && IP.IsMulticast() { + pChannel.StreamURL = fmt.Sprintf("http://%s/udp/%s/", i.BaseConfig.Udpxy, trackURI.Host) + log.Debugf("Multicast stream detected and udpxy is configured, track URL rewritten from %s to %s", track.URI, pChannel.StreamURL) + } + } + + epgVal := track.Tags["tvg-id"] + if i.BaseConfig.EPGMatchKey != "" { + epgVal = track.Tags[i.BaseConfig.EPGMatchKey] + } + + if xmlChan, ok := channelMap[epgVal]; ok { + pChannel.EPGMatch = epgVal + pChannel.EPGChannel = &xmlChan + } + + return pChannel, nil +} + +func (i *customProvider) ProcessProgramme(programme xmltv.Programme) *xmltv.Programme { + return &programme +} + +func (i *customProvider) Configuration() Configuration { + return i.BaseConfig +} + +func (i *customProvider) RegexKey() string { + return i.BaseConfig.FilterKey +} diff --git a/internal/providers/eternal.go b/internal/providers/eternal.go new file mode 100644 index 0000000..72bc1b6 --- /dev/null +++ b/internal/providers/eternal.go @@ -0,0 +1,4 @@ +package providers + +// M3U:http://live.eternaltv.net:25461/get.php?username=xxxxxxx&password=xxxxxx&output=ts&type=m3uplus +// XMLTV: http://live.eternaltv.net:25461/xmltv.php?username=xxxxx&password=xxxxx&type=m3uplus&output=ts diff --git a/internal/providers/hellraiser.go b/internal/providers/hellraiser.go new file mode 100644 index 0000000..0608474 --- /dev/null +++ b/internal/providers/hellraiser.go @@ -0,0 +1,4 @@ +package providers + +// Playlist URL: http://liquidit.info:8080/get.php?username=xxxx&password=xxxxxxx&type=m3uplus&output=ts +// XMLTV URL: http://liquidit.info:8080/xmltv.php?username=xxxxxx&password=xxxxxx diff --git a/internal/providers/iptv-epg.go b/internal/providers/iptv-epg.go new file mode 100644 index 0000000..f34c496 --- /dev/null +++ b/internal/providers/iptv-epg.go @@ -0,0 +1,92 @@ +package providers + +import ( + "fmt" + "strconv" + "strings" + + m3u "github.com/tellytv/telly/internal/m3uplus" + "github.com/tellytv/telly/internal/xmltv" +) + +// M3U: http://iptv-epg.com/.m3u +// XMLTV: http://iptv-epg.com/.xml + +type iptvepg struct { + BaseConfig Configuration +} + +func newIPTVEPG(config *Configuration) (Provider, error) { + return &iptvepg{*config}, nil +} + +func (i *iptvepg) Name() string { + return "IPTV-EPG" +} + +func (i *iptvepg) PlaylistURL() string { + return fmt.Sprintf("http://iptv-epg.com/%s.m3u", i.BaseConfig.Username) +} + +func (i *iptvepg) EPGURL() string { + return fmt.Sprintf("http://iptv-epg.com/%s.xml", i.BaseConfig.Password) +} + +// ParseTrack matches the provided M3U track an XMLTV channel and returns a ProviderChannel. +func (i *iptvepg) ParseTrack(track m3u.Track, channelMap map[string]xmltv.Channel) (*ProviderChannel, error) { + channelVal := track.Tags["tvg-chno"] + if i.BaseConfig.ChannelNumberKey != "" { + channelVal = track.Tags[i.BaseConfig.ChannelNumberKey] + } + + channelNumber, channelNumberErr := strconv.Atoi(channelVal) + if channelNumberErr != nil { + return nil, channelNumberErr + } + + nameVal := track.Name + if i.BaseConfig.NameKey != "" { + nameVal = track.Tags[i.BaseConfig.NameKey] + } + + logoVal := track.Tags["tvg-logo"] + if i.BaseConfig.LogoKey != "" { + logoVal = track.Tags[i.BaseConfig.LogoKey] + } + + pChannel := &ProviderChannel{ + Name: nameVal, + Logo: logoVal, + Number: channelNumber, + StreamURL: track.URI.String(), + StreamID: channelNumber, + HD: strings.Contains(strings.ToLower(track.Name), "hd"), + StreamFormat: "Unknown", + Track: track, + OnDemand: false, + } + + epgVal := track.Tags["tvg-id"] + if i.BaseConfig.EPGMatchKey != "" { + epgVal = track.Tags[i.BaseConfig.EPGMatchKey] + } + + if xmlChan, ok := channelMap[epgVal]; ok { + pChannel.EPGMatch = epgVal + pChannel.EPGChannel = &xmlChan + } + + return pChannel, nil +} + +func (i *iptvepg) ProcessProgramme(programme xmltv.Programme) *xmltv.Programme { + return &programme +} + +func (i *iptvepg) Configuration() Configuration { + return i.BaseConfig +} + +func (i *iptvepg) RegexKey() string { + return "group-title" +} diff --git a/internal/providers/iris.go b/internal/providers/iris.go new file mode 100644 index 0000000..7271336 --- /dev/null +++ b/internal/providers/iris.go @@ -0,0 +1,81 @@ +package providers + +import ( + "fmt" + "strings" + + m3u "github.com/tellytv/telly/internal/m3uplus" + "github.com/tellytv/telly/internal/xmltv" +) + +// http://irislinks.net:83/get.php?username=username&password=password&type=m3uplus&output=ts +// http://irislinks.net:83/xmltv.php?username=username&password=password + +type iris struct { + BaseConfig Configuration +} + +func newIris(config *Configuration) (Provider, error) { + return &iris{*config}, nil +} + +func (i *iris) Name() string { + return "Iris" +} + +func (i *iris) PlaylistURL() string { + return fmt.Sprintf("http://irislinks.net:83/get.php?username=%s&password=%s&type=m3u_plus&output=ts", i.BaseConfig.Username, i.BaseConfig.Password) +} + +func (i *iris) EPGURL() string { + return fmt.Sprintf("http://irislinks.net:83/xmltv.php?username=%s&password=%s", i.BaseConfig.Username, i.BaseConfig.Password) +} + +// ParseTrack matches the provided M3U track an XMLTV channel and returns a ProviderChannel. +func (i *iris) ParseTrack(track m3u.Track, channelMap map[string]xmltv.Channel) (*ProviderChannel, error) { + nameVal := track.Name + if i.BaseConfig.NameKey != "" { + nameVal = track.Tags[i.BaseConfig.NameKey] + } + + logoVal := track.Tags["tvg-logo"] + if i.BaseConfig.LogoKey != "" { + logoVal = track.Tags[i.BaseConfig.LogoKey] + } + + pChannel := &ProviderChannel{ + Name: nameVal, + Logo: logoVal, + Number: 0, + StreamURL: track.URI.String(), + StreamID: 0, + HD: strings.Contains(strings.ToLower(track.Name), "hd"), + StreamFormat: "Unknown", + Track: track, + OnDemand: false, + } + + epgVal := track.Tags["tvg-id"] + if i.BaseConfig.EPGMatchKey != "" { + epgVal = track.Tags[i.BaseConfig.EPGMatchKey] + } + + if xmlChan, ok := channelMap[epgVal]; ok { + pChannel.EPGMatch = epgVal + pChannel.EPGChannel = &xmlChan + } + + return pChannel, nil +} + +func (i *iris) ProcessProgramme(programme xmltv.Programme) *xmltv.Programme { + return &programme +} + +func (i *iris) Configuration() Configuration { + return i.BaseConfig +} + +func (i *iris) RegexKey() string { + return "group-title" +} diff --git a/internal/providers/main.go b/internal/providers/main.go new file mode 100644 index 0000000..28039fb --- /dev/null +++ b/internal/providers/main.go @@ -0,0 +1,98 @@ +package providers + +import ( + "regexp" + "strings" + + m3u "github.com/tellytv/telly/internal/m3uplus" + "github.com/tellytv/telly/internal/xmltv" +) + +var streamNumberRegex = regexp.MustCompile(`/(\d+).(ts|.*.m3u8)`).FindAllStringSubmatch +var channelNumberRegex = regexp.MustCompile(`^[0-9]+[[:space:]]?$`).MatchString +var callSignRegex = regexp.MustCompile(`^[A-Z0-9]+$`).MatchString +var hdRegex = regexp.MustCompile(`hd|4k`) + +type Configuration struct { + Name string `json:"-"` + Provider string + + Username string `json:"username"` + Password string `json:"password"` + + M3U string `json:"-"` + EPG string `json:"-"` + + Udpxy string `json:"udpxy"` + + VideoOnDemand bool `json:"-"` + + Filter string + FilterKey string + FilterRaw bool + + SortKey string + SortReverse bool + + Favorites []string + FavoriteTag string + + IncludeOnly []string + IncludeOnlyTag string + + CacheFiles bool + + NameKey string + LogoKey string + ChannelNumberKey string + EPGMatchKey string +} + +func (i *Configuration) GetProvider() (Provider, error) { + switch strings.ToLower(i.Provider) { + default: + return newCustomProvider(i) + } +} + +// ProviderChannel describes a channel available in the providers lineup with necessary pieces parsed into fields. +type ProviderChannel struct { + Name string + StreamID int // Should be the integer just before .ts. + Number int + Logo string + StreamURL string + HD bool + Quality string + OnDemand bool + StreamFormat string + Favorite bool + + EPGMatch string + EPGChannel *xmltv.Channel + EPGProgrammes []xmltv.Programme + Track m3u.Track +} + +// Provider describes a IPTV provider configuration. +type Provider interface { + Name() string + PlaylistURL() string + EPGURL() string + + // These are functions to extract information from playlists. + ParseTrack(track m3u.Track, channelMap map[string]xmltv.Channel) (*ProviderChannel, error) + ProcessProgramme(programme xmltv.Programme) *xmltv.Programme + + RegexKey() string + Configuration() Configuration +} + +func contains(s []string, e string) bool { + for _, ss := range s { + if e == ss { + return true + } + } + return false +} diff --git a/internal/providers/tnt.go b/internal/providers/tnt.go new file mode 100644 index 0000000..b5c08b3 --- /dev/null +++ b/internal/providers/tnt.go @@ -0,0 +1,7 @@ +package providers + +// M3U: http://thesepeanutz.xyz:2052/get.php?username=xxx&password=xxx&type=m3u_plus&output=ts +// XMLTV: http://thesepeanutz.xyz:2052/xmltv.php?username=xxx&password=xxx + +// EPG: http://tntcloud.xyz:2052/xmltv.php?username=XXX&password=XXX +// M3U: http://tntcloud.xyz:2052/get.php?username=XXX&password=XXX&type=m3u_plus&output=ts diff --git a/internal/xmltv/example.xml b/internal/xmltv/example.xml new file mode 100644 index 0000000..f71df21 --- /dev/null +++ b/internal/xmltv/example.xml @@ -0,0 +1,182 @@ + + + + + + 13 KERA + 13 KERA TX42822:- + 13 + 13 KERA fcc + KERA + KERA + PBS Affiliate + + + + 11 KTVT + 11 KTVT TX42822:- + 11 + 11 KTVT fcc + KTVT + KTVT + CBS Affiliate + + + + NOW on PBS + Jordan's Queen Rania has made job creation a priority to help curb the staggering unemployment rates among youths in the Middle East. + 20080711 + Newsmagazine + Interview + Public affairs + Series + EP01006886.0028 + 427 + + + + + + Mystery! + Foyle's War, Series IV: Bleak Midwinter + Foyle investigates an explosion at a munitions factory, which he comes to believe may have been premeditated. + 20070701 + Anthology + Mystery + Series + EP00003026.0665 + 2705 + + + + + + Mystery! + Foyle's War, Series IV: Casualties of War + The murder of a prominent scientist may have been due to a gambling debt. + 20070708 + Anthology + Mystery + Series + EP00003026.0666 + 2706 + + + + + + BBC World News + International issues. + News + Series + SH00315789.0000 + + + + + Sit and Be Fit + 20070924 + Exercise + Series + EP00003847.0074 + 901 + + + + + + The Early Show + Republican candidate John McCain; premiere of the film "The Dark Knight." + 20080715 + Talk + News + Series + EP00337003.2361 + + + + + Rachael Ray + Actresses Kim Raver, Brooke Shields and Lindsay Price ("Lipstick Jungle"); women in their 40s tell why they got breast implants; a 30-minute meal. + + Rachael Ray + + 20080306 + Talk + Series + EP00847333.0303 + 2119 + + + + + + The Price Is Right + Contestants bid for prizes then compete for fabulous showcases. + + Bart Eskander + Roger Dobkowitz + Drew Carey + + Game show + Series + SH00004372.0000 + + + + TV-G + + + + Jeopardy! + + Alex Trebek + + 20080715 + Game show + Series + EP00002348.1700 + 5507 + + + TV-G + + + + The Young and the Restless + Sabrina Offers Victoria a Truce + Jeff thinks Kyon stole the face cream; Nikki asks Nick to give David a chance; Amber begs Adrian to go to Australia. + + Peter Bergman + Eric Braeden + Jeanne Cooper + Melody Thomas Scott + + 20080715 + Soap + Series + EP00004422.1359 + 8937 + + + + TV-14 + + + diff --git a/internal/xmltv/xmltv.dtd b/internal/xmltv/xmltv.dtd new file mode 100644 index 0000000..3c4812e --- /dev/null +++ b/internal/xmltv/xmltv.dtd @@ -0,0 +1,575 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/xmltv/xmltv.go b/internal/xmltv/xmltv.go new file mode 100644 index 0000000..731d6d4 --- /dev/null +++ b/internal/xmltv/xmltv.go @@ -0,0 +1,279 @@ +// Package xmltv provides structures for parsing XMLTV data. +package xmltv + +import ( + "encoding/xml" + "fmt" + "os" + "strings" + "time" + + "golang.org/x/net/html/charset" +) + +// Time that holds the time which is parsed from XML +type Time struct { + time.Time +} + +// MarshalXMLAttr is used to marshal a Go time.Time into the XMLTV Format. +func (t *Time) MarshalXMLAttr(name xml.Name) (xml.Attr, error) { + return xml.Attr{ + Name: name, + Value: t.Format("20060102150405 -0700"), + }, nil +} + +// UnmarshalXMLAttr is used to unmarshal a time in the XMLTV format to a time.Time. +func (t *Time) UnmarshalXMLAttr(attr xml.Attr) error { + // This is a barebones handling of broken XMLTV entries like this one: + // + // What's that negative stop time about? Ignore it + if strings.HasPrefix(attr.Value, "-") { + return nil + } + + t1, err := time.Parse("20060102150405 -0700", attr.Value) + if err != nil { + return err + } + + *t = Time{t1} + return nil +} + +type Date time.Time + +func (p Date) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + t := time.Time(p) + if t.IsZero() { + return e.EncodeElement(nil, start) + } + return e.EncodeElement(t.Format("20060102"), start) +} + +func (p *Date) UnmarshalXML(d *xml.Decoder, start xml.StartElement) (err error) { + var content string + if e := d.DecodeElement(&content, &start); e != nil { + return fmt.Errorf("get the type Date field of %s error", start.Name.Local) + } + + dateFormat := "20060102" + + if len(content) == 4 { + dateFormat = "2006" + } + + if strings.Contains(content, "|") { + content = strings.Split(content, "|")[0] + dateFormat = "2006" + } + + if v, e := time.Parse(dateFormat, content); e != nil { + return fmt.Errorf("the type Date field of %s is not a time, value is: %s", start.Name.Local, content) + } else { + *p = Date(v) + } + return nil +} + +func (p Date) MarshalJSON() ([]byte, error) { + t := time.Time(p) + str := "\"" + t.Format("20060102") + "\"" + + return []byte(str), nil +} + +func (p *Date) UnmarshalJSON(text []byte) (err error) { + strDate := string(text[1 : 8+1]) + + if v, e := time.Parse("20060102", strDate); e != nil { + return fmt.Errorf("Date should be a time, error value is: %s", strDate) + } else { + *p = Date(v) + } + return nil +} + +// TV is the root element. +type TV struct { + XMLName xml.Name `xml:"tv" json:"-"` + Channels []Channel `xml:"channel" json:"channels"` + Programmes []Programme `xml:"programme" json:"programmes"` + Date string `xml:"date,attr,omitempty" json:"date,omitempty"` + SourceInfoURL string `xml:"source-info-url,attr,omitempty" json:"source_info_url,omitempty"` + SourceInfoName string `xml:"source-info-name,attr,omitempty" json:"source_info_name,omitempty"` + SourceDataURL string `xml:"source-data-url,attr,omitempty" json:"source_data_url,omitempty"` + GeneratorInfoName string `xml:"generator-info-name,attr,omitempty" json:"generator_info_name,omitempty"` + GeneratorInfoURL string `xml:"generator-info-url,attr,omitempty" json:"generator_info_url,omitempty"` +} + +// LoadXML loads the XMLTV XML from file. +func (t *TV) LoadXML(f *os.File) error { + decoder := xml.NewDecoder(f) + decoder.CharsetReader = charset.NewReaderLabel + + err := decoder.Decode(&t) + if err != nil { + return err + } + + return nil +} + +// Channel details of a channel +type Channel struct { + DisplayNames []CommonElement `xml:"display-name" json:"display_names" ` + Icons []Icon `xml:"icon,omitempty" json:"icons,omitempty"` + URLs []string `xml:"url,omitempty" json:"urls,omitempty" ` + ID string `xml:"id,attr" json:"id,omitempty" ` + + // These fields are outside of the XMLTV spec. + // LCN is the local channel number. Plex will show it in place of the channel ID if it exists. + LCN int `xml:"lcn" json:"lcn,omitempty"` +} + +// Programme details of a single programme transmission +type Programme struct { + ID string `xml:"id,attr,omitempty" json:"id,omitempty"` // not defined by standard, but often present + Titles []CommonElement `xml:"title" json:"titles"` + SecondaryTitles []CommonElement `xml:"sub-title,omitempty" json:"secondary_titles,omitempty"` + Descriptions []CommonElement `xml:"desc,omitempty" json:"descriptions,omitempty"` + Credits *Credits `xml:"credits,omitempty" json:"credits,omitempty"` + Date Date `xml:"date,omitempty" json:"date,omitempty"` + Categories []CommonElement `xml:"category,omitempty" json:"categories,omitempty"` + Keywords []CommonElement `xml:"keyword,omitempty" json:"keywords,omitempty"` + Languages []CommonElement `xml:"language,omitempty" json:"languages,omitempty"` + OrigLanguages []CommonElement `xml:"orig-language,omitempty" json:"orig_languages,omitempty"` + Length *Length `xml:"length,omitempty" json:"length,omitempty"` + Icons []Icon `xml:"icon,omitempty" json:"icons,omitempty"` + URLs []string `xml:"url,omitempty" json:"urls,omitempty"` + Countries []CommonElement `xml:"country,omitempty" json:"countries,omitempty"` + EpisodeNums []EpisodeNum `xml:"episode-num,omitempty" json:"episode_nums,omitempty"` + Video *Video `xml:"video,omitempty" json:"video,omitempty"` + Audio *Audio `xml:"audio,omitempty" json:"audio,omitempty"` + PreviouslyShown *PreviouslyShown `xml:"previously-shown,omitempty" json:"previously_shown,omitempty"` + Premiere *CommonElement `xml:"premiere,omitempty" json:"premiere,omitempty"` + LastChance *CommonElement `xml:"last-chance,omitempty" json:"last_chance,omitempty"` + New *ElementPresent `xml:"new" json:"new,omitempty"` + Subtitles []Subtitle `xml:"subtitles,omitempty" json:"subtitles,omitempty"` + Ratings []Rating `xml:"rating,omitempty" json:"ratings,omitempty"` + StarRatings []Rating `xml:"star-rating,omitempty" json:"star_ratings,omitempty"` + Reviews []Review `xml:"review,omitempty" json:"reviews,omitempty"` + Start *Time `xml:"start,attr" json:"start"` + Stop *Time `xml:"stop,attr,omitempty" json:"stop,omitempty"` + PDCStart *Time `xml:"pdc-start,attr,omitempty" json:"pdc_start,omitempty"` + VPSStart *Time `xml:"vps-start,attr,omitempty" json:"vps_start,omitempty"` + Showview string `xml:"showview,attr,omitempty" json:"showview,omitempty"` + Videoplus string `xml:"videoplus,attr,omitempty" json:"videoplus,omitempty"` + Channel string `xml:"channel,attr" json:"channel"` + Clumpidx string `xml:"clumpidx,attr,omitempty" json:"clumpidx,omitempty"` +} + +// CommonElement element structure that is common, i.e. Italy +type CommonElement struct { + Lang string `xml:"lang,attr,omitempty" json:"lang,omitempty" ` + Value string `xml:",chardata" json:"value,omitempty"` +} + +// ElementPresent used to determine if element is present or not +type ElementPresent bool + +// MarshalXML used to determine if the element is present or not. see https://stackoverflow.com/a/46516243 +func (c *ElementPresent) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + if c == nil { + return e.EncodeElement(nil, start) + } + return e.EncodeElement("", start) +} + +// UnmarshalXML used to determine if the element is present or not. see https://stackoverflow.com/a/46516243 +func (c *ElementPresent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var v string + if decodeErr := d.DecodeElement(&v, &start); decodeErr != nil { + return decodeErr + } + *c = true + return nil +} + +// Icon associated with the element that contains it +type Icon struct { + Source string `xml:"src,attr" json:"source"` + Width int `xml:"width,attr,omitempty" json:"width,omitempty"` + Height int `xml:"height,attr,omitempty" json:"height,omitempty"` +} + +// Credits for the programme +type Credits struct { + Directors []string `xml:"director,omitempty" json:"directors,omitempty"` + Actors []Actor `xml:"actor,omitempty" json:"actors,omitempty"` + Writers []string `xml:"writer,omitempty" json:"writers,omitempty"` + Adapters []string `xml:"adapter,omitempty" json:"adapters,omitempty"` + Producers []string `xml:"producer,omitempty" json:"producers,omitempty"` + Composers []string `xml:"composer,omitempty" json:"composers,omitempty"` + Editors []string `xml:"editor,omitempty" json:"editors,omitempty"` + Presenters []string `xml:"presenter,omitempty" json:"presenters,omitempty"` + Commentators []string `xml:"commentator,omitempty" json:"commentators,omitempty"` + Guests []string `xml:"guest,omitempty" json:"guests,omitempty"` +} + +// Actor in a programme +type Actor struct { + Role string `xml:"role,attr,omitempty" json:"role,omitempty"` + Value string `xml:",chardata" json:"value"` +} + +// Length of the programme +type Length struct { + Units string `xml:"units,attr" json:"units"` + Value string `xml:",chardata" json:"value"` +} + +// EpisodeNum of the programme +type EpisodeNum struct { + System string `xml:"system,attr,omitempty" json:"system,omitempty"` + Value string `xml:",chardata" json:"value"` +} + +// Video details of the programme +type Video struct { + Present string `xml:"present,omitempty" json:"present,omitempty"` + Colour string `xml:"colour,omitempty" json:"colour,omitempty"` + Aspect string `xml:"aspect,omitempty" json:"aspect,omitempty"` + Quality string `xml:"quality,omitempty" json:"quality,omitempty"` +} + +// Audio details of the programme +type Audio struct { + Present string `xml:"present,omitempty" json:"present,omitempty"` + Stereo string `xml:"stereo,omitempty" json:"stereo,omitempty"` +} + +// PreviouslyShown When and where the programme was last shown, if known. +type PreviouslyShown struct { + Start string `xml:"start,attr,omitempty" json:"start,omitempty"` + Channel string `xml:"channel,attr,omitempty" json:"channel,omitempty"` +} + +// Subtitle in a programme +type Subtitle struct { + Language *CommonElement `xml:"language,omitempty" json:"language,omitempty"` + Type string `xml:"type,attr,omitempty" json:"type,omitempty"` +} + +// Rating of a programme +type Rating struct { + Value string `xml:"value" json:"value"` + Icons []Icon `xml:"icon,omitempty" json:"icons,omitempty"` + System string `xml:"system,attr,omitempty" json:"system,omitempty"` +} + +// Review of a programme +type Review struct { + Value string `xml:",chardata" json:"value"` + Type string `xml:"type" json:"type"` + Source string `xml:"source,omitempty" json:"source,omitempty"` + Reviewer string `xml:"reviewer,omitempty" json:"reviewer,omitempty"` + Lang string `xml:"lang,omitempty" json:"lang,omitempty"` +} diff --git a/internal/xmltv/xmltv_test.go b/internal/xmltv/xmltv_test.go new file mode 100644 index 0000000..b3767d5 --- /dev/null +++ b/internal/xmltv/xmltv_test.go @@ -0,0 +1,141 @@ +package xmltv + +import ( + "encoding/xml" + "fmt" + "io" + "os" + "reflect" + "testing" + "time" + + "github.com/kr/pretty" +) + +func dummyReader(charset string, input io.Reader) (io.Reader, error) { + return input, nil +} + +func TestDecode(t *testing.T) { + dir, err := os.Getwd() + if err != nil { + t.Fatal(err) + } + + // Example downloaded from http://wiki.xmltv.org/index.php/internal/xmltvFormat + // One may check it with `xmllint --noout --dtdvalid xmltv.dtd example.xml` + f, err := os.Open(fmt.Sprintf("%s/example.xml", dir)) + if err != nil { + t.Fatal(err) + } + defer f.Close() + + var tv TV + dec := xml.NewDecoder(f) + dec.CharsetReader = dummyReader + err = dec.Decode(&tv) + if err != nil { + t.Fatal(err) + } + + ch := Channel{ + ID: "I10436.labs.zap2it.com", + DisplayNames: []CommonElement{ + CommonElement{ + Value: "13 KERA", + }, + CommonElement{ + Value: "13 KERA TX42822:-", + }, + CommonElement{ + Value: "13", + }, + CommonElement{ + Value: "13 KERA fcc", + }, + CommonElement{ + Value: "KERA", + }, + CommonElement{ + Value: "KERA", + }, + CommonElement{ + Value: "PBS Affiliate", + }, + }, + Icons: []Icon{ + Icon{ + Source: `file://C:\Perl\site/share/xmltv/icons/KERA.gif`, + }, + }, + } + if !reflect.DeepEqual(ch, tv.Channels[0]) { + t.Errorf("\texpected: %# v\n\t\tactual: %# v\n", pretty.Formatter(ch), pretty.Formatter(tv.Channels[0])) + } + + loc := time.FixedZone("", -6*60*60) + date := time.Date(2008, 07, 11, 0, 0, 0, 0, time.UTC) + pr := Programme{ + ID: "someId", + Date: Date(date), + Channel: "I10436.labs.zap2it.com", + Start: &Time{time.Date(2008, 07, 15, 0, 30, 0, 0, loc)}, + Stop: &Time{time.Date(2008, 07, 15, 1, 0, 0, 0, loc)}, + Titles: []CommonElement{ + CommonElement{ + Lang: "en", + Value: "NOW on PBS", + }, + }, + Descriptions: []CommonElement{ + CommonElement{ + Lang: "en", + Value: "Jordan's Queen Rania has made job creation a priority to help curb the staggering unemployment rates among youths in the Middle East.", + }, + }, + Categories: []CommonElement{ + CommonElement{ + Lang: "en", + Value: "Newsmagazine", + }, + CommonElement{ + Lang: "en", + Value: "Interview", + }, + CommonElement{ + Lang: "en", + Value: "Public affairs", + }, + CommonElement{ + Lang: "en", + Value: "Series", + }, + }, + EpisodeNums: []EpisodeNum{ + EpisodeNum{ + System: "dd_progid", + Value: "EP01006886.0028", + }, + EpisodeNum{ + System: "onscreen", + Value: "427", + }, + }, + Audio: &Audio{ + Stereo: "stereo", + }, + PreviouslyShown: &PreviouslyShown{ + Start: "20080711000000", + }, + Subtitles: []Subtitle{ + Subtitle{ + Type: "teletext", + }, + }, + } + if !reflect.DeepEqual(pr, tv.Programmes[0]) { + expected := fmt.Sprintf("\texpected: %# v\n\t\t\texpected start: %s\n\t\t\texpected stop : %s", pretty.Formatter(pr), pr.Start, pr.Stop) + actual := fmt.Sprintf("\tactual: %# v\n\t\t\tactual start: %s\n\t\t\tactual stop: %s", pretty.Formatter(tv.Programmes[0]), tv.Programmes[0].Start, tv.Programmes[0].Stop) + t.Errorf("%s\n%s\n", expected, actual) + } +} diff --git a/lineup.go b/lineup.go new file mode 100644 index 0000000..53b6715 --- /dev/null +++ b/lineup.go @@ -0,0 +1,889 @@ +package main + +import ( + "compress/gzip" + "encoding/xml" + "fmt" + "io" + "net/http" + "os" + "regexp" + "sort" + "strconv" + "strings" + "time" + + "github.com/spf13/viper" + schedulesdirect "github.com/tellytv/go.schedulesdirect" + m3u "github.com/tellytv/telly/internal/m3uplus" + "github.com/tellytv/telly/internal/providers" + "github.com/tellytv/telly/internal/xmltv" +) + +// var channelNumberRegex = regexp.MustCompile(`^[0-9]+[[:space:]]?$`).MatchString +// var callSignRegex = regexp.MustCompile(`^[A-Z0-9]+$`).MatchString +// var hdRegex = regexp.MustCompile(`hd|4k`) +var xmlNSRegex = regexp.MustCompile(`(\d).(\d).(?:(\d)/(\d))?`) +var ddProgIDRegex = regexp.MustCompile(`(?m)(EP|SH|MV|SP)(\d{7,8}).(\d+).?(?:(\d).(\d))?`) + +// hdHomeRunLineupItem is a HDHomeRun specification compatible representation of a Track available in the lineup. +type hdHomeRunLineupItem struct { + XMLName xml.Name `xml:"Program" json:"-"` + + AudioCodec string `xml:",omitempty" json:",omitempty"` + DRM convertibleBoolean `xml:",omitempty" json:",string,omitempty"` + Favorite convertibleBoolean `xml:",omitempty" json:",string,omitempty"` + GuideName string `xml:",omitempty" json:",omitempty"` + GuideNumber int `xml:",omitempty" json:",string,omitempty"` + HD convertibleBoolean `xml:",omitempty" json:",string,omitempty"` + URL string `xml:",omitempty" json:",omitempty"` + VideoCodec string `xml:",omitempty" json:",omitempty"` + + provider providers.Provider + providerChannel providers.ProviderChannel +} + +func newHDHRItem(provider *providers.Provider, providerChannel *providers.ProviderChannel) hdHomeRunLineupItem { + return hdHomeRunLineupItem{ + DRM: convertibleBoolean(false), + GuideName: providerChannel.Name, + GuideNumber: providerChannel.Number, + Favorite: convertibleBoolean(providerChannel.Favorite), + HD: convertibleBoolean(providerChannel.HD), + URL: fmt.Sprintf("http://%s/auto/v%d", viper.GetString("web.base-address"), providerChannel.Number), + provider: *provider, + providerChannel: *providerChannel, + } +} + +// lineup contains the state of the application. +type lineup struct { + Sources []providers.Provider + + Scanning bool + + // Stores the channel number for found channels without a number. + assignedChannelNumber int + // If true, use channel numbers found in EPG, if any, before assigning. + xmlTVChannelNumbers bool + + channels map[int]hdHomeRunLineupItem + + sd *schedulesdirect.Client + + FfmpegEnabled bool +} + +// newLineup returns a new lineup for the given config struct. +func newLineup() *lineup { + var cfgs []providers.Configuration + + if unmarshalErr := viper.UnmarshalKey("source", &cfgs); unmarshalErr != nil { + log.WithError(unmarshalErr).Panicln("Unable to unmarshal source configuration to slice of providers.Configuration, check your configuration!") + } + + if viper.GetString("iptv.playlist") != "" { + log.Warnln("Legacy --iptv.playlist argument or environment variable provided, using Custom provider with default configuration, this may fail! If so, you should use a configuration file for full flexibility.") + regexStr := ".*" + if viper.IsSet("filter.regex") { + regexStr = viper.GetString("filter.regex") + } + cfgs = append(cfgs, providers.Configuration{ + Name: "Legacy provider created using arguments/environment variables", + M3U: viper.GetString("iptv.playlist"), + Provider: "custom", + Filter: regexStr, + FilterRaw: true, + }) + } + + useFFMpeg := viper.IsSet("iptv.ffmpeg") + if useFFMpeg { + useFFMpeg = viper.GetBool("iptv.ffmpeg") + } + + lineup := &lineup{ + assignedChannelNumber: viper.GetInt("iptv.starting-channel"), + xmlTVChannelNumbers: viper.GetBool("iptv.xmltv-channels"), + channels: make(map[int]hdHomeRunLineupItem), + FfmpegEnabled: useFFMpeg, + } + + if viper.IsSet("schedulesdirect.username") && viper.IsSet("schedulesdirect.password") { + sdClient, sdClientErr := schedulesdirect.NewClient(viper.GetString("schedulesdirect.username"), viper.GetString("schedulesdirect.password")) + if sdClientErr != nil { + log.WithError(sdClientErr).Panicln("error setting up schedules direct client") + } + + lineup.sd = sdClient + } + + for _, cfg := range cfgs { + provider, providerErr := cfg.GetProvider() + if providerErr != nil { + panic(providerErr) + } + + lineup.Sources = append(lineup.Sources, provider) + } + + return lineup +} + +// Scan processes all sources. +func (l *lineup) Scan() error { + + l.Scanning = true + + totalAddedChannels := 0 + + for _, provider := range l.Sources { + addedChannels, providerErr := l.processProvider(provider) + if providerErr != nil { + log.WithError(providerErr).Errorln("error when processing provider") + } + totalAddedChannels = totalAddedChannels + addedChannels + } + + if totalAddedChannels > 420 { + log.Panicf("telly has loaded more than 420 channels (%d) into the lineup. Plex does not deal well with more than this amount and will more than likely hang when trying to fetch channels. You must use regular expressions to filter out channels. You can also start another Telly instance.", totalAddedChannels) + } + + l.Scanning = false + + return nil +} + +func (l *lineup) processProvider(provider providers.Provider) (int, error) { + addedChannels := 0 + m3u, channelMap, programmeMap, prepareErr := l.prepareProvider(provider) + if prepareErr != nil { + log.WithError(prepareErr).Errorln("error when preparing provider") + return 0, prepareErr + } + + if provider.Configuration().SortKey != "" { + sortKey := provider.Configuration().SortKey + sort.Slice(m3u.Tracks, func(i, j int) bool { + if _, ok := m3u.Tracks[i].Tags[sortKey]; ok { + log.Panicf("the provided sort key (%s) doesn't exist in the M3U!", sortKey) + return false + } + ii := m3u.Tracks[i].Tags[sortKey] + jj := m3u.Tracks[j].Tags[sortKey] + if provider.Configuration().SortReverse { + return ii < jj + } + return ii > jj + }) + } + + successChannels := []string{} + failedChannels := []string{} + + for _, track := range m3u.Tracks { + // First, we run the filter. + if !l.FilterTrack(provider, track) { + failedChannels = append(failedChannels, track.Name) + continue + } else { + successChannels = append(successChannels, track.Name) + } + + // Then we do the provider specific translation to a hdHomeRunLineupItem. + channel, channelErr := provider.ParseTrack(track, channelMap) + if channelErr != nil { + return addedChannels, channelErr + } + + channel, processErr := l.processProviderChannel(channel, programmeMap) + if processErr != nil { + log.WithError(processErr).Errorln("error processing track") + continue + } else if channel == nil { + log.Infof("Channel %s was returned empty from the provider (%s)", track.Name, provider.Name()) + continue + } + addedChannels = addedChannels + 1 + + l.channels[channel.Number] = newHDHRItem(&provider, channel) + } + + log.Debugf("These channels (%d) passed the filter and successfully parsed: %s", len(successChannels), strings.Join(successChannels, ", ")) + log.Debugf("These channels (%d) did NOT pass the filter: %s", len(failedChannels), strings.Join(failedChannels, ", ")) + + log.Infof("Loaded %d channels into the lineup from %s", addedChannels, provider.Name()) + + if addedChannels == 0 { + log.Infof("Check your filter; %d channels were blocked by it", len(failedChannels)) + } + + return addedChannels, nil +} + +func (l *lineup) prepareProvider(provider providers.Provider) (*m3u.Playlist, map[string]xmltv.Channel, map[string][]xmltv.Programme, error) { + cacheFiles := provider.Configuration().CacheFiles + + reader, m3uErr := getM3U(provider.PlaylistURL(), cacheFiles) + if m3uErr != nil { + log.WithError(m3uErr).Errorln("unable to get m3u file") + return nil, nil, nil, m3uErr + } + + rawPlaylist, err := m3u.Decode(reader) + if err != nil { + log.WithError(err).Errorln("unable to parse m3u file") + return nil, nil, nil, err + } + + for _, playlistTrack := range rawPlaylist.Tracks { + if (playlistTrack.URI.Scheme == "http" || playlistTrack.URI.Scheme == "udp") && !l.FfmpegEnabled { + log.Errorf("The playlist you tried to add has at least one entry using a protocol other than http or udp and you have ffmpeg disabled in your config. This will most likely not work. Offending URI is %s", playlistTrack.URI) + } + } + + if closeM3UErr := reader.Close(); closeM3UErr != nil { + log.WithError(closeM3UErr).Panicln("error when closing m3u reader") + } + + channelMap, programmeMap, epgErr := l.prepareEPG(provider, cacheFiles) + if epgErr != nil { + log.WithError(epgErr).Errorln("error when parsing EPG") + return nil, nil, nil, epgErr + } + + return rawPlaylist, channelMap, programmeMap, nil +} + +func (l *lineup) processProviderChannel(channel *providers.ProviderChannel, programmeMap map[string][]xmltv.Programme) (*providers.ProviderChannel, error) { + if channel.EPGChannel != nil { + channel.EPGProgrammes = programmeMap[channel.EPGMatch] + } + + if !l.xmlTVChannelNumbers || channel.Number == 0 { + channel.Number = l.assignedChannelNumber + l.assignedChannelNumber = l.assignedChannelNumber + 1 + } + + if channel.EPGChannel != nil && channel.EPGChannel.LCN == 0 { + channel.EPGChannel.LCN = channel.Number + } + + if channel.Logo != "" && channel.EPGChannel != nil && !containsIcon(channel.EPGChannel.Icons, channel.Logo) { + if viper.GetBool("misc.ignore-epg-icons") { + channel.EPGChannel.Icons = nil + } + channel.EPGChannel.Icons = append(channel.EPGChannel.Icons, xmltv.Icon{Source: channel.Logo}) + } + + return channel, nil +} + +func (l *lineup) FilterTrack(provider providers.Provider, track m3u.Track) bool { + config := provider.Configuration() + if config.Filter == "" && len(config.IncludeOnly) == 0 { + return true + } + + if v, ok := track.Tags[config.IncludeOnlyTag]; len(config.IncludeOnly) > 0 && ok { + return contains(config.IncludeOnly, v) + } + + filterRegex, regexErr := regexp.Compile(config.Filter) + if regexErr != nil { + log.WithError(regexErr).Panicln("your regex is invalid") + return false + } + + if config.FilterRaw { + return filterRegex.MatchString(track.Raw) + } + + log.Debugf("track.Tags %+v", track.Tags) + + filterKey := provider.RegexKey() + if config.FilterKey != "" { + filterKey = config.FilterKey + } + + if key, ok := track.Tags[filterKey]; key != "" && !ok { + log.Warnf("the provided filter key (%s) does not exist or is blank, skipping track: %s", config.FilterKey, track.Raw) + return false + } + + log.Debugf("Checking if filter (%s) matches string %s", config.Filter, track.Tags[filterKey]) + + return filterRegex.MatchString(track.Tags[filterKey]) + +} + +func (l *lineup) prepareEPG(provider providers.Provider, cacheFiles bool) (map[string]xmltv.Channel, map[string][]xmltv.Programme, error) { + var epg *xmltv.TV + epgChannelMap := make(map[string]xmltv.Channel) + epgProgrammeMap := make(map[string][]xmltv.Programme) + if provider.EPGURL() != "" { + var epgErr error + epg, epgErr = getXMLTV(provider.EPGURL(), cacheFiles) + if epgErr != nil { + return epgChannelMap, epgProgrammeMap, epgErr + } + + augmentWithSD := viper.IsSet("schedulesdirect.username") && viper.IsSet("schedulesdirect.password") + + sdEligible := make(map[string]xmltv.Programme) // TMSID:programme + haveAllInfo := make(map[string][]xmltv.Programme) // channel number:[]programme + + for _, channel := range epg.Channels { + epgChannelMap[channel.ID] = channel + + for _, programme := range epg.Programmes { + if programme.Channel == channel.ID { + ddProgID := "" + if augmentWithSD { + for _, epNum := range programme.EpisodeNums { + if epNum.System == "dd_progid" { + ddProgID = epNum.Value + } + } + } + if augmentWithSD == true && ddProgID != "" { + idType, uniqID, epID, _, _, extractErr := extractDDProgID(ddProgID) + if extractErr != nil { + log.WithError(extractErr).Errorln("error extracting dd_progid") + continue + } + cleanID := fmt.Sprintf("%s%s%s", idType, padNumberWithZero(uniqID, 8), padNumberWithZero(epID, 4)) + if len(cleanID) < 14 { + log.Warnf("found an invalid TMS ID/dd_progid, expected length of exactly 14, got %d: %s\n", len(cleanID), cleanID) + continue + } + + sdEligible[cleanID] = programme + } else { + haveAllInfo[channel.ID] = append(haveAllInfo[channel.ID], programme) + } + } + } + } + + if augmentWithSD { + tmsIDs := make([]string, 0) + + for tmsID := range sdEligible { + idType, uniqID, epID, _, _, extractErr := extractDDProgID(tmsID) + if extractErr != nil { + log.WithError(extractErr).Errorln("error extracting dd_progid") + continue + } + cleanID := fmt.Sprintf("%s%s%s", idType, padNumberWithZero(uniqID, 8), padNumberWithZero(epID, 4)) + if len(cleanID) < 14 { + log.Warnf("found an invalid TMS ID/dd_progid, expected length of exactly 14, got %d: %s\n", len(cleanID), cleanID) + continue + } + tmsIDs = append(tmsIDs, cleanID) + } + + log.Infof("Requesting guide data for %d programs from Schedules Direct", len(tmsIDs)) + + allResponses := make([]schedulesdirect.ProgramInfo, 0) + + artworkMap := make(map[string][]schedulesdirect.ProgramArtwork) + + chunks := chunkStringSlice(tmsIDs, 5000) + + log.Infof("Making %d requests to Schedules Direct for program information, this might take a while", len(chunks)) + + for _, chunk := range chunks { + moreInfo, moreInfoErr := l.sd.GetProgramInfo(chunk) + if moreInfoErr != nil { + log.WithError(moreInfoErr).Errorln("Error when getting more program details from Schedules Direct") + return epgChannelMap, epgProgrammeMap, moreInfoErr + } + + log.Debugf("received %d responses for chunk", len(moreInfo)) + + allResponses = append(allResponses, moreInfo...) + } + + artworkTMSIDs := make([]string, 0) + + for _, entry := range allResponses { + if entry.HasArtwork() { + artworkTMSIDs = append(artworkTMSIDs, entry.ProgramID) + } + } + + chunks = chunkStringSlice(artworkTMSIDs, 500) + + log.Infof("Making %d requests to Schedules Direct for artwork, this might take a while", len(chunks)) + + for _, chunk := range chunks { + artwork, artworkErr := l.sd.GetArtworkForProgramIDs(chunk) + if artworkErr != nil { + log.WithError(artworkErr).Errorln("Error when getting program artwork from Schedules Direct") + return epgChannelMap, epgProgrammeMap, artworkErr + } + + for _, artworks := range artwork { + if artworks.ProgramID == "" || artworks.Artwork == nil { + continue + } + artworkMap[artworks.ProgramID] = append(artworkMap[artworks.ProgramID], *artworks.Artwork...) + } + } + + log.Debugf("Got %d responses from SD", len(allResponses)) + + for _, sdResponse := range allResponses { + programme := sdEligible[sdResponse.ProgramID] + mergedProgramme := MergeSchedulesDirectAndXMLTVProgramme(&programme, sdResponse, artworkMap[sdResponse.ProgramID]) + haveAllInfo[mergedProgramme.Channel] = append(haveAllInfo[mergedProgramme.Channel], *mergedProgramme) + } + } + + for _, programmes := range haveAllInfo { + for _, programme := range programmes { + processedProgram := *provider.ProcessProgramme(programme) + hasXMLTV := false + itemType := "" + for _, epNum := range processedProgram.EpisodeNums { + if epNum.System == "dd_progid" { + idType, _, _, _, _, extractErr := extractDDProgID(epNum.Value) + if extractErr != nil { + log.WithError(extractErr).Errorln("error extracting dd_progid") + continue + } + itemType = idType + } + if epNum.System == "xmltv_ns" { + hasXMLTV = true + } + } + if (itemType == "SH" || itemType == "EP") && !hasXMLTV { + t := time.Time(processedProgram.Date) + if !t.IsZero() { + processedProgram.EpisodeNums = append(processedProgram.EpisodeNums, xmltv.EpisodeNum{System: "original-air-date", Value: t.Format("2006-01-02 15:04:05")}) + } + } + epgProgrammeMap[programme.Channel] = append(epgProgrammeMap[programme.Channel], processedProgram) + } + } + + } + + return epgChannelMap, epgProgrammeMap, nil +} + +func getM3U(path string, cacheFiles bool) (io.ReadCloser, error) { + safePath := safeStringsRegex.ReplaceAllStringFunc(path, stringSafer) + log.Infof("Loading M3U from %s", safePath) + + file, _, err := getFile(path, cacheFiles) + if err != nil { + return nil, err + } + + return file, nil +} + +func getXMLTV(path string, cacheFiles bool) (*xmltv.TV, error) { + safePath := safeStringsRegex.ReplaceAllStringFunc(path, stringSafer) + log.Infof("Loading XMLTV from %s", safePath) + file, _, err := getFile(path, cacheFiles) + if err != nil { + return nil, err + } + + decoder := xml.NewDecoder(file) + tvSetup := new(xmltv.TV) + if err := decoder.Decode(tvSetup); err != nil { + log.WithError(err).Errorln("Could not decode xmltv programme") + return nil, err + } + + if closeXMLErr := file.Close(); closeXMLErr != nil { + log.WithError(closeXMLErr).Panicln("error when closing xml reader") + } + + return tvSetup, nil +} + +func getFile(path string, cacheFiles bool) (io.ReadCloser, string, error) { + transport := "disk" + + if strings.HasPrefix(strings.ToLower(path), "http") { + + transport = "http" + + req, reqErr := http.NewRequest("GET", path, nil) + if reqErr != nil { + return nil, transport, reqErr + } + + // For whatever reason, some providers only allow access from a "real" User-Agent. + req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36") + + resp, err := http.Get(path) + if err != nil { + return nil, transport, err + } + + if strings.HasSuffix(strings.ToLower(path), ".gz") || resp.Header.Get("Content-Type") == "application/x-gzip" { + log.Infof("File (%s) is gzipp'ed, ungzipping now, this might take a while", path) + gz, gzErr := gzip.NewReader(resp.Body) + if gzErr != nil { + return nil, transport, gzErr + } + + if cacheFiles { + return writeFile(path, transport, gz) + } + + return gz, transport, nil + } + + if cacheFiles { + return writeFile(path, transport, resp.Body) + } + + return resp.Body, transport, nil + } + + file, fileErr := os.Open(path) + if fileErr != nil { + return nil, transport, fileErr + } + + return file, transport, nil +} + +func writeFile(path, transport string, reader io.ReadCloser) (io.ReadCloser, string, error) { + // buf := new(bytes.Buffer) + // buf.ReadFrom(reader) + // buf.Bytes() + return reader, transport, nil +} + +func containsIcon(s []xmltv.Icon, e string) bool { + for _, ss := range s { + if e == ss.Source { + return true + } + } + return false +} + +func chunkStringSlice(sl []string, chunkSize int) [][]string { + var divided [][]string + + for i := 0; i < len(sl); i += chunkSize { + end := i + chunkSize + + if end > len(sl) { + end = len(sl) + } + + divided = append(divided, sl[i:end]) + } + return divided +} + +func MergeSchedulesDirectAndXMLTVProgramme(programme *xmltv.Programme, sdProgram schedulesdirect.ProgramInfo, artworks []schedulesdirect.ProgramArtwork) *xmltv.Programme { + + allTitles := make([]string, 0) + + for _, title := range programme.Titles { + allTitles = append(allTitles, title.Value) + } + + for _, title := range sdProgram.Titles { + allTitles = append(allTitles, title.Title120) + } + + for _, title := range UniqueStrings(allTitles) { + programme.Titles = append(programme.Titles, xmltv.CommonElement{Value: title}) + } + + allKeywords := make([]string, 0) + + for _, keyword := range programme.Keywords { + allKeywords = append(allKeywords, keyword.Value) + } + + for _, keywords := range sdProgram.Keywords { + for _, keyword := range keywords { + allKeywords = append(allKeywords, keyword) + } + } + + for _, keyword := range UniqueStrings(allKeywords) { + programme.Keywords = append(programme.Keywords, xmltv.CommonElement{Value: keyword}) + } + + // FIXME: We should really be making sure that we passthrough languages. + allDescriptions := make([]string, 0) + + for _, description := range programme.Descriptions { + allDescriptions = append(allDescriptions, description.Value) + } + + for _, descriptions := range sdProgram.Descriptions { + for _, description := range descriptions { + if description.Description != "" { + allDescriptions = append(allDescriptions, description.Description) + } + if description.Description != "" { + allDescriptions = append(allDescriptions, description.Description) + } + } + } + + for _, description := range UniqueStrings(allDescriptions) { + programme.Descriptions = append(programme.Descriptions, xmltv.CommonElement{Value: description}) + } + + allRatings := make(map[string]string, 0) + + for _, rating := range programme.Ratings { + allRatings[rating.System] = rating.Value + } + + for _, rating := range sdProgram.ContentRating { + allRatings[rating.Body] = rating.Code + } + + for system, rating := range allRatings { + programme.Ratings = append(programme.Ratings, xmltv.Rating{Value: rating, System: system}) + } + + for _, artwork := range artworks { + programme.Icons = append(programme.Icons, xmltv.Icon{ + Source: getImageURL(artwork.URI), + Width: artwork.Width, + Height: artwork.Height, + }) + } + + hasXMLTVNS := false + ddProgID := "" + + for _, epNum := range programme.EpisodeNums { + if epNum.System == "xmltv_ns" { + hasXMLTVNS = true + } else if epNum.System == "dd_progid" { + ddProgID = epNum.Value + } + } + + if !hasXMLTVNS { + seasonNumber := 0 + episodeNumber := 0 + totalSeasons := 0 + totalEpisodes := 0 + numbersFilled := false + + for _, meta := range sdProgram.Metadata { + for _, metadata := range meta { + if metadata.Season > 0 { + seasonNumber = metadata.Season - 1 // SD metadata isnt 0 index + numbersFilled = true + } + if metadata.Episode > 0 { + episodeNumber = metadata.Episode - 1 + numbersFilled = true + } + if metadata.TotalEpisodes > 0 { + totalEpisodes = metadata.TotalEpisodes + numbersFilled = true + } + if metadata.TotalSeasons > 0 { + totalSeasons = metadata.TotalSeasons + numbersFilled = true + } + } + } + + if numbersFilled { + seasonNumberStr := fmt.Sprintf("%d", seasonNumber) + if totalSeasons > 0 { + seasonNumberStr = fmt.Sprintf("%d/%d", seasonNumber, totalSeasons) + } + episodeNumberStr := fmt.Sprintf("%d", episodeNumber) + if totalEpisodes > 0 { + episodeNumberStr = fmt.Sprintf("%d/%d", episodeNumber, totalEpisodes) + } + + partNumber := 0 + totalParts := 0 + + if ddProgID != "" { + var extractErr error + _, _, _, partNumber, totalParts, extractErr = extractDDProgID(ddProgID) + if extractErr != nil { + panic(extractErr) + } + } + + partStr := "0" + if partNumber > 0 { + partStr = fmt.Sprintf("%d", partNumber) + if totalParts > 0 { + partStr = fmt.Sprintf("%d/%d", partNumber, totalParts) + } + } + + xmlTVNS := fmt.Sprintf("%s.%s.%s", seasonNumberStr, episodeNumberStr, partStr) + programme.EpisodeNums = append(programme.EpisodeNums, xmltv.EpisodeNum{System: "xmltv_ns", Value: xmlTVNS}) + } + } + + return programme +} + +func extractXMLTVNS(str string) (int, int, int, int, error) { + matches := xmlNSRegex.FindAllStringSubmatch(str, -1) + + if len(matches) == 0 { + return 0, 0, 0, 0, fmt.Errorf("invalid xmltv_ns: %s", str) + } + + season, seasonErr := strconv.Atoi(matches[0][1]) + if seasonErr != nil { + return 0, 0, 0, 0, seasonErr + } + + episode, episodeErr := strconv.Atoi(matches[0][2]) + if episodeErr != nil { + return 0, 0, 0, 0, episodeErr + } + + currentPartNum := 0 + totalPartsNum := 0 + + if len(matches[0]) > 2 && matches[0][3] != "" { + currentPart, currentPartErr := strconv.Atoi(matches[0][3]) + if currentPartErr != nil { + return 0, 0, 0, 0, currentPartErr + } + currentPartNum = currentPart + } + + if len(matches[0]) > 3 && matches[0][4] != "" { + totalParts, totalPartsErr := strconv.Atoi(matches[0][4]) + if totalPartsErr != nil { + return 0, 0, 0, 0, totalPartsErr + } + totalPartsNum = totalParts + } + + // if season > 0 { + // season = season - 1 + // } + + // if episode > 0 { + // episode = episode - 1 + // } + + // if currentPartNum > 0 { + // currentPartNum = currentPartNum - 1 + // } + + // if totalPartsNum > 0 { + // totalPartsNum = totalPartsNum - 1 + // } + + return season, episode, currentPartNum, totalPartsNum, nil +} + +// extractDDProgID returns type, ID, episode ID, part number, total parts, error. +func extractDDProgID(progID string) (string, int, int, int, int, error) { + matches := ddProgIDRegex.FindAllStringSubmatch(progID, -1) + + if len(matches) == 0 { + return "", 0, 0, 0, 0, fmt.Errorf("invalid dd_progid: %s", progID) + } + + itemType := matches[0][1] + + itemID, itemIDErr := strconv.Atoi(matches[0][2]) + if itemIDErr != nil { + return itemType, 0, 0, 0, 0, itemIDErr + } + + specificID, specificIDErr := strconv.Atoi(matches[0][3]) + if specificIDErr != nil { + return itemType, itemID, 0, 0, 0, specificIDErr + } + + currentPartNum := 0 + totalPartsNum := 0 + + if len(matches[0]) > 2 && matches[0][4] != "" { + currentPart, currentPartErr := strconv.Atoi(matches[0][4]) + if currentPartErr != nil { + return itemType, itemID, specificID, 0, 0, currentPartErr + } + currentPartNum = currentPart + } + + if len(matches[0]) > 3 && matches[0][5] != "" { + totalParts, totalPartsErr := strconv.Atoi(matches[0][5]) + if totalPartsErr != nil { + return itemType, itemID, specificID, currentPartNum, 0, totalPartsErr + } + totalPartsNum = totalParts + } + + return itemType, itemID, specificID, currentPartNum, totalPartsNum, nil +} + +func UniqueStrings(input []string) []string { + u := make([]string, 0, len(input)) + m := make(map[string]bool) + + for _, val := range input { + if _, ok := m[val]; !ok { + m[val] = true + u = append(u, val) + } + } + + return u +} + +func getImageURL(imageURI string) string { + if strings.HasPrefix(imageURI, "https://s3.amazonaws.com") { + return imageURI + } + return fmt.Sprint(schedulesdirect.DefaultBaseURL, schedulesdirect.APIVersion, "/image/", imageURI) +} + +func padNumberWithZero(value int, expectedLength int) string { + padded := fmt.Sprintf("%02d", value) + valLength := countDigits(value) + if valLength != expectedLength { + return fmt.Sprintf("%s%d", strings.Repeat("0", expectedLength-valLength), value) + } + return padded +} + +func countDigits(i int) int { + count := 0 + if i == 0 { + count = 1 + } + for i != 0 { + i /= 10 + count = count + 1 + } + return count +} + +func contains(s []string, e string) bool { + for _, ss := range s { + if e == ss { + return true + } + } + return false +} diff --git a/main.go b/main.go index 896263f..4112fa0 100644 --- a/main.go +++ b/main.go @@ -1,24 +1,32 @@ package main import ( - "encoding/base64" + "encoding/json" + fflag "flag" "fmt" - "io" - "net/http" + "net" "os" - "strconv" + "regexp" "strings" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/common/version" "github.com/sirupsen/logrus" - "github.com/tellytv/telly/m3u" - kingpin "gopkg.in/alecthomas/kingpin.v2" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" ) var ( - log = logrus.New() - opts = config{} + namespace = "telly" + namespaceWithVersion = fmt.Sprintf("%s %s", namespace, version.Version) + log = &logrus.Logger{ + Out: os.Stderr, + Formatter: &logrus.TextFormatter{ + FullTimestamp: true, + }, + Hooks: make(logrus.LevelHooks), + Level: logrus.DebugLevel, + } exposedChannels = prometheus.NewGauge( prometheus.GaugeOpts{ @@ -26,167 +34,171 @@ var ( Help: "Number of exposed channels.", }, ) + + safeStringsRegex = regexp.MustCompile(`(?m)(username|password|token)=[\w=]+(&?)`) + + stringSafer = func(input string) string { + ret := input + if strings.HasPrefix(input, "username=") { + ret = "username=REDACTED" + } else if strings.HasPrefix(input, "password=") { + ret = "password=REDACTED" + } else if strings.HasPrefix(input, "token=") { + ret = "token=bm90Zm9yeW91" // "notforyou" + } + if strings.HasSuffix(input, "&") { + return fmt.Sprintf("%s&", ret) + } + return ret + } ) func main() { // Discovery flags - kingpin.Flag("discovery.device-id", "8 digits used to uniquely identify the device. $(TELLY_DISCOVERY_DEVICE_ID)").Envar("TELLY_DISCOVERY_DEVICE_ID").Default("12345678").IntVar(&opts.DeviceID) - kingpin.Flag("discovery.device-friendly-name", "Name exposed via discovery. Useful if you are running two instances of telly and want to differentiate between them $(TELLY_DISCOVERY_DEVICE_FRIENDLY_NAME)").Envar("TELLY_DISCOVERY_DEVICE_FRIENDLY_NAME").Default("telly").StringVar(&opts.FriendlyName) - kingpin.Flag("discovery.device-auth", "Only change this if you know what you're doing $(TELLY_DISCOVERY_DEVICE_AUTH)").Envar("TELLY_DISCOVERY_DEVICE_AUTH").Default("telly123").Hidden().StringVar(&opts.DeviceAuth) - kingpin.Flag("discovery.device-manufacturer", "Manufacturer exposed via discovery. $(TELLY_DISCOVERY_DEVICE_MANUFACTURER)").Envar("TELLY_DISCOVERY_DEVICE_MANUFACTURER").Default("Silicondust").StringVar(&opts.Manufacturer) - kingpin.Flag("discovery.device-model-number", "Model number exposed via discovery. $(TELLY_DISCOVERY_DEVICE_MODEL_NUMBER)").Envar("TELLY_DISCOVERY_DEVICE_MODEL_NUMBER").Default("HDTC-2US").StringVar(&opts.ModelNumber) - kingpin.Flag("discovery.device-firmware-name", "Firmware name exposed via discovery. $(TELLY_DISCOVERY_DEVICE_FIRMWARE_NAME)").Envar("TELLY_DISCOVERY_DEVICE_FIRMWARE_NAME").Default("hdhomeruntc_atsc").StringVar(&opts.FirmwareName) - kingpin.Flag("discovery.device-firmware-version", "Firmware version exposed via discovery. $(TELLY_DISCOVERY_DEVICE_FIRMWARE_VERSION)").Envar("TELLY_DISCOVERY_DEVICE_FIRMWARE_VERSION").Default("20150826").StringVar(&opts.FirmwareVersion) - kingpin.Flag("discovery.ssdp", "Turn on SSDP announcement of telly to the local network $(TELLY_DISCOVERY_SSDP)").Envar("TELLY_DISCOVERY_SSDP").Default("true").BoolVar(&opts.SSDP) + flag.String("discovery.device-id", "12345678", "8 alpha-numeric characters used to uniquely identify the device. $(TELLY_DISCOVERY_DEVICE_ID)") + flag.String("discovery.device-friendly-name", "telly", "Name exposed via discovery. Useful if you are running two instances of telly and want to differentiate between them $(TELLY_DISCOVERY_DEVICE_FRIENDLY_NAME)") + flag.String("discovery.device-auth", "telly123", "Only change this if you know what you're doing $(TELLY_DISCOVERY_DEVICE_AUTH)") + flag.String("discovery.device-manufacturer", "Silicondust", "Manufacturer exposed via discovery. $(TELLY_DISCOVERY_DEVICE_MANUFACTURER)") + flag.String("discovery.device-model-number", "HDTC-2US", "Model number exposed via discovery. $(TELLY_DISCOVERY_DEVICE_MODEL_NUMBER)") + flag.String("discovery.device-firmware-name", "hdhomeruntc_atsc", "Firmware name exposed via discovery. $(TELLY_DISCOVERY_DEVICE_FIRMWARE_NAME)") + flag.String("discovery.device-firmware-version", "20150826", "Firmware version exposed via discovery. $(TELLY_DISCOVERY_DEVICE_FIRMWARE_VERSION)") + flag.Bool("discovery.ssdp", true, "Turn on SSDP announcement of telly to the local network $(TELLY_DISCOVERY_SSDP)") // Regex/filtering flags - kingpin.Flag("filter.regex-inclusive", "Whether the provided regex is inclusive (whitelisting) or exclusive (blacklisting). If true (--filter.regex-inclusive), only channels matching the provided regex pattern will be exposed. If false (--no-filter.regex-inclusive), only channels NOT matching the provided pattern will be exposed. $(TELLY_FILTER_REGEX_MODE)").Envar("TELLY_FILTER_REGEX_MODE").Default("false").BoolVar(&opts.RegexInclusive) - kingpin.Flag("filter.regex", "Use regex to filter for channels that you want. A basic example would be .*UK.*. $(TELLY_FILTER_REGEX)").Envar("TELLY_FILTER_REGEX").Default(".*").RegexpVar(&opts.Regex) + flag.Bool("filter.regex-inclusive", false, "Whether the provided regex is inclusive (whitelisting) or exclusive (blacklisting). If true (--filter.regex-inclusive), only channels matching the provided regex pattern will be exposed. If false (--no-filter.regex-inclusive), only channels NOT matching the provided pattern will be exposed. $(TELLY_FILTER_REGEX_INCLUSIVE)") + flag.String("filter.regex", ".*", "Use regex to filter for channels that you want. A basic example would be .*UK.*. $(TELLY_FILTER_REGEX)") // Web flags - kingpin.Flag("web.listen-address", "Address to listen on for web interface and telemetry $(TELLY_WEB_LISTEN_ADDRESS)").Envar("TELLY_WEB_LISTEN_ADDRESS").Default("localhost:6077").TCPVar(&opts.ListenAddress) - kingpin.Flag("web.base-address", "The address to expose via discovery. Useful with reverse proxy $(TELLY_WEB_BASE_ADDRESS)").Envar("TELLY_WEB_BASE_ADDRESS").Default("localhost:6077").TCPVar(&opts.BaseAddress) + flag.StringP("web.listen-address", "l", "localhost:6077", "Address to listen on for web interface and telemetry $(TELLY_WEB_LISTEN_ADDRESS)") + flag.StringP("web.base-address", "b", "localhost:6077", "The address to expose via discovery. Useful with reverse proxy $(TELLY_WEB_BASE_ADDRESS)") // Log flags - kingpin.Flag("log.level", "Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal] $(TELLY_LOG_LEVEL)").Envar("TELLY_LOG_LEVEL").Default(logrus.InfoLevel.String()).StringVar(&opts.LogLevel) - kingpin.Flag("log.requests", "Log HTTP requests $(TELLY_LOG_REQUESTS)").Envar("TELLY_LOG_REQUESTS").Default("false").BoolVar(&opts.LogRequests) + flag.String("log.level", logrus.InfoLevel.String(), "Only log messages with the given severity or above. Valid levels: [debug, info, warn, error, fatal] $(TELLY_LOG_LEVEL)") + flag.Bool("log.requests", false, "Log HTTP requests $(TELLY_LOG_REQUESTS)") // IPTV flags - kingpin.Flag("iptv.playlist", "Location of playlist M3U file. Can be on disk or a URL. $(TELLY_IPTV_PLAYLIST)").Envar("TELLY_IPTV_PLAYLIST").Default("iptv.m3u").StringVar(&opts.M3UPath) - kingpin.Flag("iptv.streams", "Number of concurrent streams allowed $(TELLY_IPTV_STREAMS)").Envar("TELLY_IPTV_STREAMS").Default("1").IntVar(&opts.ConcurrentStreams) - kingpin.Flag("iptv.direct", "If true, stream URLs will not be obfuscated to hide them from Plex. $(TELLY_IPTV_DIRECT)").Envar("TELLY_IPTV_DIRECT").Default("false").BoolVar(&opts.DirectMode) - kingpin.Flag("iptv.starting-channel", "The channel number to start exposing from. $(TELLY_IPTV_STARTING_CHANNEL)").Envar("TELLY_IPTV_STARTING_CHANNEL").Default("10000").IntVar(&opts.StartingChannel) - - kingpin.Version(version.Print("telly")) - kingpin.HelpFlag.Short('h') - kingpin.Parse() - - log.Infoln("Starting telly", version.Info()) - log.Infoln("Build context", version.BuildContext()) - - prometheus.MustRegister(version.NewCollector("telly"), exposedChannels) - - level, parseLevelErr := logrus.ParseLevel(opts.LogLevel) - if parseLevelErr != nil { - log.WithError(parseLevelErr).Panicln("error setting log level!") + flag.String("iptv.playlist", "", "Path to an M3U file on disk or at a URL. $(TELLY_IPTV_PLAYLIST)") + flag.Int("iptv.streams", 1, "Number of concurrent streams allowed $(TELLY_IPTV_STREAMS)") + flag.Int("iptv.starting-channel", 10000, "The channel number to start exposing from. $(TELLY_IPTV_STARTING_CHANNEL)") + flag.Bool("iptv.xmltv-channels", true, "Use channel numbers discovered via XMLTV file, if provided. $(TELLY_IPTV_XMLTV_CHANNELS)") + + // Misc flags + flag.StringP("config.file", "c", "", "Path to your config file. If not set, configuration is searched for in the current working directory, $HOME/.telly/ and /etc/telly/. If provided, it will override all other arguments and environment variables. $(TELLY_CONFIG_FILE)") + flag.Bool("version", false, "Show application version") + + flag.CommandLine.AddGoFlagSet(fflag.CommandLine) + + deprecatedFlags := []string{ + "discovery.device-id", + "discovery.device-friendly-name", + "discovery.device-auth", + "discovery.device-manufacturer", + "discovery.device-model-number", + "discovery.device-firmware-name", + "discovery.device-firmware-version", + "discovery.ssdp", + "iptv.playlist", + "iptv.streams", + "iptv.starting-channel", + "iptv.xmltv-channels", + "filter.regex-inclusive", + "filter.regex", } - log.SetLevel(level) - opts.DeviceUUID = fmt.Sprintf("%d-AE2A-4E54-BBC9-33AF7D5D6A92", opts.DeviceID) - - if opts.BaseAddress.IP.IsUnspecified() { - log.Panicln("base URL is set to 0.0.0.0, this will not work. please use the --web.base-address option and set it to the (local) ip address telly is running on.") + for _, depFlag := range deprecatedFlags { + if depErr := flag.CommandLine.MarkDeprecated(depFlag, "use the configuration file instead."); depErr != nil { + log.WithError(depErr).Panicf("error marking flag %s as deprecated", depFlag) + } } - if opts.ListenAddress.IP.IsUnspecified() && opts.BaseAddress.IP.IsLoopback() { - log.Warnln("You are listening on all interfaces but your base URL is localhost (meaning Plex will try and load localhost to access your streams) - is this intended?") + flag.Parse() + if bindErr := viper.BindPFlags(flag.CommandLine); bindErr != nil { + log.WithError(bindErr).Panicln("error binding flags to viper") } - if opts.M3UPath == "iptv.m3u" { - log.Warnln("using default m3u option, 'iptv.m3u'. launch telly with the --iptv.playlist=yourfile.m3u option to change this!") + if flag.Lookup("version").Changed { + fmt.Println(version.Print(namespace)) + os.Exit(0) } - m3uReader, readErr := getM3U(opts) - if readErr != nil { - log.WithError(readErr).Panicln("error getting m3u") + if flag.Lookup("config.file").Changed { + viper.SetConfigFile(flag.Lookup("config.file").Value.String()) + } else { + viper.SetConfigName("telly.config") + viper.AddConfigPath("/etc/telly/") + viper.AddConfigPath("$HOME/.telly") + viper.AddConfigPath(".") + viper.SetEnvPrefix(namespace) + viper.AutomaticEnv() } - playlist, err := m3u.Decode(m3uReader) + err := viper.ReadInConfig() if err != nil { - log.WithError(err).Panicln("unable to parse m3u file") - } - - channels, filterErr := filterTracks(playlist.Tracks) - if filterErr != nil { - log.WithError(filterErr).Panicln("error during filtering of channels, check your regex and try again") + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + log.WithError(err).Panicln("fatal error while reading config file:") + } } - log.Debugln("Building lineup") - - opts.lineup = buildLineup(opts, channels) - - channelCount := len(channels) - exposedChannels.Set(float64(channelCount)) - log.Infof("found %d channels", channelCount) + prometheus.MustRegister(version.NewCollector("telly"), exposedChannels) - if channelCount > 420 { - log.Warnln("telly has loaded more than 420 channels. Plex does not deal well with more than this amount and will more than likely hang when trying to fetch channels. You have been warned!") + level, parseLevelErr := logrus.ParseLevel(viper.GetString("log.level")) + if parseLevelErr != nil { + log.WithError(parseLevelErr).Panicln("error setting log level!") } + log.SetLevel(level) - opts.FriendlyName = fmt.Sprintf("HDHomerun (%s)", opts.FriendlyName) - - serve(opts) -} - -func buildLineup(opts config, channels []Track) []LineupItem { - lineup := make([]LineupItem, 0) - gn := opts.StartingChannel - - for _, track := range channels { - - var finalName string - if track.TvgName == "" { - finalName = track.Name - } else { - finalName = track.TvgName - } + log.Infoln("telly is preparing to go live", version.Info()) + log.Debugln("Build context", version.BuildContext()) - // base64 url - fullTrackURI := track.URI - if !opts.DirectMode { - trackURI := base64.StdEncoding.EncodeToString([]byte(track.URI)) - fullTrackURI = fmt.Sprintf("http://%s/stream/%s", opts.BaseAddress.String(), trackURI) - } + validateConfig() - if strings.Contains(track.URI, ".m3u8") { - log.Warnln("your .m3u contains .m3u8's. Plex has either stopped supporting m3u8 or it is a bug in a recent version - please use .ts! telly will automatically convert these in a future version. See telly github issue #108") - } + viper.Set("discovery.device-uuid", fmt.Sprintf("%s-AE2A-4E54-BBC9-33AF7D5D6A92", viper.GetString("discovery.device-id"))) - lu := LineupItem{ - GuideNumber: strconv.Itoa(gn), - GuideName: finalName, - URL: fullTrackURI, + if log.Level == logrus.DebugLevel { + js, jsErr := json.MarshalIndent(viper.AllSettings(), "", " ") + if jsErr != nil { + log.WithError(jsErr).Panicln("error marshal indenting viper config to JSON") } + log.Debugf("Loaded configuration %s", js) + } - lineup = append(lineup, lu) + lineup := newLineup() - gn = gn + 1 + if scanErr := lineup.Scan(); scanErr != nil { + log.WithError(scanErr).Panicln("Error scanning lineup!") } - return lineup + serve(lineup) } -func getM3U(opts config) (io.Reader, error) { - if strings.HasPrefix(strings.ToLower(opts.M3UPath), "http") { - log.Debugf("Downloading M3U from %s", opts.M3UPath) - resp, err := http.Get(opts.M3UPath) - if err != nil { - return nil, err +func validateConfig() { + if viper.IsSet("filter.regexstr") { + if _, regexErr := regexp.Compile(viper.GetString("filter.regex")); regexErr != nil { + log.WithError(regexErr).Panicln("Error when compiling regex, is it valid?") } - //defer resp.Body.Close() - - return resp.Body, nil } - log.Debugf("Reading M3U file %s...", opts.M3UPath) - - return os.Open(opts.M3UPath) -} + if !(viper.IsSet("source")) { + log.Warnln("There is no source element in the configuration, the config file is likely missing.") + } -func filterTracks(tracks []*m3u.Track) ([]Track, error) { - allowedTracks := make([]Track, 0) + var addrErr error + if _, addrErr = net.ResolveTCPAddr("tcp", viper.GetString("web.listenaddress")); addrErr != nil { + log.WithError(addrErr).Panic("Error when parsing Listen address, please check the address and try again.") + return + } - for _, oldTrack := range tracks { - track := Track{Track: oldTrack} - if unmarshalErr := oldTrack.UnmarshalTags(&track); unmarshalErr != nil { - return nil, unmarshalErr - } + if _, addrErr = net.ResolveTCPAddr("tcp", viper.GetString("web.base-address")); addrErr != nil { + log.WithError(addrErr).Panic("Error when parsing Base addresses, please check the address and try again.") + return + } - if opts.Regex.MatchString(track.Name) == opts.RegexInclusive { - allowedTracks = append(allowedTracks, track) - } + if getTCPAddr("web.base-address").IP.IsUnspecified() { + log.Panicln("base URL is set to 0.0.0.0, this will not work. please use the --web.baseaddress option and set it to the (local) ip address telly is running on.") } - return allowedTracks, nil + if getTCPAddr("web.listenaddress").IP.IsUnspecified() && getTCPAddr("web.base-address").IP.IsLoopback() { + log.Warnln("You are listening on all interfaces but your base URL is localhost (meaning Plex will try and load localhost to access your streams) - is this intended?") + } } diff --git a/routes.go b/routes.go index 854e155..225d87a 100644 --- a/routes.go +++ b/routes.go @@ -1,31 +1,45 @@ package main import ( - "encoding/base64" + "bufio" + "bytes" + "encoding/xml" "fmt" + "io" "net/http" + "os/exec" + "sort" + "strconv" + "strings" "time" + "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" + "github.com/gobuffalo/packr" ssdp "github.com/koron/go-ssdp" "github.com/sirupsen/logrus" - ginprometheus "github.com/zsais/go-gin-prometheus" + "github.com/spf13/viper" + ginprometheus "github.com/tellytv/telly/internal/go-gin-prometheus" + "github.com/tellytv/telly/internal/xmltv" ) -func serve(opts config) { - discoveryData := opts.DiscoveryData() +func serve(lineup *lineup) { + discoveryData := getDiscoveryData() log.Debugln("creating device xml") upnp := discoveryData.UPNP() log.Debugln("creating webserver routes") - gin.SetMode(gin.ReleaseMode) + if viper.GetString("log.level") != logrus.DebugLevel.String() { + gin.SetMode(gin.ReleaseMode) + } router := gin.New() + router.Use(cors.Default()) router.Use(gin.Recovery()) - if opts.LogRequests { + if viper.GetBool("log.logrequests") { router.Use(ginrus()) } @@ -34,28 +48,63 @@ func serve(opts config) { router.GET("/", deviceXML(upnp)) router.GET("/discover.json", discovery(discoveryData)) - router.GET("/lineup_status.json", lineupStatus(LineupStatus{ - ScanInProgress: 0, - ScanPossible: 1, - Source: "Cable", - SourceList: []string{"Cable"}, - })) - router.GET("/lineup.post", func(c *gin.Context) { - c.AbortWithStatus(http.StatusNotImplemented) + router.GET("/lineup_status.json", func(c *gin.Context) { + payload := LineupStatus{ + ScanInProgress: convertibleBoolean(false), + ScanPossible: convertibleBoolean(true), + Source: "Cable", + SourceList: []string{"Cable"}, + } + if lineup.Scanning { + payload = LineupStatus{ + ScanInProgress: convertibleBoolean(true), + // Gotta fake out Plex. + Progress: 50, + Found: 50, + } + } + + c.JSON(http.StatusOK, payload) + }) + router.POST("/lineup.post", func(c *gin.Context) { + scanAction := c.Query("scan") + if scanAction == "start" { + if refreshErr := lineup.Scan(); refreshErr != nil { + c.AbortWithError(http.StatusInternalServerError, refreshErr) + } + c.AbortWithStatus(http.StatusOK) + return + } else if scanAction == "abort" { + c.AbortWithStatus(http.StatusOK) + return + } + c.String(http.StatusBadRequest, "%s is not a valid scan command", scanAction) }) router.GET("/device.xml", deviceXML(upnp)) - router.GET("/lineup.json", lineup(opts.lineup)) - router.GET("/stream/:channelID", stream) + router.GET("/lineup.json", serveLineup(lineup)) + router.GET("/lineup.xml", serveLineup(lineup)) + router.GET("/auto/:channelID", stream(lineup)) + router.GET("/epg.xml", xmlTV(lineup)) + router.GET("/debug.json", func(c *gin.Context) { + c.JSON(http.StatusOK, lineup) + }) - if opts.SSDP { - log.Debugln("advertising telly service on network via UPNP/SSDP") - if ssdpErr := setupSSDP(opts.BaseAddress.String(), opts.FriendlyName, opts.DeviceUUID); ssdpErr != nil { + if viper.GetBool("discovery.ssdp") { + if _, ssdpErr := setupSSDP(viper.GetString("web.base-address"), viper.GetString("discovery.device-friendly-name"), viper.GetString("discovery.device-uuid")); ssdpErr != nil { log.WithError(ssdpErr).Errorln("telly cannot advertise over ssdp") } } - log.Infof("Listening and serving HTTP on %s", opts.ListenAddress) - if err := router.Run(opts.ListenAddress.String()); err != nil { + box := packr.NewBox("./frontend/dist/telly-fe") + + router.StaticFS("/manage", box) + + log.Infof("telly is live and on the air!") + log.Infof("Broadcasting from http://%s/", viper.GetString("web.base-address")) + log.Infof("EPG URL: http://%s/epg.xml", viper.GetString("web.base-address")) + log.Infof("Lineup JSON: http://%s/lineup.json", viper.GetString("web.base-address")) + + if err := router.Run(viper.GetString("web.listen-address")); err != nil { log.WithError(err).Panicln("Error starting up web server") } } @@ -72,33 +121,129 @@ func discovery(data DiscoveryData) gin.HandlerFunc { } } -func lineupStatus(status LineupStatus) gin.HandlerFunc { +type hdhrLineupContainer struct { + XMLName xml.Name `xml:"Lineup" json:"-"` + Programs []hdHomeRunLineupItem +} + +func serveLineup(lineup *lineup) gin.HandlerFunc { return func(c *gin.Context) { - c.JSON(http.StatusOK, status) + channels := make([]hdHomeRunLineupItem, 0) + for _, channel := range lineup.channels { + channels = append(channels, channel) + } + sort.Slice(channels, func(i, j int) bool { + return channels[i].GuideNumber < channels[j].GuideNumber + }) + if strings.HasSuffix(c.Request.URL.String(), ".xml") { + buf, marshallErr := xml.MarshalIndent(hdhrLineupContainer{Programs: channels}, "", "\t") + if marshallErr != nil { + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error marshalling lineup to XML")) + } + c.Data(http.StatusOK, "application/xml", []byte(``+"\n"+string(buf))) + return + } + c.JSON(http.StatusOK, channels) } } -func lineup(lineup []LineupItem) gin.HandlerFunc { +func xmlTV(lineup *lineup) gin.HandlerFunc { + epg := &xmltv.TV{ + GeneratorInfoName: namespaceWithVersion, + GeneratorInfoURL: "https://github.com/tellytv/telly", + } + + for _, channel := range lineup.channels { + if channel.providerChannel.EPGChannel != nil { + epg.Channels = append(epg.Channels, *channel.providerChannel.EPGChannel) + epg.Programmes = append(epg.Programmes, channel.providerChannel.EPGProgrammes...) + } + } + + sort.Slice(epg.Channels, func(i, j int) bool { return epg.Channels[i].LCN < epg.Channels[j].LCN }) + return func(c *gin.Context) { - c.JSON(http.StatusOK, lineup) + buf, marshallErr := xml.MarshalIndent(epg, "", "\t") + if marshallErr != nil { + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error marshalling EPG to XML")) + } + c.Data(http.StatusOK, "application/xml", []byte(xml.Header+``+"\n"+string(buf))) } } -func stream(c *gin.Context) { +func stream(lineup *lineup) gin.HandlerFunc { + return func(c *gin.Context) { + channelIDStr := c.Param("channelID")[1:] + channelID, channelIDErr := strconv.Atoi(channelIDStr) + if channelIDErr != nil { + c.AbortWithError(http.StatusBadRequest, fmt.Errorf("that (%s) doesn't appear to be a valid channel number", channelIDStr)) + return + } - channelID := c.Param("channelID") + if channel, ok := lineup.channels[channelID]; ok { + channelURI := channel.providerChannel.Track.URI - log.Debugf("Parsing URI %s to %s", c.Request.RequestURI, channelID) + log.Infof("Serving channel number %d", channelID) - decodedStreamURI, decodeErr := base64.StdEncoding.DecodeString(channelID) - if decodeErr != nil { - log.WithError(decodeErr).Errorf("Invalid base64: %s", channelID) - c.AbortWithError(http.StatusBadRequest, decodeErr) // nolint: errcheck - return - } + if !lineup.FfmpegEnabled { + log.Debugf("Redirecting caller to %s", channelURI) + c.Redirect(http.StatusMovedPermanently, channelURI.String()) + return + } + + log.Infoln("Remuxing stream with ffmpeg") + run := exec.Command("ffmpeg", "-i", "pipe:0", "-c:v", "copy", "-f", "mpegts", "pipe:1") + log.Debugf("Executing ffmpeg as \"%s\"", strings.Join(run.Args, " ")) + ffmpegout, err := run.StdoutPipe() + if err != nil { + log.WithError(err).Errorln("StdoutPipe Error") + return + } + + stderr, stderrErr := run.StderrPipe() + if stderrErr != nil { + log.WithError(stderrErr).Errorln("Error creating ffmpeg stderr pipe") + } - log.Debugln("Redirecting to:", string(decodedStreamURI)) - c.Redirect(http.StatusMovedPermanently, string(decodedStreamURI)) + if startErr := run.Start(); startErr != nil { + log.WithError(startErr).Errorln("Error starting ffmpeg") + return + } + defer run.Wait() + + go func() { + scanner := bufio.NewScanner(stderr) + scanner.Split(split) + for scanner.Scan() { + log.Println(scanner.Text()) + } + }() + + continueStream := true + c.Header("Content-Type", `video/mpeg; codecs="avc1.4D401E"`) + + c.Stream(func(w io.Writer) bool { + defer func() { + log.Infoln("Stopped streaming", channelID) + if killErr := run.Process.Kill(); killErr != nil { + panic(killErr) + } + continueStream = false + return + }() + if _, copyErr := io.Copy(w, ffmpegout); copyErr != nil { + log.WithError(copyErr).Errorln("Error when copying data") + continueStream = false + return false + } + return continueStream + }) + + return + } + + c.AbortWithError(http.StatusNotFound, fmt.Errorf("unknown channel number %d", channelID)) + } } func ginrus() gin.HandlerFunc { @@ -133,7 +278,7 @@ func ginrus() gin.HandlerFunc { } } -func setupSSDP(baseAddress, deviceName, deviceUUID string) error { +func setupSSDP(baseAddress, deviceName, deviceUUID string) (*ssdp.Advertiser, error) { log.Debugf("Advertising telly as %s (%s)", deviceName, deviceUUID) adv, err := ssdp.Advertise( @@ -144,7 +289,7 @@ func setupSSDP(baseAddress, deviceName, deviceUUID string) error { 1800) if err != nil { - return err + return nil, err } go func(advertiser *ssdp.Advertiser) { @@ -158,5 +303,24 @@ func setupSSDP(baseAddress, deviceName, deviceUUID string) error { } }(adv) - return nil + return adv, nil +} + +func split(data []byte, atEOF bool) (advance int, token []byte, spliterror error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexByte(data, '\n'); i >= 0 { + // We have a full newline-terminated line. + return i + 1, data[0:i], nil + } + if i := bytes.IndexByte(data, '\r'); i >= 0 { + // We have a cr terminated line + return i + 1, data[0:i], nil + } + if atEOF { + return len(data), data, nil + } + + return 0, nil, nil } diff --git a/structs.go b/structs.go index 01eb301..c3977a3 100644 --- a/structs.go +++ b/structs.go @@ -1,58 +1,11 @@ package main import ( + "encoding/json" "encoding/xml" "fmt" - "net" - "regexp" - "strconv" - - "github.com/tellytv/telly/m3u" ) -type config struct { - RegexInclusive bool - Regex *regexp.Regexp - - DirectMode bool - M3UPath string - ConcurrentStreams int - StartingChannel int - - DeviceAuth string - DeviceID int - DeviceUUID string - FriendlyName string - Manufacturer string - ModelNumber string - FirmwareName string - FirmwareVersion string - SSDP bool - - LogRequests bool - LogLevel string - - ListenAddress *net.TCPAddr - BaseAddress *net.TCPAddr - - lineup []LineupItem -} - -func (c *config) DiscoveryData() DiscoveryData { - return DiscoveryData{ - FriendlyName: c.FriendlyName, - Manufacturer: c.Manufacturer, - ModelNumber: c.ModelNumber, - FirmwareName: c.FirmwareName, - TunerCount: c.ConcurrentStreams, - FirmwareVersion: c.FirmwareVersion, - DeviceID: strconv.Itoa(c.DeviceID), - DeviceAuth: c.DeviceAuth, - BaseURL: fmt.Sprintf("http://%s", c.BaseAddress), - LineupURL: fmt.Sprintf("http://%s/lineup.json", c.BaseAddress), - } -} - // DiscoveryData contains data about telly to expose in the HDHomeRun format for Plex detection. type DiscoveryData struct { FriendlyName string @@ -87,29 +40,12 @@ func (d *DiscoveryData) UPNP() UPNP { // LineupStatus exposes the status of the channel lineup. type LineupStatus struct { - ScanInProgress int - ScanPossible int - Source string - SourceList []string -} - -// LineupItem is a single channel found in the playlist. -type LineupItem struct { - GuideNumber string - GuideName string - URL string -} - -// Track describes a single M3U segment. This struct includes m3u.Track as well as specific IPTV fields we want to get. -type Track struct { - *m3u.Track - Catchup string `m3u:"catchup"` - CatchupDays string `m3u:"catchup-days"` - CatchupSource string `m3u:"catchup-source"` - GroupTitle string `m3u:"group-title"` - TvgID string `m3u:"tvg-id"` - TvgLogo string `m3u:"tvg-logo"` - TvgName string `m3u:"tvg-name"` + ScanInProgress convertibleBoolean + ScanPossible convertibleBoolean `json:",omitempty"` + Source string `json:",omitempty"` + SourceList []string `json:",omitempty"` + Progress int `json:",omitempty"` // Percent complete + Found int `json:",omitempty"` // Number of found channels } type upnpVersion struct { @@ -134,3 +70,52 @@ type UPNP struct { URLBase string `xml:"URLBase"` Device upnpDevice `xml:"device"` } + +type convertibleBoolean bool + +func (bit *convertibleBoolean) MarshalJSON() ([]byte, error) { + var bitSetVar int8 + if *bit { + bitSetVar = 1 + } + + return json.Marshal(bitSetVar) +} + +func (bit *convertibleBoolean) UnmarshalJSON(data []byte) error { + asString := string(data) + if asString == "1" || asString == "true" { + *bit = true + } else if asString == "0" || asString == "false" { + *bit = false + } else { + return fmt.Errorf("Boolean unmarshal error: invalid input %s", asString) + } + return nil +} + +// MarshalXML used to determine if the element is present or not. see https://stackoverflow.com/a/46516243 +func (bit *convertibleBoolean) MarshalXML(e *xml.Encoder, start xml.StartElement) error { + var bitSetVar int8 + if *bit { + bitSetVar = 1 + } + + return e.EncodeElement(bitSetVar, start) +} + +// UnmarshalXML used to determine if the element is present or not. see https://stackoverflow.com/a/46516243 +func (bit *convertibleBoolean) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + var asString string + if decodeErr := d.DecodeElement(&asString, &start); decodeErr != nil { + return decodeErr + } + if asString == "1" || asString == "true" { + *bit = true + } else if asString == "0" || asString == "false" { + *bit = false + } else { + return fmt.Errorf("Boolean unmarshal error: invalid input %s", asString) + } + return nil +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..68e756b --- /dev/null +++ b/utils.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "net" + + "github.com/spf13/viper" +) + +func getTCPAddr(key string) *net.TCPAddr { + addr, addrErr := net.ResolveTCPAddr("tcp", viper.GetString(key)) + if addrErr != nil { + panic(fmt.Errorf("error parsing address %s: %s", viper.GetString(key), addrErr)) + } + return addr +} + +func getDiscoveryData() DiscoveryData { + return DiscoveryData{ + FriendlyName: viper.GetString("discovery.device-friendly-name"), + Manufacturer: viper.GetString("discovery.device-manufacturer"), + ModelNumber: viper.GetString("discovery.device-model-number"), + FirmwareName: viper.GetString("discovery.device-firmware-name"), + TunerCount: viper.GetInt("iptv.streams"), + FirmwareVersion: viper.GetString("discovery.device-firmware-version"), + DeviceID: viper.GetString("discovery.device-id"), + DeviceAuth: viper.GetString("discovery.device-auth"), + BaseURL: fmt.Sprintf("http://%s", viper.GetString("web.base-address")), + LineupURL: fmt.Sprintf("http://%s/lineup.json", viper.GetString("web.base-address")), + } +}