From 1e6382e259a779016b8df81fb4a95555e3dbde5d Mon Sep 17 00:00:00 2001 From: MuZhou233 Date: Wed, 10 Jan 2024 17:17:39 +0000 Subject: [PATCH] feat: init commit Release-As: 0.0.1 --- .gitattributes | 5 + .github/workflows/codeql-analysis.yml | 67 +++++ .github/workflows/golangci-lint.yml | 24 ++ .github/workflows/release-please.yml | 37 +++ .gitignore | 40 +++ .goreleaser.yml | 68 +++++ .release-please-manifest.json | 3 + Dockerfile | 13 + LICENSE | 21 ++ Makefile | 67 +++++ README.md | 13 + cmd/main.go | 60 ++++ go.mod | 59 ++++ go.sum | 384 +++++++++++++++++++++++ internal/biz/locale.go | 26 ++ internal/biz/steam.go | 192 ++++++++++++ internal/client/steam.go | 41 +++ internal/client/steam_test.go | 60 ++++ internal/client/store_api.go | 69 +++++ internal/client/web_api.go | 91 ++++++ internal/handler.go | 122 ++++++++ internal/model/language.go | 418 ++++++++++++++++++++++++++ internal/model/model.go | 313 +++++++++++++++++++ release-please-config.json | 10 + 24 files changed, 2203 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/codeql-analysis.yml create mode 100644 .github/workflows/golangci-lint.yml create mode 100644 .github/workflows/release-please.yml create mode 100644 .gitignore create mode 100644 .goreleaser.yml create mode 100644 .release-please-manifest.json create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/biz/locale.go create mode 100644 internal/biz/steam.go create mode 100644 internal/client/steam.go create mode 100644 internal/client/steam_test.go create mode 100644 internal/client/store_api.go create mode 100644 internal/client/web_api.go create mode 100644 internal/handler.go create mode 100644 internal/model/language.go create mode 100644 internal/model/model.go create mode 100644 release-please-config.json diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..c2e97be --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# Treat all Go files in this repo as binary, with no git magic updating +# line endings. Windows users contributing to Go will need to use a +# modern version of git and editors capable of LF line endings. + +*.go -text diff=golang eol=lf \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..5f025a5 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,67 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + schedule: + - cron: '18 7 * * 1' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..5eb147e --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,24 @@ +name: golangci-lint +on: + push: + tags: + - v* + branches: + - master + - main + pull_request: +permissions: + contents: write + # Optional: allow read access to pull request. Use with `only-new-issues` option. + pull-requests: read +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/setup-go@v5 + with: + go-version: '1.20' + - uses: actions/checkout@v4 + - name: golangci-lint + run: make init && make lint diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..2c4b9ee --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,37 @@ +name: release-please +on: + push: + branches: + - master + - main +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + releases_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: google-github-actions/release-please-action@v4 + id: release + with: + config-file: release-please-config.json + + goreleaser: + runs-on: ubuntu-latest + needs: release-please + if: ${{ needs.release-please.outputs.releases_created }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - run: git fetch --force --tags + - uses: actions/setup-go@v5 + with: + go-version: '1.20.7' + cache: true + - name: setup release environment + run: |- + echo -e 'GITHUB_TOKEN=${{secrets.GITHUB_TOKEN}}' > .release-env + - name: release publish + run: make release + - run: ls ./dist \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9970888 --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Reference https://github.com/github/gitignore/blob/master/Go.gitignore +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# OS General +Thumbs.db +.DS_Store + +# project +*.cert +*.key +*.log +bin/ + +# Develop tools +.vscode/ +.idea/ +*.swp + +dist/ +.release-env +*.db \ No newline at end of file diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..ff21899 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,68 @@ +# This is an example .goreleaser.yml file with some sensible defaults. +# Make sure to check the documentation at https://goreleaser.com +before: + hooks: + # You may remove this if you don't use go modules. + - go mod tidy + # you may remove this if you don't need go generate + #- go generate ./... +builds: + - id: tuihub-steam + main: ./cmd + binary: tuihub-steam + goos: + - darwin + - windows + - linux + goarch: + - amd64 + - arm64 + goamd64: + - v1 + flags: + - -tags=release + ldflags: + - -s -w -X main.version={{.Summary}} + +archives: + - format: tar.gz + # this name template makes the OS and Arch compatible with the results of uname. + name_template: >- + {{ .ProjectName }}_{{ .Version }}_ + {{- title .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}_v{{ .Arm }}{{ end }} + {{- if .Amd64 }}_{{ .Amd64 }}{{ end }} + # use zip for windows archives + format_overrides: + - goos: windows + format: zip + # Additional files/template/globs you want to add to the archive. + # Defaults are any files matching `LICENSE*`, `README*`, `CHANGELOG*`, + # `license*`, `readme*` and `changelog*`. + files: + - LICENSE + - README.md + - CHANGELOG.md +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ incpatch .Version }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +dockers: + - image_templates: + - "ghcr.io/tuihub/tuihub-steam:latest" + - "ghcr.io/tuihub/tuihub-steam:{{ .Tag }}" + +# The lines beneath this are called `modelines`. See `:help modeline` +# Feel free to remove those if you don't want/use them. +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..46b1b67 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.0" +} \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6dc9e76 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM debian:stable-slim + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + netbase \ + && rm -rf /var/lib/apt/lists/ \ + && apt-get autoremove -y && apt-get autoclean -y + +COPY tuihub-steam /app/server + +WORKDIR /app + +CMD ["./server"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5e9ba4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 TuiHub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..24163d3 --- /dev/null +++ b/Makefile @@ -0,0 +1,67 @@ +SHELL:=/bin/bash +LINT_FILE_TAG=v0.2.0 +LINT_FILE_URL=https://raw.githubusercontent.com/tuihub/librarian/$(LINT_FILE_TAG)/.golangci.yml + +GOLANG_CROSS_VERSION ?= v1.20.7 +PACKAGE_NAME := github.com/tuihub/tuihub-steam +VERSION=$(shell git describe --tags --always) +PROTO_VERSION=$(shell go list -m -f '{{.Version}}' github.com/tuihub/protos) + +.PHONY: init +# init env +init: + go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 + +.PHONY: lint +# lint files +lint: + golangci-lint run --fix -c <(curl -sSL $(LINT_FILE_URL)) + golangci-lint run -c <(curl -sSL $(LINT_FILE_URL)) # re-run to make sure fixes are valid, useful in some condition + +.PHONY: release-dry-run +# build server in release mode, for manual test +release-dry-run: + @docker run \ + --rm \ + -e PROTO_VERSION=$(PROTO_VERSION) \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/$(PACKAGE_NAME) \ + -w /go/src/$(PACKAGE_NAME) \ + ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ + release --clean --skip-validate --skip-publish + +.PHONY: release +# build server in release mode, for CI, do not run manually +release: + @if [ ! -f ".release-env" ]; then \ + echo "\033[91m.release-env is required for release\033[0m";\ + exit 1;\ + fi + docker run \ + --rm \ + -e PROTO_VERSION=$(PROTO_VERSION) \ + --env-file .release-env \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v `pwd`:/go/src/$(PACKAGE_NAME) \ + -w /go/src/$(PACKAGE_NAME) \ + ghcr.io/goreleaser/goreleaser-cross:${GOLANG_CROSS_VERSION} \ + release + +# show help +help: + @echo '' + @echo 'Usage:' + @echo ' make [target]' + @echo '' + @echo 'Targets:' + @awk '/^[a-zA-Z\-\_0-9]+:/ { \ + helpMessage = match(lastLine, /^# (.*)/); \ + if (helpMessage) { \ + helpCommand = substr($$1, 0, index($$1, ":")-1); \ + helpMessage = substr(lastLine, RSTART + 2, RLENGTH); \ + printf "\033[36m%-22s\033[0m %s\n", helpCommand,helpMessage; \ + } \ + } \ + { lastLine = $$0 }' $(MAKEFILE_LIST) + +.DEFAULT_GOAL := help diff --git a/README.md b/README.md new file mode 100644 index 0000000..94e22b6 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# tuihub-steam + +Steam plugin for TuiHub server. + +## Features + +- Enable ability to get steam user information. +- Enable ability to get steam game information. + +## Dependencies + +- [tuihub-go](https://github.com/tuihub/tuihub-go) +- [colly](https://github.com/gocolly/colly) \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..69dd9b3 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,60 @@ +package main + +import ( + "context" + "os" + + porter "github.com/tuihub/protos/pkg/librarian/porter/v1" + librarian "github.com/tuihub/protos/pkg/librarian/v1" + "github.com/tuihub/tuihub-go" + "github.com/tuihub/tuihub-go/logger" + "github.com/tuihub/tuihub-steam/internal" +) + +// go build -ldflags "-X main.version=x.y.z". +var ( + // version is the version of the compiled software. + version string +) + +func main() { + apiKey, exist := os.LookupEnv("STEAM_API_KEY") + if !exist || apiKey == "" { + panic("STEAM_API_KEY is required") + } + config := tuihub.PorterConfig{ + Name: "tuihub-steam", + Version: version, + GlobalName: "github.com/tuihub/tuihub-steam", + FeatureSummary: &porter.PorterFeatureSummary{ + SupportedAccounts: []*porter.PorterFeatureSummary_Account{ + { + Platform: tuihub.WellKnownToString( + librarian.WellKnownAccountPlatform_WELL_KNOWN_ACCOUNT_PLATFORM_STEAM, + ), + AppRelationTypes: []librarian.AccountAppRelationType{ + librarian.AccountAppRelationType_ACCOUNT_APP_RELATION_TYPE_OWN, + }, + }, + }, + SupportedAppSources: []string{ + tuihub.WellKnownToString(librarian.WellKnownAppSource_WELL_KNOWN_APP_SOURCE_STEAM), + }, + SupportedFeedSources: nil, + SupportedNotifyDestinations: nil, + }, + } + server, err := tuihub.NewPorter( + context.Background(), + config, + internal.NewHandler(apiKey), + ) + if err != nil { + logger.Error(err) + os.Exit(1) + } + if err = server.Run(); err != nil { + logger.Error(err) + os.Exit(1) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..ecdc56f --- /dev/null +++ b/go.mod @@ -0,0 +1,59 @@ +module github.com/tuihub/tuihub-steam + +go 1.20 + +require ( + github.com/Xuanwo/go-locale v1.1.0 + github.com/go-kratos/kratos/v2 v2.7.2 + github.com/gocolly/colly/v2 v2.1.0 + github.com/google/go-querystring v1.1.0 + github.com/tuihub/protos v0.3.0 + github.com/tuihub/tuihub-go v0.0.5 +) + +require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231115204500-e097f827e652.2 // indirect + github.com/PuerkitoBio/goquery v1.5.1 // indirect + github.com/andybalholm/cascadia v1.2.0 // indirect + github.com/antchfx/htmlquery v1.2.3 // indirect + github.com/antchfx/xmlquery v1.2.4 // indirect + github.com/antchfx/xpath v1.1.8 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/fatih/color v1.14.1 // indirect + github.com/go-kratos/aegis v0.2.0 // indirect + github.com/go-kratos/kratos/contrib/registry/consul/v2 v2.0.0-20231219111544-85740b179b09 // indirect + github.com/go-playground/form/v4 v4.2.0 // indirect + github.com/gobwas/glob v0.2.3 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.5.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/hashicorp/consul/api v1.26.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/serf v0.10.1 // indirect + github.com/kennygrant/sanitize v1.2.4 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect + github.com/temoto/robotstxt v1.1.1 // indirect + golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect + golang.org/x/net v0.19.0 // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 // indirect + google.golang.org/grpc v1.60.1 // indirect + google.golang.org/protobuf v1.32.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..57d8586 --- /dev/null +++ b/go.sum @@ -0,0 +1,384 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231115204500-e097f827e652.2 h1:iEPA5SBtdLJNwQis/SrcCuDWJh5E1V0mVO4Ih7/mRbg= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231115204500-e097f827e652.2/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/PuerkitoBio/goquery v1.5.1 h1:PSPBGne8NIUWw+/7vFBV+kG2J/5MOjbzc7154OaKCSE= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/Xuanwo/go-locale v1.1.0 h1:51gUxhxl66oXAjI9uPGb2O0qwPECpriKQb2hl35mQkg= +github.com/Xuanwo/go-locale v1.1.0/go.mod h1:UKrHoZB3FPIk9wIG2/tVSobnHgNnceGSH3Y8DY5cASs= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE= +github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY= +github.com/antchfx/htmlquery v1.2.3 h1:sP3NFDneHx2stfNXCKbhHFo8XgNjCACnU/4AO5gWz6M= +github.com/antchfx/htmlquery v1.2.3/go.mod h1:B0ABL+F5irhhMWg54ymEZinzMSi0Kt3I2if0BLYa3V0= +github.com/antchfx/xmlquery v1.2.4 h1:T/SH1bYdzdjTMoz2RgsfVKbM5uWh3gjDYYepFqQmFv4= +github.com/antchfx/xmlquery v1.2.4/go.mod h1:KQQuESaxSlqugE2ZBcM/qn+ebIpt+d+4Xx7YcSGAIrM= +github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/antchfx/xpath v1.1.8 h1:PcL6bIX42Px5usSx6xRYw/wjB3wYGkj0MJ9MBzEKVgk= +github.com/antchfx/xpath v1.1.8/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k= +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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.11.2-0.20230627204322-7d0032219fcb h1:kxNVXsNro/lpR5WD+P1FI/yUHn2G03Glber3k8cQL2Y= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kratos/aegis v0.2.0 h1:dObzCDWn3XVjUkgxyBp6ZeWtx/do0DPZ7LY3yNSJLUQ= +github.com/go-kratos/aegis v0.2.0/go.mod h1:v0R2m73WgEEYB3XYu6aE2WcMwsZkJ/Rzuf5eVccm7bI= +github.com/go-kratos/kratos/contrib/registry/consul/v2 v2.0.0-20231219111544-85740b179b09 h1:CQLh4u2gVu4T6YqQTSw7Mg0tUAZpKpCbU9E43U9Ysew= +github.com/go-kratos/kratos/contrib/registry/consul/v2 v2.0.0-20231219111544-85740b179b09/go.mod h1:yb9i4gq3jCW5zMFqzi50J0Xh3n4MRbcMlnuDBXYpvjs= +github.com/go-kratos/kratos/v2 v2.7.2 h1:WVPGFNLKpv+0odMnCPxM4ZHa2hy9I5FOnwpG3Vv4w5c= +github.com/go-kratos/kratos/v2 v2.7.2/go.mod h1:rppuc8+pGL2UtXA29bgFHWKqaaF6b6GB2XIYiDvFBRk= +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-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= +github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/gocolly/colly v1.2.0/go.mod h1:Hof5T3ZswNVsOHYmba1u03W65HDWgpV5HifSuueE0EA= +github.com/gocolly/colly/v2 v2.1.0 h1:k0DuZkDoCsx51bKpRJNEmcxcp+W5N8ziuwGaSDuFoGs= +github.com/gocolly/colly/v2 v2.1.0/go.mod h1:I2MuhsLjQ+Ex+IzK3afNS8/1qP3AedHOusRPcRdC5o0= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= +github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= +github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= +github.com/kennygrant/sanitize v1.2.4/go.mod h1:LGsjYYtgxbetdg5owWB2mpgUL6e2nfw2eObZ0u0qvak= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= +github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.7 h1:I6tZjLXD2Q1kjvNbIzB1wvQBsXmKXiVrhpRE8ZjP5jY= +github.com/smartystreets/goconvey v1.6.7/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +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/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +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/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/temoto/robotstxt v1.1.1 h1:Gh8RCs8ouX3hRSxxK7B1mO5RFByQ4CmJZDwgom++JaA= +github.com/temoto/robotstxt v1.1.1/go.mod h1:+1AmkuG3IYkh1kv0d2qEB9Le88ehNO0zwOr3ujewlOo= +github.com/tuihub/protos v0.3.0 h1:DyZMmLjayRbG4dDRWuUDWwqIQXkHDEGFlX2o+Bfs9gM= +github.com/tuihub/protos v0.3.0/go.mod h1:oEU+QANr6l/E9StiEBW0W0vrnfGSjsQAVQGbFHPZOPc= +github.com/tuihub/tuihub-go v0.0.5 h1:7eE7A5PBFkvJi8ht1xNJzjMe/LnCG/M2SHz41r6bypM= +github.com/tuihub/tuihub-go v0.0.5/go.mod h1:iex84u09QcC60wf5k+YzOWH5Z5NJjj30xghnVnNwHw8= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +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-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/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-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211023085530-d6a326fbbf70/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 h1:SeZZZx0cP0fqUyA+oRzP9k7cSwJlvDFiROO72uwD6i0= +google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97/go.mod h1:t1VqOqqvce95G3hIDCT5FeO3YUc6Q4Oe24L/+rNMxRk= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= +google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97 h1:6GQBEOdGkX6MMTLT9V+TjtIRZCw9VPD5Z+yHY9wMgS0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231002182017-d307bd883b97/go.mod h1:v7nGkzlmW8P3n/bKmWBn2WpBjpOEx8Q6gMueudAmKfY= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.60.1 h1:26+wFr+cNqSGFcOXcabYC0lUVJVRa2Sb2ortSK7VrEU= +google.golang.org/grpc v1.60.1/go.mod h1:OlCHIeLYqSSsLi6i49B5QGdzaMZK9+M7LXN2FKz4eGM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= +google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/biz/locale.go b/internal/biz/locale.go new file mode 100644 index 0000000..fe464ce --- /dev/null +++ b/internal/biz/locale.go @@ -0,0 +1,26 @@ +package biz + +import "github.com/Xuanwo/go-locale" + +type Locale int + +const ( + LocaleEn Locale = iota + LocaleChs + LocaleCht +) + +func GetLocale() Locale { + tag, err := locale.Detect() + if err != nil { + return LocaleEn + } + base, _, region := tag.Raw() + if base.String() == "zh" { + if region.String() == "TW" || region.String() == "HK" || region.String() == "MO" { + return LocaleCht + } + return LocaleChs + } + return LocaleEn +} diff --git a/internal/biz/steam.go b/internal/biz/steam.go new file mode 100644 index 0000000..9964187 --- /dev/null +++ b/internal/biz/steam.go @@ -0,0 +1,192 @@ +package biz + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/tuihub/tuihub-steam/internal/client" + "github.com/tuihub/tuihub-steam/internal/model" + + "github.com/go-kratos/kratos/v2/errors" +) + +type User struct { + SteamID string + Name string + ProfileURL string + AvatarURL string +} + +type App struct { + AppID uint + StoreURL string + Name string + Type AppType + ShortDescription string + HeroImageURL string + LogoImageURL string + IconImageURL string + Description string + ReleaseDate string + Developer string + Publisher string +} + +type AppType string + +const ( + AppTypeGame AppType = "game" + AppTypeApplication AppType = "application" + AppTypeTool AppType = "tool" + AppTypeDemo AppType = "demo" + AppTypeDeprected AppType = "deprected" + AppTypeDLC AppType = "dlc" + AppTypeGuide AppType = "guide" + AppTypeDriver AppType = "driver" + AppTypeConfig AppType = "config" + AppTypeHardware AppType = "hardware" + AppTypeFranchise AppType = "franchise" + AppTypeVideo AppType = "video" + AppTypePlugin AppType = "plugin" + AppTypeMusic AppType = "music" + AppTypeSeries AppType = "series" + AppTypeComic AppType = "comic" + AppTypeBeta AppType = "beta" +) + +type SteamUseCase struct { + c *client.Steam + locale Locale +} + +func NewSteamUseCase(apiKey string) *SteamUseCase { + cli, err := client.NewSteam(apiKey) + if err != nil { + panic(err) + } + return &SteamUseCase{ + c: cli, + locale: GetLocale(), + } +} + +func (s *SteamUseCase) FeatureEnabled() bool { + return s.c != nil +} + +func (s *SteamUseCase) language() model.LanguageCode { + switch s.locale { + case LocaleEn: + return model.LanguageEnglish + case LocaleChs: + return model.LanguageChineseSimplified + case LocaleCht: + return model.LanguageChineseTraditional + default: + return model.LanguageEnglish + } +} + +func (s *SteamUseCase) GetUser(ctx context.Context, steamID string) (*User, error) { + if !s.FeatureEnabled() { + return nil, errors.BadRequest("request disabled feature", "") + } + id, err := strconv.ParseUint(steamID, 10, 64) + if err != nil { + return nil, err + } + resp, err := s.c.GetPlayerSummary(ctx, model.GetPlayerSummariesRequest{ + SteamID: id, + }) + if err != nil { + return nil, err + } + return &User{ + SteamID: steamID, + Name: resp.Nickname, + ProfileURL: resp.ProfileURL, + AvatarURL: resp.Avatar, + }, nil +} + +func (s *SteamUseCase) GetOwnedGames(ctx context.Context, steamID string) ([]*App, error) { + if !s.FeatureEnabled() { + return nil, errors.BadRequest("request disabled feature", "") + } + id, err := strconv.ParseUint(steamID, 10, 64) + if err != nil { + return nil, err + } + resp, err := s.c.GetOwnedGames(ctx, model.GetOwnedGamesRequest{ + SteamID: id, + IncludeAppInfo: true, + IncludePlayedFreeGames: true, + IncludeFreeSub: true, + SkipUnvettedApps: false, + Language: "", + IncludeExtendedAppInfo: false, + }) + if err != nil { + return nil, err + } + const imgBaseURL = "http://media.steampowered.com/steamcommunity/public/images/apps" + res := make([]*App, len(resp.Games)) + for i, game := range resp.Games { + res[i] = new(App) + res[i].AppID = game.AppID + res[i].Name = game.Name + if len(game.ImgLogoURL) > 0 { + res[i].LogoImageURL = fmt.Sprintf("%s/%d/%s.jpg", imgBaseURL, game.AppID, game.ImgLogoURL) + } + if len(game.ImgIconURL) > 0 { + res[i].IconImageURL = fmt.Sprintf("%s/%d/%s.jpg", imgBaseURL, game.AppID, game.ImgIconURL) + } + } + return res, nil +} + +func (s *SteamUseCase) GetAppDetails(ctx context.Context, appID int) (*App, error) { + if !s.FeatureEnabled() { + return nil, errors.BadRequest("request disabled feature", "") + } + resp, err := s.c.GetAppDetails(ctx, model.GetAppDetailsRequest{ + AppIDs: []int{appID}, + CountryCode: "", + Language: s.language(), + }) + if err != nil { + return nil, err + } + if len(resp) != 1 { + return nil, errors.InternalServer("unexpected result", "") + } + var res *App + for _, app := range resp { + res = new(App) + res.AppID = uint(appID) + res.StoreURL = fmt.Sprintf("https://store.steampowered.com/app/%d", appID) + if app.Success { + res = &App{ + AppID: uint(app.Data.AppID), + StoreURL: fmt.Sprintf("https://store.steampowered.com/app/%d", app.Data.AppID), + Name: app.Data.Name, + Type: AppType(app.Data.Type), + ShortDescription: app.Data.ShortDescription, + HeroImageURL: app.Data.HeaderImage, + LogoImageURL: "", + IconImageURL: "", + Description: app.Data.DetailedDescription, + ReleaseDate: "Coming Soon", + Developer: strings.Join(app.Data.Developers, ","), + Publisher: strings.Join(app.Data.Publishers, ","), + } + if !app.Data.ReleaseDate.ComingSoon { + res.ReleaseDate = app.Data.ReleaseDate.Date + } + } + break + } + return res, nil +} diff --git a/internal/client/steam.go b/internal/client/steam.go new file mode 100644 index 0000000..446e5a2 --- /dev/null +++ b/internal/client/steam.go @@ -0,0 +1,41 @@ +package client + +import ( + "github.com/tuihub/tuihub-go/logger" + + "github.com/go-kratos/kratos/v2/log" + "github.com/gocolly/colly/v2" + "github.com/gocolly/colly/v2/debug" +) + +type Steam struct { + *StoreAPI + *WebAPI +} + +func NewSteam(apiKey string) (*Steam, error) { + c := NewColly() + s, err := NewStoreAPI(c) + if err != nil { + return nil, err + } + w, err := NewWebAPI(c, apiKey) + if err != nil { + return nil, err + } + return &Steam{ + StoreAPI: s, + WebAPI: w, + }, nil +} + +func NewColly() *colly.Collector { + return colly.NewCollector( + colly.Debugger(&debug.LogDebugger{ + Output: logger.NewWriter(log.LevelDebug), + Prefix: "[colly]", + Flag: 0, + }), + colly.AllowURLRevisit(), + ) +} diff --git a/internal/client/steam_test.go b/internal/client/steam_test.go new file mode 100644 index 0000000..c124c62 --- /dev/null +++ b/internal/client/steam_test.go @@ -0,0 +1,60 @@ +package client_test + +import ( + "context" + "os" + "strconv" + "testing" + + "github.com/tuihub/tuihub-go/logger" + "github.com/tuihub/tuihub-steam/internal/client" + "github.com/tuihub/tuihub-steam/internal/model" +) + +func getAPIKey() string { + return os.Getenv("STEAM_API_KEY") +} +func getSteamID() uint64 { + idStr, exist := os.LookupEnv("STEAM_ID") + if !exist { + return 0 + } + id, err := strconv.ParseInt(idStr, 10, 64) + if err != nil { + panic(err) + } + return uint64(id) +} +func getAppID() int { + return 10 +} + +func TestWebAPI_GetPlayerSummary(t *testing.T) { + r, _ := client.NewWebAPI(client.NewColly(), getAPIKey()) + res, err := r.GetPlayerSummary(context.Background(), model.GetPlayerSummariesRequest{SteamID: getSteamID()}) + logger.Infof("res %+v, err: %+v", res, err) +} + +func TestWebAPI_GetOwnedGames(t *testing.T) { + r, _ := client.NewWebAPI(client.NewColly(), getAPIKey()) + res, err := r.GetOwnedGames(context.Background(), model.GetOwnedGamesRequest{ + SteamID: getSteamID(), + IncludeAppInfo: false, + IncludePlayedFreeGames: false, + IncludeFreeSub: false, + SkipUnvettedApps: false, + Language: "", + IncludeExtendedAppInfo: false, + }) + logger.Infof("res %+v, err: %+v", res, err) +} + +func TestStoreAPI_GetAppDetails(t *testing.T) { + r, _ := client.NewStoreAPI(client.NewColly()) + res, err := r.GetAppDetails(context.Background(), model.GetAppDetailsRequest{ + AppIDs: []int{getAppID()}, + CountryCode: model.ProductCCUS, + Language: model.LanguageEnglish, + }) + logger.Infof("res %+v, err: %+v", res, err) +} diff --git a/internal/client/store_api.go b/internal/client/store_api.go new file mode 100644 index 0000000..f114604 --- /dev/null +++ b/internal/client/store_api.go @@ -0,0 +1,69 @@ +package client + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/tuihub/tuihub-steam/internal/model" + + "github.com/gocolly/colly/v2" + "github.com/google/go-querystring/query" +) + +type StoreAPI struct { + c *colly.Collector +} + +func NewStoreAPI(c *colly.Collector) (*StoreAPI, error) { + err := c.Limit(&colly.LimitRule{ + DomainRegexp: "", + DomainGlob: "*store.steampowered.com*", + Delay: 5 * time.Second, //nolint:gomnd // This API is now rate limited to 200 requests per 5 minutes + RandomDelay: 0, + Parallelism: 1, + }) + if err != nil { + return nil, err + } + return &StoreAPI{ + c: c, + }, nil +} + +func (s *StoreAPI) GetAppDetails( + ctx context.Context, + req model.GetAppDetailsRequest, +) (map[string]model.AppDetailsBasic, error) { + res := map[string]model.AppDetailsBasic{} + reqStr, err := query.Values(req) + if err != nil { + return nil, err + } + err = s.Get(ctx, "/appdetails", reqStr, &res) + if err != nil { + return nil, err + } + + return res, nil +} + +func (s *StoreAPI) Get(ctx context.Context, path string, query url.Values, data interface{}) error { + u := "https://store.steampowered.com/api" + path + "?" + query.Encode() + c := s.c.Clone() + var err error + c.OnResponse(func(response *colly.Response) { + if response.StatusCode != http.StatusOK { + err = fmt.Errorf("request %s failed with code %d", path, response.StatusCode) + } + err = json.Unmarshal(response.Body, data) + }) + if err2 := c.Visit(u); err2 != nil { + return err2 + } + c.Wait() + return err +} diff --git a/internal/client/web_api.go b/internal/client/web_api.go new file mode 100644 index 0000000..534b5cb --- /dev/null +++ b/internal/client/web_api.go @@ -0,0 +1,91 @@ +package client + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "time" + + "github.com/tuihub/tuihub-steam/internal/model" + + "github.com/gocolly/colly/v2" + "github.com/google/go-querystring/query" +) + +type WebAPI struct { + key string + c *colly.Collector +} + +func NewWebAPI(c *colly.Collector, apiKey string) (*WebAPI, error) { + err := c.Limit(&colly.LimitRule{ + DomainRegexp: "", + DomainGlob: "*api.steampowered.com*", + Delay: time.Second, + RandomDelay: 0, + Parallelism: 1, + }) + if err != nil { + return nil, err + } + return &WebAPI{ + key: apiKey, + c: c, + }, nil +} + +func (s *WebAPI) GetPlayerSummary( + ctx context.Context, + req model.GetPlayerSummariesRequest, +) (*model.PlayerSummary, error) { + res := new(model.GetPlayerSummariesResponse) + reqStr, err := query.Values(req) + if err != nil { + return nil, err + } + err = s.Get(ctx, "ISteamUser/GetPlayerSummaries/v2", reqStr, res) + if err != nil { + return nil, err + } + if len(res.Response.Players) == 0 { + return nil, errors.New("empty response") + } + + return &res.Response.Players[0], nil +} + +func (s *WebAPI) GetOwnedGames(ctx context.Context, req model.GetOwnedGamesRequest) (*model.OwnedGames, error) { + res := new(model.GetOwnedGamesResponse) + reqStr, err := query.Values(req) + if err != nil { + return nil, err + } + err = s.Get(ctx, "IPlayerService/GetOwnedGames/v1", reqStr, res) + if err != nil { + return nil, err + } + + return &res.Response, nil +} + +func (s *WebAPI) Get(ctx context.Context, path string, query url.Values, data interface{}) error { + query.Set("format", "json") + query.Set("key", s.key) + u := "https://api.steampowered.com/" + path + "?" + query.Encode() + c := s.c.Clone() + var err error + c.OnResponse(func(response *colly.Response) { + if response.StatusCode != http.StatusOK { + err = fmt.Errorf("request %s failed with code %d", path, response.StatusCode) + } + err = json.Unmarshal(response.Body, data) + }) + if err2 := c.Visit(u); err2 != nil { + return err2 + } + c.Wait() + return err +} diff --git a/internal/handler.go b/internal/handler.go new file mode 100644 index 0000000..839a66a --- /dev/null +++ b/internal/handler.go @@ -0,0 +1,122 @@ +package internal + +import ( + "context" + "strconv" + + porter "github.com/tuihub/protos/pkg/librarian/porter/v1" + librarian "github.com/tuihub/protos/pkg/librarian/v1" + "github.com/tuihub/tuihub-go" + "github.com/tuihub/tuihub-steam/internal/biz" + + "github.com/go-kratos/kratos/v2/errors" +) + +type Handler struct { + steam *biz.SteamUseCase +} + +func NewHandler(apiKey string) *Handler { + return &Handler{ + steam: biz.NewSteamUseCase(apiKey), + } +} + +func (h Handler) PullAccount(ctx context.Context, req *porter.PullAccountRequest) ( + *porter.PullAccountResponse, error) { + u, err := h.steam.GetUser(ctx, req.GetAccountId().GetPlatformAccountId()) + if err != nil { + return nil, err + } + return &porter.PullAccountResponse{Account: &librarian.Account{ + Id: nil, + Platform: req.GetAccountId().GetPlatform(), + PlatformAccountId: req.GetAccountId().GetPlatformAccountId(), + Name: u.Name, + ProfileUrl: u.ProfileURL, + AvatarUrl: u.AvatarURL, + LatestUpdateTime: nil, + }}, nil +} + +func (h Handler) PullApp(ctx context.Context, req *porter.PullAppRequest) ( + *porter.PullAppResponse, error) { + appID, err := strconv.Atoi(req.GetAppId().GetSourceAppId()) + if err != nil { + return nil, err + } + a, err := h.steam.GetAppDetails(ctx, appID) + if err != nil { + return nil, err + } + return &porter.PullAppResponse{App: &librarian.App{ + Id: nil, + Internal: false, + Source: req.GetAppId().GetSource(), + SourceAppId: req.GetAppId().GetSourceAppId(), + SourceUrl: &a.StoreURL, + Details: &librarian.AppDetails{ // TODO + Description: a.Description, + ReleaseDate: a.ReleaseDate, + Developer: a.Developer, + Publisher: a.Publisher, + Version: "", + ImageUrls: nil, + }, + Name: a.Name, + Type: ToPBAppType(a.Type), + ShortDescription: a.ShortDescription, + IconImageUrl: "", + HeroImageUrl: a.HeroImageURL, + Tags: nil, + AltNames: nil, + }}, nil +} + +func (h Handler) PullAccountAppRelation(ctx context.Context, req *porter.PullAccountAppRelationRequest) ( + *porter.PullAccountAppRelationResponse, error) { + al, err := h.steam.GetOwnedGames(ctx, req.GetAccountId().GetPlatformAccountId()) + if err != nil { + return nil, err + } + appList := make([]*librarian.App, len(al)) + for i, a := range al { + appList[i] = &librarian.App{ // TODO + Id: nil, + Internal: false, + Source: tuihub.WellKnownToString( + librarian.WellKnownAppSource_WELL_KNOWN_APP_SOURCE_STEAM, + ), + SourceAppId: strconv.Itoa(int(a.AppID)), + SourceUrl: nil, + Details: nil, + Name: a.Name, + Type: 0, + ShortDescription: "", + IconImageUrl: a.IconImageURL, + HeroImageUrl: a.HeroImageURL, + Tags: nil, + AltNames: nil, + } + } + return &porter.PullAccountAppRelationResponse{AppList: appList}, nil +} + +func (h Handler) PullFeed(ctx context.Context, request *porter.PullFeedRequest) ( + *porter.PullFeedResponse, error) { + return nil, errors.BadRequest("not supported", "") +} + +func (h Handler) PushFeedItems(ctx context.Context, request *porter.PushFeedItemsRequest) ( + *porter.PushFeedItemsResponse, error) { + return nil, errors.BadRequest("not supported", "") +} + +func ToPBAppType(t biz.AppType) librarian.AppType { + switch t { + case biz.AppTypeGame: + return librarian.AppType_APP_TYPE_GAME + default: + return librarian.AppType_APP_TYPE_UNSPECIFIED + } +} diff --git a/internal/model/language.go b/internal/model/language.go new file mode 100644 index 0000000..6a6256d --- /dev/null +++ b/internal/model/language.go @@ -0,0 +1,418 @@ +package model + +// https://partner.steamgames.com/doc/store/localization +type LanguageCode string + +const ( + LanguageArabic LanguageCode = "arabic" + LanguageBulgarian LanguageCode = "bulgarian" + LanguageChineseSimplified LanguageCode = "schinese" + LanguageChineseTraditional LanguageCode = "tchinese" + LanguageCzech LanguageCode = "czech" + LanguageDanish LanguageCode = "danish" + LanguageDutch LanguageCode = "dutch" + LanguageEnglish LanguageCode = "english" + LanguageFinnish LanguageCode = "finnish" + LanguageFrench LanguageCode = "french" + LanguageGerman LanguageCode = "german" + LanguageGreek LanguageCode = "greek" + LanguageHungarian LanguageCode = "hungarian" + LanguageItalian LanguageCode = "italian" + LanguageJapanese LanguageCode = "japanese" + LanguageKorean LanguageCode = "koreana" + LanguageNorwegian LanguageCode = "norwegian" + LanguagePolish LanguageCode = "polish" + LanguagePortuguese LanguageCode = "portuguese" + LanguagePortugueseBrazil LanguageCode = "brazilian" + LanguageRomanian LanguageCode = "romanian" + LanguageRussian LanguageCode = "russian" + LanguageSpanishSpain LanguageCode = "spanish" + LanguageSpanishLatinAmerica LanguageCode = "latam" + LanguageSwedish LanguageCode = "swedish" + LanguageThai LanguageCode = "thai" + LanguageTurkish LanguageCode = "turkish" + LanguageUkrainian LanguageCode = "ukrainian" + LanguageVietnamese LanguageCode = "vietnamese" +) + +var LanguageCodes = []LanguageCode{ //nolint:gochecknoglobals //TODO + LanguageArabic, LanguageBulgarian, LanguageChineseSimplified, LanguageChineseTraditional, LanguageCzech, + LanguageDanish, LanguageDutch, LanguageEnglish, LanguageFinnish, LanguageFrench, LanguageGerman, LanguageGreek, + LanguageHungarian, LanguageItalian, LanguageJapanese, LanguageKorean, LanguageNorwegian, LanguagePolish, + LanguagePortuguese, LanguagePortugueseBrazil, LanguageRomanian, LanguageRussian, LanguageSpanishSpain, + LanguageSpanishLatinAmerica, LanguageSwedish, LanguageThai, LanguageTurkish, LanguageUkrainian, LanguageVietnamese, +} + +type LanguageCodeWeb string + +const ( + LanguageWebAR LanguageCodeWeb = "ar" + LanguageWebBG LanguageCodeWeb = "bg" + LanguageWebZHCN LanguageCodeWeb = "zh-CN" + LanguageWebZHTW LanguageCodeWeb = "zh-TW" + LanguageWebCS LanguageCodeWeb = "cs" + LanguageWebDA LanguageCodeWeb = "da" + LanguageWebNL LanguageCodeWeb = "nl" + LanguageWebEN LanguageCodeWeb = "en" + LanguageWebFI LanguageCodeWeb = "fi" + LanguageWebFR LanguageCodeWeb = "fr" + LanguageWebDE LanguageCodeWeb = "de" + LanguageWebEL LanguageCodeWeb = "el" + LanguageWebHU LanguageCodeWeb = "hu" + LanguageWebIT LanguageCodeWeb = "it" + LanguageWebJA LanguageCodeWeb = "ja" + LanguageWebKO LanguageCodeWeb = "ko" + LanguageWebNO LanguageCodeWeb = "no" + LanguageWebPL LanguageCodeWeb = "pl" + LanguageWebPT LanguageCodeWeb = "pt" + LanguageWebPTBR LanguageCodeWeb = "pt-BR" + LanguageWebRO LanguageCodeWeb = "ro" + LanguageWebRU LanguageCodeWeb = "ru" + LanguageWebES LanguageCodeWeb = "es" + LanguageWebES419 LanguageCodeWeb = "es-419" + LanguageWebSV LanguageCodeWeb = "sv" + LanguageWebTH LanguageCodeWeb = "th" + LanguageWebTR LanguageCodeWeb = "tr" + LanguageWebUK LanguageCodeWeb = "uk" + LanguageWebVN LanguageCodeWeb = "vn" +) + +type Language struct { + EnglishName string + NativeName string + LanguageCode LanguageCode + WebAPILanguageCode LanguageCodeWeb +} + +var Languages = []Language{ //nolint:gochecknoglobals //TODO + { + EnglishName: "Arabic", + NativeName: "العربية", + LanguageCode: LanguageArabic, + WebAPILanguageCode: LanguageWebAR, + }, + { + EnglishName: "Bulgarian", + NativeName: "български език", + LanguageCode: LanguageBulgarian, + WebAPILanguageCode: LanguageWebBG, + }, + { + EnglishName: "Chinese (Simplified)", + NativeName: "简体中文", + LanguageCode: LanguageChineseSimplified, + WebAPILanguageCode: LanguageWebZHCN, + }, + { + EnglishName: "Chinese (Traditional)", + NativeName: "繁體中文", + LanguageCode: LanguageChineseTraditional, + WebAPILanguageCode: LanguageWebZHTW, + }, + { + EnglishName: "Czech", + NativeName: "čeština", + LanguageCode: LanguageCzech, + WebAPILanguageCode: LanguageWebCS, + }, + { + EnglishName: "Danish", + NativeName: "Dansk", + LanguageCode: LanguageDanish, + WebAPILanguageCode: LanguageWebDA, + }, + { + EnglishName: "Dutch", + NativeName: "Nederlands", + LanguageCode: LanguageDutch, + WebAPILanguageCode: LanguageWebNL, + }, + { + EnglishName: "English", + NativeName: "English", + LanguageCode: LanguageEnglish, + WebAPILanguageCode: LanguageWebEN, + }, + { + EnglishName: "Finnish", + NativeName: "Suomi", + LanguageCode: LanguageFinnish, + WebAPILanguageCode: LanguageWebFI, + }, + { + EnglishName: "French", + NativeName: "Français", + LanguageCode: LanguageFrench, + WebAPILanguageCode: LanguageWebFR, + }, + { + EnglishName: "German", + NativeName: "Deutsch", + LanguageCode: LanguageGerman, + WebAPILanguageCode: LanguageWebDE, + }, + { + EnglishName: "Greek", + NativeName: "Ελληνικά", + LanguageCode: LanguageGreek, + WebAPILanguageCode: LanguageWebEL, + }, + { + EnglishName: "Hungarian", + NativeName: "Magyar", + LanguageCode: LanguageHungarian, + WebAPILanguageCode: LanguageWebHU, + }, + { + EnglishName: "Italian", + NativeName: "Italiano", + LanguageCode: LanguageItalian, + WebAPILanguageCode: LanguageWebIT, + }, + { + EnglishName: "Japanese", + NativeName: "日本語", + LanguageCode: LanguageJapanese, + WebAPILanguageCode: LanguageWebJA, + }, + { + EnglishName: "Korean", + NativeName: "한국어", + LanguageCode: LanguageKorean, + WebAPILanguageCode: LanguageWebKO, + }, + { + EnglishName: "Norwegian", + NativeName: "Norsk", + LanguageCode: LanguageNorwegian, + WebAPILanguageCode: LanguageWebNO, + }, + { + EnglishName: "Polish", + NativeName: "Polski", + LanguageCode: LanguagePolish, + WebAPILanguageCode: LanguageWebPL, + }, + { + EnglishName: "Portuguese", + NativeName: "Português", + LanguageCode: LanguagePortuguese, + WebAPILanguageCode: LanguageWebPT, + }, + { + EnglishName: "Portuguese-Brazil", + NativeName: "Português-Brasil", + LanguageCode: LanguagePortugueseBrazil, + WebAPILanguageCode: LanguageWebPTBR, + }, + { + EnglishName: "Romanian", + NativeName: "Română", + LanguageCode: LanguageRomanian, + WebAPILanguageCode: LanguageWebRO, + }, + { + EnglishName: "Russian", + NativeName: "Русский", + LanguageCode: LanguageRussian, + WebAPILanguageCode: LanguageWebRU, + }, + { + EnglishName: "Spanish-Spain", + NativeName: "Español-España", + LanguageCode: LanguageSpanishSpain, + WebAPILanguageCode: LanguageWebES, + }, + { + EnglishName: "Spanish-Latin America", + NativeName: "Español-Latinoamérica", + LanguageCode: LanguageSpanishLatinAmerica, + WebAPILanguageCode: LanguageWebES419, + }, + { + EnglishName: "Swedish", + NativeName: "Svenska", + LanguageCode: LanguageSwedish, + WebAPILanguageCode: LanguageWebSV, + }, + { + EnglishName: "Thai", + NativeName: "ไทย", + LanguageCode: LanguageThai, + WebAPILanguageCode: LanguageWebTH, + }, + { + EnglishName: "Turkish", + NativeName: "Türkçe", + LanguageCode: LanguageTurkish, + WebAPILanguageCode: LanguageWebTR, + }, + { + EnglishName: "Ukrainian", + NativeName: "Українська", + LanguageCode: LanguageUkrainian, + WebAPILanguageCode: LanguageWebUK, + }, + { + EnglishName: "Vietnamese", + NativeName: "Tiếng Việt", + LanguageCode: LanguageVietnamese, + WebAPILanguageCode: LanguageWebVN, + }, +} + +// https://partner.steamgames.com/doc/store/pricing/currencies +type CurrencyCode string + +const ( + CurrencyAED CurrencyCode = "AED" + CurrencyARS CurrencyCode = "ARS" + CurrencyAUD CurrencyCode = "AUD" + CurrencyBRL CurrencyCode = "BRL" + CurrencyCAD CurrencyCode = "CAD" + CurrencyCHF CurrencyCode = "CHF" + CurrencyCLP CurrencyCode = "CLP" + CurrencyCNY CurrencyCode = "CNY" + CurrencyCOP CurrencyCode = "COP" + CurrencyCRC CurrencyCode = "CRC" + CurrencyEUR CurrencyCode = "EUR" + CurrencyGBP CurrencyCode = "GBP" + CurrencyHKD CurrencyCode = "HKD" + CurrencyILS CurrencyCode = "ILS" + CurrencyIDR CurrencyCode = "IDR" + CurrencyINR CurrencyCode = "INR" + CurrencyJPY CurrencyCode = "JPY" + CurrencyKRW CurrencyCode = "KRW" + CurrencyKWD CurrencyCode = "KWD" + CurrencyKZT CurrencyCode = "KZT" + CurrencyMXN CurrencyCode = "MXN" + CurrencyMYR CurrencyCode = "MYR" + CurrencyNOK CurrencyCode = "NOK" + CurrencyNZD CurrencyCode = "NZD" + CurrencyPEN CurrencyCode = "PEN" + CurrencyPHP CurrencyCode = "PHP" + CurrencyPLN CurrencyCode = "PLN" + CurrencyQAR CurrencyCode = "QAR" + CurrencyRUB CurrencyCode = "RUB" + CurrencySAR CurrencyCode = "SAR" + CurrencySGD CurrencyCode = "SGD" + CurrencyTHB CurrencyCode = "THB" + CurrencyTRY CurrencyCode = "TRY" + CurrencyTWD CurrencyCode = "TWD" + CurrencyUAH CurrencyCode = "UAH" + CurrencyUSD CurrencyCode = "USD" + CurrencyUYU CurrencyCode = "UYU" + CurrencyVND CurrencyCode = "VND" + CurrencyZAR CurrencyCode = "ZAR" +) + +type Currency struct { + CurrencyCode CurrencyCode + Description string +} + +var Currencies = []Currency{ //nolint:gochecknoglobals //TODO + {CurrencyCode: CurrencyARS, Description: "Argentine Peso"}, + {CurrencyCode: CurrencyAUD, Description: "Australian Dollars"}, + {CurrencyCode: CurrencyBRL, Description: "Brazilian Reals"}, + {CurrencyCode: CurrencyCAD, Description: "Canadian Dollars"}, + {CurrencyCode: CurrencyCLP, Description: "Chilean Peso"}, + {CurrencyCode: CurrencyCNY, Description: "Chinese Renminbi (yuan)"}, + {CurrencyCode: CurrencyCOP, Description: "Colombian Peso"}, + {CurrencyCode: CurrencyCRC, Description: "Costa Rican Colón"}, + {CurrencyCode: CurrencyEUR, Description: "European Union Euro"}, + {CurrencyCode: CurrencyHKD, Description: "Hong Kong Dollar"}, + {CurrencyCode: CurrencyINR, Description: "Indian Rupee"}, + {CurrencyCode: CurrencyIDR, Description: "Indonesian Rupiah"}, + {CurrencyCode: CurrencyILS, Description: "Israeli New Shekel"}, + {CurrencyCode: CurrencyJPY, Description: "Japanese Yen"}, + {CurrencyCode: CurrencyKZT, Description: "Kazakhstani Tenge"}, + {CurrencyCode: CurrencyKWD, Description: "Kuwaiti Dinar"}, + {CurrencyCode: CurrencyMYR, Description: "Malaysian Ringgit"}, + {CurrencyCode: CurrencyMXN, Description: "Mexican Peso"}, + {CurrencyCode: CurrencyTWD, Description: "New Taiwan Dollar"}, + {CurrencyCode: CurrencyNZD, Description: "New Zealand Dollar"}, + {CurrencyCode: CurrencyNOK, Description: "Norwegian Krone"}, + {CurrencyCode: CurrencyPEN, Description: "Peruvian Nuevo Sol"}, + {CurrencyCode: CurrencyPHP, Description: "Philippine Peso"}, + {CurrencyCode: CurrencyPLN, Description: "Polish Złoty"}, + {CurrencyCode: CurrencyQAR, Description: "Qatari Riyal"}, + {CurrencyCode: CurrencyRUB, Description: "Russian Rouble"}, + {CurrencyCode: CurrencySAR, Description: "Saudi Riyal"}, + {CurrencyCode: CurrencySGD, Description: "Singapore Dollar"}, + {CurrencyCode: CurrencyZAR, Description: "South African Rand"}, + {CurrencyCode: CurrencyKRW, Description: "South Korean Won"}, + {CurrencyCode: CurrencyCHF, Description: "Swiss Francs"}, + {CurrencyCode: CurrencyTHB, Description: "Thai Baht"}, + {CurrencyCode: CurrencyTRY, Description: "Turkish Lira"}, + {CurrencyCode: CurrencyUAH, Description: "Ukrainian Hryvnia"}, + {CurrencyCode: CurrencyAED, Description: "United Arab Emirates Dirham"}, + {CurrencyCode: CurrencyGBP, Description: "United Kingdom Pound"}, + {CurrencyCode: CurrencyUSD, Description: "United States Dollar"}, + {CurrencyCode: CurrencyUYU, Description: "Uruguayan Peso"}, + {CurrencyCode: CurrencyVND, Description: "Vietnamese Dong"}, +} + +// Country codes to get info on apps/packages. +type ProductCC string + +const ( + ProductCCAE ProductCC = "ae" + ProductCCAR ProductCC = "ar" + ProductCCAU ProductCC = "au" + ProductCCAZ ProductCC = "az" + ProductCCBR ProductCC = "br" + ProductCCCA ProductCC = "ca" + ProductCCCH ProductCC = "ch" + ProductCCCL ProductCC = "cl" + ProductCCCN ProductCC = "cn" + ProductCCCO ProductCC = "co" + ProductCCCR ProductCC = "cr" + ProductCCEU ProductCC = "eu" + ProductCCHK ProductCC = "hk" + ProductCCID ProductCC = "id" + ProductCCIL ProductCC = "il" + ProductCCIN ProductCC = "in" + ProductCCJP ProductCC = "jp" + ProductCCKR ProductCC = "kr" + ProductCCKW ProductCC = "kw" + ProductCCKZ ProductCC = "kz" + ProductCCMX ProductCC = "mx" + ProductCCMY ProductCC = "my" + ProductCCNO ProductCC = "no" + ProductCCNZ ProductCC = "nz" + ProductCCPE ProductCC = "pe" + ProductCCPH ProductCC = "ph" + ProductCCPK ProductCC = "pk" + ProductCCPL ProductCC = "pl" + ProductCCQA ProductCC = "qa" + ProductCCRU ProductCC = "ru" + ProductCCSA ProductCC = "sa" + ProductCCSG ProductCC = "sg" + ProductCCTH ProductCC = "th" + ProductCCTR ProductCC = "tr" + ProductCCTW ProductCC = "tw" + ProductCCUA ProductCC = "ua" + ProductCCUK ProductCC = "uk" + ProductCCUS ProductCC = "us" + ProductCCUY ProductCC = "uy" + ProductCCVN ProductCC = "vn" + ProductCCZA ProductCC = "za" +) + +var ProductCCs = []ProductCC{ //nolint:gochecknoglobals //TODO + ProductCCAE, ProductCCAR, ProductCCAU, ProductCCAZ, ProductCCBR, ProductCCCA, ProductCCCH, ProductCCCL, ProductCCCN, + ProductCCCO, ProductCCCR, ProductCCEU, ProductCCHK, ProductCCID, ProductCCIL, ProductCCIN, ProductCCJP, ProductCCKR, + ProductCCKW, ProductCCKZ, ProductCCMX, ProductCCMY, ProductCCNO, ProductCCNZ, ProductCCPE, ProductCCPH, ProductCCPK, + ProductCCPL, ProductCCQA, ProductCCRU, ProductCCSA, ProductCCSG, ProductCCTH, ProductCCTR, ProductCCTW, ProductCCUA, + ProductCCUK, ProductCCUS, ProductCCUY, ProductCCVN, ProductCCZA, +} + +func IsProductCC(i string) bool { + for _, v := range ProductCCs { + if string(v) == i { + return true + } + } + return false +} diff --git a/internal/model/model.go b/internal/model/model.go new file mode 100644 index 0000000..17fec02 --- /dev/null +++ b/internal/model/model.go @@ -0,0 +1,313 @@ +package model + +// https://github.com/babelshift/SteamWebAPI2 + +type GetPlayerSummariesRequest struct { + SteamID uint64 `url:"steamids,string"` +} + +type GetPlayerSummariesResponse struct { + Response struct { + Players []PlayerSummary `json:"players"` + } `json:"response"` +} + +type PlayerSummary struct { + // Unique Steam ID of the player. + SteamID int64 `json:"steamid,string"` + // Determines the visibility of the user's profile (public, private, friends) + ProfileVisibility ProfileVisibility `json:"communityvisibilitystate"` + // If set to 1, the user has configured his profile. + ProfileState uint `json:"profilestate"` + // User's current nick name (displayed in profile and friends list) + Nickname string `json:"personaname"` + // The URL for the player's Steam Community profile + ProfileURL string `json:"profileurl"` + // The normal sized uploaded avatar image for the user's Steam profile + Avatar string `json:"avatar"` + // The medium sized uploaded avatar image for the user's Steam profile + AvatarMedium string `json:"avatarmedium"` + // The full sized uploaded avatar image for the user's Steam profile + AvatarFull string `json:"avatarfull"` + // + AvatarHash string `json:"avatarhash"` + // The date at which the user last logged off Steam + LastLogOffDate int64 `json:"lastlogoff"` + // The selected privacy/visibility level of the player's comments section on their Steam Community profile + CommentPermission CommentPermission `json:"commentpermission"` + // The current status of the user on the Steam network + UserStatus UserStatus `json:"personastate"` + // The player's real name as entered on their Steam profile + RealName string `json:"realname"` + // The player's selected primary group to display on their Steam profile + PrimaryGroupID string `json:"primaryclanid"` + // The date at which the user created their Steam account + AccountCreatedDate int64 `json:"timecreated"` + // + PersonaStateFlags int `json:"personastateflags"` + // The player's selected country + CountryCode string `json:"loccountrycode"` + // The player's selected state + StateCode string + // The player's selected city. + // This seems to refer to a database city id, so I'm not sure how to make use of this field. + CityCode uint `json:"loccityid"` + // The name of the game that a player is currently playing + PlayingGameName string `json:"gameextrainfo"` + // The id of the game that the player is currently playing. + // This doesn't seem to be an appid, so I'm not sure how to make use of this field. + PlayingGameID string `json:"gameid"` + // The IP of the server the user is currently playing on. + PlayingGameServerIP string `json:"gameserverip"` +} + +// ProfileVisibility Indicates the selected privacy/visibility level of the player's Steam Community profile. +type ProfileVisibility int + +const ( + ProfileVisibilityUnknown ProfileVisibility = 0 + ProfileVisibilityPrivate ProfileVisibility = 1 + ProfileVisibilityFriendsOnly ProfileVisibility = 2 + ProfileVisibilityPublic ProfileVisibility = 3 +) + +// CommentPermission Indicates the selected privacy/visibility level of the player's comments section +// on their Steam Community profile. +type CommentPermission int + +const ( + CommentPermissionUnknown ProfileVisibility = 0 + CommentPermissionFriendsOnly ProfileVisibility = 1 + CommentPermissionPrivate ProfileVisibility = 2 + CommentPermissionPublic ProfileVisibility = 3 +) + +// UserStatus Indicates the current status of the user on the Steam network. +type UserStatus int + +const ( + UserStatusOffline UserStatus = 0 + UserStatusOnline UserStatus = 1 + UserStatusBusy UserStatus = 2 + UserStatusAway UserStatus = 3 + UserStatusSnooze UserStatus = 4 + UserStatusUnknown UserStatus = 5 + UserStatusInGame UserStatus = 6 +) + +type GetOwnedGamesRequest struct { + SteamID uint64 `url:"steamid,string"` + // true if we want additional details (name, icon) about each game + IncludeAppInfo bool `url:"include_appinfo,omitempty"` + // free games are excluded by default. If this is set, free games the user has played will be returned. + IncludePlayedFreeGames bool `url:"include_played_free_games,omitempty"` + // some games are in the free sub, which are excluded by default. + IncludeFreeSub bool `url:"include_free_sub,omitempty"` + // if set, skip unvetted store apps + SkipUnvettedApps bool `url:"skip_unvetted_apps,omitempty"` + // will return appinfo in this language + Language string `url:"language,omitempty"` + // true if we want even more details (capsule, sortas, and capabilities) about each game. + // include_appinfo must also be true. + IncludeExtendedAppInfo bool `url:"include_extended_appinfo,omitempty"` +} + +type GetOwnedGamesResponse struct { + Response OwnedGames `json:"response"` +} + +type OwnedGames struct { + GameCount int `json:"game_count"` + Games []struct { + AppID uint `json:"appid"` + Name string `json:"name"` + PlaytimeForever uint `json:"playtime_forever"` + PlaytimeWindows uint `json:"playtime_windows_forever"` + PlaytimeMac uint `json:"playtime_mac_forever"` + PlaytimeLinux uint `json:"playtime_linux_forever"` + ImgIconURL string `json:"img_icon_url"` + ImgLogoURL string `json:"img_logo_url"` + HasCommunityVisibleStats bool `json:"has_community_visible_stats"` + } `json:"games"` +} + +type GetAppDetailsRequest struct { + AppIDs []int `url:"appids"` + CountryCode ProductCC `url:"cc,omitempty"` + Language LanguageCode `url:"l,omitempty"` +} + +type AppDetailsBase struct { + Type string `json:"type"` + Name string `json:"name"` + AppID int `json:"steam_appid"` + IsFree bool `json:"is_free"` + ControllerSupport string `json:"controller_support"` + DetailedDescription string `json:"detailed_description"` + AboutTheGame string `json:"about_the_game"` + ShortDescription string `json:"short_description"` + SupportedLanguages string `json:"supported_languages"` + Reviews string `json:"reviews"` + HeaderImage string `json:"header_image"` + Website string `json:"website"` + Developers []string `json:"developers"` + Publishers []string `json:"publishers"` + ReleaseDate struct { + ComingSoon bool `json:"coming_soon"` + Date string `json:"date"` + } `json:"release_date"` +} + +type AppDetailsBasic struct { + Success bool `json:"success"` + Data *AppDetailsBase `json:"data"` +} + +type AppDetails struct { + Success bool `json:"success"` + Data *struct { + AppDetailsBase + RequiredAge int `json:"required_age"` // may be string in some time + DLC []int `json:"dlc"` + Fullgame struct { + AppID int `json:"appid,string"` + Name string `json:"name"` + } `json:"fullgame"` + PcRequirements struct { + Minimum string `json:"minimum"` + Recommended string `json:"recommended"` + } `json:"pc_requirements"` + MacRequirements struct { // may be an empty slice some time + Minimum string `json:"minimum"` + Recommended string `json:"recommended"` + } `json:"mac_requirements"` + LinuxRequirements struct { // may be an empty slice some time + Minimum string `json:"minimum"` + Recommended string `json:"recommended"` + } `json:"linux_requirements"` + LegalNotice string `json:"legal_notice"` + ExtUserAccountNotice string `json:"ext_user_account_notice"` + DRMNotice string `json:"drm_notice"` + Demos []struct { + AppID int `json:"appid,string"` + Description string `json:"description"` + } `json:"demos"` + PriceOverview *struct { + Currency CurrencyCode `json:"currency"` + Initial int `json:"initial"` + Final int `json:"final"` + DiscountPercent int `json:"discount_percent"` + InitialFormatted string `json:"initial_formatted"` + FinalFormatted string `json:"final_formatted"` + RecurringSub interface{} `json:"recurring_sub"` // Either "false" or a sub id int + RecurringSubDesc string `json:"recurring_sub_desc"` + } `json:"price_overview"` + Packages []int `json:"packages"` + PackageGroups []struct { + Name string `json:"name"` + Title string `json:"title"` + Description string `json:"description"` + SelectionText string `json:"selection_text"` + SaveText string `json:"save_text"` + DisplayType int `json:"display_type"` + IsRecurringSubscription string `json:"is_recurring_subscription"` + Subs []struct { + PackageID int `json:"packageid"` + PercentSavingsText string `json:"percent_savings_text"` + PercentSavings int `json:"percent_savings"` + OptionText string `json:"option_text"` + OptionDescription string `json:"option_description"` + CanGetFreeLicense string `json:"can_get_free_license"` + IsFreeLicense bool `json:"is_free_license"` + PriceInCentsWithDiscount int `json:"price_in_cents_with_discount"` + } `json:"subs"` + } `json:"package_groups"` + Platforms struct { + Windows bool `json:"windows"` + Mac bool `json:"mac"` + Linux bool `json:"linux"` + } `json:"platforms"` + Metacritic struct { + Score int8 `json:"score"` + URL string `json:"url"` + } `json:"metacritic"` + Categories AppDetailsCategory `json:"categories"` + Genres AppDetailsGenre `json:"genres"` + Screenshots []struct { + ID int `json:"id"` + PathThumbnail string `json:"path_thumbnail"` + PathFull string `json:"path_full"` + } `json:"screenshots"` + Movies []struct { + ID int `json:"id"` + Name string `json:"name"` + Thumbnail string `json:"thumbnail"` + Webm struct { + Num480 string `json:"480"` + Max string `json:"max"` + } `json:"webm"` + Highlight bool `json:"highlight"` + } `json:"movies"` + Recommendations struct { + Total int `json:"total"` + } `json:"recommendations"` + Achievements struct { + Total int `json:"total"` + Highlighted []struct { + Name string `json:"name"` + Path string `json:"path"` + } `json:"highlighted"` + } `json:"achievements"` + SupportInfo struct { + URL string `json:"url"` + Email string `json:"email"` + } `json:"support_info"` + Background string `json:"background"` + ContentDescriptors struct { + IDs interface{} + Notes interface{} + } `json:"content_descriptors"` + } `json:"data"` +} + +type AppDetailsGenre []struct { + ID int `json:"id,string"` + Description string `json:"description"` +} + +func (g AppDetailsGenre) IDs() []int { + ids := make([]int, len(g)) + for i, v := range g { + ids[i] = v.ID + } + return ids +} + +func (g AppDetailsGenre) Names() []string { + names := make([]string, len(g)) + for i, v := range g { + names[i] = v.Description + } + return names +} + +type AppDetailsCategory []struct { + ID int `json:"id"` + Description string `json:"description"` +} + +func (c AppDetailsCategory) IDs() []int { + ids := make([]int, len(c)) + for i, v := range c { + ids[i] = v.ID + } + return ids +} + +func (c AppDetailsCategory) Names() []string { + names := make([]string, len(c)) + for i, v := range c { + names[i] = v.Description + } + return names +} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..2dae5ce --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,10 @@ +{ + "release-type": "go", + "packages": { + ".": { + "package-name": "" + } + }, + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true +} \ No newline at end of file