14 Commits

Author SHA1 Message Date
f14c0cd9c2 fix ci 2021-11-12 05:41:37 +08:00
63d4e6e1d2 improve ci 2021-11-12 05:35:53 +08:00
be6d3e5a44 improve ci 2021-11-12 05:24:53 +08:00
ae4b46bf57 improve cli 2021-11-12 00:23:37 +08:00
3f3980de38 fix #18: skip noop decoder 2021-11-11 23:43:20 +08:00
6fd5bd5863 fix #18: support upper case extension 2021-11-11 23:27:07 +08:00
0a13671df2 chore: update deps 2021-11-11 23:18:17 +08:00
28d84e4dc2 Fix: algorithm bugs in qmc
Optimize: algorithm in `kgm`
2021-08-03 04:17:10 +08:00
60c15894c0 Fixed #12: Add sniffer for .dff 2021-05-25 12:21:11 +08:00
15e340eac4 Update RawDecoder & Support .kwm as .aac 2021-05-23 19:59:29 +08:00
c836ac7cb5 Clean TODOs 2021-05-23 19:58:23 +08:00
ad301e0ff2 Update Help Text 2021-05-16 17:21:20 +08:00
1f0aefb72d Update README.md 2021-05-16 17:18:37 +08:00
c71ad9cc79 Close #10 Drag to Decrypt 2021-05-16 17:15:52 +08:00
17 changed files with 298 additions and 129 deletions

View File

@ -17,17 +17,23 @@ on:
jobs: jobs:
test: test:
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
os: [ windows-latest, ubuntu-latest, macos-latest ]
include: include:
- os: ubuntu-latest - target: "linux/amd64"
GOOS: "linux"
GOARCH: "amd64"
BIN_SUFFIX: "" BIN_SUFFIX: ""
- os: macos-latest - target: "windows/amd64"
BIN_SUFFIX: "" GOOS: "windows"
- os: windows-latest GOARCH: "amd64"
BIN_SUFFIX: ".exe" BIN_SUFFIX: ".exe"
- target: "darwin/amd64"
GOOS: "darwin"
GOARCH: "amd64"
BIN_SUFFIX: ""
steps: steps:
- name: Checkout codebase - name: Checkout codebase
uses: actions/checkout@v2 uses: actions/checkout@v2
@ -37,7 +43,7 @@ jobs:
- name: Set up Go 1.x - name: Set up Go 1.x
uses: actions/setup-go@v2 uses: actions/setup-go@v2
with: with:
go-version: ^1.16 go-version: ^1.17
- name: Setup vars - name: Setup vars
id: vars id: vars
@ -47,11 +53,13 @@ jobs:
- name: Build - name: Build
env: env:
GOOS: ${{ matrix.GOOS }}
GOARCH: ${{ matrix.GOARCH }}
CGO_ENABLED: 0 CGO_ENABLED: 0
run: go build -trimpath -ldflags="-w -s -X main.AppVersion=${{ steps.vars.outputs.git_tag }}" -v -o um-${{ runner.os }}${{ matrix.BIN_SUFFIX }} ./cmd/um run: go build -v -trimpath -ldflags="-w -s -X main.AppVersion=${{ steps.vars.outputs.git_tag }}" -o um-${{ matrix.GOOS }}-${{ matrix.GOARCH }}${{ matrix.BIN_SUFFIX }} ./cmd/um
- name: Publish artifact - name: Publish artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: um-${{ runner.os }}${{ matrix.BIN_SUFFIX }} name: um-${{ matrix.GOOS }}-${{ matrix.GOARCH }}
path: ./um-${{ runner.os }}${{ matrix.BIN_SUFFIX }} path: ./um-${{ matrix.GOOS }}-${{ matrix.GOARCH }}${{ matrix.BIN_SUFFIX }}

