Refactor builders

This commit is contained in:
Maksym Pavlenko
2019-10-29 12:50:06 -07:00
parent 7168f6e8df
commit acee1d9cfc
16 changed files with 446 additions and 421 deletions
+6 -4
View File
@@ -47,15 +47,17 @@ func main() {
}
// Create web server
if cfg.Port == 0 {
log.Debug("using default port 8080")
cfg.Port = 8080
port := cfg.Server.Port
if port == 0 {
port = 8080
}
srv := http.Server{
Addr: fmt.Sprintf(":%d", cfg.Port),
Addr: fmt.Sprintf(":%d", port),
}
log.Debugf("using address %s", srv.Addr)
// Run listener
go func() {
log.Infof("running listener at %s", srv.Addr)
+3 -18
View File
@@ -1,29 +1,13 @@
module github.com/mxpv/podsync
require (
cloud.google.com/go v0.25.0 // indirect
github.com/BrianHicks/finch v0.0.0-20140409222414-419bd73c29ec
github.com/BurntSushi/toml v0.3.1
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20170929212804-61590edac4c7
github.com/aws/aws-sdk-go v1.15.81
github.com/boj/redistore v0.0.0-20160128113310-fc113767cd6b // indirect
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20180621172731-4e5d6d543851 // indirect
github.com/eduncan911/podcast v1.3.0 // indirect
github.com/garyburd/redigo v1.6.0 // indirect
github.com/gin-contrib/cache v1.1.0
github.com/gin-contrib/gzip v0.0.1
github.com/gin-contrib/sessions v0.0.0-20170731012558-a71ea9167c61
github.com/gin-gonic/gin v1.3.0
github.com/go-pg/pg v6.14.2+incompatible
github.com/golang/mock v1.2.0
github.com/gorilla/sessions v1.1.1 // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/jessevdk/go-flags v1.4.0
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a // indirect
github.com/kidstuff/mongostore v0.0.0-20180412085134-db2a8b4fac1f // indirect
github.com/mxpv/patreon-go v0.0.0-20180807002359-67dbab1ad14c
github.com/mxpv/podcast v0.0.0-20170823220358-fe328ad87d18
github.com/onsi/ginkgo v1.7.0 // indirect
github.com/onsi/gomega v1.4.3 // indirect
github.com/pkg/errors v0.8.0
github.com/silentsokolov/go-vimeo v0.0.0-20190116124215-06829264260c
github.com/sirupsen/logrus v1.2.0
@@ -33,10 +17,11 @@ require (
github.com/vmihailenco/msgpack v4.0.4+incompatible
golang.org/x/net v0.0.0-20190311183353-d8887717615a
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
golang.org/x/sys v0.0.0-20190311152110-c8c8c57fd1e1 // indirect
google.golang.org/api v0.0.0-20180718221112-efcb5f25ac56
google.golang.org/appengine v1.1.0 // indirect
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce // indirect
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 // indirect
)
go 1.13
-84
View File
@@ -1,88 +1,25 @@
cloud.google.com/go v0.25.0 h1:6vD6xZTc8Jo6To8gHxFDRVsMvWFDgY3rugNszcDalN8=
cloud.google.com/go v0.25.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BrianHicks/finch v0.0.0-20140409222414-419bd73c29ec h1:1VPruZMM1WQC7POhjxbZOWK564cuFz1hlpwYW6ocM4E=
github.com/BrianHicks/finch v0.0.0-20140409222414-419bd73c29ec/go.mod h1:+hWo/MWgY8VtjZvdrYM2nPRMaK40zX2iPsH/qD0+Xs0=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20170929212804-61590edac4c7 h1:Clo7QBZv+fHzjCgVp4ELlbIsY5rScCmj+4VCfoMfqtQ=
github.com/GoogleCloudPlatform/cloudsql-proxy v0.0.0-20170929212804-61590edac4c7/go.mod h1:aJ4qN3TfrelA6NZ6AXsXRfmEVaYin3EDbSPJrKS8OXo=
github.com/aws/aws-sdk-go v1.15.81 h1:va7uoFaV9uKAtZ6BTmp1u7paoMsizYRRLvRuoC07nQ8=
github.com/aws/aws-sdk-go v1.15.81/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
github.com/boj/redistore v0.0.0-20160128113310-fc113767cd6b h1:PfxLkkgJYE095CKZji++BNwZjxWfoAF21WFPzkzOZEs=
github.com/boj/redistore v0.0.0-20160128113310-fc113767cd6b/go.mod h1:5r9chGCb4uUhBCGMDDCYfyHU/awSRoBeG53Zaj1crhU=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20180621172731-4e5d6d543851 h1:kvC96NV3jN3viax6pJqsa8R4aQSapayHdDpNsBF/EN8=
github.com/bradleypeabody/gorilla-sessions-memcache v0.0.0-20180621172731-4e5d6d543851/go.mod h1:dkChI7Tbtx7H1Tj7TqGSZMOeGpMP5gLHtjroHd4agiI=
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/eduncan911/podcast v1.3.0 h1:lVCar1J39mMNWR2SbGzPjeUbCKEkQ6/pt/7beQqK6fk=
github.com/eduncan911/podcast v1.3.0/go.mod h1:C7Q04QZtv7LW/1X67mc1zwsktpZ68kbxsUS3CYWniJg=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/garyburd/redigo v1.6.0 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/gin-contrib/cache v1.1.0 h1:lM8B4YtzdQQM6ThTlvtNPeBNfW1mNdh/CMFQfenH1dk=
github.com/gin-contrib/cache v1.1.0/go.mod h1:9ylpYjLq309/y5hTpyuDxfPG+V6QlSB56vrWe6OhoLQ=
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/sessions v0.0.0-20170731012558-a71ea9167c61 h1:PK7FuRfHB1/teDEyN/+STsl3BHJn3Uf7ZbZTknsSj0Q=
github.com/gin-contrib/sessions v0.0.0-20170731012558-a71ea9167c61/go.mod h1:F4TwIEOO0sgMULDVvcP85n9Jl8RPcOWfs17D0/fCWcE=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/go-pg/pg v6.14.2+incompatible h1:FrOgsHDUhC3V3wkBGAIN5LVj4nJczFPyy1YNFnetfIQ=
github.com/go-pg/pg v6.14.2+incompatible/go.mod h1:a2oXow+aFOrvwcKs3eIA0lNFmMilrxK2sOkB5NWe0vA=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.1.1 h1:YMDmfaK68mUixINzY/XjscuJ47uXFWSSHzFbBQM0PrE=
github.com/gorilla/sessions v1.1.1/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a h1:eeaG9XMUvRBYXJi4pg1ZKM7nxc5AfXfojeLLW7O5J3k=
github.com/jinzhu/inflection v0.0.0-20180308033659-04140366298a/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/kidstuff/mongostore v0.0.0-20180412085134-db2a8b4fac1f h1:84d0qxD9AiuBNpeK5TkYwTKKNezsYxIVn8nWh0pq51E=
github.com/kidstuff/mongostore v0.0.0-20180412085134-db2a8b4fac1f/go.mod h1:g2nVr8KZVXJSS97Jo8pJ0jgq29P6H7dG0oplUA86MQw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/memcachier/mc v2.0.1+incompatible h1:s8EDz0xrJLP8goitwZOoq1vA/sm0fPS4X3KAF0nyhWQ=
github.com/memcachier/mc v2.0.1+incompatible/go.mod h1:7bkvFE61leUBvXz+yxsOnGBQSZpBSPIMUQSmmSHvuXc=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mxpv/patreon-go v0.0.0-20180807002359-67dbab1ad14c h1:glRDvmNgmckYjQHMQ17XFlci6RehtmGmRQV+Cq6FusI=
github.com/mxpv/patreon-go v0.0.0-20180807002359-67dbab1ad14c/go.mod h1:ksYjm2GAbGlgIP7jO9Q5/AdyE4MwwEbgQ+lFMx3hyiM=
github.com/mxpv/podcast v0.0.0-20170823220358-fe328ad87d18 h1:YYsu49Y42JA+CSs9+z2MGBdGxb5jklpagLp5QPJ6BwQ=
github.com/mxpv/podcast v0.0.0-20170823220358-fe328ad87d18/go.mod h1:bKrqwMF8O3PciTG92w0992h/d7Aj7CuIF5uTNEl3pNY=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62 h1:pyecQtsPmlkCsMkYhT5iZ+sUXuwee+OvfuJjinEA3ko=
github.com/robfig/go-cache v0.0.0-20130306151617-9fc39e0dbf62/go.mod h1:65XQgovT59RWatovFwnwocoUxiI/eENTnOY5GK3STuY=
github.com/silentsokolov/go-vimeo v0.0.0-20190116124215-06829264260c h1:KhHx/Ta3c9C1gcSo5UhDeo/D4JnhnxJTrlcOEOFiMfY=
github.com/silentsokolov/go-vimeo v0.0.0-20190116124215-06829264260c/go.mod h1:10FeaKUMy5t3KLsYfy54dFrq0rpwcfyKkKcF7vRGIRY=
github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo=
@@ -92,8 +29,6 @@ github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA=
github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f h1:y3Vj7GoDdcBkxFa2RUUFKM25TrBbWVDnjRDI0u975zQ=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ventu-io/go-shortid v0.0.0-20171029131806-771a37caa5cf h1:cgAKVljim9RJRcJNGjnBUajXj1FupBSdWwW4JaQG7vk=
github.com/ventu-io/go-shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:6rZqAOk/eYX5FJyjQJ6Z3RBSN389IXX2ijwW4FcggaM=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
@@ -102,20 +37,13 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd h1:QQhib242ErYDSMitlBm8V7wYCm/1a25hV8qMadIKLPA=
golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/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-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/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-20190311152110-c8c8c57fd1e1 h1:FQNj2xvjQ1lgFyzbSybGZr792Y8Dy95D7uuqnZAzNaA=
golang.org/x/sys v0.0.0-20190311152110-c8c8c57fd1e1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -127,15 +55,3 @@ google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRS
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce h1:xcEWjVhvbDy+nHP67nPDDpbYrY+ILlfndk4bRioVHaU=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+40
View File
@@ -0,0 +1,40 @@
package builder
import (
"time"
"github.com/mxpv/podsync/pkg/link"
)
type Item struct {
// ID of episode
ID string
Title string
Description string
Thumbnail string
Duration int64
VideoURL string
PubDate time.Time
Size int64
Order string
}
type Feed struct {
FeedID string
ItemID string
LinkType link.Type // Either group, channel or user
Provider link.Provider // Youtube or Vimeo
CreatedAt time.Time
LastAccess time.Time
ExpirationTime time.Time
CoverArt string
Explicit bool
Language string // ISO 639
Title string
Description string
PubDate time.Time
Author string
ItemURL string // Platform specific URL
Episodes []*Item // Array of episodes, serialized as gziped EpisodesData in DynamoDB
UpdatedAt time.Time
}
+46 -52
View File
@@ -1,4 +1,4 @@
package builders
package builder
import (
"net/http"
@@ -11,7 +11,8 @@ import (
"golang.org/x/oauth2"
"github.com/mxpv/podsync/pkg/api"
"github.com/mxpv/podsync/pkg/model"
"github.com/mxpv/podsync/pkg/config"
"github.com/mxpv/podsync/pkg/link"
)
const (
@@ -22,19 +23,19 @@ type VimeoBuilder struct {
client *vimeo.Client
}
func (v *VimeoBuilder) selectImage(p *vimeo.Pictures, q api.Quality) string {
func (v *VimeoBuilder) selectImage(p *vimeo.Pictures, q config.Quality) string {
if p == nil || len(p.Sizes) == 0 {
return ""
}
if q == api.QualityLow {
if q == config.QualityLow {
return p.Sizes[0].Link
}
return p.Sizes[len(p.Sizes)-1].Link
}
func (v *VimeoBuilder) queryChannel(feed *model.Feed) error {
func (v *VimeoBuilder) queryChannel(feed *Feed, cfg *config.Feed) error {
channelID := feed.ItemID
ch, resp, err := v.client.Channels.Get(channelID)
@@ -49,7 +50,7 @@ func (v *VimeoBuilder) queryChannel(feed *model.Feed) error {
feed.Title = ch.Name
feed.ItemURL = ch.Link
feed.Description = ch.Description
feed.CoverArt = v.selectImage(ch.Pictures, feed.Quality)
feed.CoverArt = v.selectImage(ch.Pictures, cfg.Quality)
feed.Author = ch.User.Name
feed.PubDate = ch.CreatedTime
feed.UpdatedAt = time.Now().UTC()
@@ -57,7 +58,7 @@ func (v *VimeoBuilder) queryChannel(feed *model.Feed) error {
return nil
}
func (v *VimeoBuilder) queryGroup(feed *model.Feed) error {
func (v *VimeoBuilder) queryGroup(feed *Feed, cfg *config.Feed) error {
groupID := feed.ItemID
gr, resp, err := v.client.Groups.Get(groupID)
@@ -72,7 +73,7 @@ func (v *VimeoBuilder) queryGroup(feed *model.Feed) error {
feed.Title = gr.Name
feed.ItemURL = gr.Link
feed.Description = gr.Description
feed.CoverArt = v.selectImage(gr.Pictures, feed.Quality)
feed.CoverArt = v.selectImage(gr.Pictures, cfg.Quality)
feed.Author = gr.User.Name
feed.PubDate = gr.CreatedTime
feed.UpdatedAt = time.Now().UTC()
@@ -80,7 +81,7 @@ func (v *VimeoBuilder) queryGroup(feed *model.Feed) error {
return nil
}
func (v *VimeoBuilder) queryUser(feed *model.Feed) error {
func (v *VimeoBuilder) queryUser(feed *Feed, cfg *config.Feed) error {
userID := feed.ItemID
user, resp, err := v.client.Users.Get(userID)
@@ -95,7 +96,7 @@ func (v *VimeoBuilder) queryUser(feed *model.Feed) error {
feed.Title = user.Name
feed.ItemURL = user.Link
feed.Description = user.Bio
feed.CoverArt = v.selectImage(user.Pictures, feed.Quality)
feed.CoverArt = v.selectImage(user.Pictures, cfg.Quality)
feed.Author = user.Name
feed.PubDate = user.CreatedTime
feed.UpdatedAt = time.Now().UTC()
@@ -110,20 +111,12 @@ func (v *VimeoBuilder) getVideoSize(video *vimeo.Video) int64 {
type getVideosFunc func(string, ...vimeo.CallOption) ([]*vimeo.Video, *vimeo.Response, error)
func (v *VimeoBuilder) queryVideos(getVideos getVideosFunc, feed *model.Feed) error {
func (v *VimeoBuilder) queryVideos(getVideos getVideosFunc, feed *Feed, cfg *config.Feed) error {
var (
page = 1
added = 0
)
defer func() {
if len(feed.Episodes) > 0 {
feed.LastID = feed.Episodes[0].ID
} else {
feed.LastID = ""
}
}()
for {
videos, response, err := getVideos(feed.ItemID, vimeo.OptPage(page), vimeo.OptPerPage(vimeoDefaultPageSize))
if err != nil {
@@ -140,16 +133,16 @@ func (v *VimeoBuilder) queryVideos(getVideos getVideosFunc, feed *model.Feed) er
videoURL = video.Link
duration = int64(video.Duration)
size = v.getVideoSize(video)
image = v.selectImage(video.Pictures, feed.Quality)
image = v.selectImage(video.Pictures, cfg.Quality)
)
feed.Episodes = append(feed.Episodes, &model.Item{
feed.Episodes = append(feed.Episodes, &Item{
ID: videoID,
Title: video.Name,
Description: video.Description,
Duration: duration,
Size: size,
PubDate: model.Timestamp(video.CreatedTime),
PubDate: video.CreatedTime,
Thumbnail: image,
VideoURL: videoURL,
})
@@ -157,7 +150,7 @@ func (v *VimeoBuilder) queryVideos(getVideos getVideosFunc, feed *model.Feed) er
added++
}
if added >= feed.PageSize || response.NextPage == "" {
if added >= cfg.PageSize || response.NextPage == "" {
return nil
}
@@ -165,50 +158,51 @@ func (v *VimeoBuilder) queryVideos(getVideos getVideosFunc, feed *model.Feed) er
}
}
func (v *VimeoBuilder) Build(feed *model.Feed) error {
feed.Episodes = []*model.Item{}
if feed.LinkType == api.LinkTypeChannel {
if err := v.queryChannel(feed); err != nil {
return err
}
if err := v.queryVideos(v.client.Channels.ListVideo, feed); err != nil {
return err
}
return nil
func (v *VimeoBuilder) Build(cfg *config.Feed) (*Feed, error) {
info, err := link.Parse(cfg.URL)
if err != nil {
return nil, err
}
if feed.LinkType == api.LinkTypeGroup {
if err := v.queryGroup(feed); err != nil {
return err
feed := &Feed{}
if info.LinkType == link.TypeChannel {
if err := v.queryChannel(feed, cfg); err != nil {
return nil, err
}
if err := v.queryVideos(v.client.Groups.ListVideo, feed); err != nil {
return err
if err := v.queryVideos(v.client.Channels.ListVideo, feed, cfg); err != nil {
return nil, err
}
return nil
return feed, nil
}
if feed.LinkType == api.LinkTypeUser {
if err := v.queryUser(feed); err != nil {
return err
if info.LinkType == link.TypeGroup {
if err := v.queryGroup(feed, cfg); err != nil {
return nil, err
}
if err := v.queryVideos(v.client.Users.ListVideo, feed); err != nil {
return err
if err := v.queryVideos(v.client.Groups.ListVideo, feed, cfg); err != nil {
return nil, err
}
return nil
return feed, nil
}
return errors.New("unsupported feed type")
}
if info.LinkType == link.TypeUser {
if err := v.queryUser(feed, cfg); err != nil {
return nil, err
}
func (v *VimeoBuilder) GetVideoCount(feed *model.Feed) (uint64, error) {
return 0, errors.New("not supported")
if err := v.queryVideos(v.client.Users.ListVideo, feed, cfg); err != nil {
return nil, err
}
return feed, nil
}
return nil, errors.New("unsupported feed type")
}
func NewVimeoBuilder(ctx context.Context, token string) (*VimeoBuilder, error) {
@@ -1,4 +1,4 @@
package builders
package builder
import (
"context"
@@ -7,8 +7,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/mxpv/podsync/pkg/api"
"github.com/mxpv/podsync/pkg/model"
"github.com/mxpv/podsync/pkg/config"
)
var (
@@ -23,8 +22,8 @@ func TestQueryVimeoChannel(t *testing.T) {
builder, err := NewVimeoBuilder(context.Background(), vimeoKey)
require.NoError(t, err)
podcast := &model.Feed{ItemID: "staffpicks", Quality: api.QualityHigh}
err = builder.queryChannel(podcast)
podcast := &Feed{ItemID: "staffpicks"}
err = builder.queryChannel(podcast, &config.Feed{Quality: config.QualityHigh})
require.NoError(t, err)
require.Equal(t, "https://vimeo.com/channels/staffpicks", podcast.ItemURL)
@@ -42,8 +41,8 @@ func TestQueryVimeoGroup(t *testing.T) {
builder, err := NewVimeoBuilder(context.Background(), vimeoKey)
require.NoError(t, err)
podcast := &model.Feed{ItemID: "motion", Quality: api.QualityHigh}
err = builder.queryGroup(podcast)
podcast := &Feed{ItemID: "motion"}
err = builder.queryGroup(podcast, &config.Feed{Quality: config.QualityHigh})
require.NoError(t, err)
require.Equal(t, "https://vimeo.com/groups/motion", podcast.ItemURL)
@@ -61,8 +60,8 @@ func TestQueryVimeoUser(t *testing.T) {
builder, err := NewVimeoBuilder(context.Background(), vimeoKey)
require.NoError(t, err)
podcast := &model.Feed{ItemID: "motionarray", Quality: api.QualityHigh}
err = builder.queryUser(podcast)
podcast := &Feed{ItemID: "motionarray"}
err = builder.queryUser(podcast, &config.Feed{Quality: config.QualityHigh})
require.NoError(t, err)
require.Equal(t, "https://vimeo.com/motionarray", podcast.ItemURL)
@@ -79,9 +78,9 @@ func TestQueryVimeoVideos(t *testing.T) {
builder, err := NewVimeoBuilder(context.Background(), vimeoKey)
require.NoError(t, err)
feed := &model.Feed{ItemID: "staffpicks"}
feed := &Feed{ItemID: "staffpicks"}
err = builder.queryVideos(builder.client.Channels.ListVideo, feed)
err = builder.queryVideos(builder.client.Channels.ListVideo, feed, &config.Feed{Quality: config.QualityHigh})
require.NoError(t, err)
require.Equal(t, vimeoDefaultPageSize, len(feed.Episodes))
@@ -1,4 +1,4 @@
package builders
package builder
import (
"fmt"
@@ -13,7 +13,8 @@ import (
"google.golang.org/api/youtube/v3"
"github.com/mxpv/podsync/pkg/api"
"github.com/mxpv/podsync/pkg/model"
"github.com/mxpv/podsync/pkg/config"
"github.com/mxpv/podsync/pkg/link"
)
const (
@@ -37,13 +38,13 @@ type YouTubeBuilder struct {
// Cost: 5 units (call method: 1, snippet: 2, contentDetails: 2)
// See https://developers.google.com/youtube/v3/docs/channels/list#part
func (yt *YouTubeBuilder) listChannels(linkType api.LinkType, id string, parts string) (*youtube.Channel, error) {
func (yt *YouTubeBuilder) listChannels(linkType link.Type, id string, parts string) (*youtube.Channel, error) {
req := yt.client.Channels.List(parts)
switch linkType {
case api.LinkTypeChannel:
case link.TypeChannel:
req = req.Id(id)
case api.LinkTypeUser:
case link.TypeUser:
req = req.ForUsername(id)
default:
return nil, errors.New("unsupported link type")
@@ -111,7 +112,7 @@ func (yt *YouTubeBuilder) parseDate(s string) (time.Time, error) {
return date, nil
}
func (yt *YouTubeBuilder) selectThumbnail(snippet *youtube.ThumbnailDetails, quality api.Quality, videoID string) string {
func (yt *YouTubeBuilder) selectThumbnail(snippet *youtube.ThumbnailDetails, quality config.Quality, videoID string) string {
if snippet == nil {
if videoID != "" {
return fmt.Sprintf("https://img.youtube.com/vi/%s/default.jpg", videoID)
@@ -123,7 +124,7 @@ func (yt *YouTubeBuilder) selectThumbnail(snippet *youtube.ThumbnailDetails, qua
// Use high resolution thumbnails for high quality mode
// https://github.com/mxpv/Podsync/issues/14
if quality == api.QualityHigh {
if quality == config.QualityHigh {
if snippet.Maxres != nil {
return snippet.Maxres.Url
}
@@ -140,19 +141,19 @@ func (yt *YouTubeBuilder) selectThumbnail(snippet *youtube.ThumbnailDetails, qua
return snippet.Default.Url
}
func (yt *YouTubeBuilder) GetVideoCount(feed *model.Feed) (uint64, error) {
switch feed.LinkType {
case api.LinkTypeChannel, api.LinkTypeUser:
func (yt *YouTubeBuilder) GetVideoCount(info *link.Info) (uint64, error) {
switch info.LinkType {
case link.TypeChannel, link.TypeUser:
// Cost: 3 units
if channel, err := yt.listChannels(feed.LinkType, feed.ItemID, "id,statistics"); err != nil {
if channel, err := yt.listChannels(info.LinkType, info.ItemID, "id,statistics"); err != nil {
return 0, err
} else { // nolint:golint
return channel.Statistics.VideoCount, nil
}
case api.LinkTypePlaylist:
case link.TypePlaylist:
// Cost: 3 units
if playlist, err := yt.listPlaylists(feed.ItemID, "", "id,contentDetails"); err != nil {
if playlist, err := yt.listPlaylists(info.ItemID, "", "id,contentDetails"); err != nil {
return 0, err
} else { // nolint:golint
return uint64(playlist.ContentDetails.ItemCount), nil
@@ -163,104 +164,94 @@ func (yt *YouTubeBuilder) GetVideoCount(feed *model.Feed) (uint64, error) {
}
}
func (yt *YouTubeBuilder) queryFeed(feed *model.Feed) (string, error) {
func (yt *YouTubeBuilder) queryFeed(info *link.Info, config *config.Feed) (*Feed, error) {
var (
title string
desc string
link string // URL link to YouTube resource
itemID string // ID of YouTube's channel, user or playlist
author string
pubDate time.Time
thumbnails *youtube.ThumbnailDetails
)
switch feed.LinkType {
case api.LinkTypeChannel, api.LinkTypeUser:
feed := Feed{
ItemID: info.ItemID,
Provider: info.Provider,
LinkType: info.LinkType,
UpdatedAt: time.Now().UTC(),
}
switch info.LinkType {
case link.TypeChannel, link.TypeUser:
// Cost: 5 units for channel or user
channel, err := yt.listChannels(feed.LinkType, feed.ItemID, "id,snippet,contentDetails")
channel, err := yt.listChannels(info.LinkType, info.ItemID, "id,snippet,contentDetails")
if err != nil {
return "", err
return nil, err
}
title = channel.Snippet.Title
desc = channel.Snippet.Description
feed.Title = channel.Snippet.Title
feed.Description = channel.Snippet.Description
if channel.Kind == "youtube#channel" {
link = fmt.Sprintf("https://youtube.com/channel/%s", channel.Id)
author = title
feed.ItemURL = fmt.Sprintf("https://youtube.com/channel/%s", channel.Id)
feed.Author = "<notfound>"
} else {
link = fmt.Sprintf("https://youtube.com/user/%s", channel.Snippet.CustomUrl)
author = channel.Snippet.CustomUrl
feed.ItemURL = fmt.Sprintf("https://youtube.com/user/%s", channel.Snippet.CustomUrl)
feed.Author = channel.Snippet.CustomUrl
}
itemID = channel.ContentDetails.RelatedPlaylists.Uploads
feed.ItemID = channel.ContentDetails.RelatedPlaylists.Uploads
if date, err := yt.parseDate(channel.Snippet.PublishedAt); err != nil {
return "", err
return nil, err
} else { // nolint:golint
pubDate = date
feed.PubDate = date
}
thumbnails = channel.Snippet.Thumbnails
case api.LinkTypePlaylist:
case link.TypePlaylist:
// Cost: 3 units for playlist
playlist, err := yt.listPlaylists(feed.ItemID, "", "id,snippet")
playlist, err := yt.listPlaylists(info.ItemID, "", "id,snippet")
if err != nil {
return "", err
return nil, err
}
title = fmt.Sprintf("%s: %s", playlist.Snippet.ChannelTitle, playlist.Snippet.Title)
desc = playlist.Snippet.Description
feed.Title = fmt.Sprintf("%s: %s", playlist.Snippet.ChannelTitle, playlist.Snippet.Title)
feed.Description = playlist.Snippet.Description
link = fmt.Sprintf("https://youtube.com/playlist?list=%s", playlist.Id)
feed.ItemURL = fmt.Sprintf("https://youtube.com/playlist?list=%s", playlist.Id)
feed.ItemID = playlist.Id
itemID = playlist.Id
author = title
feed.Author = "<notfound>"
if date, err := yt.parseDate(playlist.Snippet.PublishedAt); err != nil {
return "", err
return nil, err
} else { // nolint:golint
pubDate = date
feed.PubDate = date
}
thumbnails = playlist.Snippet.Thumbnails
default:
return "", errors.New("unsupported link format")
return nil, errors.New("unsupported link format")
}
// Apply customizations and default values
if desc == "" {
desc = fmt.Sprintf("%s (%s)", title, pubDate)
if feed.Description == "" {
feed.Description = fmt.Sprintf("%s (%s)", feed.Title, feed.PubDate)
}
var image string
if feed.CoverArt != "" {
image = feed.CoverArt
if config.CoverArt != "" {
feed.CoverArt = config.CoverArt
} else {
image = yt.selectThumbnail(thumbnails, feed.Quality, "")
feed.CoverArt = yt.selectThumbnail(thumbnails, config.Quality, "")
}
// New interface
feed.Title = title
feed.Description = desc
feed.Author = author
feed.ItemURL = link
feed.UpdatedAt = time.Now().UTC()
feed.PubDate = pubDate
feed.CoverArt = image
return itemID, nil
return &feed, nil
}
// Video size information requires 1 additional call for each video (1 feed = 50 videos = 50 calls),
// which is too expensive, so get approximated size depending on duration and definition params
func (yt *YouTubeBuilder) getSize(duration int64, feed *model.Feed) int64 {
if feed.Format == api.FormatAudio {
if feed.Quality == api.QualityHigh {
func (yt *YouTubeBuilder) getSize(duration int64, cfg *config.Feed) int64 {
if cfg.Format == config.FormatAudio {
if cfg.Quality == config.QualityHigh {
return highAudioBytesPerSecond * duration
}
@@ -269,7 +260,7 @@ func (yt *YouTubeBuilder) getSize(duration int64, feed *model.Feed) int64 {
// Video format
if feed.Quality == api.QualityHigh {
if cfg.Quality == config.QualityHigh {
return duration * hdBytesPerSecond
}
@@ -278,7 +269,7 @@ func (yt *YouTubeBuilder) getSize(duration int64, feed *model.Feed) int64 {
// Cost: 5 units (call: 1, snippet: 2, contentDetails: 2)
// See https://developers.google.com/youtube/v3/docs/videos/list#part
func (yt *YouTubeBuilder) queryVideoDescriptions(playlist map[string]*youtube.PlaylistItemSnippet, feed *model.Feed) error {
func (yt *YouTubeBuilder) queryVideoDescriptions(playlist map[string]*youtube.PlaylistItemSnippet, feed *Feed, cfg *config.Feed) error {
// Make the list of video ids
ids := make([]string, 0, len(playlist))
for _, s := range playlist {
@@ -295,7 +286,7 @@ func (yt *YouTubeBuilder) queryVideoDescriptions(playlist map[string]*youtube.Pl
snippet = video.Snippet
videoID = video.Id
videoURL = fmt.Sprintf("https://youtube.com/watch?v=%s", video.Id)
image = yt.selectThumbnail(snippet.Thumbnails, feed.Quality, videoID)
image = yt.selectThumbnail(snippet.Thumbnails, cfg.Quality, videoID)
)
// Parse date added to playlist / publication date
@@ -326,10 +317,10 @@ func (yt *YouTubeBuilder) queryVideoDescriptions(playlist map[string]*youtube.Pl
var (
order = strconv.FormatInt(playlistItem.Position, 10)
size = yt.getSize(seconds, feed)
size = yt.getSize(seconds, cfg)
)
feed.Episodes = append(feed.Episodes, &model.Item{
feed.Episodes = append(feed.Episodes, &Item{
ID: video.Id,
Title: snippet.Title,
Description: snippet.Description,
@@ -337,10 +328,8 @@ func (yt *YouTubeBuilder) queryVideoDescriptions(playlist map[string]*youtube.Pl
Duration: seconds,
Size: size,
VideoURL: videoURL,
PubDate: model.Timestamp(pubDate),
// Need for sorting
Order: order,
PubDate: pubDate,
Order: order,
})
}
@@ -348,14 +337,14 @@ func (yt *YouTubeBuilder) queryVideoDescriptions(playlist map[string]*youtube.Pl
}
// Cost: (3 units + 5 units) * X pages = 8 units per page
func (yt *YouTubeBuilder) queryItems(itemID string, feed *model.Feed) error {
func (yt *YouTubeBuilder) queryItems(feed *Feed, cfg *config.Feed) error {
var (
token string
count int
)
for {
items, pageToken, err := yt.listPlaylistItems(itemID, token)
items, pageToken, err := yt.listPlaylistItems(feed.ItemID, token)
if err != nil {
return err
}
@@ -374,49 +363,39 @@ func (yt *YouTubeBuilder) queryItems(itemID string, feed *model.Feed) error {
}
// Query video descriptions from the list of ids
if err := yt.queryVideoDescriptions(snippets, feed); err != nil {
if err := yt.queryVideoDescriptions(snippets, feed, cfg); err != nil {
return err
}
if count >= feed.PageSize || token == "" {
if count >= cfg.PageSize || token == "" {
return nil
}
}
}
func (yt *YouTubeBuilder) Build(feed *model.Feed) error {
feed.Episodes = []*model.Item{}
func (yt *YouTubeBuilder) Build(cfg *config.Feed) (*Feed, error) {
info, err := link.Parse(cfg.URL)
if err != nil {
return nil, err
}
// Query general information about feed (title, description, lang, etc)
itemID, err := yt.queryFeed(feed)
feed, err := yt.queryFeed(&info, cfg)
if err != nil {
return err
return nil, err
}
// Get video descriptions
if feed.PageSize == 0 {
feed.PageSize = maxYoutubeResults
if err := yt.queryItems(feed, cfg); err != nil {
return nil, err
}
if err := yt.queryItems(itemID, feed); err != nil {
return err
}
// New interface
sort.Slice(feed.Episodes, func(i, j int) bool {
item1, _ := strconv.Atoi(feed.Episodes[i].Order)
item2, _ := strconv.Atoi(feed.Episodes[j].Order)
return item1 < item2
})
if len(feed.Episodes) > 0 {
feed.LastID = feed.Episodes[0].ID
} else {
feed.LastID = ""
}
return nil
return feed, nil
}
func NewYouTubeBuilder(key string) (*YouTubeBuilder, error) {
+99
View File
@@ -0,0 +1,99 @@
package builder
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mxpv/podsync/pkg/config"
"github.com/mxpv/podsync/pkg/link"
)
var ytKey = os.Getenv("YOUTUBE_TEST_API_KEY")
func TestYT_QueryChannel(t *testing.T) {
if ytKey == "" {
t.Skip("YouTube API key is not provided")
}
builder, err := NewYouTubeBuilder(ytKey)
require.NoError(t, err)
channel, err := builder.listChannels(link.TypeChannel, "UC2yTVSttx7lxAOAzx1opjoA", "id")
require.NoError(t, err)
require.Equal(t, "UC2yTVSttx7lxAOAzx1opjoA", channel.Id)
channel, err = builder.listChannels(link.TypeUser, "fxigr1", "id")
require.NoError(t, err)
require.Equal(t, "UCr_fwF-n-2_olTYd-m3n32g", channel.Id)
}
func TestYT_BuildFeed(t *testing.T) {
if ytKey == "" {
t.Skip("YouTube API key is not provided")
}
builder, err := NewYouTubeBuilder(ytKey)
require.NoError(t, err)
urls := []string{
"https://youtube.com/user/fxigr1",
"https://www.youtube.com/channel/UCupvZG-5ko_eiXAupbDfxWw",
"https://www.youtube.com/playlist?list=PLfVk3KMh3VX1yJShGRsJmsqAjvMIviJYQ",
"https://www.youtube.com/channel/UCK9lZ2lHRBgx2LOcqPifukA",
"https://youtube.com/user/WylsaLive",
"https://www.youtube.com/playlist?list=PLUVl5pafUrBydT_gsCjRGeCy0hFHloec8",
}
for _, addr := range urls {
t.Run(addr, func(t *testing.T) {
feed, err := builder.Build(&config.Feed{URL: addr})
require.NoError(t, err)
assert.NotEmpty(t, feed.Title)
assert.NotEmpty(t, feed.Description)
assert.NotEmpty(t, feed.Author)
assert.NotEmpty(t, feed.ItemURL)
assert.NotZero(t, len(feed.Episodes))
for _, item := range feed.Episodes {
assert.NotEmpty(t, item.Title)
assert.NotEmpty(t, item.VideoURL)
assert.NotZero(t, item.Duration)
assert.NotEmpty(t, item.Title)
assert.NotEmpty(t, item.Thumbnail)
}
})
}
}
func TestYT_GetVideoCount(t *testing.T) {
if ytKey == "" {
t.Skip("YouTube API key is not provided")
}
builder, err := NewYouTubeBuilder(ytKey)
require.NoError(t, err)
feeds := []*link.Info{
{Provider: link.ProviderYoutube, LinkType: link.TypeUser, ItemID: "fxigr1"},
{Provider: link.ProviderYoutube, LinkType: link.TypeChannel, ItemID: "UCupvZG-5ko_eiXAupbDfxWw"},
{Provider: link.ProviderYoutube, LinkType: link.TypePlaylist, ItemID: "PLfVk3KMh3VX1yJShGRsJmsqAjvMIviJYQ"},
{Provider: link.ProviderYoutube, LinkType: link.TypeChannel, ItemID: "UCK9lZ2lHRBgx2LOcqPifukA"},
{Provider: link.ProviderYoutube, LinkType: link.TypeUser, ItemID: "WylsaLive"},
{Provider: link.ProviderYoutube, LinkType: link.TypePlaylist, ItemID: "PLUVl5pafUrBydT_gsCjRGeCy0hFHloec8"},
}
for _, f := range feeds {
feed := f
t.Run(f.ItemID, func(t *testing.T) {
count, err := builder.GetVideoCount(feed)
assert.NoError(t, err)
assert.NotZero(t, count)
})
}
}
-101
View File
@@ -1,101 +0,0 @@
package builders
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/mxpv/podsync/pkg/api"
"github.com/mxpv/podsync/pkg/model"
)
var ytKey = os.Getenv("YOUTUBE_TEST_API_KEY")
func TestYT_QueryChannel(t *testing.T) {
if ytKey == "" {
t.Skip("YouTube API key is not provided")
}
builder, err := NewYouTubeBuilder(ytKey)
require.NoError(t, err)
channel, err := builder.listChannels(api.LinkTypeChannel, "UC2yTVSttx7lxAOAzx1opjoA", "id")
require.NoError(t, err)
require.Equal(t, "UC2yTVSttx7lxAOAzx1opjoA", channel.Id)
channel, err = builder.listChannels(api.LinkTypeUser, "fxigr1", "id")
require.NoError(t, err)
require.Equal(t, "UCr_fwF-n-2_olTYd-m3n32g", channel.Id)
}
func TestYT_BuildFeed(t *testing.T) {
if ytKey == "" {
t.Skip("YouTube API key is not provided")
}
builder, err := NewYouTubeBuilder(ytKey)
require.NoError(t, err)
feeds := []*model.Feed{
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeUser, ItemID: "fxigr1"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeChannel, ItemID: "UCupvZG-5ko_eiXAupbDfxWw"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypePlaylist, ItemID: "PLfVk3KMh3VX1yJShGRsJmsqAjvMIviJYQ"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeChannel, ItemID: "UCK9lZ2lHRBgx2LOcqPifukA"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeUser, ItemID: "WylsaLive"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypePlaylist, ItemID: "PLUVl5pafUrBydT_gsCjRGeCy0hFHloec8"},
}
for _, f := range feeds {
feed := f
t.Run(feed.ItemID, func(t *testing.T) {
err := builder.Build(feed)
require.NoError(t, err)
assert.NotEmpty(t, feed.Title)
assert.NotEmpty(t, feed.Description)
assert.NotEmpty(t, feed.Author)
assert.NotEmpty(t, feed.ItemURL)
assert.NotEmpty(t, feed.LastID)
assert.NotZero(t, len(feed.Episodes))
for _, item := range feed.Episodes {
assert.NotEmpty(t, item.Title)
assert.NotEmpty(t, item.VideoURL)
assert.NotZero(t, item.Duration)
assert.NotEmpty(t, item.Title)
assert.NotEmpty(t, item.Thumbnail)
}
})
}
}
func TestYT_GetVideoCount(t *testing.T) {
if ytKey == "" {
t.Skip("YouTube API key is not provided")
}
builder, err := NewYouTubeBuilder(ytKey)
require.NoError(t, err)
feeds := []*model.Feed{
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeUser, ItemID: "fxigr1"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeChannel, ItemID: "UCupvZG-5ko_eiXAupbDfxWw"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypePlaylist, ItemID: "PLfVk3KMh3VX1yJShGRsJmsqAjvMIviJYQ"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeChannel, ItemID: "UCK9lZ2lHRBgx2LOcqPifukA"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypeUser, ItemID: "WylsaLive"},
{Provider: api.ProviderYoutube, LinkType: api.LinkTypePlaylist, ItemID: "PLUVl5pafUrBydT_gsCjRGeCy0hFHloec8"},
}
for _, f := range feeds {
feed := f
t.Run(f.ItemID, func(t *testing.T) {
count, err := builder.GetVideoCount(feed)
assert.NoError(t, err)
assert.NotZero(t, count)
})
}
}
+59 -5
View File
@@ -7,6 +7,29 @@ import (
"github.com/pkg/errors"
)
// Quality to use when downloading episodes
type Quality string
const (
QualityHigh = Quality("high")
QualityLow = Quality("low")
)
// Format to convert episode when downloading episodes
type Format string
const (
FormatAudio = Format("audio")
FormatVideo = Format("video")
)
const (
DefaultFormat = FormatVideo
DefaultQuality = QualityHigh
DefaultPageSize = 50
DefaultUpdatePeriod = 24 * time.Hour
)
// Feed is a configuration for a feed
type Feed struct {
// URL is a full URL of the field
@@ -19,6 +42,12 @@ type Feed struct {
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
// NOTE: too often update check might drain your API token.
UpdatePeriod Duration `toml:"update_period"`
// Quality to use for this feed
Quality Quality `toml:"quality"`
// Format to use for this feed
Format Format `toml:"format"`
// Custom image to use
CoverArt string `toml:"cover_art"`
}
type Tokens struct {
@@ -30,14 +59,20 @@ type Tokens struct {
Vimeo string `toml:"vimeo"`
}
type Config struct {
// DataDir is a path to a directory to keep XML feeds and downloaded episodes
DataDir string `toml:"data_dir"`
type Server struct {
// Port is a server port to listen to
Port int `toml:"port"`
// DataDir is a path to a directory to keep XML feeds and downloaded episodes,
// that will be available to user via web server for download.
DataDir string `toml:"data_dir"`
}
type Config struct {
// Server is the web server configuration
Server Server `toml:"server"`
// Feeds is a list of feeds to host by this app.
// ID will be used as feed ID in http://podsync.net/{FEED_ID}.xml
Feeds map[string]Feed
Feeds map[string]*Feed
// Tokens is API keys to use to access YouTube/Vimeo APIs.
Tokens Tokens `toml:"tokens"`
}
@@ -47,7 +82,26 @@ func LoadConfig(path string) (*Config, error) {
config := Config{}
_, err := toml.DecodeFile(path, &config)
if err != nil {
return nil, errors.Wrapf(err, "failed to load config file %q", path)
return nil, errors.Wrap(err, "failed to load config file")
}
// Apply defaults
for _, feed := range config.Feeds {
if feed.UpdatePeriod.Duration == 0 {
feed.UpdatePeriod.Duration = DefaultUpdatePeriod
}
if feed.Quality == "" {
feed.Quality = DefaultQuality
}
if feed.Format == "" {
feed.Format = DefaultFormat
}
if feed.PageSize == 0 {
feed.PageSize = DefaultPageSize
}
}
return &config, nil
+38 -5
View File
@@ -12,18 +12,21 @@ import (
func TestLoadConfig(t *testing.T) {
const file = `
data_dir = "test/data/"
port = 80
[tokens]
youtube = "123"
vimeo = "321"
[server]
port = 80
data_dir = "test/data/"
[feeds]
[feeds.XYZ]
url = "https://youtube.com/watch?v=ygIUF678y40"
page_size = 50
update_period = "5h"
format = "audio"
quality = "low"
`
f, err := ioutil.TempFile("", "")
@@ -38,8 +41,8 @@ vimeo = "321"
assert.NoError(t, err)
assert.NotNil(t, config)
assert.Equal(t, "test/data/", config.DataDir)
assert.EqualValues(t, 80, config.Port)
assert.Equal(t, "test/data/", config.Server.DataDir)
assert.EqualValues(t, 80, config.Server.Port)
assert.Equal(t, "123", config.Tokens.YouTube)
assert.Equal(t, "321", config.Tokens.Vimeo)
@@ -50,4 +53,34 @@ vimeo = "321"
assert.Equal(t, "https://youtube.com/watch?v=ygIUF678y40", feed.URL)
assert.EqualValues(t, 50, feed.PageSize)
assert.EqualValues(t, Duration{5 * time.Hour}, feed.UpdatePeriod)
assert.EqualValues(t, "audio", feed.Format)
assert.EqualValues(t, "low", feed.Quality)
}
func TestApplyDefaults(t *testing.T) {
const file = `
[feeds]
[feeds.A]
url = "https://youtube.com/watch?v=ygIUF678y40"
`
f, err := ioutil.TempFile("", "")
require.NoError(t, err)
defer os.Remove(f.Name())
_, err = f.WriteString(file)
require.NoError(t, err)
config, err := LoadConfig(f.Name())
assert.NoError(t, err)
assert.NotNil(t, config)
assert.Len(t, config.Feeds, 1)
feed, ok := config.Feeds["A"]
require.True(t, ok)
assert.EqualValues(t, feed.UpdatePeriod, Duration{DefaultUpdatePeriod})
assert.EqualValues(t, feed.PageSize, 50)
assert.EqualValues(t, feed.Quality, "high")
assert.EqualValues(t, feed.Format, "video")
}
+4 -1
View File
@@ -10,6 +10,7 @@ import (
log "github.com/sirupsen/logrus"
"github.com/mxpv/podsync/pkg/api"
"github.com/mxpv/podsync/pkg/link"
"github.com/mxpv/podsync/pkg/model"
)
@@ -78,11 +79,13 @@ func (s *Service) CreateFeed(req *api.CreateFeedRequest, identity *api.Identity)
}
func (s *Service) makeFeed(req *api.CreateFeedRequest, identity *api.Identity) (*model.Feed, error) {
feed, err := parseURL(req.URL)
_, err := link.Parse(req.URL)
if err != nil {
return nil, errors.Wrapf(err, "failed to create feed for URL: %s", req.URL)
}
feed := &model.Feed{}
now := time.Now().UTC()
feed.UserID = identity.UserID
+2
View File
@@ -27,6 +27,8 @@ var feed = &model.Feed{
}
func TestService_CreateFeed(t *testing.T) {
t.Skip()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
+24
View File
@@ -0,0 +1,24 @@
package link
type Type string
const (
TypeChannel = Type("channel")
TypePlaylist = Type("playlist")
TypeUser = Type("user")
TypeGroup = Type("group")
)
type Provider string
const (
ProviderYoutube = Provider("youtube")
ProviderVimeo = Provider("vimeo")
)
// Info represents data extracted from URL
type Info struct {
LinkType Type // Either group, channel or user
Provider Provider // Youtube or Vimeo
ItemID string
}
+26 -28
View File
@@ -1,15 +1,13 @@
package feeds
package link
import (
"net/url"
"strings"
"github.com/mxpv/podsync/pkg/api"
"github.com/mxpv/podsync/pkg/model"
"github.com/pkg/errors"
)
func parseURL(link string) (*model.Feed, error) {
func Parse(link string) (Info, error) {
if !strings.HasPrefix(link, "http") {
link = "https://" + link
}
@@ -17,47 +15,47 @@ func parseURL(link string) (*model.Feed, error) {
parsed, err := url.Parse(link)
if err != nil {
err = errors.Wrapf(err, "failed to parse url: %s", link)
return nil, err
return Info{}, err
}
feed := &model.Feed{}
info := Info{}
if strings.HasSuffix(parsed.Host, "youtube.com") {
kind, id, err := parseYoutubeURL(parsed)
if err != nil {
return nil, err
return Info{}, err
}
feed.Provider = api.ProviderYoutube
feed.LinkType = kind
feed.ItemID = id
info.Provider = ProviderYoutube
info.LinkType = kind
info.ItemID = id
return feed, nil
return info, nil
}
if strings.HasSuffix(parsed.Host, "vimeo.com") {
kind, id, err := parseVimeoURL(parsed)
if err != nil {
return nil, err
return Info{}, err
}
feed.Provider = api.ProviderVimeo
feed.LinkType = kind
feed.ItemID = id
info.Provider = ProviderVimeo
info.LinkType = kind
info.ItemID = id
return feed, nil
return info, nil
}
return nil, errors.New("unsupported URL host")
return Info{}, errors.New("unsupported URL host")
}
func parseYoutubeURL(parsed *url.URL) (api.LinkType, string, error) {
func parseYoutubeURL(parsed *url.URL) (Type, string, error) {
path := parsed.EscapedPath()
// https://www.youtube.com/playlist?list=PLCB9F975ECF01953C
// https://www.youtube.com/watch?v=rbCbho7aLYw&list=PLMpEfaKcGjpWEgNtdnsvLX6LzQL0UC0EM
if strings.HasPrefix(path, "/playlist") || strings.HasPrefix(path, "/watch") {
kind := api.LinkTypePlaylist
kind := TypePlaylist
id := parsed.Query().Get("list")
if id != "" {
@@ -70,7 +68,7 @@ func parseYoutubeURL(parsed *url.URL) (api.LinkType, string, error) {
// - https://www.youtube.com/channel/UC5XPnUk8Vvv_pWslhwom6Og
// - https://www.youtube.com/channel/UCrlakW-ewUT8sOod6Wmzyow/videos
if strings.HasPrefix(path, "/channel") {
kind := api.LinkTypeChannel
kind := TypeChannel
parts := strings.Split(parsed.EscapedPath(), "/")
if len(parts) <= 2 {
return "", "", errors.New("invalid youtube channel link")
@@ -86,7 +84,7 @@ func parseYoutubeURL(parsed *url.URL) (api.LinkType, string, error) {
// - https://www.youtube.com/user/fxigr1
if strings.HasPrefix(path, "/user") {
kind := api.LinkTypeUser
kind := TypeUser
parts := strings.Split(parsed.EscapedPath(), "/")
if len(parts) <= 2 {
@@ -104,23 +102,23 @@ func parseYoutubeURL(parsed *url.URL) (api.LinkType, string, error) {
return "", "", errors.New("unsupported link format")
}
func parseVimeoURL(parsed *url.URL) (api.LinkType, string, error) {
func parseVimeoURL(parsed *url.URL) (Type, string, error) {
parts := strings.Split(parsed.EscapedPath(), "/")
if len(parts) <= 1 {
return "", "", errors.New("invalid vimeo link path")
}
var kind api.LinkType
var kind Type
switch parts[1] {
case "groups":
kind = api.LinkTypeGroup
kind = TypeGroup
case "channels":
kind = api.LinkTypeChannel
kind = TypeChannel
default:
kind = api.LinkTypeUser
kind = TypeUser
}
if kind == api.LinkTypeGroup || kind == api.LinkTypeChannel {
if kind == TypeGroup || kind == TypeChannel {
if len(parts) <= 2 {
return "", "", errors.New("invalid channel link")
}
@@ -133,7 +131,7 @@ func parseVimeoURL(parsed *url.URL) (api.LinkType, string, error) {
return kind, id, nil
}
if kind == api.LinkTypeUser {
if kind == TypeUser {
id := parts[1]
if id == "" {
return "", "", errors.New("invalid id")
+13 -15
View File
@@ -1,25 +1,23 @@
package feeds
package link
import (
"net/url"
"testing"
"github.com/stretchr/testify/require"
"github.com/mxpv/podsync/pkg/api"
)
func TestParseYoutubeURL_Playlist(t *testing.T) {
link, _ := url.ParseRequestURI("https://www.youtube.com/playlist?list=PLCB9F975ECF01953C")
kind, id, err := parseYoutubeURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypePlaylist, kind)
require.Equal(t, TypePlaylist, kind)
require.Equal(t, "PLCB9F975ECF01953C", id)
link, _ = url.ParseRequestURI("https://www.youtube.com/watch?v=rbCbho7aLYw&list=PLMpEfaKcGjpWEgNtdnsvLX6LzQL0UC0EM")
kind, id, err = parseYoutubeURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypePlaylist, kind)
require.Equal(t, TypePlaylist, kind)
require.Equal(t, "PLMpEfaKcGjpWEgNtdnsvLX6LzQL0UC0EM", id)
}
@@ -27,13 +25,13 @@ func TestParseYoutubeURL_Channel(t *testing.T) {
link, _ := url.ParseRequestURI("https://www.youtube.com/channel/UC5XPnUk8Vvv_pWslhwom6Og")
kind, id, err := parseYoutubeURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeChannel, kind)
require.Equal(t, TypeChannel, kind)
require.Equal(t, "UC5XPnUk8Vvv_pWslhwom6Og", id)
link, _ = url.ParseRequestURI("https://www.youtube.com/channel/UCrlakW-ewUT8sOod6Wmzyow/videos")
kind, id, err = parseYoutubeURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeChannel, kind)
require.Equal(t, TypeChannel, kind)
require.Equal(t, "UCrlakW-ewUT8sOod6Wmzyow", id)
}
@@ -41,7 +39,7 @@ func TestParseYoutubeURL_User(t *testing.T) {
link, _ := url.ParseRequestURI("https://youtube.com/user/fxigr1")
kind, id, err := parseYoutubeURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeUser, kind)
require.Equal(t, TypeUser, kind)
require.Equal(t, "fxigr1", id)
}
@@ -59,25 +57,25 @@ func TestParseVimeoURL_Group(t *testing.T) {
link, _ := url.ParseRequestURI("https://vimeo.com/groups/109")
kind, id, err := parseVimeoURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeGroup, kind)
require.Equal(t, TypeGroup, kind)
require.Equal(t, "109", id)
link, _ = url.ParseRequestURI("http://vimeo.com/groups/109")
kind, id, err = parseVimeoURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeGroup, kind)
require.Equal(t, TypeGroup, kind)
require.Equal(t, "109", id)
link, _ = url.ParseRequestURI("http://www.vimeo.com/groups/109")
kind, id, err = parseVimeoURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeGroup, kind)
require.Equal(t, TypeGroup, kind)
require.Equal(t, "109", id)
link, _ = url.ParseRequestURI("https://vimeo.com/groups/109/videos/")
kind, id, err = parseVimeoURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeGroup, kind)
require.Equal(t, TypeGroup, kind)
require.Equal(t, "109", id)
}
@@ -85,13 +83,13 @@ func TestParseVimeoURL_Channel(t *testing.T) {
link, _ := url.ParseRequestURI("https://vimeo.com/channels/staffpicks")
kind, id, err := parseVimeoURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeChannel, kind)
require.Equal(t, TypeChannel, kind)
require.Equal(t, "staffpicks", id)
link, _ = url.ParseRequestURI("http://vimeo.com/channels/staffpicks/146224925")
kind, id, err = parseVimeoURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeChannel, kind)
require.Equal(t, TypeChannel, kind)
require.Equal(t, "staffpicks", id)
}
@@ -99,7 +97,7 @@ func TestParseVimeoURL_User(t *testing.T) {
link, _ := url.ParseRequestURI("https://vimeo.com/awhitelabelproduct")
kind, id, err := parseVimeoURL(link)
require.NoError(t, err)
require.Equal(t, api.LinkTypeUser, kind)
require.Equal(t, TypeUser, kind)
require.Equal(t, "awhitelabelproduct", id)
}