84
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,84 @@
name: Create Release
on:
push:
tags:
- "v*"
jobs:
create_release:
runs-on: ubuntu-latest
steps:
- name: Get current time
id: date
run: echo "::set-output name=date::$(date +'%Y/%m/%d')"
- name: Create release
id: release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: "Build ${{ steps.date.outputs.date }}"
draft: true
outputs:
upload_url: "${{ steps.release.outputs.upload_url }}"
build:
needs:
- create_release
runs-on: ubuntu-latest
strategy:
matrix:
include:
- target: "linux/amd64"
GOOS: "linux"
GOARCH: "amd64"
BIN_SUFFIX: ""
- target: "windows/amd64"
GOOS: "windows"
GOARCH: "amd64"
BIN_SUFFIX: ".exe"
- target: "windows/386"
GOOS: "windows"
GOARCH: "386"
BIN_SUFFIX: ".exe"
- target: "darwin/amd64"
GOOS: "darwin"
GOARCH: "amd64"
BIN_SUFFIX: ""
steps:
- name: Checkout codebase
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Setup vars
id: vars
run: |
echo "::set-output name=short_sha::$(git rev-parse --short HEAD)"
echo "::set-output name=git_tag::$(git describe --tags --always)"
- name: Set up Go 1.x
uses: actions/setup-go@v2
with:
go-version: ^1.17
- name: Build
env:
GOOS: ${{ matrix.GOOS }}
GOARCH: ${{ matrix.GOARCH }}
CGO_ENABLED: 0
run: go build -trimpath -v -ldflags="-w -s -X main.AppVersion=${{ steps.vars.outputs.git_tag }}" -o um ./cmd/um
- name: Upload release assets
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create_release.outputs.upload_url }}
asset_path: um
asset_name: um-${{ matrix.GOOS }}-${{ matrix.GOARCH }}${{ matrix.BIN_SUFFIX }}
asset_content_type: application/octet-stream

View File

@ -1,18 +1,25 @@
# Unlock Music Project - CLI Edition # Unlock Music Project - CLI Edition
Original: Web Edition https://github.com/ix64/unlock-music Original: Web Edition https://github.com/ix64/unlock-music
- [Release Download](https://github.com/unlock-music/cli/releases/latest) - [Release Download](https://github.com/unlock-music/cli/releases/latest)
## Features ## Features
- [x] All Algorithm Supported By `ix64/unlock-music` - [x] All Algorithm Supported By `ix64/unlock-music`
- [ ] Complete Cover Image - [ ] Complete Cover Image
- [ ] Parse Meta Data - [ ] Parse Meta Data
- [ ] Complete Meta Data - [ ] Complete Meta Data
## Hou to Build ## Hou to Build
- Requirements: **Golang 1.16**
- Requirements: **Golang 1.17**
1. Clone this repo `git clone https://github.com/unlock-music/cli && cd cli` 1. Clone this repo `git clone https://github.com/unlock-music/cli && cd cli`
2. Build the executable `go build ./cmd/um` 2. Build the executable `go build ./cmd/um`
## How to use ## How to use
- Drag the encrypted file to `um.exe` (Tested on Windows)
- Run: `./um [-o <output dir>] [-i] <input dir/file>`
- Use `./um -h` to show help menu - Use `./um -h` to show help menu

View File

@ -1,12 +1,30 @@
package common package common
import (
"path/filepath"
"strings"
)
type NewDecoderFunc func([]byte) Decoder type NewDecoderFunc func([]byte) Decoder
var decoderRegistry = make(map[string][]NewDecoderFunc) type decoderItem struct {
noop bool
decoder NewDecoderFunc
}
func RegisterDecoder(ext string, dispatchFunc NewDecoderFunc) { var decoderRegistry = make(map[string][]decoderItem)
decoderRegistry[ext] = append(decoderRegistry[ext], dispatchFunc)
func RegisterDecoder(ext string, noop bool, dispatchFunc NewDecoderFunc) {
decoderRegistry[ext] = append(decoderRegistry[ext],
decoderItem{noop: noop, decoder: dispatchFunc})
} }
func GetDecoder(ext string) []NewDecoderFunc { func GetDecoder(filename string, skipNoop bool) (rs []NewDecoderFunc) {
return decoderRegistry[ext] ext := strings.ToLower(strings.TrimLeft(filepath.Ext(filename), "."))
for _, dec := range decoderRegistry[ext] {
if skipNoop && dec.noop {
continue
}
rs = append(rs, dec.decoder)
}
return
} }

View File

@ -1,17 +1,27 @@
package common package common
import (
"errors"
"strings"
)
type RawDecoder struct { type RawDecoder struct {
file []byte file []byte
audioExt string audioExt string
} }
//goland:noinspection GoUnusedExportedFunction
func NewRawDecoder(file []byte) Decoder { func NewRawDecoder(file []byte) Decoder {
return &RawDecoder{file: file} return &RawDecoder{file: file}
} }
func (d RawDecoder) Validate() error { func (d *RawDecoder) Validate() error {
return nil for ext, sniffer := range snifferRegistry {
if sniffer(d.file) {
d.audioExt = strings.ToLower(ext)
return nil
}
}
return errors.New("audio doesn't recognized")
} }
func (d RawDecoder) Decode() error { func (d RawDecoder) Decode() error {
@ -27,21 +37,19 @@ func (d RawDecoder) GetAudioData() []byte {
} }
func (d RawDecoder) GetAudioExt() string { func (d RawDecoder) GetAudioExt() string {
return "." + d.audioExt return d.audioExt
} }
func (d RawDecoder) GetMeta() Meta { func (d RawDecoder) GetMeta() Meta {
return nil return nil
} }
func DecoderFuncWithExt(ext string) NewDecoderFunc {
return func(file []byte) Decoder {
return &RawDecoder{file: file, audioExt: ext}
}
}
func init() { func init() {
/*RegisterDecoder("mp3", DecoderFuncWithExt("mp3")) RegisterDecoder("mp3", true, NewRawDecoder)
RegisterDecoder("flac", DecoderFuncWithExt("flac")) RegisterDecoder("flac", true, NewRawDecoder)
RegisterDecoder("wav", DecoderFuncWithExt("wav")) RegisterDecoder("ogg", true, NewRawDecoder)
RegisterDecoder("ogg", DecoderFuncWithExt("ogg")) RegisterDecoder("m4a", true, NewRawDecoder)
RegisterDecoder("m4a", DecoderFuncWithExt("m4a"))*/ RegisterDecoder("wav", true, NewRawDecoder)
RegisterDecoder("wma", true, NewRawDecoder)
RegisterDecoder("aac", true, NewRawDecoder)
} }

View File

@ -5,12 +5,14 @@ import "bytes"
type Sniffer func(header []byte) bool type Sniffer func(header []byte) bool
var snifferRegistry = map[string]Sniffer{ var snifferRegistry = map[string]Sniffer{
".m4a": SnifferM4A, ".mp3": SnifferMP3,
".ogg": SnifferOGG,
".flac": SnifferFLAC, ".flac": SnifferFLAC,
".ogg": SnifferOGG,
".m4a": SnifferM4A,
".wav": SnifferWAV, ".wav": SnifferWAV,
".wma": SnifferWMA, ".wma": SnifferWMA,
".mp3": SnifferMP3, ".aac": SnifferAAC,
".dff": SnifferDFF,
} }
func SniffAll(header []byte) (string, bool) { func SniffAll(header []byte) (string, bool) {
@ -42,3 +44,12 @@ func SnifferWAV(header []byte) bool {
func SnifferWMA(header []byte) bool { func SnifferWMA(header []byte) bool {
return bytes.HasPrefix(header, []byte("\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c")) return bytes.HasPrefix(header, []byte("\x30\x26\xb2\x75\x8e\x66\xcf\x11\xa6\xd9\x00\xaa\x00\x62\xce\x6c"))
} }
func SnifferAAC(header []byte) bool {
return bytes.HasPrefix(header, []byte{0xFF, 0xF1})
}
// SnifferDFF sniff a DSDIFF format
// reference to: https://www.sonicstudio.com/pdf/dsd/DSDIFF_1.5_Spec.pdf
func SnifferDFF(header []byte) bool {
return bytes.HasPrefix(header, []byte("FRM8"))
}

View File

@ -40,7 +40,7 @@ func (d Decoder) GetAudioData() []byte {
} }
func (d Decoder) GetAudioExt() string { func (d Decoder) GetAudioExt() string {
return "" return "" // use sniffer
} }
func (d Decoder) GetMeta() common.Meta { func (d Decoder) GetMeta() common.Meta {
@ -58,9 +58,8 @@ func (d *Decoder) Validate() error {
d.key = d.file[0x1c:0x2c] d.key = d.file[0x1c:0x2c]
d.key = append(d.key, 0x00) d.key = append(d.key, 0x00)
_ = d.file[0x2c:0x3c] //key2 _ = d.file[0x2c:0x3c] //todo: key2
return nil return nil
} }
func (d *Decoder) Decode() error { func (d *Decoder) Decode() error {
@ -69,18 +68,15 @@ func (d *Decoder) Decode() error {
lenData := len(dataEncrypted) lenData := len(dataEncrypted)
initMask() initMask()
if fullMaskLen < lenData { if fullMaskLen < lenData {
logging.Log().Warn("文件过大,处理后的音频不完整,请向我们报告此文件的信息") logging.Log().Warn("The file is too large and the processed audio is incomplete, " +
"please report to us about this file at https://github.com/unlock-music/cli/issues")
lenData = fullMaskLen lenData = fullMaskLen
} }
d.audio = make([]byte, lenData) d.audio = make([]byte, lenData)
for i := 0; i < lenData; i++ { for i := 0; i < lenData; i++ {
med8 := d.key[i%17] ^ dataEncrypted[i] med8 := dataEncrypted[i] ^ d.key[i%17] ^ maskV2PreDef[i%(16*17)] ^ maskV2[i>>4]
med8 ^= (med8 & 0xf) << 4 d.audio[i] = med8 ^ (med8&0xf)<<4
msk8 := maskV2PreDef[i%272] ^ maskV2[i>>4]
msk8 ^= (msk8 & 0xf) << 4
d.audio[i] = med8 ^ msk8
} }
if d.isVpr { if d.isVpr {
for i := 0; i < lenData; i++ { for i := 0; i < lenData; i++ {
@ -91,8 +87,8 @@ func (d *Decoder) Decode() error {
} }
func init() { func init() {
// Kugou // Kugou
common.RegisterDecoder("kgm", NewDecoder) common.RegisterDecoder("kgm", false, NewDecoder)
common.RegisterDecoder("kgma", NewDecoder) common.RegisterDecoder("kgma", false, NewDecoder)
// Viper // Viper
common.RegisterDecoder("vpr", NewDecoder) common.RegisterDecoder("vpr", false, NewDecoder)
} }

View File

@ -6,7 +6,7 @@ import (
"github.com/ulikunitz/xz" "github.com/ulikunitz/xz"
"github.com/unlock-music/cli/internal/logging" "github.com/unlock-music/cli/internal/logging"
"go.uber.org/zap" "go.uber.org/zap"
"io/ioutil" "io"
) )
var maskDiffVpr = []byte{ var maskDiffVpr = []byte{
@ -41,7 +41,7 @@ var maskV2 []byte
var fullMaskLen int var fullMaskLen int
var initMaskOK = false var initMaskOK = false
//todo: 根据需求解压Mask大小 //todo: decompress mask on demand
func initMask() { func initMask() {
if initMaskOK { if initMaskOK {
return return
@ -50,7 +50,7 @@ func initMask() {
if err != nil { if err != nil {
logging.Log().Fatal("load kgm mask failed", zap.Error(err)) logging.Log().Fatal("load kgm mask failed", zap.Error(err))
} }
maskV2, err = ioutil.ReadAll(maskReader) maskV2, err = io.ReadAll(maskReader)
if err != nil { if err != nil {
logging.Log().Fatal("load kgm mask failed", zap.Error(err)) logging.Log().Fatal("load kgm mask failed", zap.Error(err))
} }

View File

@ -124,5 +124,6 @@ func padOrTruncate(raw string, length int) string {
func init() { func init() {
// Kuwo Mp3/Flac // Kuwo Mp3/Flac
common.RegisterDecoder("kwm", NewDecoder) common.RegisterDecoder("kwm", false, NewDecoder)
common.RegisterDecoder("kwm", false, common.NewRawDecoder)
} }

View File

@ -61,7 +61,6 @@ func (d *Decoder) Validate() error {
return nil return nil
} }
//todo: 读取前进行检查长度,防止越界
func (d *Decoder) readKeyData() error { func (d *Decoder) readKeyData() error {
if d.offsetKey == 0 || d.offsetKey+4 > d.fileLen { if d.offsetKey == 0 || d.offsetKey+4 > d.fileLen {
return errors.New("invalid cover file offset") return errors.New("invalid cover file offset")
@ -247,5 +246,5 @@ func (d Decoder) GetMeta() common.Meta {
func init() { func init() {
// Netease Mp3/Flac // Netease Mp3/Flac
common.RegisterDecoder("ncm", NewDecoder) common.RegisterDecoder("ncm", false, NewDecoder)
} }

View File

@ -39,7 +39,7 @@ func NewKey256FromMask44(mask44 []byte) (*Key256Mask, error) {
return q, nil return q, nil
} }
func (q *Key256Mask) getMatrix44() (mask44 []byte, err error) { func (q *Key256Mask) GetMatrix44() ([]byte, error) {
if len(q.matrix) != 128 { if len(q.matrix) != 128 {
return nil, ErrMaskLength128 return nil, ErrMaskLength128
} }
@ -53,7 +53,7 @@ func (q *Key256Mask) getMatrix44() (mask44 []byte, err error) {
return nil, ErrMaskDecode return nil, ErrMaskDecode
} }
} }
q.matrix[idx44] = q.matrix[it256[0]] matrix44[idx44] = q.matrix[it256[0]]
idx44++ idx44++
} }
} }

View File

@ -102,27 +102,27 @@ func DecoderFuncWithExt(ext string) common.NewDecoderFunc {
//goland:noinspection SpellCheckingInspection //goland:noinspection SpellCheckingInspection
func init() { func init() {
common.RegisterDecoder("qmc0", DecoderFuncWithExt("mp3")) //QQ Music Mp3 common.RegisterDecoder("qmc0", false, DecoderFuncWithExt("mp3")) //QQ Music Mp3
common.RegisterDecoder("qmc3", DecoderFuncWithExt("mp3")) //QQ Music Mp3 common.RegisterDecoder("qmc3", false, DecoderFuncWithExt("mp3")) //QQ Music Mp3
common.RegisterDecoder("qmc2", DecoderFuncWithExt("m4a")) //QQ Music M4A common.RegisterDecoder("qmc2", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
common.RegisterDecoder("qmc4", DecoderFuncWithExt("m4a")) //QQ Music M4A common.RegisterDecoder("qmc4", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
common.RegisterDecoder("qmc6", DecoderFuncWithExt("m4a")) //QQ Music M4A common.RegisterDecoder("qmc6", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
common.RegisterDecoder("qmc8", DecoderFuncWithExt("m4a")) //QQ Music M4A common.RegisterDecoder("qmc8", false, DecoderFuncWithExt("m4a")) //QQ Music M4A
common.RegisterDecoder("qmcflac", DecoderFuncWithExt("flac")) //QQ Music Flac common.RegisterDecoder("qmcflac", false, DecoderFuncWithExt("flac")) //QQ Music Flac
common.RegisterDecoder("qmcogg", DecoderFuncWithExt("ogg")) //QQ Music Ogg common.RegisterDecoder("qmcogg", false, DecoderFuncWithExt("ogg")) //QQ Music Ogg
common.RegisterDecoder("tkm", DecoderFuncWithExt("m4a")) //QQ Music Accompaniment M4a common.RegisterDecoder("tkm", false, DecoderFuncWithExt("m4a")) //QQ Music Accompaniment M4a
common.RegisterDecoder("bkcmp3", DecoderFuncWithExt("mp3")) //Moo Music Mp3 common.RegisterDecoder("bkcmp3", false, DecoderFuncWithExt("mp3")) //Moo Music Mp3
common.RegisterDecoder("bkcflac", DecoderFuncWithExt("flac")) //Moo Music Flac common.RegisterDecoder("bkcflac", false, DecoderFuncWithExt("flac")) //Moo Music Flac
common.RegisterDecoder("666c6163", DecoderFuncWithExt("flac")) //QQ Music Weiyun Flac common.RegisterDecoder("666c6163", false, DecoderFuncWithExt("flac")) //QQ Music Weiyun Flac
common.RegisterDecoder("6d7033", DecoderFuncWithExt("mp3")) //QQ Music Weiyun Mp3 common.RegisterDecoder("6d7033", false, DecoderFuncWithExt("mp3")) //QQ Music Weiyun Mp3
common.RegisterDecoder("6f6767", DecoderFuncWithExt("ogg")) //QQ Music Weiyun Ogg common.RegisterDecoder("6f6767", false, DecoderFuncWithExt("ogg")) //QQ Music Weiyun Ogg
common.RegisterDecoder("6d3461", DecoderFuncWithExt("m4a")) //QQ Music Weiyun M4a common.RegisterDecoder("6d3461", false, DecoderFuncWithExt("m4a")) //QQ Music Weiyun M4a
common.RegisterDecoder("776176", DecoderFuncWithExt("wav")) //QQ Music Weiyun Wav common.RegisterDecoder("776176", false, DecoderFuncWithExt("wav")) //QQ Music Weiyun Wav
common.RegisterDecoder("mgg", NewMgg256Decoder) //QQ Music New Ogg common.RegisterDecoder("mgg", false, NewMgg256Decoder) //QQ Music New Ogg
common.RegisterDecoder("mflac", NewMflac256Decoder) //QQ Music New Flac common.RegisterDecoder("mflac", false, NewMflac256Decoder) //QQ Music New Flac
} }

View File

@ -70,10 +70,10 @@ func DecoderFuncWithExt(ext string) common.NewDecoderFunc {
func init() { func init() {
// QQ Music IOS M4a // QQ Music IOS M4a
common.RegisterDecoder("tm2", DecoderFuncWithExt("m4a")) common.RegisterDecoder("tm2", false, DecoderFuncWithExt("m4a"))
common.RegisterDecoder("tm6", DecoderFuncWithExt("m4a")) common.RegisterDecoder("tm6", false, DecoderFuncWithExt("m4a"))
// QQ Music IOS Mp3 // QQ Music IOS Mp3
common.RegisterDecoder("tm0", common.DecoderFuncWithExt("mp3")) common.RegisterDecoder("tm0", false, common.NewRawDecoder)
common.RegisterDecoder("tm3", common.DecoderFuncWithExt("mp3")) common.RegisterDecoder("tm3", false, common.NewRawDecoder)
} }

View File

@ -97,10 +97,10 @@ func DecoderFuncWithExt(ext string) common.NewDecoderFunc {
func init() { func init() {
// Xiami Wav/M4a/Mp3/Flac // Xiami Wav/M4a/Mp3/Flac
common.RegisterDecoder("xm", NewDecoder) common.RegisterDecoder("xm", false, NewDecoder)
// Xiami Typed Format // Xiami Typed Format
common.RegisterDecoder("wav", DecoderFuncWithExt("wav")) common.RegisterDecoder("wav", false, DecoderFuncWithExt("wav"))
common.RegisterDecoder("mp3", DecoderFuncWithExt("mp3")) common.RegisterDecoder("mp3", false, DecoderFuncWithExt("mp3"))
common.RegisterDecoder("flac", DecoderFuncWithExt("flac")) common.RegisterDecoder("flac", false, DecoderFuncWithExt("flac"))
common.RegisterDecoder("m4a", DecoderFuncWithExt("m4a")) common.RegisterDecoder("m4a", false, DecoderFuncWithExt("m4a"))
} }

View File

@ -2,6 +2,7 @@ package main
import ( import (
"errors" "errors"
"fmt"
"github.com/unlock-music/cli/algo/common" "github.com/unlock-music/cli/algo/common"
_ "github.com/unlock-music/cli/algo/kgm" _ "github.com/unlock-music/cli/algo/kgm"
_ "github.com/unlock-music/cli/algo/kwm" _ "github.com/unlock-music/cli/algo/kwm"
@ -12,38 +13,67 @@ import (
"github.com/unlock-music/cli/internal/logging" "github.com/unlock-music/cli/internal/logging"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v2"
"go.uber.org/zap" "go.uber.org/zap"
"log"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
) )
var AppVersion = "0.0.3" var AppVersion = "v0.0.6"
func main() { func main() {
app := cli.App{ app := cli.App{
Name: "Unlock Music CLI", Name: "Unlock Music CLI",
HelpName: "um", HelpName: "um",
Usage: "Unlock your encrypted music file https://github.com/unlock-music/cli", Usage: "Unlock your encrypted music file https://github.com/unlock-music/cli",
Version: AppVersion, Version: fmt.Sprintf("%s (%s,%s/%s)", AppVersion, runtime.Version(), runtime.GOOS, runtime.GOARCH),
Flags: []cli.Flag{ Flags: []cli.Flag{
&cli.StringFlag{Name: "input", Aliases: []string{"i"}, Usage: "path to input file or dir", Required: true}, &cli.StringFlag{Name: "input", Aliases: []string{"i"}, Usage: "path to input file or dir", Required: false},
&cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "path to output dir", Required: true}, &cli.StringFlag{Name: "output", Aliases: []string{"o"}, Usage: "path to output dir", Required: false},
&cli.BoolFlag{Name: "skip-noop", Aliases: []string{"n"}, Usage: "skip noop decoder", Required: false, Value: true},
}, },
Action: appMain, Action: appMain,
Copyright: "Copyright (c) 2020 Unlock Music https://github.com/unlock-music/cli/blob/master/LICENSE", Copyright: "Copyright (c) 2020 - 2021 Unlock Music https://github.com/unlock-music/cli/blob/master/LICENSE",
HideHelpCommand: true, HideHelpCommand: true,
UsageText: "um -i /path/to/input -o /path/to/output/dir", UsageText: "um [-o /path/to/output/dir] [--extra-flags] [-i] /path/to/input",
} }
err := app.Run(os.Args) err := app.Run(os.Args)
if err != nil { if err != nil {
log.Fatal(err) logging.Log().Fatal("run app failed", zap.Error(err))
} }
} }
func appMain(c *cli.Context) error { func appMain(c *cli.Context) (err error) {
input := c.String("input") input := c.String("input")
if input == "" {
switch c.Args().Len() {
case 0:
input, err = os.Getwd()
if err != nil {
return err
}
case 1:
input = c.Args().Get(0)
default:
return errors.New("please specify input file (or directory)")
}
}
output := c.String("output") output := c.String("output")
if output == "" {
var err error
output, err = os.Getwd()
if err != nil {
return err
}
if input == output {
return errors.New("input and output path are same")
}
}
skipNoop := c.Bool("skip-noop")
inputStat, err := os.Stat(input) inputStat, err := os.Stat(input)
if err != nil { if err != nil {
return err return err
@ -62,10 +92,9 @@ func appMain(c *cli.Context) error {
} }
if inputStat.IsDir() { if inputStat.IsDir() {
return dealDirectory(input, output) return dealDirectory(input, output, skipNoop)
} else { } else {
ext := strings.TrimLeft(filepath.Ext(inputStat.Name()), ".") allDec := common.GetDecoder(inputStat.Name(), skipNoop)
allDec := common.GetDecoder(ext)
if len(allDec) == 0 { if len(allDec) == 0 {
logging.Log().Fatal("skipping while no suitable decoder") logging.Log().Fatal("skipping while no suitable decoder")
} }
@ -73,7 +102,7 @@ func appMain(c *cli.Context) error {
} }
} }
func dealDirectory(inputDir string, outputDir string) error { func dealDirectory(inputDir string, outputDir string, skipNoop bool) error {
items, err := os.ReadDir(inputDir) items, err := os.ReadDir(inputDir)
if err != nil { if err != nil {
return err return err
@ -82,8 +111,7 @@ func dealDirectory(inputDir string, outputDir string) error {
if item.IsDir() { if item.IsDir() {
continue continue
} }
ext := strings.TrimLeft(filepath.Ext(item.Name()), ".") allDec := common.GetDecoder(item.Name(), skipNoop)
allDec := common.GetDecoder(ext)
if len(allDec) == 0 { if len(allDec) == 0 {
logging.Log().Info("skipping while no suitable decoder", zap.String("file", item.Name())) logging.Log().Info("skipping while no suitable decoder", zap.String("file", item.Name()))
continue continue

15
go.mod
View File

@ -1,12 +1,17 @@
module github.com/unlock-music/cli module github.com/unlock-music/cli
go 1.16 go 1.17
require ( require (
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/ulikunitz/xz v0.5.10 github.com/ulikunitz/xz v0.5.10
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.19.1
go.uber.org/zap v1.16.0 )
require (
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/multierr v1.6.0 // indirect
) )

58
go.sum
View File

@ -1,13 +1,11 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.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.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -15,51 +13,57 @@ github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
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/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI=
go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= gopkg.in/yaml.v2 v2.2.8/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.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=