├── .gitignore ├── Makefile ├── README.md ├── build.sh ├── config.toml ├── go.mod ├── go.sum ├── main.go ├── pkg ├── archive │ └── archive.go ├── client │ ├── client.go │ ├── decode.go │ └── req.go ├── config │ ├── config.go │ ├── loader.go │ └── viper.go ├── img │ ├── imaging.go │ ├── imaging_test.go │ └── img.go ├── logger │ └── logger.go ├── metadata │ ├── emby.go │ └── metadata.go └── scraper │ ├── default.go │ ├── default_test.go │ ├── dmm.go │ ├── dmm_test.go │ ├── dmmapi.go │ ├── dmmapi_digital.go │ ├── dmmapi_test.go │ ├── fanza.go │ ├── fanza_test.go │ ├── fc2.go │ ├── fc2_test.go │ ├── gyutto.go │ ├── gyutto_test.go │ ├── heyzo.go │ ├── heyzo_test.go │ ├── javcup.go │ ├── javcup_test.go │ ├── mgstage.go │ ├── mgstage_test.go │ ├── regexp.go │ ├── regexp_test.go │ └── scraper.go └── third_party └── dmm-go-sdk └── api ├── actress.go ├── actress_test.go ├── api.go ├── api_test.go ├── author.go ├── author_test.go ├── floor.go ├── floor_test.go ├── genre.go ├── genre_test.go ├── maker.go ├── maker_test.go ├── product.go ├── product_test.go ├── series.go └── series_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | bin/* -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GOBUILD=go build -trimpath -ldflags '-w -s' -o 2 | BIN=bin/better-av-tool 3 | BIN2=bin/better-av-tool.exe 4 | SOURCE=. 5 | VERSION=1.5.3 6 | 7 | docker: 8 | $(GOBUILD) $(BIN) $(SOURCE) 9 | mac: 10 | GOOS=darwin GOARCH=amd64 $(GOBUILD) $(BIN) $(SOURCE) 11 | cp config.toml bin/ 12 | zip bin/better-av-tool-darwin-amd64-v$(VERSION).zip $(BIN) bin/config.toml 13 | win: 14 | GOOS=windows GOARCH=amd64 $(GOBUILD) $(BIN2) $(SOURCE) 15 | cp config.toml bin/ 16 | zip bin/better-av-tool-windows-amd64-v$(VERSION).zip $(BIN2) bin/config.toml 17 | m1: 18 | GOOS=darwin GOARCH=arm64 $(GOBUILD) $(BIN) $(SOURCE) 19 | cp config.toml bin/ 20 | zip bin/better-av-tool-darwin-arm64-v$(VERSION).zip $(BIN) bin/config.toml 21 | pi: 22 | GOOS=linux GOARCH=arm64 $(GOBUILD) $(BIN) $(SOURCE) 23 | cp config.toml bin/ 24 | zip bin/better-av-tool-linux-arm64-v$(VERSION).zip $(BIN) bin/config.toml 25 | nas: 26 | GOOS=linux GOARCH=amd64 $(GOBUILD) $(BIN) $(SOURCE) 27 | cp config.toml bin/ 28 | zip bin/better-av-tool-linux-amd64-v$(VERSION).zip $(BIN) bin/config.toml 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dmm-scraper 2 | 3 | [![Go Report Card](https://goreportcard.com/badge/github.com/CheerChen/dmm-scraper)](https://goreportcard.com/report/github.com/CheerChen/dmm-scraper) 4 | [![Downloads](https://img.shields.io/github/downloads/CheerChen/dmm-scraper/total.svg)](https://github.com/CheerChen/dmm-scraper/releases) 5 | [![Release](https://img.shields.io/github/release/CheerChen/dmm-scraper.svg?label=Release)](https://github.com/CheerChen/dmm-scraper/releases) 6 | 7 | ## 特性 8 | 9 | - 批量抓取资料和封面的影片刮削器 10 | - 以日站原始内容为主(DMM,MGStage,FC2等) 11 | - 优化了生成的 nfo 部分标签以更好适配 emby 12 | - 可查询部分已下架影片(仅限FC2,Gyutto) 13 | 14 | ## 基本用法 15 | 16 | - 移动程序到影片目录执行,程序会扫描查询*同目录*影片并生成影片的nfo文件以及封面 17 | - (新)封面可以配置是否剪裁 18 | - 避免查询失败,建议配置`config.toml`中的代理地址 19 | 20 | ```toml 21 | ## 范例 22 | [output] 23 | # 支持输出项目 {year} {maker} {num} {actor} 24 | # actor按第一位输出 25 | path = 'output/{year}/{num}' 26 | # 是否要切封面 27 | needCut = true 28 | 29 | [proxy] 30 | ## proxy [socks5://][127.0.0.1:]<1-65535>; 代理 31 | ## 协议:socks4, socks5, http, https 32 | socket = "socks5://127.0.0.1:7891" 33 | ## 设置是否启用代理 34 | enable = true 35 | 36 | [DMMApi] 37 | # 有 DMM affiliate 可以填,加快查询速度 38 | apiId = "" 39 | affiliateId = "" 40 | ``` 41 | 42 | ## 支持来源 43 | 44 | - 通用番号(xxx-000)依次查询 Fanza、DMM 45 | - 支持 DMM 自有影片特征番号(xxx00000) 46 | - 支持 MGStage 部分特征番号(000xxx-000) 47 | - 支持 FC2 特征番号(fc2-000000/fc2-ppv-000000) 48 | - 支持 Heyzo 特征番号 49 | - 支持 VR 特征番号查询、大部分动画番号(GLOD,ACRN,JDXA) 50 | - 在 Gyutto 上架的自制影片(item000000) 51 | 52 | ## 影片文件 53 | 54 | - 会一并移动并重命名 55 | - 有需要请注意备份 56 | 57 | ## NFO files 生成 58 | 59 | - 按照 [kodi movie 类型的规范](https://kodi.wiki/view/NFO_files/Movies#nfo_Tags) 60 | 61 | ## Changelog 62 | 63 | - **15 Dec 2021 (v1.4.0)** : 增加直接从 dmmapi 刮削的配置(需要开通 dmm affiliate) 64 | - **20 Oct 2021 (v1.3.5)** : 支持 FanzaVR 刮削器 65 | - **21 Sep 2021 (v1.3.4)** : 修复多个分片重命名覆盖问题 66 | - **5 Aug 2021 (v1.3.3)** : 修复标题番号缺失问题;重构 Scraper 包;改良输出日志 67 | - **2 Aug 2021 (v1.3.1)** : 修复 MGStage 查询失败问题;替换(logrus=>golog) 68 | - **30 Jul 2021 (v1.3.0)** : 修复切封面方向;由于识别日文不稳定不再支持 Sx Syndrome 刮削 69 | - **5 Mar 2021 (v1.2.1)** : 重构包;替换多个基础库(grab=>req, cutter=>imaging) 70 | - **7 Aug 2020 (v1.1.0)** : 标题格式正规化为大写番号,便于 emby 搜索;提高 DMM 多个搜索结果时正确率 71 | - **14 Jul 2020 (v1.0.1)** : 修复 DMM 查询失败的问题(需要年龄确认) 72 | - **28 May 2020 (v1.0.0)** : 增加 Sx Syndrome\Sx Friend 的刮削器;可以自定义生成路径;第一个稳定版本 73 | - **3 Apr 2020 (v0.9.3)** : 开始使用配置文件替换传参 74 | - **18 Mar 2020 (v0.9.2)** : 支持 xxx00000 特征 75 | - **16 Mar 2020 (v0.9.1)** : 修复获取多个演员的bug 76 | - **10 Mar 2020 (v0.9.0)** : 增加 DMM 的搜索类型 `digital/videoa` 77 | - **14 Feb 2020 (v0.8.1)** : 修复解析发片日期的bug 78 | - **17 Jan 2020 (v0.8.0)** : 增加 Heyzo 刮削器 79 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags '-w -s' -o bin/better-av-tool.exe . -------------------------------------------------------------------------------- /config.toml: -------------------------------------------------------------------------------- 1 | # 配置文件 2 | 3 | [output] 4 | path = 'output/{year}/{num}' 5 | needCut = true 6 | 7 | [proxy] 8 | ## proxy [socks5://][127.0.0.1:]<1-65535>; 9 | ## 协议:socks4, socks5, http, https 10 | socket = "" 11 | enable = false 12 | 13 | [DMMApi] 14 | apiId = "" 15 | affiliateId = "" -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dmm-scraper 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.5.0 7 | github.com/chromedp/chromedp v0.9.5 8 | github.com/disintegration/imaging v1.6.2 9 | github.com/dlclark/regexp2 v1.4.0 10 | github.com/imroc/req v0.3.0 11 | github.com/kataras/golog v0.1.7 12 | github.com/mitchellh/mapstructure v1.1.2 13 | github.com/spf13/afero v1.2.2 // indirect 14 | github.com/spf13/viper v1.6.2 15 | github.com/stretchr/testify v1.4.0 // indirect 16 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 17 | golang.org/x/text v0.3.3 18 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect 19 | gopkg.in/yaml.v2 v2.2.8 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 3 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 4 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 5 | github.com/PuerkitoBio/goquery v1.5.0 h1:uGvmFXOA73IKluu/F84Xd1tt/z07GYm8X49XKHP7EJk= 6 | github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8kdmcsrnKqMg= 7 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 8 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 9 | github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= 10 | github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 11 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 12 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 13 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 14 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 15 | github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732 h1:XYUCaZrW8ckGWlCRJKCSoh/iFwlpX316a8yY9IFEzv8= 16 | github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= 17 | github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93rg= 18 | github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y= 19 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= 20 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 21 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 22 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 23 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 24 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 25 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 26 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 27 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 28 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 29 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 30 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 31 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 32 | github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= 33 | github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= 34 | github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= 35 | github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= 36 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 37 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 38 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 39 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 40 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 41 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 42 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 43 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 44 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 45 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 46 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 47 | github.com/gobwas/ws v1.3.2 h1:zlnbNHxumkRvfPWgfXu8RBwyNR1x8wh9cf5PTOCqs9Q= 48 | github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= 49 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 50 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 51 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 52 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 53 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 54 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 55 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 56 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 57 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 58 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= 59 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 60 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 61 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 62 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 63 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 64 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 65 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 66 | github.com/imroc/req v0.3.0 h1:3EioagmlSG+z+KySToa+Ylo3pTFZs+jh3Brl7ngU12U= 67 | github.com/imroc/req v0.3.0/go.mod h1:F+NZ+2EFSo6EFXdeIbpfE9hcC233id70kf0byW97Caw= 68 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 69 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 70 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 71 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 72 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 73 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 74 | github.com/kataras/golog v0.1.7 h1:0TY5tHn5L5DlRIikepcaRR/6oInIr9AiWsxzt0vvlBE= 75 | github.com/kataras/golog v0.1.7/go.mod h1:jOSQ+C5fUqsNSwurB/oAHq1IFSb0KI3l6GMa7xB6dZA= 76 | github.com/kataras/pio v0.0.10 h1:b0qtPUqOpM2O+bqa5wr2O6dN4cQNwSmFd6HQqgVae0g= 77 | github.com/kataras/pio v0.0.10/go.mod h1:gS3ui9xSD+lAUpbYnjOGiQyY7sUMJO+EHpiRzhtZ5no= 78 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 79 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 80 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 81 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 82 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 83 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 84 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 85 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 86 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 87 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo= 88 | github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs= 89 | github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= 90 | github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 91 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 92 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 93 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 94 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 95 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 96 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 97 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 98 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= 99 | github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0= 100 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 101 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 102 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 103 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 104 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 105 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 106 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 107 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 108 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 109 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 110 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 111 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 112 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 113 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 114 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 115 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 116 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= 117 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 118 | github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= 119 | github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 120 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 121 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 122 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 123 | github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= 124 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 125 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= 126 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 127 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 128 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 129 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 130 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 131 | github.com/spf13/viper v1.6.2 h1:7aKfF+e8/k68gda3LOjo5RxiUqddoFxVq4BKBPrxk5E= 132 | github.com/spf13/viper v1.6.2/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= 133 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 134 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 135 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 136 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 137 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 138 | github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= 139 | github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= 140 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 141 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 142 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 143 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 144 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 145 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 146 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 147 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 148 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 149 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 150 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 151 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= 152 | golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 153 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 154 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 155 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 156 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 157 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 158 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 159 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 160 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 161 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 162 | golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= 163 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 164 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 165 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 166 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 167 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 168 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 169 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 170 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 171 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 172 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 173 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 174 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 175 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 176 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 177 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 178 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 179 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 180 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 181 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 182 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 183 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 184 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 185 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 186 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 187 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 188 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 189 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 190 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 191 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 192 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 193 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 194 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 195 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 196 | gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= 197 | gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 198 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 199 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 200 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 201 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 202 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 203 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 204 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 205 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 206 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | "path" 8 | "path/filepath" 9 | "strings" 10 | 11 | "dmm-scraper/pkg/config" 12 | "dmm-scraper/pkg/img" 13 | "dmm-scraper/pkg/logger" 14 | "dmm-scraper/pkg/metadata" 15 | "dmm-scraper/pkg/scraper" 16 | ) 17 | 18 | var ( 19 | posterWidth = 378 20 | ) 21 | 22 | func isValidVideo(ext string) bool { 23 | switch strings.ToLower(ext) { 24 | case 25 | ".wmv", 26 | ".mp4", 27 | ".avi", 28 | ".mkv": 29 | return true 30 | } 31 | return false 32 | } 33 | 34 | func MyProgress(l logger.Logger, sType, filename string) func(current, total int64) { 35 | return func(current, total int64) { 36 | l.Infof(fmt.Sprintf("%s downloading %s ... %f%%", sType, filename, float32(current)/float32(total)*100)) 37 | } 38 | } 39 | 40 | func main() { 41 | var err error 42 | log := logger.New() 43 | 44 | conf, err := config.NewLoader().LoadFile("config") 45 | if err != nil { 46 | log.Errorf("Error reading config file, %s", err) 47 | log.Warnf("Loading default config") 48 | conf = config.Default() 49 | } 50 | 51 | scraper.Setup(conf) 52 | 53 | files, err := ioutil.ReadDir(".") 54 | if err != nil { 55 | log.Fatal(err) 56 | } 57 | for _, f := range files { 58 | if f.IsDir() { 59 | continue 60 | } 61 | ext := filepath.Ext(f.Name()) 62 | if !isValidVideo(ext) { 63 | continue 64 | } 65 | log.Infof("Check file: %s", f.Name()) 66 | name := strings.TrimSuffix(f.Name(), ext) 67 | 68 | // 用正则处理文件名 69 | if query, scrapers := scraper.GetQuery(name); query != "" { 70 | 71 | for _, s := range scrapers { 72 | log.Infof("%s capturing query: %s", s.GetType(), query) 73 | 74 | // fetch 75 | err = s.FetchDoc(query) 76 | if err != nil { 77 | log.Error(err) 78 | continue 79 | } 80 | 81 | if s.GetNumber() == "" { 82 | log.Errorf("%s get num empty", s.GetType()) 83 | continue 84 | } 85 | 86 | num := s.GetFormatNumber() 87 | log.Infof("%s get num %s format: %s", s.GetType(), s.GetNumber(), num) 88 | 89 | // mkdir 90 | outputPath := scraper.GetOutputPath(s, conf.Output.Path) 91 | log.Infof("%s making output path: %s", s.GetType(), outputPath) 92 | err = os.MkdirAll(outputPath, 0700) 93 | if err != nil && !os.IsExist(err) { 94 | log.Error(err) 95 | break 96 | } 97 | 98 | // build nfo 99 | movieNfo := metadata.NewMovieNfo(s) 100 | poster := fmt.Sprintf("%s.jpg", num) 101 | // movieNfo.SetPoster(poster) 102 | movieNfo.SetTitle(num) 103 | 104 | posterPath := path.Join(outputPath, poster) 105 | err = scraper.Download(s.GetCover(), posterPath, MyProgress(log, s.GetType(), poster)) 106 | if err != nil { 107 | log.Error(err) 108 | break 109 | } 110 | 111 | if s.NeedCut() { 112 | log.Infof("%s cropping poster: %s", s.GetType(), posterPath) 113 | imgOperation := img.NewOperation() 114 | err = imgOperation.CropAndSave(posterPath, posterPath, posterWidth, 0) 115 | if err != nil { 116 | log.Error(err) 117 | } 118 | } 119 | 120 | nfo := path.Join(outputPath, fmt.Sprintf("%s.nfo", num)) 121 | log.Infof("%s writing nfo file: %s", s.GetType(), nfo) 122 | err = movieNfo.Save(nfo) 123 | if err != nil { 124 | log.Error(err) 125 | break 126 | } 127 | 128 | log.Infof("%s moving video file to: %s", s.GetType(), outputPath) 129 | // if file exist no overwrite 130 | err = MoveFile(f.Name(), outputPath, num, 1) 131 | if err != nil { 132 | log.Error(err) 133 | } 134 | break 135 | } 136 | } 137 | } 138 | } 139 | 140 | func MoveFile(oldPath, outputPath, num string, index int) error { 141 | var filename string 142 | if _, err := os.Stat(oldPath); os.IsNotExist(err) { 143 | return err 144 | } 145 | 146 | if index != 1 { 147 | filename = fmt.Sprintf("%s-cd%d%s", num, index, filepath.Ext(oldPath)) 148 | } else { 149 | filename = fmt.Sprintf("%s%s", num, filepath.Ext(oldPath)) 150 | } 151 | newPath := path.Join(outputPath, filename) 152 | if _, err := os.Stat(newPath); err == nil { 153 | index += 1 154 | return MoveFile(oldPath, outputPath, num, index) 155 | } 156 | return os.Rename(oldPath, newPath) 157 | } 158 | -------------------------------------------------------------------------------- /pkg/archive/archive.go: -------------------------------------------------------------------------------- 1 | package archive 2 | 3 | type AvailableResp struct { 4 | ArchivedSnapshots struct { 5 | Closest struct { 6 | Status string `json:"status"` 7 | Available bool `json:"available"` 8 | URL string `json:"url"` 9 | Timestamp string `json:"timestamp"` 10 | } `json:"closest"` 11 | } `json:"archived_snapshots"` 12 | URL string `json:"url"` 13 | } 14 | 15 | const ( 16 | GetAvailableUrl = "https://archive.org/wayback/available?url=%s" 17 | ) 18 | 19 | 20 | -------------------------------------------------------------------------------- /pkg/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/imroc/req" 8 | ) 9 | 10 | // Client ... 11 | type Client interface { 12 | SetProxyUrl(rawurl string) error 13 | Get(url string, v ...interface{}) (*http.Response, error) 14 | GetJSON(url string, v interface{}) error 15 | Post(url string, v ...interface{}) (*http.Response, error) 16 | Download(url, filename string, progress func(current, total int64)) error 17 | } 18 | 19 | // New ... 20 | func New() Client { 21 | return &ReqClient{ 22 | req.New(), 23 | } 24 | } 25 | 26 | // DefaultProgress ... 27 | func DefaultProgress() func(current, total int64) { 28 | return func(current, total int64) { 29 | fmt.Println(float32(current)/float32(total)*100, "%") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /pkg/client/decode.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "golang.org/x/net/html/charset" 7 | "golang.org/x/text/encoding" 8 | "golang.org/x/text/transform" 9 | "io" 10 | "io/ioutil" 11 | ) 12 | 13 | func ToUtf8Encoding(body io.Reader) (r io.Reader, name string, certain bool, err error) { 14 | 15 | b, err := ioutil.ReadAll(body) 16 | if err != nil { 17 | return 18 | } 19 | e, name, certain, err := DetermineEncodingFromReader(bytes.NewReader(b)) 20 | if err != nil { 21 | return 22 | } 23 | 24 | r = transform.NewReader(bytes.NewReader(b), e.NewDecoder()) 25 | return 26 | } 27 | 28 | func DetermineEncodingFromReader(r io.Reader) (e encoding.Encoding, name string, certain bool, err error) { 29 | b, err := bufio.NewReader(r).Peek(1024) 30 | if err != nil { 31 | return 32 | } 33 | 34 | e, name, certain = charset.DetermineEncoding(b, "") 35 | return 36 | } 37 | -------------------------------------------------------------------------------- /pkg/client/req.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/imroc/req" 7 | ) 8 | 9 | // ReqClient ... 10 | type ReqClient struct { 11 | *req.Req 12 | } 13 | 14 | // Get ... 15 | func (rc *ReqClient) Get(url string, v ...interface{}) (*http.Response, error) { 16 | r, err := rc.Req.Get(url, v...) 17 | if err != nil { 18 | return nil, err 19 | } 20 | return r.Response(), nil 21 | } 22 | 23 | // GetJSON ... 24 | func (rc *ReqClient) GetJSON(url string, v interface{}) error { 25 | r, err := rc.Req.Get(url) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | return r.ToJSON(v) 31 | } 32 | 33 | // Post ... 34 | func (rc *ReqClient) Post(url string, v ...interface{}) (*http.Response, error) { 35 | r, err := rc.Req.Post(url, v...) 36 | return r.Response(), err 37 | } 38 | 39 | // Download ... 40 | func (rc *ReqClient) Download(url, filename string, progress func(current, total int64)) error { 41 | r, err := rc.Req.Get(url, req.DownloadProgress(progress)) 42 | if err != nil { 43 | return err 44 | } 45 | return r.ToFile(filename) 46 | } 47 | -------------------------------------------------------------------------------- /pkg/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // Output returns the configuration of output 4 | type Output struct { 5 | Path string 6 | NeedCut bool 7 | } 8 | 9 | // Proxy returns the configuration of proxy 10 | type Proxy struct { 11 | Enable bool 12 | Socket string 13 | } 14 | 15 | // DMMApi returns the configuration of DMMApi 16 | type DMMApi struct { 17 | ApiId string 18 | AffiliateId string 19 | } 20 | 21 | // Configs ... 22 | type Configs struct { 23 | Output Output 24 | Proxy Proxy 25 | DMMApi DMMApi 26 | } 27 | 28 | // Default ... 29 | func Default() *Configs { 30 | return &Configs{ 31 | Output: Output{ 32 | Path: "output/{year}/{num}", 33 | NeedCut: true, 34 | }, 35 | Proxy: Proxy{ 36 | Enable: false, 37 | Socket: "", 38 | }, 39 | DMMApi: DMMApi{ 40 | ApiId: "", 41 | AffiliateId: "", 42 | }, 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /pkg/config/loader.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "github.com/spf13/viper" 4 | 5 | // Loader ... 6 | type Loader interface { 7 | LoadFile(filename string) (*Configs, error) 8 | } 9 | 10 | // NewLoader ... 11 | func NewLoader() Loader { 12 | return &ViperLoader{ 13 | viper.New(), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /pkg/config/viper.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/spf13/viper" 5 | ) 6 | 7 | // ViperLoader ... 8 | type ViperLoader struct { 9 | *viper.Viper 10 | } 11 | 12 | // LoadFile ... 13 | func (v *ViperLoader) LoadFile(filename string) (*Configs, error) { 14 | c := &Configs{} 15 | viper.SetConfigName(filename) 16 | viper.AddConfigPath(".") 17 | if err := viper.ReadInConfig(); err != nil { 18 | return nil, err 19 | } 20 | err := viper.Unmarshal(c) 21 | return c, err 22 | } 23 | -------------------------------------------------------------------------------- /pkg/img/imaging.go: -------------------------------------------------------------------------------- 1 | package img 2 | 3 | import ( 4 | "image" 5 | 6 | "github.com/disintegration/imaging" 7 | ) 8 | 9 | // Imaging ... 10 | type Imaging struct { 11 | } 12 | 13 | // Crop ... 14 | func (i *Imaging) Crop(src image.Image, w, h int) (image.Image, error) { 15 | return imaging.CropAnchor(src, w, h, imaging.TopRight), nil 16 | } 17 | 18 | // Open ... 19 | func (i *Imaging) Open(filename string) (image.Image, error) { 20 | return imaging.Open(filename) 21 | } 22 | 23 | // Save ... 24 | func (i *Imaging) Save(img image.Image, filename string) (err error) { 25 | return imaging.Save(img, filename) 26 | } 27 | 28 | // CropAndSave ... 29 | func (i *Imaging) CropAndSave(src, dst string, w, h int) error { 30 | img, err := i.Open(src) 31 | if err != nil { 32 | return err 33 | } 34 | if w == 0 { 35 | w = img.Bounds().Dx() 36 | } 37 | if h == 0 { 38 | h = img.Bounds().Dy() 39 | } 40 | croped, err := i.Crop(img, w, h) 41 | if err != nil { 42 | return err 43 | } 44 | return i.Save(croped, dst) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/img/imaging_test.go: -------------------------------------------------------------------------------- 1 | package img 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestImaging_CropAndSave(t *testing.T) { 8 | type args struct { 9 | src string 10 | dst string 11 | w int 12 | h int 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | wantErr bool 18 | }{ 19 | { 20 | name: "test crop", 21 | args: args{ 22 | src: "test.jpg", 23 | dst: "test1.jpg", 24 | w: 378, 25 | h: 0, 26 | }, 27 | wantErr: false, 28 | }, 29 | } 30 | for _, tt := range tests { 31 | t.Run(tt.name, func(t *testing.T) { 32 | i := &Imaging{} 33 | if err := i.CropAndSave(tt.args.src, tt.args.dst, tt.args.w, tt.args.h); (err != nil) != tt.wantErr { 34 | t.Errorf("CropAndSave() error = %v, wantErr %v", err, tt.wantErr) 35 | } 36 | }) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /pkg/img/img.go: -------------------------------------------------------------------------------- 1 | package img 2 | 3 | import "image" 4 | 5 | // Operation ... 6 | type Operation interface { 7 | Crop(img image.Image, w, h int) (image.Image, error) 8 | Open(filename string) (image.Image, error) 9 | Save(img image.Image, filename string) error 10 | CropAndSave(src, dst string, w, h int) error 11 | } 12 | 13 | // NewOperation ... 14 | func NewOperation() Operation { 15 | return &Imaging{} 16 | } 17 | -------------------------------------------------------------------------------- /pkg/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "github.com/kataras/golog" 5 | ) 6 | 7 | // Logger interface defines all the logging methods to be implemented 8 | type Logger interface { 9 | Info(args ...interface{}) 10 | Warn(args ...interface{}) 11 | Error(args ...interface{}) 12 | Debug(args ...interface{}) 13 | Fatal(args ...interface{}) 14 | 15 | Infof(format string, args ...interface{}) 16 | Warnf(format string, args ...interface{}) 17 | Errorf(format string, args ...interface{}) 18 | Debugf(format string, args ...interface{}) 19 | Fatalf(format string, args ...interface{}) 20 | } 21 | 22 | // New returns a new instance of Logger 23 | func New() Logger { 24 | return golog.New() 25 | } 26 | -------------------------------------------------------------------------------- /pkg/metadata/emby.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | import ( 4 | "encoding/xml" 5 | "fmt" 6 | "os" 7 | 8 | "dmm-scraper/pkg/scraper" 9 | ) 10 | 11 | type EmbyMovie struct { 12 | XMLName xml.Name `xml:"movie"` 13 | Plot string `xml:"plot"` 14 | Outline string `xml:"outline"` 15 | Title string `xml:"title"` 16 | Director string `xml:"director"` 17 | Year string `xml:"year"` 18 | Premiered string `xml:"premiered"` 19 | Runtime string `xml:"runtime"` 20 | Genre []string `xml:"genre"` 21 | Studio string `xml:"studio"` 22 | Tag []string `xml:"tag"` 23 | Actor []EmbyMovieActor `xml:"actor"` 24 | Label string `xml:"label"` 25 | Num string `xml:"num"` 26 | Cover string `xml:"cover"` 27 | Website string `xml:"website"` 28 | // Ratings *EmbyMovieRatings `xml:"ratings,omitempty"` 29 | } 30 | 31 | type EmbyMovieActor struct { 32 | Name string `xml:"name"` 33 | } 34 | type EmbyMovieRatings struct { 35 | Rating []EmbyMovieRating `xml:"rating"` 36 | } 37 | 38 | type EmbyMovieRating struct { 39 | Name string `xml:"name,attr"` 40 | Max string `xml:"max,attr"` 41 | Value string `xml:"value"` 42 | Votes string `xml:"votes"` 43 | } 44 | 45 | func (m *EmbyMovie) ToXML() ([]byte, error) { 46 | x, err := xml.MarshalIndent(m, "", " ") 47 | if err != nil { 48 | return x, err 49 | } 50 | x = []byte(xml.Header + string(x)) 51 | return x, nil 52 | } 53 | 54 | func (m *EmbyMovie) Save(filename string) error { 55 | b, err := m.ToXML() 56 | if err != nil { 57 | return err 58 | } 59 | return os.WriteFile(filename, b, 0644) 60 | } 61 | 62 | // func (m *EmbyMovie) SetPoster(filename string) { 63 | // m.Fanart = []EmbyMovieThumb{{Thumb: filename}} 64 | // m.Poster = filename 65 | // } 66 | 67 | func (m *EmbyMovie) SetTitle(formatNum string) { 68 | m.Title = fmt.Sprintf("%s %s", formatNum, m.Title) 69 | } 70 | 71 | func newEmbyMovieActors(names []string) []EmbyMovieActor { 72 | var actors []EmbyMovieActor 73 | for _, name := range names { 74 | actors = append(actors, EmbyMovieActor{Name: name}) 75 | } 76 | return actors 77 | } 78 | 79 | // func newEmbyRatings(name, max, value, votes string) EmbyMovieRatings { 80 | // return EmbyMovieRatings{[]EmbyMovieRating{{ 81 | // Name: name, 82 | // Max: max, 83 | // Value: value, 84 | // Votes: votes, 85 | // }}} 86 | // } 87 | 88 | // NewMovieNfo ... 89 | func NewMovieNfo(s scraper.Scraper) MovieNfo { 90 | return &EmbyMovie{ 91 | Plot: s.GetPlot(), 92 | Title: s.GetTitle(), 93 | Director: s.GetDirector(), 94 | Year: s.GetYear(), 95 | Premiered: s.GetPremiered(), 96 | Runtime: s.GetRuntime(), 97 | Genre: s.GetTags(), 98 | Tag: append(s.GetTags(), s.GetSeries(), s.GetLabel(), s.GetMaker(), s.GetDirector()), 99 | Studio: s.GetMaker(), 100 | Label: s.GetLabel(), 101 | Actor: newEmbyMovieActors(s.GetActors()), 102 | Cover: s.GetCover(), 103 | Num: s.GetNumber(), 104 | Website: s.GetWebsite(), 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /pkg/metadata/metadata.go: -------------------------------------------------------------------------------- 1 | package metadata 2 | 3 | // Metadata interface 4 | type Metadata interface { 5 | ToXML() ([]byte, error) 6 | Save(filename string) error 7 | } 8 | 9 | // MovieNfo interface 10 | type MovieNfo interface { 11 | Metadata 12 | // SetPoster(filename string) 13 | SetTitle(num string) 14 | } 15 | -------------------------------------------------------------------------------- /pkg/scraper/default.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "strings" 9 | 10 | "dmm-scraper/pkg/archive" 11 | myclient "dmm-scraper/pkg/client" 12 | 13 | "github.com/PuerkitoBio/goquery" 14 | "golang.org/x/text/encoding/japanese" 15 | "golang.org/x/text/transform" 16 | ) 17 | 18 | type DefaultScraper struct { 19 | doc *goquery.Document 20 | cookie *http.Cookie 21 | isArchive bool 22 | } 23 | 24 | func (DefaultScraper) FetchDoc(query string) (err error) { 25 | return nil 26 | } 27 | 28 | func (DefaultScraper) GetPlot() string { 29 | return "" 30 | } 31 | 32 | func (DefaultScraper) GetTitle() string { 33 | return "" 34 | } 35 | 36 | func (DefaultScraper) GetDirector() string { 37 | return "" 38 | } 39 | 40 | func (DefaultScraper) GetRuntime() string { 41 | return "" 42 | } 43 | 44 | func (DefaultScraper) GetTags() []string { 45 | return []string{} 46 | } 47 | 48 | func (DefaultScraper) GetMaker() string { 49 | return "" 50 | } 51 | 52 | func (DefaultScraper) GetActors() []string { 53 | return []string{} 54 | } 55 | 56 | func (DefaultScraper) GetLabel() string { 57 | return "" 58 | } 59 | 60 | func (DefaultScraper) GetNumber() string { 61 | return "" 62 | } 63 | 64 | func (DefaultScraper) GetFormatNumber() string { 65 | return "" 66 | } 67 | 68 | func (DefaultScraper) GetCover() string { 69 | return "" 70 | } 71 | 72 | func (s *DefaultScraper) GetWebsite() string { 73 | if s.doc == nil { 74 | return "" 75 | } 76 | return s.doc.Url.String() 77 | } 78 | 79 | func (DefaultScraper) GetPremiered() string { 80 | return "" 81 | } 82 | 83 | func (DefaultScraper) GetYear() string { 84 | return "" 85 | } 86 | 87 | func (DefaultScraper) GetSeries() string { 88 | return "" 89 | } 90 | 91 | func (DefaultScraper) GetType() string { 92 | return "" 93 | } 94 | 95 | func (DefaultScraper) NeedCut() bool { 96 | return needCut 97 | } 98 | 99 | func (s *DefaultScraper) GetDocFromURL(u string) (err error) { 100 | log.Infof("fetching %s", u) 101 | if s.cookie == nil { 102 | s.cookie = &http.Cookie{} 103 | } 104 | res, err := client.Get(u, s.cookie) 105 | if err != nil { 106 | return err 107 | } 108 | 109 | bodyBytes, _ := ioutil.ReadAll(res.Body) 110 | defer res.Body.Close() 111 | 112 | r, name, certain, err := myclient.ToUtf8Encoding(ioutil.NopCloser(bytes.NewBuffer(bodyBytes))) 113 | if err != nil { 114 | return err 115 | } 116 | log.Infof("detect content %s %v", name, certain) 117 | switch name { 118 | case "utf-8": 119 | s.doc, err = goquery.NewDocumentFromReader(r) 120 | default: 121 | reader := transform.NewReader(ioutil.NopCloser(bytes.NewBuffer(bodyBytes)), japanese.EUCJP.NewDecoder()) 122 | s.doc, err = goquery.NewDocumentFromReader(reader) // 123 | } 124 | 125 | if err != nil { 126 | return err 127 | } 128 | s.doc.Url = res.Request.URL 129 | return nil 130 | } 131 | 132 | // Download ... 133 | func Download(url, filename string, progress func(current, total int64)) error { 134 | return client.Download(url, filename, progress) 135 | } 136 | 137 | func (s *DefaultScraper) GetAvailableUrl(orginUrl string) (string, error) { 138 | 139 | resp := &archive.AvailableResp{} 140 | err := client.GetJSON(fmt.Sprintf(archive.GetAvailableUrl, orginUrl), resp) 141 | if err != nil { 142 | return "", err 143 | } 144 | 145 | return resp.ArchivedSnapshots.Closest.URL, nil 146 | } 147 | 148 | // GetOutputPath ... 149 | func GetOutputPath(s Scraper, conf string) string { 150 | p := strings.Replace(conf, "{year}", s.GetYear(), 1) 151 | if len(s.GetActors()) > 0 { 152 | p = strings.Replace(p, "{actor}", s.GetActors()[0], 1) 153 | } else { 154 | p = strings.Replace(p, "{actor}", "", 1) 155 | } 156 | p = strings.Replace(p, "{maker}", s.GetMaker(), 1) 157 | p = strings.Replace(p, "{num}", s.GetFormatNumber(), 1) 158 | 159 | return strings.Replace(p, "//", "/", -1) 160 | } 161 | -------------------------------------------------------------------------------- /pkg/scraper/default_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "dmm-scraper/pkg/config" 5 | ) 6 | 7 | type args struct { 8 | query string 9 | url string 10 | } 11 | 12 | type testCase struct { 13 | name string 14 | args args 15 | wantErr bool 16 | want string 17 | } 18 | 19 | func BeforeTest() { 20 | c, err := config.NewLoader().LoadFile("../../config") 21 | if err != nil { 22 | log.Fatalf("Error reading config file, %s", err) 23 | } 24 | Setup(c) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/scraper/dmm.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "regexp" 8 | "strings" 9 | "time" 10 | 11 | "github.com/PuerkitoBio/goquery" 12 | ) 13 | 14 | const ( 15 | dmmMonoSearchUrl = "https://www.dmm.co.jp/mono/dvd/-/search/=/searchstr=%s/" 16 | ) 17 | 18 | type DMMScraper struct { 19 | DefaultScraper 20 | } 21 | 22 | func (s *DMMScraper) GetType() string { 23 | return "DMMScraper" 24 | } 25 | 26 | // FetchDoc search once or twice to get a detail page 27 | func (s *DMMScraper) FetchDoc(query string) (err error) { 28 | s.cookie = &http.Cookie{ 29 | Name: "age_check_done", 30 | Value: "1", 31 | Path: "/", 32 | Domain: "dmm.co.jp", 33 | Expires: time.Now().Add(1 * time.Hour), 34 | } 35 | // dmm 搜索页 36 | if strings.Contains(query, "-") { 37 | strs := strings.Split(query, "-") 38 | if len(strs) == 2 { 39 | query = strs[0] + fmt.Sprintf("%05s", strs[1]) 40 | } 41 | } 42 | err = s.GetDocFromURL(fmt.Sprintf(dmmMonoSearchUrl, query)) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | var hrefs []string 48 | s.doc.Find("#list li").Each(func(i int, s *goquery.Selection) { 49 | href, _ := s.Find(".tmb a").Attr("href") 50 | hrefs = append(hrefs, href) 51 | }) 52 | 53 | if len(hrefs) == 0 { 54 | return errors.New("record not found") 55 | } 56 | 57 | var detail string 58 | for _, href := range hrefs { 59 | if isURLMatchQuery(href, query) { 60 | detail = href 61 | } 62 | } 63 | if detail == "" { 64 | return fmt.Errorf("unable to match in hrefs %v", hrefs) 65 | } 66 | 67 | return s.GetDocFromURL(detail) 68 | } 69 | 70 | func (s *DMMScraper) GetPlot() string { 71 | if s.doc == nil { 72 | return "" 73 | } 74 | val, _ := s.doc.Find("meta[property=\"og:description\"]").Attr("content") 75 | return val 76 | } 77 | 78 | func (s *DMMScraper) GetTitle() string { 79 | if s.doc == nil { 80 | return "" 81 | } 82 | val, _ := s.doc.Find("meta[property=\"og:title\"]").Attr("content") 83 | return val 84 | } 85 | 86 | func (s *DMMScraper) GetDirector() string { 87 | if s.doc == nil { 88 | return "" 89 | } 90 | return getDmmTableValue("監督", s.doc) 91 | } 92 | 93 | func (s *DMMScraper) GetRuntime() string { 94 | if s.doc == nil { 95 | return "" 96 | } 97 | return getDmmTableValue("収録時間", s.doc) 98 | } 99 | 100 | func (s *DMMScraper) GetTags() (tags []string) { 101 | if s.doc == nil { 102 | return 103 | } 104 | s.doc.Find("table[class=mg-b20] tr").EachWithBreak( 105 | func(i int, s *goquery.Selection) bool { 106 | if strings.Contains(s.Text(), "ジャンル") { 107 | s.Find("td a").Each(func(i int, ss *goquery.Selection) { 108 | tags = append(tags, ss.Text()) 109 | }) 110 | return false 111 | } 112 | return true 113 | }) 114 | return 115 | } 116 | 117 | func (s *DMMScraper) GetMaker() string { 118 | if s.doc == nil { 119 | return "" 120 | } 121 | return getDmmTableValue("メーカー", s.doc) 122 | } 123 | 124 | func (s *DMMScraper) GetActors() (actors []string) { 125 | if s.doc == nil { 126 | return 127 | } 128 | s.doc.Find("#performer a").Each(func(i int, s *goquery.Selection) { 129 | actors = append(actors, s.Text()) 130 | }) 131 | return 132 | } 133 | 134 | func (s *DMMScraper) GetLabel() string { 135 | if s.doc == nil { 136 | return "" 137 | } 138 | return getDmmTableValue("レーベル", s.doc) 139 | } 140 | 141 | func (s *DMMScraper) GetNumber() string { 142 | if s.doc == nil { 143 | return "" 144 | } 145 | return getDmmTableValue("品番", s.doc) 146 | } 147 | 148 | func (s *DMMScraper) GetFormatNumber() string { 149 | l, i := GetLabelNumber(s.GetNumber()) 150 | if l == "" { 151 | return fmt.Sprintf("%03d", i) 152 | } 153 | return strings.ToUpper(fmt.Sprintf("%s-%03d", l, i)) 154 | } 155 | 156 | func (s *DMMScraper) GetCover() string { 157 | if s.doc == nil { 158 | return "" 159 | } 160 | img, _ := s.doc.Find("meta[property=\"og:image\"]").Attr("content") 161 | return strings.Replace(img, "ps.jpg", "pl.jpg", 1) 162 | } 163 | 164 | func (s *DMMScraper) GetPremiered() (rel string) { 165 | if s.doc == nil { 166 | return "" 167 | } 168 | rel = getDmmTableValue("発売日", s.doc) 169 | if rel == "" { 170 | rel = getDmmTableValue("配信開始日", s.doc) 171 | } 172 | return strings.Replace(rel, "/", "-", -1) 173 | } 174 | 175 | func (s *DMMScraper) GetYear() (rel string) { 176 | if s.doc == nil { 177 | return "" 178 | } 179 | return regexp.MustCompile(`\d{4}`).FindString(s.GetPremiered()) 180 | } 181 | 182 | func (s *DMMScraper) GetSeries() string { 183 | if s.doc == nil { 184 | return "" 185 | } 186 | return getDmmTableValue("シリーズ", s.doc) 187 | } 188 | 189 | func (s *DMMScraper) NeedCut() bool { 190 | return needCut 191 | } 192 | 193 | func getDmmTableValue(key string, doc *goquery.Document) (val string) { 194 | doc.Find("table[class=mg-b20] tr").EachWithBreak( 195 | func(i int, s *goquery.Selection) bool { 196 | if strings.Contains(s.Text(), key) { 197 | val = s.Find("td a").Text() 198 | if val == "" { 199 | val = s.Find("td").Last().Text() 200 | } 201 | if val == "----" { 202 | val = "" 203 | } 204 | val = strings.TrimSpace(val) 205 | return false 206 | } 207 | return true 208 | }) 209 | return 210 | } 211 | 212 | func getDmmTableValue2(x int, doc *goquery.Document) (val string) { 213 | //log.Info(doc.Find("table[class=mg-b20] td[width]").Html()) 214 | return doc.Find("table[class=mg-b20] td").Eq(x - 1).Text() 215 | } 216 | -------------------------------------------------------------------------------- /pkg/scraper/dmm_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDMMScraper_FetchDoc(t *testing.T) { 8 | BeforeTest() 9 | tests := []testCase{ 10 | { 11 | name: "fetchDoc expects no error", 12 | args: args{ 13 | query: "gne-218", 14 | }, 15 | wantErr: false, 16 | }, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | s := &DMMScraper{} 21 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 22 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 23 | } 24 | got := s.GetNumber() 25 | t.Logf("GetNumber() = %v", got) 26 | got = s.GetPlot() 27 | t.Logf("GetPlot() = %v", got) 28 | got = s.GetTitle() 29 | t.Logf("GetTitle() = %v", got) 30 | got = s.GetCover() 31 | t.Logf("GetCover() = %v", got) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/scraper/dmmapi.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | 9 | "dmm-scraper/third_party/dmm-go-sdk/api" 10 | ) 11 | 12 | type DMMApiScraper struct { 13 | DefaultScraper 14 | Item api.Item 15 | } 16 | 17 | func (s *DMMApiScraper) GetType() string { 18 | return "DMMApiScraper" 19 | } 20 | 21 | // FetchDoc 22 | // ... 23 | func (s *DMMApiScraper) FetchDoc(query string) (err error) { 24 | query = strings.Replace(query, "-", "", 1) 25 | 26 | dmmProductService.SetSite(api.SiteAdult) 27 | dmmProductService.SetService("mono") 28 | //dmmapi.SetFloor("dvd") 29 | dmmProductService.SetKeyword(query) 30 | result, err := dmmProductService.Execute() 31 | if result.TotalCount == 1 { 32 | s.Item = result.Items[0] 33 | return nil 34 | } 35 | if result.TotalCount == 0 { 36 | return errors.New("record not found") 37 | } 38 | if result.ResultCount > 1 { 39 | for _, item := range result.Items { 40 | if isURLMatchQuery(item.URL, query) { 41 | s.Item = item 42 | break 43 | } 44 | } 45 | } 46 | return err 47 | } 48 | 49 | func (s *DMMApiScraper) GetPlot() string { 50 | return "" 51 | } 52 | 53 | func (s *DMMApiScraper) GetTitle() string { 54 | return s.Item.Title 55 | } 56 | 57 | func (s *DMMApiScraper) GetDirector() string { 58 | if len(s.Item.ItemInformation.Directors) > 0 { 59 | return s.Item.ItemInformation.Directors[0].Name 60 | } 61 | return "" 62 | } 63 | 64 | func (s *DMMApiScraper) GetRuntime() string { 65 | return s.Item.Volume 66 | } 67 | 68 | func (s *DMMApiScraper) GetTags() (tags []string) { 69 | for _, genre := range s.Item.ItemInformation.Genres { 70 | tags = append(tags, genre.Name) 71 | } 72 | return 73 | } 74 | 75 | func (s *DMMApiScraper) GetMaker() string { 76 | if len(s.Item.ItemInformation.Maker) > 0 { 77 | return s.Item.ItemInformation.Maker[0].Name 78 | } 79 | return "" 80 | } 81 | 82 | func (s *DMMApiScraper) GetActors() (actors []string) { 83 | for _, actor := range s.Item.ItemInformation.Actress { 84 | actors = append(actors, actor.Name) 85 | } 86 | return 87 | } 88 | 89 | func (s *DMMApiScraper) GetLabel() string { 90 | if len(s.Item.ItemInformation.Label) > 0 { 91 | return s.Item.ItemInformation.Label[0].Name 92 | } 93 | return "" 94 | } 95 | 96 | func (s *DMMApiScraper) GetNumber() string { 97 | return s.Item.ContentID 98 | } 99 | 100 | func (s *DMMApiScraper) GetFormatNumber() string { 101 | l, i := GetLabelNumber(s.GetNumber()) 102 | if l == "" { 103 | return fmt.Sprintf("%03d", i) 104 | } 105 | return strings.ToUpper(fmt.Sprintf("%s-%03d", l, i)) 106 | } 107 | 108 | func (s *DMMApiScraper) GetCover() string { 109 | return s.Item.ImageURL.Large 110 | } 111 | 112 | func (s *DMMApiScraper) GetPremiered() (rel string) { 113 | return s.Item.Date 114 | } 115 | 116 | func (s *DMMApiScraper) GetYear() (rel string) { 117 | return regexp.MustCompile(`\d{4}`).FindString(s.GetPremiered()) 118 | } 119 | 120 | func (s *DMMApiScraper) GetSeries() string { 121 | if len(s.Item.ItemInformation.Series) > 0 { 122 | return s.Item.ItemInformation.Series[0].Name 123 | } 124 | return "" 125 | } 126 | 127 | func (s *DMMApiScraper) GetWebsite() string { 128 | return s.Item.URL 129 | } 130 | 131 | func (s *DMMApiScraper) NeedCut() bool { 132 | return needCut 133 | } 134 | -------------------------------------------------------------------------------- /pkg/scraper/dmmapi_digital.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "dmm-scraper/third_party/dmm-go-sdk/api" 8 | ) 9 | 10 | type DMMApiDigitalScraper struct { 11 | DMMApiScraper 12 | } 13 | 14 | func (s *DMMApiDigitalScraper) GetType() string { 15 | return "DMMApiDigitalScraper" 16 | } 17 | 18 | // FetchDoc 19 | // ... 20 | func (s *DMMApiDigitalScraper) FetchDoc(query string) (err error) { 21 | query = strings.Replace(query, "-", "00", 1) 22 | 23 | dmmProductService.SetSite(api.SiteAdult) 24 | dmmProductService.SetService("digital") 25 | //dmmapi.SetFloor("videoa") 26 | dmmProductService.SetKeyword(query) 27 | result, err := dmmProductService.Execute() 28 | if result.TotalCount == 1 { 29 | s.Item = result.Items[0] 30 | return nil 31 | } 32 | if result.TotalCount == 0 { 33 | return errors.New("record not found") 34 | } 35 | if result.ResultCount > 1 { 36 | for _, item := range result.Items { 37 | if isURLMatchQuery(item.URL, query) { 38 | s.Item = item 39 | break 40 | } 41 | } 42 | } 43 | return err 44 | } 45 | -------------------------------------------------------------------------------- /pkg/scraper/dmmapi_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDMMApiScraper_FetchDoc(t *testing.T) { 8 | BeforeTest() 9 | tests := []testCase{ 10 | { 11 | name: "fetchDoc expects no error", 12 | args: args{ 13 | query: "ACRN-119", 14 | }, 15 | wantErr: false, 16 | }, 17 | { 18 | name: "fetchDoc expects no error", 19 | args: args{ 20 | query: "pred-352", 21 | }, 22 | wantErr: false, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | s := &DMMApiDigitalScraper{} 28 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 29 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 30 | } 31 | got := s.GetNumber() 32 | t.Logf("GetNumber() = %v", got) 33 | got = s.GetPlot() 34 | t.Logf("GetPlot() = %v", got) 35 | got = s.GetTitle() 36 | t.Logf("GetTitle() = %v", got) 37 | got = s.GetCover() 38 | t.Logf("GetCover() = %v", got) 39 | gots := s.GetActors() 40 | t.Logf("GetActors() = %v", gots) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/scraper/fanza.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "time" 9 | 10 | "github.com/PuerkitoBio/goquery" 11 | ) 12 | 13 | const ( 14 | dmmDigitalSearchUrl = "https://www.dmm.co.jp/digital/-/list/search/=/?searchstr=%s" 15 | ) 16 | 17 | type FanzaScraper struct { 18 | DMMScraper 19 | } 20 | 21 | func (s *FanzaScraper) GetType() string { 22 | return "FanzaScraper" 23 | } 24 | 25 | // FetchDoc search once or twice to get a detail page 26 | func (s *FanzaScraper) FetchDoc(query string) (err error) { 27 | s.cookie = &http.Cookie{ 28 | Name: "age_check_done", 29 | Value: "1", 30 | Path: "/", 31 | Domain: "dmm.co.jp", 32 | Expires: time.Now().Add(1 * time.Hour), 33 | } 34 | 35 | // dmm 搜索页 36 | if strings.Contains(query, "-") { 37 | strs := strings.Split(query, "-") 38 | if len(strs) == 2 { 39 | query = strs[0] + fmt.Sprintf("%05s", strs[1]) 40 | } 41 | } 42 | err = s.GetDocFromURL(fmt.Sprintf(dmmDigitalSearchUrl, query)) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | var hrefs []string 48 | s.doc.Find("#list li").Each(func(i int, s *goquery.Selection) { 49 | href, _ := s.Find(".tmb a").Attr("href") 50 | hrefs = append(hrefs, href) 51 | }) 52 | 53 | if len(hrefs) == 0 { 54 | return errors.New("record not found") 55 | } 56 | // 多个结果时,取最短长度 57 | var detail string 58 | for _, href := range hrefs { 59 | if isURLMatchQuery(href, query) { 60 | detail = href 61 | } 62 | } 63 | if detail == "" { 64 | return fmt.Errorf("unable to match in hrefs %v", hrefs) 65 | } 66 | 67 | return s.GetDocFromURL(detail) 68 | } 69 | 70 | func (s *FanzaScraper) NeedCut() bool { 71 | return needCut 72 | } 73 | -------------------------------------------------------------------------------- /pkg/scraper/fanza_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import "testing" 4 | 5 | func TestFanzaScraper_FetchDoc(t *testing.T) { 6 | BeforeTest() 7 | tests := []testCase{ 8 | { 9 | name: "fetchDoc expects no error", 10 | args: args{ 11 | query: "196glod00152", 12 | }, 13 | wantErr: false, 14 | }, 15 | } 16 | for _, tt := range tests { 17 | t.Run(tt.name, func(t *testing.T) { 18 | s := &FanzaScraper{} 19 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 20 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 21 | } 22 | got := s.GetNumber() 23 | t.Logf("GetNumber() = %v", got) 24 | got = s.GetPlot() 25 | t.Logf("GetPlot() = %v", got) 26 | got = s.GetTitle() 27 | t.Logf("GetTitle() = %v", got) 28 | got = s.GetCover() 29 | t.Logf("GetCover() = %v", got) 30 | }) 31 | } 32 | } -------------------------------------------------------------------------------- /pkg/scraper/fc2.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/PuerkitoBio/goquery" 10 | ) 11 | 12 | type Fc2Scraper struct { 13 | DefaultScraper 14 | fc2data *FC2Data 15 | } 16 | 17 | func (s *Fc2Scraper) GetType() string { 18 | return "Fc2Scraper" 19 | } 20 | 21 | const ( 22 | fc2Url = "https://adult.contents.fc2.com/article/%s/" 23 | fc2Url2 = "https://adult.contents.fc2.com/article_search.php?id=%s" 24 | ) 25 | 26 | type FC2Data struct { 27 | //Type string `json:"@type"` 28 | //ID string `json:"@id"` 29 | //Sku string `json:"sku"` 30 | Name string `json:"name"` 31 | Description string `json:"description"` 32 | Context string `json:"@context"` 33 | Image FC2Image `json:"image"` 34 | ProductID string `json:"productID"` 35 | //Brand Brand `json:"brand"` 36 | //AggregateRating AggregateRating `json:"aggregateRating"` 37 | //PotentialAction PotentialAction `json:"potentialAction"` 38 | //Offers Offers `json:"offers"` 39 | } 40 | 41 | type FC2Image struct { 42 | URL string `json:"url"` 43 | Type string `json:"@type"` 44 | } 45 | 46 | func (s *Fc2Scraper) FetchDoc(query string) (err error) { 47 | 48 | err = s.GetDocFromURL(fmt.Sprintf(fc2Url, query)) 49 | if err != nil { 50 | return err 51 | } 52 | if len(s.doc.Find(".items_notfound_header").Nodes) != 0 { 53 | var u string 54 | u, err = s.GetAvailableUrl(fmt.Sprintf(fc2Url2, query)) 55 | if err != nil { 56 | return err 57 | } 58 | s.isArchive = true 59 | err = s.GetDocFromURL(u) 60 | if err != nil { 61 | return err 62 | } 63 | } 64 | if !s.isArchive { 65 | err = getFc2Data(s) 66 | } 67 | return err 68 | } 69 | 70 | func (s *Fc2Scraper) GetTitle() string { 71 | if s.doc == nil { 72 | return "" 73 | } 74 | 75 | if s.isArchive { 76 | title := s.doc.Find("h2[class=title_bar]").Text() 77 | return strings.TrimSpace(title) 78 | } 79 | //title := s.doc.Find("div[class=items_article_headerInfo] h3").Text() 80 | return s.fc2data.Name 81 | } 82 | 83 | func (s *Fc2Scraper) GetPlot() string { 84 | if s.doc == nil { 85 | return "" 86 | } 87 | if s.isArchive { 88 | tempDoc := s.doc.Find("section[class=explain] p").Clone() 89 | explain := tempDoc.Children().Remove().End().Text() 90 | return strings.TrimSpace(explain) 91 | } 92 | //tempDoc := s.doc.Find("section[class=items_article_Contents] div").Clone() 93 | //explain := tempDoc.Children().Remove().End().Text() 94 | return s.fc2data.Description 95 | } 96 | 97 | func (s *Fc2Scraper) GetDirector() string { 98 | if s.doc == nil { 99 | return "" 100 | } 101 | if s.isArchive { 102 | a := s.doc.Find("a[class=analyticsLinkClick_toUserPage1]").Text() 103 | return strings.TrimSpace(a) 104 | } 105 | a := s.doc.Find("div[class=items_article_headerInfo] ul li").Eq(2).Find("a").Text() 106 | return strings.TrimSpace(a) 107 | } 108 | 109 | func (s *Fc2Scraper) GetRuntime() (runtime string) { 110 | return "" 111 | } 112 | 113 | func (s *Fc2Scraper) GetTags() (tags []string) { 114 | if s.doc == nil { 115 | return 116 | } 117 | if s.isArchive { 118 | s.doc.Find(".incident_tags a").Each(func(i int, ss *goquery.Selection) { 119 | tags = append(tags, strings.TrimSpace(ss.Text())) 120 | }) 121 | return 122 | } 123 | s.doc.Find("a[class=tagTag]").Each(func(i int, ss *goquery.Selection) { 124 | tags = append(tags, strings.TrimSpace(ss.Text())) 125 | }) 126 | return 127 | } 128 | 129 | func (s *Fc2Scraper) GetMaker() string { 130 | if s.doc == nil { 131 | return "" 132 | } 133 | return s.GetDirector() 134 | } 135 | 136 | func (s *Fc2Scraper) GetActors() []string { 137 | return []string{} 138 | } 139 | 140 | func (s *Fc2Scraper) GetNumber() string { 141 | if s.doc == nil { 142 | return "" 143 | } 144 | if s.isArchive { 145 | id, _ := s.doc.Find("#reviews").Attr("data-id") 146 | return strings.TrimSpace(id) 147 | } 148 | //id, _ := s.doc.Find(".items_article_TagArea").Attr("data-id") 149 | return s.fc2data.ProductID 150 | } 151 | 152 | func (s *Fc2Scraper) GetCover() string { 153 | if s.doc == nil { 154 | return "" 155 | } 156 | if s.isArchive { 157 | img, _ := s.doc.Find("section[class=explain] img").First().Attr("src") 158 | return img[strings.LastIndex(img, "http"):] 159 | } 160 | 161 | return s.fc2data.Image.URL 162 | } 163 | 164 | func (s *Fc2Scraper) GetPremiered() (rel string) { 165 | if s.doc == nil { 166 | return 167 | } 168 | if s.isArchive { 169 | rel = s.doc.Find("div[class=main_info_block] dl dd").Text() 170 | } else { 171 | rel = s.doc.Find(".items_article_Releasedate p").Text() 172 | } 173 | rel = regexp.MustCompile(`\d{4}\/(0?[1-9]|1[012])\/(0?[1-9]|[12][0-9]|3[01])`).FindString(rel) 174 | return strings.Replace(rel, "/", "-", -1) 175 | } 176 | 177 | func (s *Fc2Scraper) GetYear() (rel string) { 178 | if s.doc == nil { 179 | return "" 180 | } 181 | return regexp.MustCompile(`\d{4}`).FindString(s.GetPremiered()) 182 | } 183 | 184 | func (s *Fc2Scraper) GetFormatNumber() string { 185 | return strings.ToUpper(fmt.Sprintf("fc2-%s", s.GetNumber())) 186 | } 187 | 188 | func getFc2Data(s *Fc2Scraper) error { 189 | data := s.doc.Find("script[type='application/ld+json']").Text() 190 | s.fc2data = &FC2Data{} 191 | err := json.Unmarshal([]byte(data), s.fc2data) 192 | return err 193 | } 194 | -------------------------------------------------------------------------------- /pkg/scraper/fc2_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFc2Scraper_FetchDoc(t *testing.T) { 8 | BeforeTest() 9 | tests := []testCase{ 10 | { 11 | name: "fetchDoc expects no error", 12 | args: args{ 13 | query: "1027251", 14 | }, 15 | wantErr: false, 16 | }, 17 | } 18 | for _, tt := range tests { 19 | t.Run(tt.name, func(t *testing.T) { 20 | s := &Fc2Scraper{} 21 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 22 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 23 | } 24 | got := s.GetNumber() 25 | t.Logf("GetNumber() = %v", got) 26 | got = s.GetPlot() 27 | t.Logf("GetPlot() = %v", got) 28 | got = s.GetTitle() 29 | t.Logf("GetTitle() = %v", got) 30 | got = s.GetCover() 31 | t.Logf("GetCover() = %v", got) 32 | }) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /pkg/scraper/gyutto.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "regexp" 7 | "strings" 8 | 9 | "github.com/PuerkitoBio/goquery" 10 | ) 11 | 12 | const ( 13 | gyuttoItemUrl = "http://gyutto.com/i/%s" 14 | ) 15 | 16 | type GyuttoScraper struct { 17 | DefaultScraper 18 | } 19 | 20 | func (s *GyuttoScraper) GetType() string { 21 | return "GyuttoScraper" 22 | } 23 | 24 | func (s *GyuttoScraper) FetchDoc(query string) (err error) { 25 | u := fmt.Sprintf(gyuttoItemUrl, query) 26 | 27 | err = s.GetDocFromURL(u) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | if s.doc.Find(".BasicInfo").Length() == 0 { 33 | u, err = s.GetAvailableUrl(u) 34 | if err != nil { 35 | return err 36 | } 37 | s.isArchive = true 38 | err = s.GetDocFromURL(u) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | s.doc.Url, err = url.Parse(u) 44 | 45 | return err 46 | } 47 | 48 | func (s *GyuttoScraper) GetTitle() string { 49 | if s.doc == nil { 50 | return "" 51 | } 52 | return strings.TrimSpace(s.doc.Find(".parts_Mds01 h1").Text()) 53 | } 54 | 55 | func (s *GyuttoScraper) GetPlot() string { 56 | if s.doc == nil { 57 | return "" 58 | } 59 | return strings.TrimSpace(s.doc.Find(".unit_DetailSummary p").Text()) 60 | } 61 | 62 | func (s *GyuttoScraper) GetDirector() string { 63 | if s.doc == nil { 64 | return "" 65 | } 66 | return strings.TrimSpace(s.doc.Find(".BasicInfo").Eq(2).Find("dd a").First().Text()) 67 | } 68 | 69 | func (s *GyuttoScraper) GetRuntime() string { 70 | return "" 71 | } 72 | 73 | func (s *GyuttoScraper) GetTags() (tags []string) { 74 | if s.doc == nil { 75 | return 76 | } 77 | s.doc.Find(".BasicInfo").Eq(3).Find("dd a").Each( 78 | func(i int, s *goquery.Selection) { 79 | tags = append(tags, s.Text()) 80 | }) 81 | return 82 | } 83 | 84 | func (s *GyuttoScraper) GetMaker() string { 85 | return s.GetDirector() 86 | } 87 | 88 | func (s *GyuttoScraper) GetActors() (actors []string) { 89 | return 90 | } 91 | 92 | func (s *GyuttoScraper) GetNumber() string { 93 | if s.doc == nil { 94 | return "" 95 | } 96 | nums := regexp.MustCompile("[0-9]+").FindAllString(s.doc.Url.String(), -1) 97 | return nums[len(nums)-1] 98 | } 99 | 100 | func (s *GyuttoScraper) GetFormatNumber() string { 101 | return strings.ToUpper(fmt.Sprintf("gyutto-%s", s.GetNumber())) 102 | } 103 | 104 | func (s *GyuttoScraper) GetCover() string { 105 | if s.doc == nil { 106 | return "" 107 | } 108 | img, _ := s.doc.Find(".highslide").First().Attr("href") 109 | return "http://image.gyutto.com" + img 110 | } 111 | 112 | func (s *GyuttoScraper) GetPremiered() (rel string) { 113 | if s.doc == nil { 114 | return "" 115 | } 116 | 117 | rel = strings.TrimSpace(s.doc.Find(".BasicInfo").Text()) 118 | rel = regexp.MustCompile(`\d{4}年\d{2}月\d{2}日`).FindString(rel) 119 | rel = strings.Replace(rel, "年", "-", 1) 120 | rel = strings.Replace(rel, "月", "-", 1) 121 | rel = strings.Replace(rel, "日", "", 1) 122 | return 123 | } 124 | 125 | func (s *GyuttoScraper) GetYear() (rel string) { 126 | if s.doc == nil { 127 | return "" 128 | } 129 | return regexp.MustCompile(`\d{4}`).FindString(s.GetPremiered()) 130 | } 131 | -------------------------------------------------------------------------------- /pkg/scraper/gyutto_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import "testing" 4 | 5 | func TestGyuttoScraper_FetchDoc(t *testing.T) { 6 | BeforeTest() 7 | tests := []testCase{ 8 | { 9 | name: "fetchDoc expects no error", 10 | args: args{ 11 | query: "item207303", 12 | }, 13 | wantErr: false, 14 | }, 15 | } 16 | for _, tt := range tests { 17 | t.Run(tt.name, func(t *testing.T) { 18 | s := &GyuttoScraper{} 19 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 20 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 21 | } 22 | got := s.GetNumber() 23 | t.Logf("GetNumber() = %v", got) 24 | got = s.GetPlot() 25 | t.Logf("GetPlot() = %v", got) 26 | got = s.GetTitle() 27 | t.Logf("GetTitle() = %v", got) 28 | got = s.GetCover() 29 | t.Logf("GetCover() = %v", got) 30 | }) 31 | } 32 | } -------------------------------------------------------------------------------- /pkg/scraper/heyzo.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "html" 7 | "regexp" 8 | "strings" 9 | 10 | "github.com/PuerkitoBio/goquery" 11 | ) 12 | 13 | type HeyzoScraper struct { 14 | DefaultScraper 15 | movieId string 16 | } 17 | 18 | const ( 19 | heyzoDetailUrl = "https://www.heyzo.com/moviepages/%s/index.html" 20 | ) 21 | 22 | func (s *HeyzoScraper) GetType() string { 23 | return "HeyzoScraper" 24 | } 25 | 26 | type HeyzoData struct { 27 | Context string `json:"@context"` 28 | Type string `json:"@type"` 29 | Name string `json:"name"` 30 | Image string `json:"image"` 31 | Encoding struct { 32 | Type string `json:"@type"` 33 | EncodingFormat string `json:"encodingFormat"` 34 | } `json:"encoding"` 35 | Actor struct { 36 | Type string `json:"@type"` 37 | Name string `json:"name"` 38 | Image string `json:"image"` 39 | } `json:"actor"` 40 | Description string `json:"description"` 41 | Duration string `json:"duration"` 42 | DateCreated string `json:"dateCreated"` 43 | ReleasedEvent struct { 44 | Type string `json:"@type"` 45 | StartDate string `json:"startDate"` 46 | Location struct { 47 | Type string `json:"@type"` 48 | Name string `json:"name"` 49 | } `json:"location"` 50 | } `json:"releasedEvent"` 51 | Video struct { 52 | Type string `json:"@type"` 53 | Description string `json:"description"` 54 | Duration string `json:"duration"` 55 | Name string `json:"name"` 56 | Thumbnail string `json:"thumbnail"` 57 | ThumbnailURL string `json:"thumbnailUrl"` 58 | UploadDate string `json:"uploadDate"` 59 | Actor string `json:"actor"` 60 | Provider string `json:"provider"` 61 | } `json:"video"` 62 | AggregateRating struct { 63 | Type string `json:"@type"` 64 | RatingValue string `json:"ratingValue"` 65 | BestRating string `json:"bestRating"` 66 | ReviewCount string `json:"reviewCount"` 67 | } `json:"aggregateRating"` 68 | } 69 | 70 | func (s *HeyzoScraper) FetchDoc(query string) (err error) { 71 | s.movieId = regexp.MustCompile("[0-9]+").FindString(query) 72 | 73 | u := fmt.Sprintf(heyzoDetailUrl, s.movieId) 74 | return s.GetDocFromURL(u) 75 | } 76 | 77 | func (s *HeyzoScraper) GetPlot() string { 78 | if s.doc == nil { 79 | return "" 80 | } 81 | p := s.doc.Find("p[class=memo]").Text() 82 | return strings.TrimSpace(p) 83 | } 84 | 85 | func (s *HeyzoScraper) GetTitle() string { 86 | if s.doc == nil { 87 | return "" 88 | } 89 | j, _ := s.doc.Find("script[type='application/ld+json']").Html() 90 | d := &HeyzoData{} 91 | j = strings.ReplaceAll(html.UnescapeString(j), "\n", "") 92 | err := json.Unmarshal([]byte(j), d) 93 | if err != nil { 94 | log.Error(err) 95 | } 96 | return d.Name 97 | } 98 | 99 | func (s *HeyzoScraper) GetDirector() string { 100 | return "" 101 | } 102 | 103 | func (s *HeyzoScraper) GetRuntime() string { 104 | return "" 105 | } 106 | 107 | func (s *HeyzoScraper) GetTags() (tags []string) { 108 | if s.doc == nil { 109 | return 110 | } 111 | s.doc.Find(".tag-keyword-list").First().Find("a").Each(func(i int, ss *goquery.Selection) { 112 | tags = append(tags, strings.TrimSpace(ss.Text())) 113 | }) 114 | return 115 | } 116 | 117 | func (s *HeyzoScraper) GetMaker() string { 118 | return "HEYZO" 119 | } 120 | 121 | func (s *HeyzoScraper) GetActors() (actors []string) { 122 | if s.doc == nil { 123 | return 124 | } 125 | s.doc.Find(".table-actor a").Each(func(i int, ss *goquery.Selection) { 126 | actors = append(actors, strings.TrimSpace(ss.Text())) 127 | }) 128 | return 129 | } 130 | 131 | func (s *HeyzoScraper) GetLabel() string { 132 | return "" 133 | } 134 | 135 | func (s *HeyzoScraper) GetNumber() string { 136 | return s.movieId 137 | } 138 | 139 | func (s *HeyzoScraper) GetFormatNumber() string { 140 | return strings.ToUpper(fmt.Sprintf("heyzo-%s", s.GetNumber())) 141 | } 142 | 143 | func (s *HeyzoScraper) GetCover() string { 144 | if s.doc == nil { 145 | return "" 146 | } 147 | j, _ := s.doc.Find("script[type='application/ld+json']").Html() 148 | d := &HeyzoData{} 149 | j = strings.ReplaceAll(html.UnescapeString(j), "\n", "") 150 | err := json.Unmarshal([]byte(j), d) 151 | if err != nil { 152 | log.Error(err) 153 | } 154 | return strings.ReplaceAll(d.Image, "//", "https://") 155 | } 156 | 157 | func (s *HeyzoScraper) GetPremiered() (rel string) { 158 | if s.doc == nil { 159 | return "" 160 | } 161 | rel = s.doc.Find(".table-release-day td").Last().Text() 162 | return strings.Replace(strings.TrimSpace(rel), "/", "-", -1) 163 | } 164 | 165 | func (s *HeyzoScraper) GetYear() (rel string) { 166 | if s.doc == nil { 167 | return "" 168 | } 169 | return regexp.MustCompile(`\d{4}`).FindString(s.GetPremiered()) 170 | } 171 | 172 | func (s *HeyzoScraper) GetSeries() string { 173 | if s.doc == nil { 174 | return "" 175 | } 176 | p := s.doc.Find(".table-series td").Last().Text() 177 | if strings.TrimSpace(p) == "-----" { 178 | return "" 179 | } 180 | return strings.TrimSpace(p) 181 | } 182 | -------------------------------------------------------------------------------- /pkg/scraper/heyzo_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestHeyzoScraper_FetchDoc(t *testing.T) { 8 | BeforeTest() 9 | tests := []testCase{ 10 | { 11 | name: "fetchDoc expects no error", 12 | args: args{ 13 | query: "heyzo-1031", 14 | }, 15 | wantErr: false, 16 | }, 17 | { 18 | name: "fetchDoc expects no error", 19 | args: args{ 20 | query: "heyzo-2169", 21 | }, 22 | wantErr: false, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | s := &HeyzoScraper{} 28 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 29 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 30 | } 31 | got := s.GetNumber() 32 | t.Logf("GetNumber() = %v", got) 33 | got = s.GetPlot() 34 | t.Logf("GetPlot() = %v", got) 35 | got = s.GetTitle() 36 | t.Logf("GetTitle() = %v", got) 37 | got = s.GetCover() 38 | t.Logf("GetCover() = %v", got) 39 | }) 40 | } 41 | } -------------------------------------------------------------------------------- /pkg/scraper/javcup.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "net/url" 7 | "regexp" 8 | "strings" 9 | "time" 10 | 11 | "github.com/PuerkitoBio/goquery" 12 | "github.com/chromedp/chromedp" 13 | ) 14 | 15 | const ( 16 | javcupDetailUrl = "https://javcup.com/movie/%s" 17 | ) 18 | 19 | type JavCupScraper struct { 20 | DefaultScraper 21 | formatQuery string 22 | } 23 | 24 | func (s *JavCupScraper) GetType() string { 25 | return "JavCupScraper" 26 | } 27 | 28 | func (s *JavCupScraper) FetchDoc(query string) (err error) { 29 | //s.cookie = &http.Cookie{ 30 | // Name: "adc", 31 | // Value: "1", 32 | // Path: "/", 33 | // Domain: "mgstage.com", 34 | // Expires: time.Now().Add(1 * time.Hour), 35 | //} 36 | l, i := GetLabelNumber(query) 37 | if l == "" { 38 | return fmt.Errorf("unable to GetLabelNumber") 39 | } 40 | s.formatQuery = strings.ToUpper(fmt.Sprintf("%s-%03d", l, i)) 41 | u := fmt.Sprintf(javcupDetailUrl, s.formatQuery) 42 | 43 | ctx, cancel := chromedp.NewContext(context.Background()) 44 | defer cancel() 45 | 46 | timeoutCtx, timeoutCancel := context.WithTimeout(ctx, 10*time.Second) 47 | defer timeoutCancel() 48 | 49 | // 定义要获取的内容变量 50 | var response string 51 | 52 | // 执行任务:打开页面,等待某个特定元素加载完成,获取内容 53 | err = chromedp.Run(timeoutCtx, 54 | chromedp.Navigate(u), 55 | // 根据实际情况替换下面的选择器 56 | chromedp.WaitVisible(`#video`, chromedp.ByID), 57 | chromedp.OuterHTML(`html`, &response), 58 | ) 59 | if err != nil { 60 | return err 61 | } 62 | 63 | s.doc, err = goquery.NewDocumentFromReader(strings.NewReader(response)) // 64 | 65 | if err != nil { 66 | return err 67 | } 68 | parsedURL, _ := url.Parse(u) 69 | s.doc.Url = parsedURL 70 | 71 | return nil 72 | } 73 | 74 | func (s *JavCupScraper) GetPlot() string { 75 | if s.doc == nil { 76 | return "" 77 | } 78 | return strings.TrimSpace(s.doc.Find("div[class=movie-description] p").First().Text()) 79 | } 80 | 81 | func (s *JavCupScraper) GetTitle() string { 82 | if s.doc == nil { 83 | return "" 84 | } 85 | title := strings.TrimSpace(s.doc.Find("h1[itemprop=name]").First().Text()) 86 | return strings.TrimSpace(strings.Replace(title, s.formatQuery, "", 1)) 87 | } 88 | 89 | func (s *JavCupScraper) GetDirector() string { 90 | if s.doc == nil { 91 | return "" 92 | } 93 | return strings.TrimSpace(s.doc.Find("span[itemprop=director]").First().Text()) 94 | } 95 | 96 | func (s *JavCupScraper) GetRuntime() string { 97 | return "" 98 | } 99 | 100 | func (s *JavCupScraper) GetTags() (tags []string) { 101 | if s.doc == nil { 102 | return 103 | } 104 | 105 | s.doc.Find("p[itemprop=keywords]").Find("span").Each( 106 | func(i int, ss *goquery.Selection) { 107 | tags = append(tags, strings.TrimSpace(ss.Text())) 108 | }) 109 | return 110 | } 111 | 112 | func (s *JavCupScraper) GetMaker() string { 113 | if s.doc == nil { 114 | return "" 115 | } 116 | return strings.TrimSpace(s.doc.Find("span[itemprop=genre]").First().Text()) 117 | } 118 | 119 | func (s *JavCupScraper) GetActors() (actors []string) { 120 | if s.doc == nil { 121 | return 122 | } 123 | s.doc.Find("div[class=model-item]").Find("span").Each( 124 | func(i int, ss *goquery.Selection) { 125 | actors = append(actors, strings.TrimSpace(ss.Text())) 126 | }) 127 | 128 | return 129 | } 130 | 131 | func (s *JavCupScraper) GetLabel() string { 132 | return "" 133 | } 134 | 135 | func (s *JavCupScraper) GetNumber() string { 136 | return s.formatQuery 137 | } 138 | 139 | func (s *JavCupScraper) GetCover() string { 140 | if s.doc == nil { 141 | return "" 142 | } 143 | u, _ := s.doc.Find("#video").Attr("poster") 144 | u = strings.Replace(u, "cdn.javcup.com", "pics.dmm.co.jp", 1) 145 | u = strings.Replace(u, "img.javcup.com", "pics.dmm.co.jp", 1) 146 | return u 147 | } 148 | 149 | func (s *JavCupScraper) GetPremiered() (rel string) { 150 | if s.doc == nil { 151 | return "" 152 | } 153 | return strings.TrimSpace(s.doc.Find("span[itemprop=datePublished]").First().Text()) 154 | } 155 | 156 | func (s *JavCupScraper) GetYear() (rel string) { 157 | if s.doc == nil { 158 | return "" 159 | } 160 | return regexp.MustCompile(`\d{4}`).FindString(s.GetPremiered()) 161 | } 162 | 163 | func (s *JavCupScraper) GetSeries() string { 164 | return "" 165 | } 166 | 167 | func (s *JavCupScraper) GetFormatNumber() string { 168 | return s.GetNumber() 169 | } 170 | -------------------------------------------------------------------------------- /pkg/scraper/javcup_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestJavCupScraper_FetchDoc(t *testing.T) { 8 | BeforeTest() 9 | tests := []testCase{ 10 | { 11 | name: "fetchDoc expects no error", 12 | args: args{ 13 | query: "OYCVR020.VR", 14 | }, 15 | wantErr: false, 16 | want: "OYCVR-020", 17 | }, 18 | } 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | s := &JavCupScraper{} 22 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 23 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 24 | } 25 | //debug, _ := s.doc.Html() 26 | //t.Logf(debug) 27 | got := s.GetNumber() 28 | t.Logf("GetNumber() = %v", got) 29 | got = s.GetPlot() 30 | t.Logf("GetPlot() = %v", got) 31 | got = s.GetTitle() 32 | t.Logf("GetTitle() = %v", got) 33 | got = s.GetCover() 34 | t.Logf("GetCover() = %v", got) 35 | got = s.GetDirector() 36 | t.Logf("GetDirector() = %v", got) 37 | got = s.GetMaker() 38 | t.Logf("GetMaker() = %v", got) 39 | gots := s.GetTags() 40 | t.Logf("GetTags() = %v", gots) 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /pkg/scraper/mgstage.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "regexp" 7 | "strings" 8 | "time" 9 | 10 | "github.com/dlclark/regexp2" 11 | 12 | "github.com/PuerkitoBio/goquery" 13 | ) 14 | 15 | const ( 16 | mgstageDetailUrl = "https://www.mgstage.com/product/product_detail/%s/" 17 | ) 18 | 19 | type MGStageScraper struct { 20 | DefaultScraper 21 | } 22 | 23 | func (s *MGStageScraper) GetType() string { 24 | return "MGStageScraper" 25 | } 26 | 27 | func (s *MGStageScraper) FetchDoc(query string) (err error) { 28 | s.cookie = &http.Cookie{ 29 | Name: "adc", 30 | Value: "1", 31 | Path: "/", 32 | Domain: "mgstage.com", 33 | Expires: time.Now().Add(1 * time.Hour), 34 | } 35 | u := fmt.Sprintf(mgstageDetailUrl, strings.ToUpper(query)) 36 | return s.GetDocFromURL(u) 37 | } 38 | 39 | func (s *MGStageScraper) GetPlot() string { 40 | if s.doc == nil { 41 | return "" 42 | } 43 | return strings.TrimSpace(s.doc.Find("#introduction p").First().Text()) 44 | } 45 | 46 | func (s *MGStageScraper) GetTitle() string { 47 | if s.doc == nil { 48 | return "" 49 | } 50 | return strings.TrimSpace(s.doc.Find("h1[class=tag]").First().Text()) 51 | } 52 | 53 | func (s *MGStageScraper) GetDirector() string { 54 | return "" 55 | } 56 | 57 | func (s *MGStageScraper) GetRuntime() string { 58 | if s.doc == nil { 59 | return "" 60 | } 61 | return strings.TrimSpace(getMgstageTableValue("収録時間", s.doc).Find("td").Text()) 62 | } 63 | 64 | func (s *MGStageScraper) GetTags() (tags []string) { 65 | if s.doc == nil { 66 | return 67 | } 68 | getMgstageTableValue("ジャンル", s.doc).Find("td a").Each( 69 | func(i int, ss *goquery.Selection) { 70 | tags = append(tags, strings.TrimSpace(ss.Text())) 71 | }) 72 | return 73 | } 74 | 75 | func (s *MGStageScraper) GetMaker() string { 76 | if s.doc == nil { 77 | return "" 78 | } 79 | return strings.TrimSpace(getMgstageTableValue("メーカー", s.doc).Find("td").Text()) 80 | } 81 | 82 | func (s *MGStageScraper) GetActors() (actors []string) { 83 | if s.doc == nil { 84 | return 85 | } 86 | t := getMgstageTableValue("出演", s.doc) 87 | if t != nil { 88 | t.Find("td a").Each( 89 | func(i int, ss *goquery.Selection) { 90 | actors = append(actors, strings.TrimSpace(ss.Text())) 91 | }) 92 | } 93 | 94 | return 95 | } 96 | 97 | func (s *MGStageScraper) GetLabel() string { 98 | if s.doc == nil { 99 | return "" 100 | } 101 | return strings.TrimSpace(getMgstageTableValue("レーベル", s.doc).Find("td").Text()) 102 | } 103 | 104 | func (s *MGStageScraper) GetNumber() string { 105 | if s.doc == nil { 106 | return "" 107 | } 108 | return strings.TrimSpace(getMgstageTableValue("品番", s.doc).Find("td").Text()) 109 | } 110 | 111 | func (s *MGStageScraper) GetCover() string { 112 | if s.doc == nil { 113 | return "" 114 | } 115 | u, _ := s.doc.Find("#EnlargeImage").Attr("href") 116 | return u 117 | } 118 | 119 | func (s *MGStageScraper) GetPremiered() (rel string) { 120 | if s.doc == nil { 121 | return "" 122 | } 123 | rel = strings.TrimSpace(getMgstageTableValue("配信開始日", s.doc).Find("td").Text()) 124 | return strings.Replace(rel, "/", "-", -1) 125 | } 126 | 127 | func (s *MGStageScraper) GetYear() (rel string) { 128 | if s.doc == nil { 129 | return "" 130 | } 131 | return regexp.MustCompile(`\d{4}`).FindString(s.GetPremiered()) 132 | } 133 | 134 | func (s *MGStageScraper) GetSeries() string { 135 | if s.doc == nil { 136 | return "" 137 | } 138 | return strings.TrimSpace(getMgstageTableValue("シリーズ", s.doc).Find("td").Text()) 139 | } 140 | 141 | func (s *MGStageScraper) GetFormatNumber() string { 142 | typeMGStage, _ := regexp2.Compile(`([0-9]{3,4}|)[a-zA-Z]{2,6}-[0-9]{3,5}`, 0) 143 | match, _ := typeMGStage.FindStringMatch(s.GetNumber()) 144 | return strings.ToUpper(match.String()) 145 | } 146 | 147 | func getMgstageTableValue(key string, doc *goquery.Document) (target *goquery.Selection) { 148 | target = doc.Find("~") 149 | doc.Find("div[class=detail_data] table").Last().Find("tr").EachWithBreak( 150 | func(i int, s *goquery.Selection) bool { 151 | if strings.Contains(s.Text(), key) { 152 | target = s 153 | return false 154 | } 155 | return true 156 | }) 157 | return 158 | } 159 | -------------------------------------------------------------------------------- /pkg/scraper/mgstage_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestMGStageScraper_FetchDoc(t *testing.T) { 8 | BeforeTest() 9 | tests := []testCase{ 10 | { 11 | name: "fetchDoc expects no error", 12 | args: args{ 13 | query: "abw-108", 14 | }, 15 | wantErr: false, 16 | want: "ABW-108", 17 | }, 18 | } 19 | for _, tt := range tests { 20 | t.Run(tt.name, func(t *testing.T) { 21 | s := &MGStageScraper{} 22 | if err := s.FetchDoc(tt.args.query); (err != nil) != tt.wantErr { 23 | t.Errorf("FetchDoc() error = %v, wantErr %v", err, tt.wantErr) 24 | } 25 | got := s.GetNumber() 26 | t.Logf("GetNumber() = %v", got) 27 | got = s.GetPlot() 28 | t.Logf("GetPlot() = %v", got) 29 | got = s.GetTitle() 30 | t.Logf("GetTitle() = %v", got) 31 | got = s.GetCover() 32 | t.Logf("GetCover() = %v", got) 33 | }) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /pkg/scraper/regexp.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | "bytes" 5 | "regexp" 6 | "strconv" 7 | "strings" 8 | "unicode" 9 | 10 | "github.com/dlclark/regexp2" 11 | ) 12 | 13 | func GetQuery(name string) (query string, scrapers []Scraper) { 14 | typeGyutto, _ := regexp2.Compile(`item[0-9]{6,7}`, 0) 15 | isGyutto, _ := typeGyutto.MatchString(name) 16 | typeHeyzo, _ := regexp2.Compile(`(heyzo|HEYZO)-[0-9]{4}`, 0) 17 | isHeyzo, _ := typeHeyzo.MatchString(name) 18 | typeFC2, _ := regexp2.Compile(`(?<=(fc2|FC2|ppv|PPV)-)[0-9]{6,7}`, 0) 19 | isFC2, _ := typeFC2.MatchString(name) 20 | typeMGStage, _ := regexp2.Compile(`([0-9]{3,4}[a-zA-Z]{2,6})-[0-9]{3,4}`, 0) 21 | isMGStage, _ := typeMGStage.MatchString(name) 22 | // typeAnime, _ := regexp2.Compile(`(GLOD|JDXA|MJAD|ACRN|ORORE|DPLT)(-|)[0-9]{3,6}`, regexp2.RightToLeft) 23 | // isAnime, _ := typeDMM.MatchString(name) 24 | typeDMM, _ := regexp2.Compile(`[a-zA-Z]{2,5}(-|)[0-9]{3,6}`, regexp2.RightToLeft) 25 | isDMM, _ := typeDMM.MatchString(name) 26 | 27 | switch { 28 | case isGyutto: 29 | match, _ := typeGyutto.FindStringMatch(name) 30 | query = match.String() 31 | scrapers = append(scrapers, &GyuttoScraper{}) 32 | case isHeyzo: 33 | match, _ := typeHeyzo.FindStringMatch(name) 34 | query = match.String() 35 | scrapers = append(scrapers, &HeyzoScraper{}) 36 | case isFC2: 37 | match, _ := typeFC2.FindStringMatch(name) 38 | query = match.String() 39 | scrapers = append(scrapers, &Fc2Scraper{}) 40 | case isMGStage: 41 | match, _ := typeMGStage.FindStringMatch(name) 42 | query = match.String() 43 | scrapers = append(scrapers, &MGStageScraper{}) 44 | case isDMM: 45 | match, _ := typeDMM.FindStringMatch(name) 46 | query = match.String() 47 | if dmmProductService != nil { 48 | scrapers = append(scrapers, &DMMApiScraper{}, &DMMApiDigitalScraper{}) 49 | } else { 50 | scrapers = append(scrapers, &DMMScraper{}, &FanzaScraper{}, &JavCupScraper{}) 51 | } 52 | } 53 | 54 | return 55 | } 56 | 57 | func GetLabelNumber(s string) (string, int) { 58 | labelRe, _ := regexp.Compile(`[a-zA-Z]{2,5}(-|)[0-9]{3,7}`) 59 | numRe, _ := regexp.Compile(`[0-9]{3,7}`) 60 | l := labelRe.FindString(s) 61 | if l == "" { 62 | i, _ := strconv.Atoi(numRe.FindString(s)) 63 | return "", i 64 | } 65 | var label bytes.Buffer 66 | var number bytes.Buffer 67 | for _, c := range l { 68 | if unicode.IsLetter(c) { 69 | label.WriteRune(c) 70 | } 71 | if unicode.IsNumber(c) { 72 | number.WriteRune(c) 73 | } 74 | } 75 | l = strings.ToLower(label.String()) 76 | i, _ := strconv.Atoi(number.String()) 77 | return l, i 78 | } 79 | 80 | func isURLMatchQuery(u, query string) bool { 81 | var labelMatch bool 82 | var numMatch bool 83 | cid, _ := regexp.Compile(`cid=([^//]+)`) 84 | cids := cid.FindStringSubmatch(u) 85 | if len(cids) > 1 { 86 | label1, number1 := GetLabelNumber(cids[1]) 87 | label2, number2 := GetLabelNumber(query) 88 | //fmt.Println(label1, number1) 89 | //fmt.Println(label2, number2) 90 | if label1 == label2 { 91 | labelMatch = true 92 | } 93 | 94 | if number1 == number2 { 95 | numMatch = true 96 | } 97 | } 98 | return labelMatch && numMatch 99 | } 100 | -------------------------------------------------------------------------------- /pkg/scraper/regexp_test.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import "testing" 4 | 5 | func TestGetLabelNumber(t *testing.T) { 6 | type args struct { 7 | s string 8 | } 9 | tests := []struct { 10 | name string 11 | args args 12 | }{ 13 | { 14 | name: "test", 15 | args: args{ 16 | s: "1sdde552", 17 | }, 18 | }, 19 | } 20 | for _, tt := range tests { 21 | t.Run(tt.name, func(t *testing.T) { 22 | got, got1 := GetLabelNumber(tt.args.s) 23 | t.Logf(got, got1) 24 | }) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /pkg/scraper/scraper.go: -------------------------------------------------------------------------------- 1 | package scraper 2 | 3 | import ( 4 | myclient "dmm-scraper/pkg/client" 5 | "dmm-scraper/pkg/config" 6 | "dmm-scraper/pkg/logger" 7 | "dmm-scraper/third_party/dmm-go-sdk/api" 8 | ) 9 | 10 | // Scraper is interface 11 | type Scraper interface { 12 | FetchDoc(query string) (err error) 13 | GetPlot() string 14 | GetTitle() string 15 | GetDirector() string 16 | GetRuntime() string 17 | GetTags() []string 18 | GetMaker() string 19 | GetActors() []string 20 | GetLabel() string 21 | GetNumber() string 22 | GetFormatNumber() string 23 | GetCover() string 24 | GetWebsite() string 25 | GetPremiered() string 26 | GetYear() string 27 | GetSeries() string 28 | GetType() string 29 | NeedCut() bool 30 | } 31 | 32 | var ( 33 | client myclient.Client 34 | log logger.Logger 35 | dmmProductService *api.ProductService 36 | needCut bool 37 | ) 38 | 39 | // Setup ... 40 | func Setup(conf *config.Configs) { 41 | log = logger.New() 42 | client = myclient.New() 43 | if conf.Proxy.Enable { 44 | err := client.SetProxyUrl(conf.Proxy.Socket) 45 | if err != nil { 46 | log.Errorf("Error parse proxy url, %s, proxy disabled", err) 47 | } 48 | } 49 | if conf.DMMApi.ApiId != "" && conf.DMMApi.AffiliateId != "" { 50 | dmmProductService = api.NewProductService(conf.DMMApi.AffiliateId, conf.DMMApi.ApiId) 51 | } 52 | needCut = conf.Output.NeedCut 53 | } 54 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/actress.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/mitchellh/mapstructure" 9 | ) 10 | 11 | const ( 12 | // DefaultActressAPILength is Default length for a Actress API request 13 | DefaultActressAPILength = 20 14 | // DefaultActressMaxLength is MAX length for a Actress API request 15 | DefaultActressMaxLength = 100 16 | ) 17 | 18 | type ActressService struct { 19 | ApiID string `mapstructure:"api_id"` 20 | AffiliateID string `mapstructure:"affiliate_id"` 21 | Length int64 `mapstructure:"hits"` 22 | Offset int64 `mapstructure:"offset"` 23 | Sort string `mapstructure:"sort"` 24 | Initial string `mapstructure:"initial"` 25 | Keyword string `mapstructure:"keyword"` 26 | Bust string `mapstructure:"bust"` 27 | GteBust string `mapstructure:"gte_bust"` 28 | LteBust string `mapstructure:"lte_bust"` 29 | Waist string `mapstructure:"waist"` 30 | GteWaist string `mapstructure:"gte_waist"` 31 | LteWaist string `mapstructure:"lte_waist"` 32 | Hip string `mapstructure:"hip"` 33 | GteHip string `mapstructure:"gte_hip"` 34 | LteHip string `mapstructure:"lte_hip"` 35 | Height string `mapstructure:"height"` 36 | GteHeight string `mapstructure:"gte_height"` 37 | LteHeight string `mapstructure:"lte_height"` 38 | Birthday string `mapstructure:"birthday"` 39 | GteBirthday string `mapstructure:"gte_birthday"` 40 | LteBirthday string `mapstructure:"lte_birthday"` 41 | } 42 | 43 | type ActressRawResponse struct { 44 | Request ActressService `mapstructure:"request"` 45 | Result ActressResponse `mapstructure:"result"` 46 | } 47 | 48 | type ActressResponse struct { 49 | ResultCount int64 `mapstructure:"result_count"` 50 | TotalCount int64 `mapstructure:"total_count"` 51 | FirstPosition int64 `mapstructure:"first_position"` 52 | Actresses []Actress `mapstructure:"actress"` 53 | } 54 | 55 | type Actress struct { 56 | ID string `mapstructure:"id"` 57 | Name string `mapstructure:"name"` 58 | Ruby string `mapstructure:"ruby"` 59 | Bust string `mapstructure:"bust"` 60 | GteBust string `mapstructure:"gte_bust"` 61 | LteBust string `mapstructure:"lte_bust"` 62 | Cup string `mapstructure:"cup"` 63 | Waist string `mapstructure:"waist"` 64 | GteWaist string `mapstructure:"gte_waist"` 65 | LteWaist string `mapstructure:"lte_waist"` 66 | Hip string `mapstructure:"hip"` 67 | GteHip string `mapstructure:"gte_hip"` 68 | LteHip string `mapstructure:"lte_hip"` 69 | Height string `mapstructure:"height"` 70 | GteHeight string `mapstructure:"gte_height"` 71 | LteHeight string `mapstructure:"lte_height"` 72 | Birthday string `mapstructure:"birthday"` 73 | GteBirthday string `mapstructure:"gte_birthday"` 74 | LteBirthday string `mapstructure:"lte_birthday"` 75 | BloodType string `mapstructure:"blood_type"` 76 | Hobby string `mapstructure:"hobby"` 77 | Prefectures string `mapstructure:"prefectures"` 78 | ImageURL ActressImageList `mapstructure:"imageURL"` 79 | ListURL ActressProductList `mapstructure:"listURL"` 80 | } 81 | 82 | type ActressImageList struct { 83 | Small string `mapstructure:"small"` 84 | Large string `mapstructure:"large"` 85 | } 86 | 87 | type ActressProductList struct { 88 | Digital string `mapstructure:"digital"` 89 | Mono string `mapstructure:"mono"` 90 | Monthly string `mapstructure:"monthly"` 91 | Ppm string `mapstructure:"ppm"` 92 | Rental string `mapstructure:"rental"` 93 | } 94 | 95 | // NewActressService returns a new service for the given affiliate ID and API ID. 96 | // 97 | // NewActressServiceは渡したアフィリエイトIDとAPI IDを使用して新しい serviceを返します。 98 | func NewActressService(affiliateID, apiID string) *ActressService { 99 | return &ActressService{ 100 | ApiID: apiID, 101 | AffiliateID: affiliateID, 102 | Length: DefaultActressAPILength, 103 | Offset: DefaultAPIOffset, 104 | Sort: "", 105 | Initial: "", 106 | Keyword: "", 107 | Bust: "", 108 | GteBust: "", 109 | LteBust: "", 110 | Waist: "", 111 | GteWaist: "", 112 | LteWaist: "", 113 | Hip: "", 114 | GteHip: "", 115 | LteHip: "", 116 | Height: "", 117 | GteHeight: "", 118 | LteHeight: "", 119 | Birthday: "", 120 | GteBirthday: "", 121 | LteBirthday: "", 122 | } 123 | } 124 | 125 | // Execute requests a url is created by BuildRequestURL. 126 | // Use ExecuteWeak If you want get this response in interface{}. 127 | // 128 | // BuildRequestURLで生成したURLにリクエストします。 129 | // もし interface{} でこのレスポンスを取得したい場合は ExecuteWeak を使用してください。 130 | func (srv *ActressService) Execute() (*ActressResponse, error) { 131 | result, err := srv.ExecuteWeak() 132 | if err != nil { 133 | return nil, err 134 | } 135 | var raw ActressRawResponse 136 | if err = mapstructure.WeakDecode(result, &raw); err != nil { 137 | return nil, err 138 | } 139 | return &raw.Result, nil 140 | } 141 | 142 | // ExecuteWeak requests a url is created by BuildRequestURL. 143 | // 144 | // BuildRequestURLで生成したURLにリクエストします。 145 | func (srv *ActressService) ExecuteWeak() (interface{}, error) { 146 | reqURL, err := srv.BuildRequestURL() 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | return RequestJSON(reqURL) 152 | } 153 | 154 | // SetLength set the specified argument to ProductService.Length 155 | // 156 | // SetLengthはLengthパラメータを設定します。 157 | func (srv *ActressService) SetLength(length int64) *ActressService { 158 | srv.Length = length 159 | return srv 160 | } 161 | 162 | // SetHits set the specified argument to ProductService.Length 163 | // SetHits is the alias for SetLength 164 | // 165 | // SetHitsはLengthパラメータを設定します。 166 | func (srv *ActressService) SetHits(length int64) *ActressService { 167 | srv.SetLength(length) 168 | return srv 169 | } 170 | 171 | // SetOffset set the specified argument to ProductService.Offset 172 | // 173 | // SetOffsetはOffsetパラメータを設定します。 174 | func (srv *ActressService) SetOffset(offset int64) *ActressService { 175 | srv.Offset = offset 176 | return srv 177 | } 178 | 179 | // SetKeyword set the specified argument to ActressService.Keyword 180 | // 181 | // SetKeywordはKeywordパラメータを設定します。 182 | func (srv *ActressService) SetKeyword(keyword string) *ActressService { 183 | srv.Keyword = TrimString(keyword) 184 | return srv 185 | } 186 | 187 | // SetSort set the specified argument to ActressService.Sort 188 | // 189 | // SetSortはsortパラメータを設定します。 190 | func (srv *ActressService) SetSort(sort string) *ActressService { 191 | srv.Sort = TrimString(sort) 192 | return srv 193 | } 194 | 195 | // SetInitial sets the specified argument to ActressService.Initial. 196 | // This argment is actress name's initial and you can use only hiragana. 197 | // e.g. srv.SetInitial("あ") -> Sora Aoi(あおい そら, 蒼井そら) 198 | // 199 | // SetInitialはInitalパラメータに検索したい女優の頭文字をひらがなで設定します。 200 | func (srv *ActressService) SetInitial(initial string) *ActressService { 201 | srv.Initial = TrimString(initial) 202 | return srv 203 | } 204 | 205 | // SetBirthday sets the specified argument to ActressService.Birthday. 206 | // format YYYYMMDD 207 | // e.g. 1999/01/01 -> 19990101 208 | // 209 | // SetBirthdayはBirthdayパラメータに女優の誕生日を設定します。 210 | func (srv *ActressService) SetBirthday(birthday string) *ActressService { 211 | srv.Birthday = TrimString(birthday) 212 | return srv 213 | } 214 | 215 | // SetGteBirthday sets the specified argument to ActressService.GteBirthday. 216 | // format YYYYMMDD 217 | // e.g. 19990101 218 | // 219 | // SetGteBirthdayはBirthdayパラメータに女優の誕生日の開始値を設定します。 220 | func (srv *ActressService) SetGteBirthday(gteBirthday string) *ActressService { 221 | srv.GteBirthday = TrimString(gteBirthday) 222 | return srv 223 | } 224 | 225 | // SetLteBirthday sets the specified argument to ActressService.LteBirthday. 226 | // format YYYYMMDD 227 | // e.g. 20171231 228 | // 229 | // SetLteBirthdayはBirthdayパラメータに女優の誕生日の終了値を設定します。 230 | func (srv *ActressService) SetLteBirthday(lteBirthday string) *ActressService { 231 | srv.LteBirthday = TrimString(lteBirthday) 232 | return srv 233 | } 234 | 235 | // SetBust sets the specified argument (numeric format string) to ActressService.Bust. unit: centimeter. 236 | // 237 | // SetBustはBustパラメータに女優のバストサイズを設定します。 238 | func (srv *ActressService) SetBust(bust string) *ActressService { 239 | srv.Bust = TrimString(bust) 240 | return srv 241 | } 242 | 243 | // SetGteBust sets the specified argument (numeric format string) to ActressService.GteBust. unit: centimeter. 244 | // 245 | // SetGteBustはBustパラメータに女優のバストサイズの最小値を設定します。 246 | func (srv *ActressService) SetGteBust(gteBust string) *ActressService { 247 | srv.GteBust = TrimString(gteBust) 248 | return srv 249 | } 250 | 251 | // SetLteBust sets the specified argument (numeric format string) to ActressService.Bust. unit: centimeter. 252 | // 253 | // SetLteBustはBustパラメータに女優のバストサイズの最小値を設定します。 254 | func (srv *ActressService) SetLteBust(lteBust string) *ActressService { 255 | srv.LteBust = TrimString(lteBust) 256 | return srv 257 | } 258 | 259 | // SetWaist sets the specified argument (numeric format string) to ActressService.Waist. unit: centimeter. 260 | // 261 | // SetWaistはBirthdayパラメータに女優のウエストサイズを設定します。 262 | func (srv *ActressService) SetWaist(waist string) *ActressService { 263 | srv.Waist = TrimString(waist) 264 | return srv 265 | } 266 | 267 | // SetGteWaist sets the specified argument (numeric format string) to ActressService.GteWaist. unit: centimeter. 268 | // 269 | // SetGteWaistはBirthdayパラメータに女優のウエストサイズ最小値を設定します。 270 | func (srv *ActressService) SetGteWaist(gteWaist string) *ActressService { 271 | srv.GteWaist = TrimString(gteWaist) 272 | return srv 273 | } 274 | 275 | // SetLteWaist sets the specified argument (numeric format string) to ActressService.LteWaist. unit: centimeter. 276 | // 277 | // SetLteWaistはBirthdayパラメータに女優のウエストサイズ最大値を設定します。 278 | func (srv *ActressService) SetLteWaist(lteWaist string) *ActressService { 279 | srv.LteWaist = TrimString(lteWaist) 280 | return srv 281 | } 282 | 283 | // SetHip sets the specified argument (numeric format string) to ActressService.Hip. unit: centimeter. 284 | // 285 | // SetHipはBirthdayパラメータに女優のヒップサイズを設定します。 286 | func (srv *ActressService) SetHip(hip string) *ActressService { 287 | srv.Hip = TrimString(hip) 288 | return srv 289 | } 290 | 291 | // SetGteHip sets the specified argument (numeric format string) to ActressService.GteHip. unit: centimeter. 292 | // 293 | // SetGteHipはBirthdayパラメータに女優のヒップサイズの最長値を設定します。 294 | func (srv *ActressService) SetGteHip(gteHip string) *ActressService { 295 | srv.GteHip = TrimString(gteHip) 296 | return srv 297 | } 298 | 299 | // SetLteHip sets the specified argument (numeric format string) to ActressService.LteHip. unit: centimeter. 300 | // 301 | // SetLteHipはBirthdayパラメータに女優のヒップサイズの最大値を設定します。 302 | func (srv *ActressService) SetLteHip(lteHip string) *ActressService { 303 | srv.LteHip = TrimString(lteHip) 304 | return srv 305 | } 306 | 307 | // SetHeight sets the specified argument (numeric format string) to ActressService.Height. unit: centimeter. 308 | // 309 | // SetHeightはBirthdayパラメータに女優の身長を設定します。 310 | func (srv *ActressService) SetHeight(height string) *ActressService { 311 | srv.Height = TrimString(height) 312 | return srv 313 | } 314 | 315 | // SetGteHeight sets the specified argument (numeric format string) to ActressService.GteHeight. unit: centimeter. 316 | // 317 | // SetGteHeightはBirthdayパラメータに女優の身長の最小値を設定します。 318 | func (srv *ActressService) SetGteHeight(gteHeight string) *ActressService { 319 | srv.GteHeight = TrimString(gteHeight) 320 | return srv 321 | } 322 | 323 | // SetLteHeight sets the specified argument (numeric format string) to ActressService.Height. unit: centimeter. 324 | // 325 | // SetLteHeightはBirthdayパラメータに女優の身長の最大値を設定します。 326 | func (srv *ActressService) SetLteHeight(lteHeight string) *ActressService { 327 | srv.LteHeight = TrimString(lteHeight) 328 | return srv 329 | } 330 | 331 | // ValidateLength validates ProductService.Length within the range (1 <= value <= DEFAULT_ACTRESS_MAX_LENGTH). 332 | // Refer to ValidateRange for more information about the range to validate. 333 | // 334 | // ValidateLengthはProductService.Lengthが範囲内(1 <= value <= DEFAULT_ACTRESS_MAX_LENGTH)にあるか検証します。 335 | // 検証範囲について更に詳しく知りたい方はValidateRangeを参照してください。 336 | func (srv *ActressService) ValidateLength() bool { 337 | return ValidateRange(srv.Length, 1, DefaultActressMaxLength) 338 | } 339 | 340 | // ValidateOffset validates ActressService.Offset within the range (1 <= value). 341 | // 342 | // ValidateOffsetはActressService.Offsetが範囲内(1 <= value)にあるか検証します。 343 | func (srv *ActressService) ValidateOffset() bool { 344 | return srv.Offset >= 1 345 | } 346 | 347 | // BuildRequestURL creates url to request actress API. 348 | // 349 | // BuildRequestURLは女優検索APIにリクエストするためのURLを作成します。 350 | func (srv *ActressService) BuildRequestURL() (string, error) { 351 | if srv.ApiID == "" { 352 | return "", fmt.Errorf("set invalid ApiID parameter") 353 | } 354 | 355 | if !ValidateAffiliateID(srv.AffiliateID) { 356 | return "", fmt.Errorf("set invalid AffiliateID parameter") 357 | } 358 | 359 | queries := url.Values{} 360 | queries.Set("api_id", srv.ApiID) 361 | queries.Set("affiliate_id", srv.AffiliateID) 362 | 363 | if srv.Length != 0 { 364 | if !srv.ValidateLength() { 365 | return "", fmt.Errorf("length out of range: %d", srv.Length) 366 | } 367 | queries.Set("hits", strconv.FormatInt(srv.Length, 10)) 368 | } 369 | 370 | if srv.Offset != 0 { 371 | if !srv.ValidateOffset() { 372 | return "", fmt.Errorf("offset out of range: %d", srv.Offset) 373 | } 374 | queries.Set("offset", strconv.FormatInt(srv.Offset, 10)) 375 | } 376 | 377 | if srv.Initial != "" { 378 | queries.Set("initial", srv.Initial) 379 | } 380 | if srv.Sort != "" { 381 | queries.Set("sort", srv.Sort) 382 | } 383 | if srv.Keyword != "" { 384 | queries.Set("keyword", srv.Keyword) 385 | } 386 | if srv.Birthday != "" { 387 | queries.Set("birthday", srv.Birthday) 388 | } 389 | if srv.GteBirthday != "" { 390 | queries.Set("gte_birthday", srv.GteBirthday) 391 | } 392 | if srv.LteBirthday != "" { 393 | queries.Set("lte_birthday", srv.LteBirthday) 394 | } 395 | if srv.Bust != "" { 396 | queries.Set("bust", srv.Bust) 397 | } 398 | if srv.GteBust != "" { 399 | queries.Set("gte_bust", srv.GteBust) 400 | } 401 | if srv.LteBust != "" { 402 | queries.Set("lte_bust", srv.LteBust) 403 | } 404 | if srv.Waist != "" { 405 | queries.Set("waist", srv.Waist) 406 | } 407 | if srv.GteWaist != "" { 408 | queries.Set("gte_waist", srv.GteWaist) 409 | } 410 | if srv.LteWaist != "" { 411 | queries.Set("lte_waist", srv.LteWaist) 412 | } 413 | if srv.Hip != "" { 414 | queries.Set("hip", srv.Hip) 415 | } 416 | if srv.GteHip != "" { 417 | queries.Set("gte_hip", srv.GteHip) 418 | } 419 | if srv.LteHip != "" { 420 | queries.Set("lte_hip", srv.LteHip) 421 | } 422 | if srv.Height != "" { 423 | queries.Set("height", srv.Height) 424 | } 425 | if srv.GteHeight != "" { 426 | queries.Set("gte_height", srv.GteHeight) 427 | } 428 | if srv.LteHeight != "" { 429 | queries.Set("lte_height", srv.LteHeight) 430 | } 431 | 432 | u, err := buildAPIEndpoint("ActressSearch") 433 | if err != nil { 434 | return "", err 435 | } 436 | u.RawQuery = queries.Encode() 437 | 438 | return u.String(), nil 439 | } 440 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/actress_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestNewActressService(t *testing.T) { 11 | affiliateID := DummyAffliateID 12 | apiID := DummyAPIID 13 | 14 | srv := NewActressService(affiliateID, apiID) 15 | if srv.AffiliateID != affiliateID { 16 | t.Fatalf("ActressService.AffiliateID is expected to equal the input value(affiliateID)") 17 | } 18 | 19 | if srv.ApiID != apiID { 20 | t.Fatalf("ActressService.ApiID is expected to equal the input value(apiID)") 21 | } 22 | } 23 | 24 | func TestSetLengthInActressService(t *testing.T) { 25 | srv := dummyActressService() 26 | var length int64 = 10 27 | srv.SetLength(length) 28 | 29 | if srv.Length != length { 30 | t.Fatalf("ActressService.Length is expected to equal the input value(length)") 31 | } 32 | } 33 | 34 | func TestSetHitsInActressService(t *testing.T) { 35 | srv := dummyActressService() 36 | var hits int64 = 10 37 | srv.SetHits(hits) 38 | 39 | if srv.Length != hits { 40 | t.Fatalf("ActressService.Length is expected to equal the input value(hits)") 41 | } 42 | } 43 | 44 | func TestSetOffsetInActressService(t *testing.T) { 45 | srv := dummyActressService() 46 | var offset int64 = 10 47 | srv.SetOffset(offset) 48 | 49 | if srv.Offset != offset { 50 | t.Fatalf("ActressService.Offset is expected to equal the input value(offset)") 51 | } 52 | } 53 | 54 | func TestSetKeywordInActressService(t *testing.T) { 55 | srv := dummyActressService() 56 | 57 | keyword1 := "abcdefghijkelmnopqrstuvwxyzABCDEFGHIJKELMNOPQRSTUVWXYZ0123456789" 58 | srv.SetKeyword(keyword1) 59 | if srv.Keyword != keyword1 { 60 | t.Fatalf("ActressService.Keyword is expected to equal the input value(keyword1)") 61 | } 62 | 63 | keyword2 := "" 64 | srv.SetKeyword(keyword2) 65 | if srv.Keyword != keyword2 { 66 | t.Fatalf("ActressService.Keyword is expected to equal the input value(keyword2)") 67 | } 68 | 69 | keyword3 := "つれづれなるまゝに、日暮らし、硯にむかひて、心にうつりゆくよしなし事を、そこはかとなく書きつくれば、あやしうこそものぐるほしけれ。" 70 | srv.SetKeyword(keyword3) 71 | if srv.Keyword != keyword3 { 72 | t.Fatalf("ActressService.Keyword is expected to equal the input value(keyword3)") 73 | } 74 | 75 | keyword4 := " a b c d 0 " 76 | keyword4Expected := "a b c d 0" 77 | srv.SetKeyword(keyword4) 78 | if srv.Keyword != keyword4Expected { 79 | t.Fatalf("ActressService.Keyword is expected to equal keyword4_expected") 80 | } 81 | 82 | keyword5 := " あ ア 化A " 83 | keyword5Expected := "あ ア 化A" 84 | srv.SetKeyword(keyword5) 85 | if srv.Keyword != keyword5Expected { 86 | t.Fatalf("ActressService.Keyword is expected to equal keyword5_expected") 87 | } 88 | } 89 | 90 | func TestSetBirthdayInActressService(t *testing.T) { 91 | srv := dummyActressService() 92 | 93 | date1 := "19840723" 94 | srv.SetBirthday(date1) 95 | if srv.Birthday != date1 { 96 | t.Fatalf("ActressService.Birthday is expected to equal the input value(date1)") 97 | } 98 | 99 | date2 := "" 100 | srv.SetBirthday(date2) 101 | if srv.Birthday != date2 { 102 | t.Fatalf("ActressService.Birthday is expected to equal the input value(date2)") 103 | } 104 | 105 | date3 := "19000101-20160101" 106 | srv.SetBirthday(date3) 107 | if srv.Birthday != date3 { 108 | t.Fatalf("ActressService.Birthday is expected to equal the input value(date3)") 109 | } 110 | } 111 | 112 | func TestSetBustInActressService(t *testing.T) { 113 | srv := dummyActressService() 114 | 115 | bust1 := "D" 116 | srv.SetBust(bust1) 117 | if srv.Bust != bust1 { 118 | t.Fatalf("ActressService.Bust is expected to equal the input value(bust1)") 119 | } 120 | 121 | bust2 := "" 122 | srv.SetBust(bust2) 123 | if srv.Bust != bust2 { 124 | t.Fatalf("ActressService.Bust is expected to equal the input value(bust2)") 125 | } 126 | } 127 | 128 | func TestSetWaistInActressService(t *testing.T) { 129 | srv := dummyActressService() 130 | 131 | waist1 := "60" 132 | srv.SetWaist(waist1) 133 | if srv.Waist != waist1 { 134 | t.Fatalf("ActressService.Waist is expected to equal the input value(waist1)") 135 | } 136 | 137 | waist2 := "" 138 | srv.SetWaist(waist2) 139 | if srv.Waist != waist2 { 140 | t.Fatalf("ActressService.Waist is expected to equal the input value(waist2)") 141 | } 142 | 143 | waist3 := "50-60" 144 | srv.SetWaist(waist3) 145 | if srv.Waist != waist3 { 146 | t.Fatalf("ActressService.Waist is expected to equal the input value(waist3)") 147 | } 148 | } 149 | 150 | func TestSetHipInActressService(t *testing.T) { 151 | srv := dummyActressService() 152 | 153 | hip1 := "88" 154 | srv.SetHip(hip1) 155 | if srv.Hip != hip1 { 156 | t.Fatalf("ActressService.Hip is expected to equal the input value(hip1)") 157 | } 158 | 159 | hip2 := "" 160 | srv.SetHip(hip2) 161 | if srv.Hip != hip2 { 162 | t.Fatalf("ActressService.Hip is expected to equal the input value(hip2)") 163 | } 164 | 165 | hip3 := "80-90" 166 | srv.SetHip(hip3) 167 | if srv.Hip != hip3 { 168 | t.Fatalf("ActressService.Hip is expected to equal the input value(hip3)") 169 | } 170 | } 171 | 172 | func TestSetHeightInActressService(t *testing.T) { 173 | srv := dummyActressService() 174 | 175 | height1 := "155" 176 | srv.SetHeight(height1) 177 | if srv.Height != height1 { 178 | t.Fatalf("ActressService.Height is expected to equal the input value(height1)") 179 | } 180 | 181 | height2 := "" 182 | srv.SetHeight(height2) 183 | if srv.Height != height2 { 184 | t.Fatalf("ActressService.Height is expected to equal the input value(height2)") 185 | } 186 | 187 | height3 := "140-160" 188 | srv.SetHeight(height3) 189 | if srv.Height != height3 { 190 | t.Fatalf("ActressService.Height is expected to equal the input value(height3)") 191 | } 192 | } 193 | 194 | func TestValidateLengthInActressService(t *testing.T) { 195 | srv := dummyActressService() 196 | 197 | var target int64 198 | 199 | target = 1 200 | srv.SetLength(target) 201 | if srv.ValidateLength() == false { 202 | t.Fatalf("ActressService.ValidateLength is expected TRUE.") 203 | } 204 | 205 | target = DefaultActressAPILength 206 | srv.SetLength(target) 207 | if srv.ValidateLength() == false { 208 | t.Fatalf("ActressService.ValidateLength is expected TRUE.") 209 | } 210 | 211 | target = DefaultActressMaxLength 212 | srv.SetLength(target) 213 | if srv.ValidateLength() == false { 214 | t.Fatalf("ActressService.ValidateLength is expected TRUE.") 215 | } 216 | 217 | target = DefaultActressMaxLength + 1 218 | srv.SetLength(target) 219 | if srv.ValidateLength() == true { 220 | t.Fatalf("ActressService.ValidateLength is expected FALSE.") 221 | } 222 | 223 | target = 0 224 | srv.SetLength(target) 225 | if srv.ValidateLength() == true { 226 | t.Fatalf("ActressService.ValidateLength is expected FALSE.") 227 | } 228 | 229 | target = -1 230 | srv.SetLength(target) 231 | if srv.ValidateLength() == true { 232 | t.Fatalf("ActressService.ValidateLength is expected FALSE.") 233 | } 234 | } 235 | 236 | func TestValidateOffsetInActressService(t *testing.T) { 237 | srv := dummyActressService() 238 | 239 | var target int64 240 | 241 | target = 1 242 | srv.SetOffset(target) 243 | if srv.ValidateOffset() == false { 244 | t.Fatalf("ActressService.ValidateOffset is expected TRUE.") 245 | } 246 | 247 | target = 100 248 | srv.SetOffset(target) 249 | if srv.ValidateOffset() == false { 250 | t.Fatalf("ActressService.ValidateOffset is expected TRUE.") 251 | } 252 | 253 | target = 0 254 | srv.SetOffset(target) 255 | if srv.ValidateOffset() == true { 256 | t.Fatalf("ActressService.ValidateOffset is expected FALSE.") 257 | } 258 | 259 | target = -1 260 | srv.SetOffset(target) 261 | if srv.ValidateOffset() == true { 262 | t.Fatalf("ActressService.ValidateOffset is expected FALSE.") 263 | } 264 | } 265 | 266 | func TestBuildRequestURLInActressService(t *testing.T) { 267 | var srv *ActressService 268 | var u string 269 | var err error 270 | var expected string 271 | 272 | srv = dummyActressService() 273 | u, err = srv.BuildRequestURL() 274 | expected = APIBaseURL + "/ActressSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&hits=" + strconv.FormatInt(DefaultActressAPILength, 10) + "&offset=" + strconv.FormatInt(DefaultAPIOffset, 10) 275 | if u != expected { 276 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 277 | } 278 | if err != nil { 279 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 280 | } 281 | 282 | srv = dummyActressService() 283 | srv.SetLength(0) 284 | srv.SetOffset(0) 285 | u, err = srv.BuildRequestURL() 286 | expected = APIBaseURL + "/ActressSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID 287 | expectedBase := expected 288 | if u != expected { 289 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 290 | } 291 | if err != nil { 292 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 293 | } 294 | 295 | srv.SetLength(-1) 296 | u, err = srv.BuildRequestURL() 297 | if u != "" { 298 | t.Fatalf("ActressService.BuildRequestURL is expected empty if error occurs.") 299 | } 300 | if err == nil { 301 | t.Fatalf("ActressService.BuildRequestURL is expected to return error.") 302 | } 303 | srv.SetLength(0) 304 | 305 | srv.SetOffset(-1) 306 | u, err = srv.BuildRequestURL() 307 | if u != "" { 308 | t.Fatalf("ActressService.BuildRequestURL is expected empty if error occurs.") 309 | } 310 | if err == nil { 311 | t.Fatalf("ActressService.BuildRequestURL is expected to return error.") 312 | } 313 | srv.SetOffset(0) 314 | 315 | srv.SetInitial("あ") 316 | expected = expectedBase + "&initial=" + url.QueryEscape("あ") 317 | u, err = srv.BuildRequestURL() 318 | if u != expected { 319 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 320 | } 321 | if err != nil { 322 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 323 | } 324 | srv.SetInitial("") 325 | 326 | srv.SetSort("name") 327 | expected = expectedBase + "&sort=name" 328 | u, err = srv.BuildRequestURL() 329 | if u != expected { 330 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 331 | } 332 | if err != nil { 333 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 334 | } 335 | srv.SetSort("") 336 | 337 | srv.SetKeyword("天使もえ") 338 | expected = expectedBase + "&keyword=" + url.QueryEscape("天使もえ") 339 | u, err = srv.BuildRequestURL() 340 | if u != expected { 341 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 342 | } 343 | if err != nil { 344 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 345 | } 346 | srv.SetKeyword("") 347 | 348 | srv.SetBirthday("19940710") 349 | expected = expectedBase + "&birthday=19940710" 350 | u, err = srv.BuildRequestURL() 351 | if u != expected { 352 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 353 | } 354 | if err != nil { 355 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 356 | } 357 | srv.SetBirthday("") 358 | 359 | srv.SetGteBirthday("19800201") 360 | expected = expectedBase + ">e_birthday=19800201" 361 | u, err = srv.BuildRequestURL() 362 | if u != expected { 363 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 364 | } 365 | if err != nil { 366 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 367 | } 368 | srv.SetGteBirthday("") 369 | 370 | srv.SetLteBirthday("19940710") 371 | expected = expectedBase + "<e_birthday=19940710" 372 | u, err = srv.BuildRequestURL() 373 | if u != expected { 374 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 375 | } 376 | if err != nil { 377 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 378 | } 379 | srv.SetLteBirthday("") 380 | 381 | srv.SetBust("84") 382 | expected = expectedBase + "&bust=84" 383 | u, err = srv.BuildRequestURL() 384 | if u != expected { 385 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 386 | } 387 | if err != nil { 388 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 389 | } 390 | srv.SetBust("") 391 | 392 | srv.SetGteBust("1") 393 | expected = expectedBase + ">e_bust=1" 394 | u, err = srv.BuildRequestURL() 395 | if u != expected { 396 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 397 | } 398 | if err != nil { 399 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 400 | } 401 | srv.SetGteBust("") 402 | 403 | srv.SetLteBust("100") 404 | expected = expectedBase + "<e_bust=100" 405 | u, err = srv.BuildRequestURL() 406 | if u != expected { 407 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 408 | } 409 | if err != nil { 410 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 411 | } 412 | srv.SetLteBust("") 413 | 414 | srv.SetWaist("57") 415 | expected = expectedBase + "&waist=57" 416 | u, err = srv.BuildRequestURL() 417 | if u != expected { 418 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 419 | } 420 | if err != nil { 421 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 422 | } 423 | srv.SetWaist("") 424 | 425 | srv.SetGteWaist("51") 426 | expected = expectedBase + ">e_waist=51" 427 | u, err = srv.BuildRequestURL() 428 | if u != expected { 429 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 430 | } 431 | if err != nil { 432 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 433 | } 434 | srv.SetGteWaist("") 435 | 436 | srv.SetLteWaist("60") 437 | expected = expectedBase + "<e_waist=60" 438 | u, err = srv.BuildRequestURL() 439 | if u != expected { 440 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 441 | } 442 | if err != nil { 443 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 444 | } 445 | srv.SetLteWaist("") 446 | 447 | srv.SetHip("82") 448 | expected = expectedBase + "&hip=82" 449 | u, err = srv.BuildRequestURL() 450 | if u != expected { 451 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 452 | } 453 | if err != nil { 454 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 455 | } 456 | srv.SetHip("") 457 | 458 | srv.SetGteHip("80") 459 | expected = expectedBase + ">e_hip=80" 460 | u, err = srv.BuildRequestURL() 461 | if u != expected { 462 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 463 | } 464 | if err != nil { 465 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 466 | } 467 | srv.SetGteHip("") 468 | 469 | srv.SetLteHip("90") 470 | expected = expectedBase + "<e_hip=90" 471 | u, err = srv.BuildRequestURL() 472 | if u != expected { 473 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 474 | } 475 | if err != nil { 476 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 477 | } 478 | srv.SetLteHip("") 479 | 480 | srv.SetHeight("155") 481 | expected = expectedBase + "&height=155" 482 | u, err = srv.BuildRequestURL() 483 | if u != expected { 484 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 485 | } 486 | if err != nil { 487 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 488 | } 489 | srv.SetHeight("") 490 | 491 | srv.SetGteHeight("150") 492 | expected = expectedBase + ">e_height=150" 493 | u, err = srv.BuildRequestURL() 494 | if u != expected { 495 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 496 | } 497 | if err != nil { 498 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 499 | } 500 | srv.SetGteHeight("") 501 | 502 | srv.SetLteHeight("160") 503 | expected = expectedBase + "<e_height=160" 504 | u, err = srv.BuildRequestURL() 505 | if u != expected { 506 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 507 | } 508 | if err != nil { 509 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 510 | } 511 | srv.SetLteHeight("") 512 | } 513 | 514 | func TestBuildRequestURLWithoutApiIDInActressService(t *testing.T) { 515 | srv := dummyActressService() 516 | srv.ApiID = "" 517 | u, err := srv.BuildRequestURL() 518 | if u != "" { 519 | t.Fatalf("ActressService.BuildRequestURL is expected empty if API ID is not set.") 520 | } 521 | if err == nil { 522 | t.Fatalf("ActressService.BuildRequestURL is expected to return error.") 523 | } 524 | } 525 | 526 | func TestBuildRequestURLWithWrongAffiliateIDInActressService(t *testing.T) { 527 | srv := dummyActressService() 528 | srv.AffiliateID = "fizzbizz-100" 529 | u, err := srv.BuildRequestURL() 530 | if u != "" { 531 | t.Fatalf("ActressService.BuildRequestURL is expected empty if wrong Affiliate ID is set.") 532 | } 533 | if err == nil { 534 | t.Fatalf("ActressService.BuildRequestURL is expected to return error.") 535 | } 536 | } 537 | 538 | func TestExcuteWeakRequestActressAPIToServer(t *testing.T) { 539 | if !RequestAvailable { 540 | t.Skip("Not set valid credentials") 541 | } 542 | 543 | srv := NewActressService(TestAffiliateID, TestAPIID) 544 | srv.SetInitial("あ") 545 | srv.SetKeyword("あさみ") 546 | srv.SetBust("90") 547 | srv.SetGteBust("90") 548 | srv.SetLteBust("99") 549 | srv.SetWaist("-60") 550 | srv.SetGteWaist("50") 551 | srv.SetLteWaist("60") 552 | srv.SetHip("85-90") 553 | srv.SetGteHip("85") 554 | srv.SetLteHip("90") 555 | srv.SetHeight("160") 556 | srv.SetGteHeight("160") 557 | srv.SetLteHeight("169") 558 | srv.SetBirthday("19900101") 559 | srv.SetGteBirthday("1990-01-01") 560 | srv.SetLteBirthday("2010-12-31") 561 | srv.SetSort("-name") 562 | srv.SetLength(20) 563 | srv.SetOffset(1) 564 | 565 | rst, err := srv.Execute() 566 | if err != nil { 567 | t.Skip("Maybe, The network is down.") 568 | } 569 | 570 | if reflect.TypeOf(rst).String() != "*api.ActressResponse" { 571 | t.Fatalf("ActressService.Execute is expected to return *api.ActressResponse") 572 | } 573 | } 574 | 575 | func dummyActressService() *ActressService { 576 | return NewActressService(DummyAffliateID, DummyAPIID) 577 | } 578 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "crypto/tls" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "net/http" 9 | "net/url" 10 | "path" 11 | "regexp" 12 | "strings" 13 | ) 14 | 15 | const ( 16 | APIBaseURL = "https://api.dmm.com/affiliate/v3" 17 | APIVersion = "3" 18 | SiteGeneral = "DMM.com" 19 | SiteAdult = "DMM.R18" 20 | DefaultAPIOffset = 1 21 | DefaultAPILength = 100 22 | DefaultMaxLength = 500 23 | ) 24 | 25 | // RequestJSON requests a retirived url and returns the response is parsed JSON-encoded data 26 | // 27 | // RequestJSONは指定されたURLにリクエストしJSONで返ってきたレスポンスをパースしたデータを返します。 28 | func RequestJSON(url string) (interface{}, error) { 29 | // Ignore SSL Certificate Errors 30 | hc := &http.Client{ 31 | Transport: &http.Transport{ 32 | TLSClientConfig: &tls.Config{ 33 | InsecureSkipVerify: true, 34 | }, 35 | }, 36 | } 37 | 38 | resp, err := hc.Get(url) 39 | if err != nil { 40 | return nil, fmt.Errorf("Error at API request:%#v", err) 41 | } 42 | defer resp.Body.Close() 43 | body, err := ioutil.ReadAll(resp.Body) 44 | var result interface{} 45 | err = json.Unmarshal(body, &result) 46 | if err != nil { 47 | return nil, err 48 | } 49 | return result, nil 50 | } 51 | 52 | // TrimString wraps up strings.TrimString 53 | // 54 | // TrimStringはstrings.TrimStringのラップ関数です。 55 | func TrimString(str string) string { 56 | return strings.TrimSpace(str) 57 | } 58 | 59 | // ValidateAffiliateID validates affiliate ID. 60 | // (affiliate number range: 990 ~ 999) 61 | // e.g. dummy-999 62 | // 63 | // ValidateAffiliateIDはアフィリエイトID(例: dummy-999)のバリデーションを行います。 64 | //(アフィリエイトの数値の範囲は 990〜999です) 65 | func ValidateAffiliateID(affiliateID string) bool { 66 | if affiliateID == "" { 67 | return false 68 | } 69 | return regexp.MustCompile(`^.+-99[0-9]$`).Match([]byte(affiliateID)) 70 | } 71 | 72 | // ValidateSite validates site parameter. 73 | // 74 | // ValidateSiteはsiteパラメータのバリデーションを行います 75 | func ValidateSite(site string) bool { 76 | if site == "" { 77 | return false 78 | } 79 | if site != SiteGeneral && site != SiteAdult { 80 | return false 81 | } 82 | return true 83 | } 84 | 85 | // ValidateRange validates a retrieved number within the range ( number >= min && number <= max). 86 | // 87 | // ValidateRangeは指定された数値が最小値と最大値の範囲内にあるかどうか判定します。 88 | func ValidateRange(target, min, max int64) bool { 89 | return target >= min && target <= max 90 | } 91 | 92 | // GetAPIVersion returns API version. 93 | // 94 | // GetAPIVersionはAPIのバージョンを返します。 95 | func GetAPIVersion() string { 96 | return APIVersion 97 | } 98 | 99 | // buildAPIEndpoint returns API Endpoint path. 100 | // 101 | // buildAPIEndpointはAPIエンドポイントのフルパスを組み立てて返します。 102 | func buildAPIEndpoint(p string) (*url.URL, error) { 103 | u, err := url.Parse(APIBaseURL) 104 | if err != nil { 105 | return nil, fmt.Errorf("Parse error: %#v", err) 106 | } 107 | u.Path = path.Join(u.Path, p) 108 | return u, err 109 | } 110 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/api_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "os" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | const ( 10 | DummyAffliateID = "foobar-999" 11 | DummyAPIID = "TXpEZ5D4T2xB3J5cuSLf" 12 | ) 13 | 14 | var ( 15 | TestAffiliateID string 16 | TestAPIID string 17 | RequestAvailable bool 18 | ) 19 | 20 | func init() { 21 | TestAffiliateID = os.Getenv("DMM_TEST_AFFILIATE_ID") 22 | TestAPIID = os.Getenv("DMM_TEST_API_ID") 23 | 24 | RequestAvailable = (ValidateAffiliateID(TestAffiliateID) && TestAPIID != "") 25 | } 26 | 27 | func TestRequestJSON(t *testing.T) { 28 | testURL := "https://httpbin.org/get?foo=bar" 29 | actual, err1 := RequestJSON(testURL) 30 | 31 | if actual == nil { 32 | t.Fatalf("response is not expected empty.") 33 | } 34 | 35 | if err1 != nil { 36 | t.Fatalf("error is expected empty.") 37 | } 38 | 39 | if reflect.TypeOf(actual).String() != "map[string]interface {}" { 40 | t.Fatalf("response is expected inteface{}. but actual %s", reflect.TypeOf(actual).String()) 41 | } 42 | 43 | errURL := "https://httpbin.org/status/500" 44 | _, err2 := RequestJSON(errURL) 45 | 46 | if err2 == nil { 47 | t.Fatalf("error is not expected empty.") 48 | } 49 | } 50 | 51 | func TestValidateAffiliateID(t *testing.T) { 52 | val := "vcder56yuhnmkiuy-990" 53 | if !ValidateAffiliateID(val) { 54 | t.Fatalf("When value is %s, not expected false.", val) 55 | } 56 | 57 | val = "vcder56yuhnmkiuy-9" 58 | if ValidateAffiliateID(val) { 59 | t.Fatalf("When value is %s, not expected true.", val) 60 | } 61 | 62 | val = "vcder56yuhnmkiuy-" 63 | if ValidateAffiliateID(val) { 64 | t.Fatalf("When value is %s, not expected true.", val) 65 | } 66 | 67 | val = "-999" 68 | if ValidateAffiliateID(val) { 69 | t.Fatalf("When value is %s, not expected true.", val) 70 | } 71 | 72 | if ValidateAffiliateID("") { 73 | t.Fatalf("When value is empty, not expected true.") 74 | } 75 | } 76 | 77 | func TestValidateSite(t *testing.T) { 78 | if !ValidateSite(SiteGeneral) { 79 | t.Fatalf("When value is %s, not expected false.", SiteGeneral) 80 | } 81 | 82 | if !ValidateSite(SiteAdult) { 83 | t.Fatalf("When value is %s, not expected false.", SiteAdult) 84 | } 85 | 86 | if !ValidateSite("DMM.com") { 87 | t.Fatalf("When value is %s, not expected false.", "DMM.com") 88 | } 89 | 90 | if !ValidateSite("DMM.R18") { 91 | t.Fatalf("When value is %s, not expected false.", "DMM.R18") 92 | } 93 | 94 | if ValidateSite("DMM.co.jp") { 95 | t.Fatalf("When value is %s, not expected true.", "DMM.co.jp") 96 | } 97 | 98 | if ValidateSite("") { 99 | t.Fatalf("When value is empty, not expected true.") 100 | } 101 | } 102 | 103 | func TestTrimString(t *testing.T) { 104 | expected1 := "abcdefghijkelmnopqrstuvwxyzABCDEFGHIJKELMNOPQRSTUVWXYZ0123456789" 105 | actual1 := TrimString(expected1) 106 | if expected1 != actual1 { 107 | t.Fatalf("trimed string is expected to equal expected1") 108 | } 109 | 110 | expected2 := "" 111 | actual2 := TrimString(expected2) 112 | if expected2 != actual2 { 113 | t.Fatalf("trimed string is expected to equal expected2") 114 | } 115 | 116 | expected3 := "つれづれなるまゝに、日暮らし、硯にむかひて、心にうつりゆくよしなし事を、そこはかとなく書きつくれば、あやしうこそものぐるほしけれ。" 117 | actual3 := TrimString(expected3) 118 | if expected3 != actual3 { 119 | t.Fatalf("trimed string is expected to equal expected3") 120 | } 121 | 122 | target4 := " a b c d 0 " 123 | expected4 := "a b c d 0" 124 | actual4 := TrimString(target4) 125 | if expected4 != actual4 { 126 | t.Fatalf("trimed string is expected to equal expected4") 127 | } 128 | 129 | target5 := " あ ア 化A " 130 | expected5 := "あ ア 化A" 131 | actual5 := TrimString(target5) 132 | if expected5 != actual5 { 133 | t.Fatalf("trimed string is expected to equal expected5") 134 | } 135 | } 136 | 137 | func TestValidateRange(t *testing.T) { 138 | var target int64 139 | var min int64 140 | var max int64 141 | 142 | target = 10 143 | min = 1 144 | max = 100 145 | if !ValidateRange(target, min, max) { 146 | t.Fatalf("When target value is %d, min is %d and max is %d, not expected false", target, min, max) 147 | } 148 | 149 | target = 1 150 | if !ValidateRange(target, min, max) { 151 | t.Fatalf("When target value is %d, min is %d and max is %d, not expected false", target, min, max) 152 | } 153 | 154 | target = 100 155 | if !ValidateRange(target, min, max) { 156 | t.Fatalf("When target value is %d, min is %d and max is %d, not expected false", target, min, max) 157 | } 158 | 159 | target = 0 160 | if ValidateRange(target, min, max) { 161 | t.Fatalf("When target value is %d, min is %d and max is %d, not expected true", target, min, max) 162 | } 163 | 164 | target = 101 165 | if ValidateRange(target, min, max) { 166 | t.Fatalf("When target value is %d, min is %d and max is %d, not expected true", target, min, max) 167 | } 168 | 169 | target = 10 170 | min = 10 171 | max = 10 172 | if !ValidateRange(target, min, max) { 173 | t.Fatalf("When target value is %d, min is %d and max is %d, not expected false", target, min, max) 174 | } 175 | } 176 | 177 | func TestGetAPIVersion(t *testing.T) { 178 | if GetAPIVersion() != APIVersion { 179 | t.Errorf("This value is expected to equal APIVersion. actual:%s", GetAPIVersion()) 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/author.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/mitchellh/mapstructure" 9 | ) 10 | 11 | type AuthorService struct { 12 | ApiID string `mapstructure:"api_id"` 13 | AffiliateID string `mapstructure:"affiliate_id"` 14 | FloorID string `mapstructure:"floor_id"` 15 | Initial string `mapstructure:"initial"` 16 | Length int64 `mapstructure:"hits"` 17 | Offset int64 `mapstructure:"offset"` 18 | } 19 | 20 | type AuthorRawResponse struct { 21 | Request AuthorService `mapstructure:"request"` 22 | Result AuthorResponse `mapstructure:"result"` 23 | } 24 | 25 | type AuthorResponse struct { 26 | ResultCount int64 `mapstructure:"result_count"` 27 | TotalCount int64 `mapstructure:"total_count"` 28 | FirstPosition int64 `mapstructure:"first_position"` 29 | SiteName string `mapstructure:"site_name"` 30 | SiteCode string `mapstructure:"site_code"` 31 | ServiceName string `mapstructure:"service_name"` 32 | ServiceCode string `mapstructure:"service_code"` 33 | FloorID string `mapstructure:"floor_id"` 34 | FloorName string `mapstructure:"floor_name"` 35 | FloorCode string `mapstructure:"floor_code"` 36 | AuthorList []Author `mapstructure:"author"` 37 | } 38 | 39 | type Author struct { 40 | AuthorID string `mapstructure:"author_id"` 41 | Name string `mapstructure:"name"` 42 | Ruby string `mapstructure:"ruby"` 43 | ListURL string `mapstructure:"list_url"` 44 | } 45 | 46 | // NewAuthorService returns a new service for the given affiliate ID and API ID. 47 | // 48 | // NewAuthorServiceは渡したアフィリエイトIDとAPI IDを使用して新しい serviceを返します。 49 | func NewAuthorService(affiliateID, apiID string) *AuthorService { 50 | return &AuthorService{ 51 | ApiID: apiID, 52 | AffiliateID: affiliateID, 53 | FloorID: "", 54 | Initial: "", 55 | Length: DefaultAPILength, 56 | Offset: DefaultAPIOffset, 57 | } 58 | } 59 | 60 | // Execute requests a url is created by BuildRequestURL. 61 | // Use ExecuteWeak If you want get this response in interface{}. 62 | // 63 | // BuildRequestURLで生成したURLにリクエストします。 64 | // もし interface{} でこのレスポンスを取得したい場合は ExecuteWeak を使用してください。 65 | func (srv *AuthorService) Execute() (*AuthorResponse, error) { 66 | result, err := srv.ExecuteWeak() 67 | if err != nil { 68 | return nil, err 69 | } 70 | var raw AuthorRawResponse 71 | if err = mapstructure.WeakDecode(result, &raw); err != nil { 72 | return nil, err 73 | } 74 | return &raw.Result, nil 75 | } 76 | 77 | // ExecuteWeak requests a url is created by BuildRequestURL. 78 | // 79 | // BuildRequestURLで生成したURLにリクエストします。 80 | func (srv *AuthorService) ExecuteWeak() (interface{}, error) { 81 | reqURL, err := srv.BuildRequestURL() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return RequestJSON(reqURL) 87 | } 88 | 89 | // SetLength set the specified argument to AuthorService.Length 90 | // 91 | // SetLengthはLengthパラメータを設定します。 92 | func (srv *AuthorService) SetLength(length int64) *AuthorService { 93 | srv.Length = length 94 | return srv 95 | } 96 | 97 | // SetHits sets the specified argument to AuthorService.Length 98 | // SetHits is the alias for SetLength 99 | // 100 | // SetHitsはLengthパラメータを設定します。 101 | func (srv *AuthorService) SetHits(length int64) *AuthorService { 102 | srv.SetLength(length) 103 | return srv 104 | } 105 | 106 | // SetOffset sets the specified argument to AuthorService.Offset 107 | // 108 | // SetOffsetはOffsetパラメータを設定します。 109 | func (srv *AuthorService) SetOffset(offset int64) *AuthorService { 110 | srv.Offset = offset 111 | return srv 112 | } 113 | 114 | // SetInitial sets the specified argument to AuthorService.Initial. 115 | // This argment is author name's initial and you can use only hiragana. 116 | // e.g. srv.SetInitial("な") -> Soseki Natsume(なつめ そうせき, 夏目漱石) 117 | // 118 | // SetInitialはInitalパラメータに検索したい作者の頭文字をひらがなで設定します。 119 | func (srv *AuthorService) SetInitial(initial string) *AuthorService { 120 | srv.Initial = TrimString(initial) 121 | return srv 122 | } 123 | 124 | // SetFloorID sets the specified argument to AuthorService.FloorID. 125 | // You can retrieve Floor IDs from floor API. 126 | // 127 | // SetFloorIDはFloorIDパラメータを設定します。 128 | // フロアIDはフロアAPIから取得できます。 129 | func (srv *AuthorService) SetFloorID(floorID string) *AuthorService { 130 | srv.FloorID = TrimString(floorID) 131 | return srv 132 | } 133 | 134 | // ValidateLength validates AuthorService.Length within the range (1 <= value <= DefaultMaxLength). 135 | // Refer to ValidateRange for more information about the range to validate. 136 | // 137 | // ValidateLengthはAuthorService.Lengthが範囲内(1 <= value <= DefaultMaxLength)にあるか検証します。 138 | // 検証範囲について更に詳しく知りたい方はValidateRangeを参照してください。 139 | func (srv *AuthorService) ValidateLength() bool { 140 | return ValidateRange(srv.Length, 1, DefaultMaxLength) 141 | } 142 | 143 | // ValidateOffset validates AuthorService.Offset within the range (1 <= value). 144 | // 145 | // ValidateOffsetはAuthorService.Offsetが範囲内(1 <= value)にあるか検証します。 146 | func (srv *AuthorService) ValidateOffset() bool { 147 | return srv.Offset >= 1 148 | } 149 | 150 | // BuildRequestURL creates url to request author API. 151 | // 152 | // BuildRequestURLは作者検索APIにリクエストするためのURLを作成します。 153 | func (srv *AuthorService) BuildRequestURL() (string, error) { 154 | if srv.ApiID == "" { 155 | return "", fmt.Errorf("set invalid ApiID parameter") 156 | } 157 | if !ValidateAffiliateID(srv.AffiliateID) { 158 | return "", fmt.Errorf("set invalid AffiliateID parameter") 159 | } 160 | if srv.FloorID == "" { 161 | return "", fmt.Errorf("set invalid FloorID parameter") 162 | } 163 | 164 | queries := url.Values{} 165 | queries.Set("api_id", srv.ApiID) 166 | queries.Set("affiliate_id", srv.AffiliateID) 167 | queries.Set("floor_id", srv.FloorID) 168 | 169 | if srv.Length != 0 { 170 | if !srv.ValidateLength() { 171 | return "", fmt.Errorf("length out of range: %d", srv.Length) 172 | } 173 | queries.Set("hits", strconv.FormatInt(srv.Length, 10)) 174 | } 175 | 176 | if srv.Offset != 0 { 177 | if !srv.ValidateOffset() { 178 | return "", fmt.Errorf("offset out of range: %d", srv.Offset) 179 | } 180 | queries.Set("offset", strconv.FormatInt(srv.Offset, 10)) 181 | } 182 | 183 | if srv.Initial != "" { 184 | queries.Set("initial", srv.Initial) 185 | } 186 | 187 | u, err := buildAPIEndpoint("AuthorSearch") 188 | if err != nil { 189 | return "", err 190 | } 191 | u.RawQuery = queries.Encode() 192 | 193 | return u.String(), nil 194 | } 195 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/author_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestNewAuthorService(t *testing.T) { 11 | affiliateID := DummyAffliateID 12 | apiID := DummyAPIID 13 | 14 | srv := NewAuthorService(affiliateID, apiID) 15 | if srv.AffiliateID != affiliateID { 16 | t.Fatalf("AuthorService.AffiliateID is expected to equal the input value(affiliateID)") 17 | } 18 | 19 | if srv.ApiID != apiID { 20 | t.Fatalf("AuthorService.ApiID is expected to equal the input value(apiID)") 21 | } 22 | } 23 | 24 | func TestSetLengthInAuthorService(t *testing.T) { 25 | srv := dummyAuthorService() 26 | var length int64 = 10 27 | srv.SetLength(length) 28 | 29 | if srv.Length != length { 30 | t.Fatalf("AuthorService.Length is expected to equal the input value(length)") 31 | } 32 | } 33 | 34 | func TestSetHitsInAuthorService(t *testing.T) { 35 | srv := dummyAuthorService() 36 | var hits int64 = 10 37 | srv.SetHits(hits) 38 | 39 | if srv.Length != hits { 40 | t.Fatalf("AuthorService.Length is expected to equal the input value(hits)") 41 | } 42 | } 43 | 44 | func TestSetOffsetInAuthorService(t *testing.T) { 45 | srv := dummyAuthorService() 46 | var offset int64 = 10 47 | srv.SetOffset(offset) 48 | 49 | if srv.Offset != offset { 50 | t.Fatalf("AuthorService.Offset is expected to equal the input value(offset)") 51 | } 52 | } 53 | 54 | func TestValidateLengthInAuthorService(t *testing.T) { 55 | srv := dummyAuthorService() 56 | 57 | var target int64 58 | 59 | target = 1 60 | srv.SetLength(target) 61 | if srv.ValidateLength() == false { 62 | t.Fatalf("AuthorService.ValidateLength is expected TRUE.") 63 | } 64 | 65 | target = DefaultAPILength 66 | srv.SetLength(target) 67 | if srv.ValidateLength() == false { 68 | t.Fatalf("AuthorService.ValidateLength is expected TRUE.") 69 | } 70 | 71 | target = DefaultMaxLength 72 | srv.SetLength(target) 73 | if srv.ValidateLength() == false { 74 | t.Fatalf("AuthorService.ValidateLength is expected TRUE.") 75 | } 76 | 77 | target = DefaultMaxLength + 1 78 | srv.SetLength(target) 79 | if srv.ValidateLength() == true { 80 | t.Fatalf("AuthorService.ValidateLength is expected FALSE.") 81 | } 82 | 83 | target = 0 84 | srv.SetLength(target) 85 | if srv.ValidateLength() == true { 86 | t.Fatalf("AuthorService.ValidateLength is expected FALSE.") 87 | } 88 | 89 | target = -1 90 | srv.SetLength(target) 91 | if srv.ValidateLength() == true { 92 | t.Fatalf("AuthorService.ValidateLength is expected FALSE.") 93 | } 94 | } 95 | 96 | func TestValidateOffsetInAuthorService(t *testing.T) { 97 | srv := dummyAuthorService() 98 | 99 | var target int64 100 | 101 | target = 1 102 | srv.SetOffset(target) 103 | if srv.ValidateOffset() == false { 104 | t.Fatalf("AuthorService.ValidateOffset is expected TRUE.") 105 | } 106 | 107 | target = 100 108 | srv.SetOffset(target) 109 | if srv.ValidateOffset() == false { 110 | t.Fatalf("AuthorService.ValidateOffset is expected TRUE.") 111 | } 112 | 113 | target = 0 114 | srv.SetOffset(target) 115 | if srv.ValidateOffset() == true { 116 | t.Fatalf("AuthorService.ValidateOffset is expected FALSE.") 117 | } 118 | 119 | target = -1 120 | srv.SetOffset(target) 121 | if srv.ValidateOffset() == true { 122 | t.Fatalf("AuthorService.ValidateOffset is expected FALSE.") 123 | } 124 | } 125 | 126 | func TestBuildRequestURLInAuthorService(t *testing.T) { 127 | var srv *AuthorService 128 | var u string 129 | var err error 130 | var expected string 131 | 132 | srv = dummyAuthorService() 133 | srv.SetFloorID("40") 134 | u, err = srv.BuildRequestURL() 135 | expected = APIBaseURL + "/AuthorSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" + "&hits=" + strconv.FormatInt(DefaultAPILength, 10) + "&offset=" + strconv.FormatInt(DefaultAPIOffset, 10) 136 | if u != expected { 137 | t.Fatalf("AuthorService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 138 | } 139 | if err != nil { 140 | t.Fatalf("AuthorService.BuildRequestURL is not expected to have error") 141 | } 142 | 143 | srv = dummyAuthorService() 144 | srv.SetLength(0) 145 | srv.SetOffset(0) 146 | srv.SetFloorID("40") 147 | u, err = srv.BuildRequestURL() 148 | expected = APIBaseURL + "/AuthorSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" 149 | expectedBase := expected 150 | if u != expected { 151 | t.Fatalf("AuthorService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 152 | } 153 | if err != nil { 154 | t.Fatalf("AuthorService.BuildRequestURL is not expected to have error") 155 | } 156 | 157 | srv.SetLength(-1) 158 | u, err = srv.BuildRequestURL() 159 | if u != "" { 160 | t.Fatalf("AuthorService.BuildRequestURL is expected empty if error occurs.") 161 | } 162 | if err == nil { 163 | t.Fatalf("AuthorService.BuildRequestURL is expected to return error.") 164 | } 165 | srv.SetLength(0) 166 | 167 | srv.SetOffset(-1) 168 | u, err = srv.BuildRequestURL() 169 | if u != "" { 170 | t.Fatalf("AuthorService.BuildRequestURL is expected empty if error occurs.") 171 | } 172 | if err == nil { 173 | t.Fatalf("AuthorService.BuildRequestURL is expected to return error.") 174 | } 175 | srv.SetOffset(0) 176 | 177 | srv.SetFloorID("") 178 | u, err = srv.BuildRequestURL() 179 | if u != "" { 180 | t.Fatalf("AuthorService.BuildRequestURL is expected empty if error occurs.") 181 | } 182 | if err == nil { 183 | t.Fatalf("AuthorService.BuildRequestURL is expected to return error.") 184 | } 185 | srv.SetFloorID("40") 186 | 187 | srv.SetInitial("あ") 188 | expected = expectedBase + "&initial=" + url.QueryEscape("あ") 189 | u, err = srv.BuildRequestURL() 190 | if u != expected { 191 | t.Fatalf("AuthorService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 192 | } 193 | if err != nil { 194 | t.Fatalf("AuthorService.BuildRequestURL is not expected to have error") 195 | } 196 | srv.SetInitial("") 197 | } 198 | 199 | func TestBuildRequestURLWithoutApiIDInAuthorService(t *testing.T) { 200 | srv := dummyAuthorService() 201 | srv.ApiID = "" 202 | u, err := srv.BuildRequestURL() 203 | if u != "" { 204 | t.Fatalf("AuthorService.BuildRequestURL is expected empty if API ID is not set.") 205 | } 206 | if err == nil { 207 | t.Fatalf("AuthorService.BuildRequestURL is expected to return error.") 208 | } 209 | } 210 | 211 | func TestBuildRequestURLWithWrongAffiliateIDInAuthorService(t *testing.T) { 212 | srv := dummyAuthorService() 213 | srv.AffiliateID = "fizzbizz-100" 214 | u, err := srv.BuildRequestURL() 215 | if u != "" { 216 | t.Fatalf("AuthorService.BuildRequestURL is expected empty if wrong Affiliate ID is set.") 217 | } 218 | if err == nil { 219 | t.Fatalf("AuthorService.BuildRequestURL is expected to return error.") 220 | } 221 | } 222 | 223 | func TestExcuteRequestAuthorAPIToServer(t *testing.T) { 224 | if !RequestAvailable { 225 | t.Skip("Not set valid credentials") 226 | } 227 | 228 | srv := NewAuthorService(TestAffiliateID, TestAPIID) 229 | srv.SetFloorID("40") 230 | srv.SetInitial("あ") 231 | srv.SetLength(100) 232 | srv.SetOffset(1) 233 | 234 | rst, err := srv.Execute() 235 | if err != nil { 236 | t.Skip("Maybe, The network is down.") 237 | } 238 | 239 | if reflect.TypeOf(rst).String() != "*api.AuthorResponse" { 240 | t.Fatalf("AuthorService.Execute is expected to return *api.AuthorResponse") 241 | } 242 | } 243 | 244 | func dummyAuthorService() *AuthorService { 245 | return NewAuthorService(DummyAffliateID, DummyAPIID) 246 | } 247 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/floor.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | 7 | "github.com/mitchellh/mapstructure" 8 | ) 9 | 10 | type FloorService struct { 11 | ApiID string `mapstructure:"api_id"` 12 | AffiliateID string `mapstructure:"affiliate_id"` 13 | } 14 | 15 | type FloorRawResponse struct { 16 | Request FloorService `mapstructure:"request"` 17 | Result FloorResponse `mapstructure:"result"` 18 | } 19 | 20 | type FloorResponse struct { 21 | Site []Site 22 | } 23 | 24 | type Site struct { 25 | Name string `mapstructure:"name"` 26 | Code string `mapstructure:"code"` 27 | Services []DMMService `mapstructure:"service"` 28 | } 29 | 30 | type DMMService struct { 31 | Name string `mapstructure:"name"` 32 | Code string `mapstructure:"code"` 33 | Floors []DMMFloor `mapstructure:"floor"` 34 | } 35 | 36 | type DMMFloor struct { 37 | ID int64 `mapstructure:"id"` 38 | Name string `mapstructure:"name"` 39 | Code string `mapstructure:"code"` 40 | } 41 | 42 | // NewFloorService returns a new service for the given affiliate ID and API ID. 43 | // 44 | // NewFloorServiceは渡したアフィリエイトIDとAPI IDを使用して新しい serviceを返します。 45 | func NewFloorService(affiliateID, apiID string) *FloorService { 46 | return &FloorService{ 47 | ApiID: apiID, 48 | AffiliateID: affiliateID, 49 | } 50 | } 51 | 52 | // Execute requests a url is created by BuildRequestURL. 53 | // Use ExecuteWeak If you want get this response in interface{}. 54 | // 55 | // BuildRequestURLで生成したURLにリクエストします。 56 | // もし interface{} でこのレスポンスを取得したい場合は ExecuteWeak を使用してください。 57 | func (srv *FloorService) Execute() (*FloorResponse, error) { 58 | result, err := srv.ExecuteWeak() 59 | if err != nil { 60 | return nil, err 61 | } 62 | var raw FloorRawResponse 63 | if err = mapstructure.WeakDecode(result, &raw); err != nil { 64 | return nil, err 65 | } 66 | return &raw.Result, nil 67 | } 68 | 69 | // ExecuteWeak requests a url is created by BuildRequestURL. 70 | // 71 | // BuildRequestURLで生成したURLにリクエストします。 72 | func (srv *FloorService) ExecuteWeak() (interface{}, error) { 73 | reqURL, err := srv.BuildRequestURL() 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | return RequestJSON(reqURL) 79 | } 80 | 81 | // BuildRequestURL creates url to request floor API. 82 | // 83 | // BuildRequestURLはフロアAPIにリクエストするためのURLを作成します。 84 | func (srv *FloorService) BuildRequestURL() (string, error) { 85 | if srv.ApiID == "" { 86 | return "", fmt.Errorf("set invalid ApiID parameter") 87 | } 88 | if !ValidateAffiliateID(srv.AffiliateID) { 89 | return "", fmt.Errorf("set invalid AffiliateID parameter") 90 | } 91 | 92 | queries := url.Values{} 93 | queries.Set("api_id", srv.ApiID) 94 | queries.Set("affiliate_id", srv.AffiliateID) 95 | 96 | u, err := buildAPIEndpoint("FloorList") 97 | if err != nil { 98 | return "", err 99 | } 100 | u.RawQuery = queries.Encode() 101 | 102 | return u.String(), nil 103 | } 104 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/floor_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestNewFloorService(t *testing.T) { 9 | affiliateID := "foobar-999" 10 | apiID := "TXpEZ5D4T2xB3J5cuSLf" 11 | 12 | srv := NewFloorService(affiliateID, apiID) 13 | if srv.AffiliateID != affiliateID { 14 | t.Fatalf("FloorService.AffiliateID is expected to equal the input value(affiliateID)") 15 | } 16 | 17 | if srv.ApiID != apiID { 18 | t.Fatalf("FloorService.ApiID is expected to equal the input value(apiID)") 19 | } 20 | } 21 | 22 | func TestBuildRequestURLInFloorService(t *testing.T) { 23 | var srv *FloorService 24 | var u string 25 | var err error 26 | var expected string 27 | 28 | srv = dummyFloorService() 29 | u, err = srv.BuildRequestURL() 30 | expected = APIBaseURL + "/FloorList?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID 31 | if u != expected { 32 | t.Fatalf("FloorService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 33 | } 34 | if err != nil { 35 | t.Fatalf("FloorService.BuildRequestURL is not expected to have error") 36 | } 37 | } 38 | 39 | func TestBuildRequestURLWithoutApiIDInInFloorService(t *testing.T) { 40 | srv := dummyFloorService() 41 | srv.ApiID = "" 42 | u, err := srv.BuildRequestURL() 43 | if u != "" { 44 | t.Fatalf("FloorService.BuildRequestURL is expected empty if API ID is not set.") 45 | } 46 | if err == nil { 47 | t.Fatalf("FloorService.BuildRequestURL is expected to return error.") 48 | } 49 | } 50 | 51 | func TestBuildRequestURLWithWrongAffiliateIDInFloorService(t *testing.T) { 52 | srv := dummyFloorService() 53 | srv.AffiliateID = "fizzbizz-100" 54 | u, err := srv.BuildRequestURL() 55 | if u != "" { 56 | t.Fatalf("FloorService.BuildRequestURL is expected empty if wrong Affiliate ID is set.") 57 | } 58 | if err == nil { 59 | t.Fatalf("FloorService.BuildRequestURL is expected to return error.") 60 | } 61 | } 62 | 63 | func TestExcuteRequestFloorAPIToServer(t *testing.T) { 64 | if !RequestAvailable { 65 | t.Skip("Not set valid credentials") 66 | } 67 | 68 | srv := NewFloorService(TestAffiliateID, TestAPIID) 69 | rst, err := srv.Execute() 70 | if err != nil { 71 | t.Skip("Maybe, The network is down.") 72 | } 73 | 74 | if reflect.TypeOf(rst).String() != "*api.FloorResponse" { 75 | t.Fatalf("FloorService.Execute is expected to return *api.FloorResponse") 76 | } 77 | } 78 | 79 | func dummyFloorService() *FloorService { 80 | return NewFloorService(DummyAffliateID, DummyAPIID) 81 | } 82 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/genre.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/mitchellh/mapstructure" 9 | ) 10 | 11 | type GenreService struct { 12 | ApiID string `mapstructure:"api_id"` 13 | AffiliateID string `mapstructure:"affiliate_id"` 14 | FloorID string `mapstructure:"floor_id"` 15 | Initial string `mapstructure:"initial"` 16 | Length int64 `mapstructure:"hits"` 17 | Offset int64 `mapstructure:"offset"` 18 | } 19 | 20 | type GenreRawResponse struct { 21 | Request GenreService `mapstructure:"request"` 22 | Result GenreResponse `mapstructure:"result"` 23 | } 24 | 25 | type GenreResponse struct { 26 | ResultCount int64 `mapstructure:"result_count"` 27 | TotalCount int64 `mapstructure:"total_count"` 28 | FirstPosition int64 `mapstructure:"first_position"` 29 | SiteName string `mapstructure:"site_name"` 30 | SiteCode string `mapstructure:"site_code"` 31 | ServiceName string `mapstructure:"service_name"` 32 | ServiceCode string `mapstructure:"service_code"` 33 | FloorID string `mapstructure:"floor_id"` 34 | FloorName string `mapstructure:"floor_name"` 35 | FloorCode string `mapstructure:"floor_code"` 36 | GenreList []Genre `mapstructure:"genre"` 37 | } 38 | 39 | type Genre struct { 40 | GenreID string `mapstructure:"genre_id"` 41 | Name string `mapstructure:"name"` 42 | Ruby string `mapstructure:"ruby"` 43 | ListURL string `mapstructure:"list_url"` 44 | } 45 | 46 | // NewGenreService returns a new service for the given affiliate ID and API ID. 47 | // 48 | // NewGenreServiceは渡したアフィリエイトIDとAPI IDを使用して新しい serviceを返します。 49 | func NewGenreService(affiliateID, apiID string) *GenreService { 50 | return &GenreService{ 51 | ApiID: apiID, 52 | AffiliateID: affiliateID, 53 | FloorID: "", 54 | Initial: "", 55 | Length: DefaultAPILength, 56 | Offset: DefaultAPIOffset, 57 | } 58 | } 59 | 60 | // Execute requests a url is created by BuildRequestURL. 61 | // Use ExecuteWeak If you want get this response in interface{}. 62 | // 63 | // BuildRequestURLで生成したURLにリクエストします。 64 | // もし interface{} でこのレスポンスを取得したい場合は ExecuteWeak を使用してください。 65 | func (srv *GenreService) Execute() (*GenreResponse, error) { 66 | result, err := srv.ExecuteWeak() 67 | if err != nil { 68 | return nil, err 69 | } 70 | var raw GenreRawResponse 71 | if err = mapstructure.WeakDecode(result, &raw); err != nil { 72 | return nil, err 73 | } 74 | return &raw.Result, nil 75 | } 76 | 77 | // ExecuteWeak requests a url is created by BuildRequestURL. 78 | // 79 | // BuildRequestURLで生成したURLにリクエストします。 80 | func (srv *GenreService) ExecuteWeak() (interface{}, error) { 81 | reqURL, err := srv.BuildRequestURL() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return RequestJSON(reqURL) 87 | } 88 | 89 | // SetLength set the specified argument to GenreService.Length 90 | // 91 | // SetLengthはLengthパラメータを設定します。 92 | func (srv *GenreService) SetLength(length int64) *GenreService { 93 | srv.Length = length 94 | return srv 95 | } 96 | 97 | // SetHits set the specified argument to GenreService.Length 98 | // SetHits is the alias for SetLength 99 | // 100 | // SetHitsはLengthパラメータを設定します。 101 | func (srv *GenreService) SetHits(length int64) *GenreService { 102 | srv.SetLength(length) 103 | return srv 104 | } 105 | 106 | // SetOffset set the specified argument to GenreService.Offset 107 | // 108 | // SetOffsetはOffsetパラメータを設定します。 109 | func (srv *GenreService) SetOffset(offset int64) *GenreService { 110 | srv.Offset = offset 111 | return srv 112 | } 113 | 114 | // SetInitial sets the specified argument to GenreService.Initial. 115 | // This argment is author name's initial and you can use only hiragana. 116 | // e.g. srv.SetInitial("ろ") -> robot(ろぼっと, ロボット) 117 | // 118 | // SetInitialはInitalパラメータに検索したい作者の頭文字をひらがなで設定します。 119 | func (srv *GenreService) SetInitial(initial string) *GenreService { 120 | srv.Initial = TrimString(initial) 121 | return srv 122 | } 123 | 124 | // SetFloorID sets the specified argument to GenreService.FloorID. 125 | // You can retrieve Floor IDs from floor API. 126 | // 127 | // SetFloorIDはFloorIDパラメータを設定します。 128 | // フロアIDはフロアAPIから取得できます。 129 | func (srv *GenreService) SetFloorID(floorID string) *GenreService { 130 | srv.FloorID = TrimString(floorID) 131 | return srv 132 | } 133 | 134 | // ValidateLength validates GenreService.Length within the range (1 <= value <= DefaultMaxLength). 135 | // Refer to ValidateRange for more information about the range to validate. 136 | // 137 | // ValidateLengthはGenreService.Lengthが範囲内(1 <= value <= DefaultMaxLength)にあるか検証します。 138 | // 検証範囲について更に詳しく知りたい方はValidateRangeを参照してください。 139 | func (srv *GenreService) ValidateLength() bool { 140 | return ValidateRange(srv.Length, 1, DefaultMaxLength) 141 | } 142 | 143 | // ValidateOffset validates GenreService.Offset within the range (1 <= value). 144 | // 145 | // ValidateOffsetはGenreService.Offsetが範囲内(1 <= value)にあるか検証します。 146 | func (srv *GenreService) ValidateOffset() bool { 147 | return srv.Offset >= 1 148 | } 149 | 150 | // BuildRequestURL creates url to request genre API. 151 | // 152 | // BuildRequestURLはジャンル検索APIにリクエストするためのURLを作成します。 153 | func (srv *GenreService) BuildRequestURL() (string, error) { 154 | if srv.ApiID == "" { 155 | return "", fmt.Errorf("set invalid ApiID parameter") 156 | } 157 | if !ValidateAffiliateID(srv.AffiliateID) { 158 | return "", fmt.Errorf("set invalid AffiliateID parameter") 159 | } 160 | if srv.FloorID == "" { 161 | return "", fmt.Errorf("set invalid FloorID parameter") 162 | } 163 | 164 | queries := url.Values{} 165 | queries.Set("api_id", srv.ApiID) 166 | queries.Set("affiliate_id", srv.AffiliateID) 167 | queries.Set("floor_id", srv.FloorID) 168 | 169 | if srv.Length != 0 { 170 | if !srv.ValidateLength() { 171 | return "", fmt.Errorf("length out of range: %d", srv.Length) 172 | } 173 | queries.Set("hits", strconv.FormatInt(srv.Length, 10)) 174 | } 175 | 176 | if srv.Offset != 0 { 177 | if !srv.ValidateOffset() { 178 | return "", fmt.Errorf("offset out of range: %d", srv.Offset) 179 | } 180 | queries.Set("offset", strconv.FormatInt(srv.Offset, 10)) 181 | } 182 | 183 | if srv.Initial != "" { 184 | queries.Set("initial", srv.Initial) 185 | } 186 | 187 | u, err := buildAPIEndpoint("GenreSearch") 188 | if err != nil { 189 | return "", err 190 | } 191 | u.RawQuery = queries.Encode() 192 | 193 | return u.String(), nil 194 | } 195 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/genre_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestNewGenreService(t *testing.T) { 11 | affiliateID := DummyAffliateID 12 | apiID := DummyAPIID 13 | 14 | srv := NewGenreService(affiliateID, apiID) 15 | if srv.AffiliateID != affiliateID { 16 | t.Fatalf("GenreService.AffiliateID is expected to equal the input value(affiliateID)") 17 | } 18 | 19 | if srv.ApiID != apiID { 20 | t.Fatalf("GenreService.ApiID is expected to equal the input value(apiID)") 21 | } 22 | } 23 | 24 | func TestSetLengthInGenreService(t *testing.T) { 25 | srv := dummyGenreService() 26 | var length int64 = 10 27 | srv.SetLength(length) 28 | 29 | if srv.Length != length { 30 | t.Fatalf("GenreService.Length is expected to equal the input value(length)") 31 | } 32 | } 33 | 34 | func TestSetHitsInGenreService(t *testing.T) { 35 | srv := dummyGenreService() 36 | var hits int64 = 10 37 | srv.SetHits(hits) 38 | 39 | if srv.Length != hits { 40 | t.Fatalf("GenreService.Length is expected to equal the input value(hits)") 41 | } 42 | } 43 | 44 | func TestSetOffsetInGenreService(t *testing.T) { 45 | srv := dummyGenreService() 46 | var offset int64 = 10 47 | srv.SetOffset(offset) 48 | 49 | if srv.Offset != offset { 50 | t.Fatalf("GenreService.Offset is expected to equal the input value(offset)") 51 | } 52 | } 53 | 54 | func TestValidateLengthInGenreService(t *testing.T) { 55 | srv := dummyGenreService() 56 | 57 | var target int64 58 | 59 | target = 1 60 | srv.SetLength(target) 61 | if srv.ValidateLength() == false { 62 | t.Fatalf("GenreService.ValidateLength is expected TRUE.") 63 | } 64 | 65 | target = DefaultAPILength 66 | srv.SetLength(target) 67 | if srv.ValidateLength() == false { 68 | t.Fatalf("GenreService.ValidateLength is expected TRUE.") 69 | } 70 | 71 | target = DefaultMaxLength 72 | srv.SetLength(target) 73 | if srv.ValidateLength() == false { 74 | t.Fatalf("GenreService.ValidateLength is expected TRUE.") 75 | } 76 | 77 | target = DefaultMaxLength + 1 78 | srv.SetLength(target) 79 | if srv.ValidateLength() == true { 80 | t.Fatalf("GenreService.ValidateLength is expected FALSE.") 81 | } 82 | 83 | target = 0 84 | srv.SetLength(target) 85 | if srv.ValidateLength() == true { 86 | t.Fatalf("GenreService.ValidateLength is expected FALSE.") 87 | } 88 | 89 | target = -1 90 | srv.SetLength(target) 91 | if srv.ValidateLength() == true { 92 | t.Fatalf("GenreService.ValidateLength is expected FALSE.") 93 | } 94 | } 95 | 96 | func TestValidateOffsetInGenreService(t *testing.T) { 97 | srv := dummyGenreService() 98 | 99 | var target int64 100 | 101 | target = 1 102 | srv.SetOffset(target) 103 | if srv.ValidateOffset() == false { 104 | t.Fatalf("GenreService.ValidateOffset is expected TRUE.") 105 | } 106 | 107 | target = 100 108 | srv.SetOffset(target) 109 | if srv.ValidateOffset() == false { 110 | t.Fatalf("GenreService.ValidateOffset is expected TRUE.") 111 | } 112 | 113 | target = 0 114 | srv.SetOffset(target) 115 | if srv.ValidateOffset() == true { 116 | t.Fatalf("GenreService.ValidateOffset is expected FALSE.") 117 | } 118 | 119 | target = -1 120 | srv.SetOffset(target) 121 | if srv.ValidateOffset() == true { 122 | t.Fatalf("GenreService.ValidateOffset is expected FALSE.") 123 | } 124 | } 125 | 126 | func TestBuildRequestURLInGenreService(t *testing.T) { 127 | var srv *GenreService 128 | var u string 129 | var err error 130 | var expected string 131 | 132 | srv = dummyGenreService() 133 | srv.SetFloorID("40") 134 | u, err = srv.BuildRequestURL() 135 | expected = APIBaseURL + "/GenreSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" + "&hits=" + strconv.FormatInt(DefaultAPILength, 10) + "&offset=" + strconv.FormatInt(DefaultAPIOffset, 10) 136 | if u != expected { 137 | t.Fatalf("GenreService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 138 | } 139 | if err != nil { 140 | t.Fatalf("GenreService.BuildRequestURL is not expected to have error") 141 | } 142 | 143 | srv = dummyGenreService() 144 | srv.SetLength(0) 145 | srv.SetOffset(0) 146 | srv.SetFloorID("40") 147 | u, err = srv.BuildRequestURL() 148 | expected = APIBaseURL + "/GenreSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" 149 | expectedBase := expected 150 | if u != expected { 151 | t.Fatalf("GenreService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 152 | } 153 | if err != nil { 154 | t.Fatalf("GenreService.BuildRequestURL is not expected to have error") 155 | } 156 | 157 | srv.SetLength(-1) 158 | u, err = srv.BuildRequestURL() 159 | if u != "" { 160 | t.Fatalf("GenreService.BuildRequestURL is expected empty if error occurs.") 161 | } 162 | if err == nil { 163 | t.Fatalf("GenreService.BuildRequestURL is expected to return error.") 164 | } 165 | srv.SetLength(0) 166 | 167 | srv.SetOffset(-1) 168 | u, err = srv.BuildRequestURL() 169 | if u != "" { 170 | t.Fatalf("GenreService.BuildRequestURL is expected empty if error occurs.") 171 | } 172 | if err == nil { 173 | t.Fatalf("GenreService.BuildRequestURL is expected to return error.") 174 | } 175 | srv.SetOffset(0) 176 | 177 | srv.SetFloorID("") 178 | u, err = srv.BuildRequestURL() 179 | if u != "" { 180 | t.Fatalf("GenreService.BuildRequestURL is expected empty if error occurs.") 181 | } 182 | if err == nil { 183 | t.Fatalf("GenreService.BuildRequestURL is expected to return error.") 184 | } 185 | srv.SetFloorID("40") 186 | 187 | srv.SetInitial("あ") 188 | expected = expectedBase + "&initial=" + url.QueryEscape("あ") 189 | u, err = srv.BuildRequestURL() 190 | if u != expected { 191 | t.Fatalf("GenreService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 192 | } 193 | if err != nil { 194 | t.Fatalf("GenreService.BuildRequestURL is not expected to have error") 195 | } 196 | srv.SetInitial("") 197 | } 198 | 199 | func TestBuildRequestURLWithoutApiIDInGenreService(t *testing.T) { 200 | srv := dummyGenreService() 201 | srv.ApiID = "" 202 | u, err := srv.BuildRequestURL() 203 | if u != "" { 204 | t.Fatalf("GenreService.BuildRequestURL is expected empty if API ID is not set.") 205 | } 206 | if err == nil { 207 | t.Fatalf("GenreService.BuildRequestURL is expected to return error.") 208 | } 209 | } 210 | 211 | func TestBuildRequestURLWithWrongAffiliateIDInGenreService(t *testing.T) { 212 | srv := dummyGenreService() 213 | srv.AffiliateID = "fizzbizz-100" 214 | u, err := srv.BuildRequestURL() 215 | if u != "" { 216 | t.Fatalf("GenreService.BuildRequestURL is expected empty if wrong Affiliate ID is set.") 217 | } 218 | if err == nil { 219 | t.Fatalf("GenreService.BuildRequestURL is expected to return error.") 220 | } 221 | } 222 | 223 | func TestExcuteRequestAPIServer(t *testing.T) { 224 | if !RequestAvailable { 225 | t.Skip("Not set valid credentials") 226 | } 227 | } 228 | 229 | func TestExcuteRequestGenreAPIToServer(t *testing.T) { 230 | if !RequestAvailable { 231 | t.Skip("Not set valid credentials") 232 | } 233 | 234 | srv := NewGenreService(TestAffiliateID, TestAPIID) 235 | srv.SetFloorID("40") 236 | srv.SetInitial("あ") 237 | srv.SetLength(100) 238 | srv.SetOffset(1) 239 | 240 | rst, err := srv.Execute() 241 | if err != nil { 242 | t.Skip("Maybe, The network is down.") 243 | } 244 | 245 | if reflect.TypeOf(rst).String() != "*api.GenreResponse" { 246 | t.Fatalf("GenreService.Execute is expected to return *api.GenreResponse") 247 | } 248 | } 249 | 250 | func dummyGenreService() *GenreService { 251 | return NewGenreService(DummyAffliateID, DummyAPIID) 252 | } 253 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/maker.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/mitchellh/mapstructure" 9 | ) 10 | 11 | type MakerService struct { 12 | ApiID string `mapstructure:"api_id"` 13 | AffiliateID string `mapstructure:"affiliate_id"` 14 | FloorID string `mapstructure:"floor_id"` 15 | Initial string `mapstructure:"initial"` 16 | Length int64 `mapstructure:"hits"` 17 | Offset int64 `mapstructure:"offset"` 18 | } 19 | 20 | type MakerRawResponse struct { 21 | Request MakerService `mapstructure:"request"` 22 | Result MakerResponse `mapstructure:"result"` 23 | } 24 | 25 | type MakerResponse struct { 26 | ResultCount int64 `mapstructure:"result_count"` 27 | TotalCount int64 `mapstructure:"total_count"` 28 | FirstPosition int64 `mapstructure:"first_position"` 29 | SiteName string `mapstructure:"site_name"` 30 | SiteCode string `mapstructure:"site_code"` 31 | ServiceName string `mapstructure:"service_name"` 32 | ServiceCode string `mapstructure:"service_code"` 33 | FloorID string `mapstructure:"floor_id"` 34 | FloorName string `mapstructure:"floor_name"` 35 | FloorCode string `mapstructure:"floor_code"` 36 | MakerList []Maker `mapstructure:"maker"` 37 | } 38 | 39 | type Maker struct { 40 | MakerID string `mapstructure:"maker_id"` 41 | Name string `mapstructure:"name"` 42 | Ruby string `mapstructure:"ruby"` 43 | ListURL string `mapstructure:"list_url"` 44 | } 45 | 46 | // NewMakerService returns a new service for the given affiliate ID and API ID. 47 | // 48 | // NewMakerServiceは渡したアフィリエイトIDとAPI IDを使用して新しい serviceを返します。 49 | func NewMakerService(affiliateID, apiID string) *MakerService { 50 | return &MakerService{ 51 | ApiID: apiID, 52 | AffiliateID: affiliateID, 53 | FloorID: "", 54 | Initial: "", 55 | Length: DefaultAPILength, 56 | Offset: DefaultAPIOffset, 57 | } 58 | } 59 | 60 | // Execute requests a url is created by BuildRequestURL. 61 | // Use ExecuteWeak If you want get this response in interface{}. 62 | // 63 | // BuildRequestURLで生成したURLにリクエストします。 64 | // もし interface{} でこのレスポンスを取得したい場合は ExecuteWeak を使用してください。 65 | func (srv *MakerService) Execute() (*MakerResponse, error) { 66 | result, err := srv.ExecuteWeak() 67 | if err != nil { 68 | return nil, err 69 | } 70 | var raw MakerRawResponse 71 | if err = mapstructure.WeakDecode(result, &raw); err != nil { 72 | return nil, err 73 | } 74 | return &raw.Result, nil 75 | } 76 | 77 | // ExecuteWeak requests a url is created by BuildRequestURL. 78 | // 79 | // BuildRequestURLで生成したURLにリクエストします。 80 | func (srv *MakerService) ExecuteWeak() (interface{}, error) { 81 | reqURL, err := srv.BuildRequestURL() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return RequestJSON(reqURL) 87 | } 88 | 89 | // SetLength set the specified argument to MakerService.Length 90 | // 91 | // SetLengthはLengthパラメータを設定します。 92 | func (srv *MakerService) SetLength(length int64) *MakerService { 93 | srv.Length = length 94 | return srv 95 | } 96 | 97 | // SetHits set the specified argument to MakerService.Length 98 | // SetHits is the alias for SetLength 99 | // 100 | // SetHitsはLengthパラメータを設定します。 101 | func (srv *MakerService) SetHits(length int64) *MakerService { 102 | srv.SetLength(length) 103 | return srv 104 | } 105 | 106 | // SetOffset set the specified argument to MakerService.Offset 107 | // 108 | // SetOffsetはOffsetパラメータを設定します。 109 | func (srv *MakerService) SetOffset(offset int64) *MakerService { 110 | srv.Offset = offset 111 | return srv 112 | } 113 | 114 | // SetInitial sets the specified argument to MakerService.Initial. 115 | // This argment is author name's initial and you can use only hiragana. 116 | // e.g. srv.SetInitial("ゆ") -> Universal Pictures(ゆにばーさるぴくちゃーず, ユニバーサル・ピクチャーズ) 117 | // 118 | // SetInitialはInitalパラメータに検索したい作者の頭文字をひらがなで設定します。 119 | func (srv *MakerService) SetInitial(initial string) *MakerService { 120 | srv.Initial = TrimString(initial) 121 | return srv 122 | } 123 | 124 | // SetFloorID sets the specified argument to MakerService.FloorID. 125 | // You can retrieve Floor IDs from floor API. 126 | // 127 | // SetFloorIDはFloorIDパラメータを設定します。 128 | // フロアIDはフロアAPIから取得できます。 129 | func (srv *MakerService) SetFloorID(floorID string) *MakerService { 130 | srv.FloorID = TrimString(floorID) 131 | return srv 132 | } 133 | 134 | // ValidateLength validates MakerService.Length within the range (1 <= value <= DefaultMaxLength). 135 | // Refer to ValidateRange for more information about the range to validate. 136 | // 137 | // ValidateLengthはMakerService.Lengthが範囲内(1 <= value <= DefaultMaxLength)にあるか検証します。 138 | // 検証範囲について更に詳しく知りたい方はValidateRangeを参照してください。 139 | func (srv *MakerService) ValidateLength() bool { 140 | return ValidateRange(srv.Length, 1, DefaultMaxLength) 141 | } 142 | 143 | // ValidateOffset validates MakerService.Offset within the range (1 <= value). 144 | // 145 | // ValidateOffsetはMakerService.Offsetが範囲内(1 <= value)にあるか検証します。 146 | func (srv *MakerService) ValidateOffset() bool { 147 | return srv.Offset >= 1 148 | } 149 | 150 | // BuildRequestURL creates url to request maker API. 151 | // 152 | // BuildRequestURLはメーカー検索APIにリクエストするためのURLを作成します。 153 | func (srv *MakerService) BuildRequestURL() (string, error) { 154 | if srv.ApiID == "" { 155 | return "", fmt.Errorf("set invalid ApiID parameter") 156 | } 157 | if !ValidateAffiliateID(srv.AffiliateID) { 158 | return "", fmt.Errorf("set invalid AffiliateID parameter") 159 | } 160 | if srv.FloorID == "" { 161 | return "", fmt.Errorf("set invalid FloorID parameter") 162 | } 163 | 164 | queries := url.Values{} 165 | queries.Set("api_id", srv.ApiID) 166 | queries.Set("affiliate_id", srv.AffiliateID) 167 | queries.Set("floor_id", srv.FloorID) 168 | 169 | if srv.Length != 0 { 170 | if !srv.ValidateLength() { 171 | return "", fmt.Errorf("length out of range: %d", srv.Length) 172 | } 173 | queries.Set("hits", strconv.FormatInt(srv.Length, 10)) 174 | } 175 | 176 | if srv.Offset != 0 { 177 | if !srv.ValidateOffset() { 178 | return "", fmt.Errorf("offset out of range: %d", srv.Offset) 179 | } 180 | queries.Set("offset", strconv.FormatInt(srv.Offset, 10)) 181 | } 182 | 183 | if srv.Initial != "" { 184 | queries.Set("initial", srv.Initial) 185 | } 186 | 187 | u, err := buildAPIEndpoint("MakerSearch") 188 | if err != nil { 189 | return "", err 190 | } 191 | u.RawQuery = queries.Encode() 192 | 193 | return u.String(), nil 194 | } 195 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/maker_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestNewMakerService(t *testing.T) { 11 | affiliateID := DummyAffliateID 12 | apiID := DummyAPIID 13 | 14 | srv := NewMakerService(affiliateID, apiID) 15 | if srv.AffiliateID != affiliateID { 16 | t.Fatalf("MakerService.AffiliateID is expected to equal the input value(affiliateID)") 17 | } 18 | 19 | if srv.ApiID != apiID { 20 | t.Fatalf("MakerService.ApiID is expected to equal the input value(apiID)") 21 | } 22 | } 23 | 24 | func TestSetLengthInMakerService(t *testing.T) { 25 | srv := dummyMakerService() 26 | var length int64 = 10 27 | srv.SetLength(length) 28 | 29 | if srv.Length != length { 30 | t.Fatalf("MakerService.Length is expected to equal the input value(length)") 31 | } 32 | } 33 | 34 | func TestSetHitsInMakerService(t *testing.T) { 35 | srv := dummyMakerService() 36 | var hits int64 = 10 37 | srv.SetHits(hits) 38 | 39 | if srv.Length != hits { 40 | t.Fatalf("MakerService.Length is expected to equal the input value(hits)") 41 | } 42 | } 43 | 44 | func TestSetOffsetInMakerService(t *testing.T) { 45 | srv := dummyMakerService() 46 | var offset int64 = 10 47 | srv.SetOffset(offset) 48 | 49 | if srv.Offset != offset { 50 | t.Fatalf("MakerService.Offset is expected to equal the input value(offset)") 51 | } 52 | } 53 | 54 | func TestValidateLengthInMakerService(t *testing.T) { 55 | srv := dummyMakerService() 56 | 57 | var target int64 58 | 59 | target = 1 60 | srv.SetLength(target) 61 | if srv.ValidateLength() == false { 62 | t.Fatalf("MakerService.ValidateLength is expected TRUE.") 63 | } 64 | 65 | target = DefaultAPILength 66 | srv.SetLength(target) 67 | if srv.ValidateLength() == false { 68 | t.Fatalf("MakerService.ValidateLength is expected TRUE.") 69 | } 70 | 71 | target = DefaultMaxLength 72 | srv.SetLength(target) 73 | if srv.ValidateLength() == false { 74 | t.Fatalf("MakerService.ValidateLength is expected TRUE.") 75 | } 76 | 77 | target = DefaultMaxLength + 1 78 | srv.SetLength(target) 79 | if srv.ValidateLength() == true { 80 | t.Fatalf("MakerService.ValidateLength is expected FALSE.") 81 | } 82 | 83 | target = 0 84 | srv.SetLength(target) 85 | if srv.ValidateLength() == true { 86 | t.Fatalf("MakerService.ValidateLength is expected FALSE.") 87 | } 88 | 89 | target = -1 90 | srv.SetLength(target) 91 | if srv.ValidateLength() == true { 92 | t.Fatalf("MakerService.ValidateLength is expected FALSE") 93 | } 94 | } 95 | 96 | func TestValidateOffsetInMakerService(t *testing.T) { 97 | srv := dummyMakerService() 98 | 99 | var target int64 100 | 101 | target = 1 102 | srv.SetOffset(target) 103 | if srv.ValidateOffset() == false { 104 | t.Fatalf("MakerService.ValidateOffset is expected TRUE") 105 | } 106 | 107 | target = 100 108 | srv.SetOffset(target) 109 | if srv.ValidateOffset() == false { 110 | t.Fatalf("MakerService.ValidateOffset is expected TRUE") 111 | } 112 | 113 | target = 0 114 | srv.SetOffset(target) 115 | if srv.ValidateOffset() == true { 116 | t.Fatalf("MakerService.ValidateOffset is expected FALSE") 117 | } 118 | 119 | target = -1 120 | srv.SetOffset(target) 121 | if srv.ValidateOffset() == true { 122 | t.Fatalf("MakerService.ValidateOffset is expected FALSE") 123 | } 124 | } 125 | 126 | func TestBuildRequestURLInMakerService(t *testing.T) { 127 | var srv *MakerService 128 | var u string 129 | var err error 130 | var expected string 131 | 132 | srv = dummyMakerService() 133 | srv.SetFloorID("40") 134 | u, err = srv.BuildRequestURL() 135 | expected = APIBaseURL + "/MakerSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" + "&hits=" + strconv.FormatInt(DefaultAPILength, 10) + "&offset=" + strconv.FormatInt(DefaultAPIOffset, 10) 136 | if u != expected { 137 | t.Fatalf("MakerService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 138 | } 139 | if err != nil { 140 | t.Fatalf("MakerService.BuildRequestURL is not expected to have error") 141 | } 142 | 143 | srv = dummyMakerService() 144 | srv.SetLength(0) 145 | srv.SetOffset(0) 146 | srv.SetFloorID("40") 147 | u, err = srv.BuildRequestURL() 148 | expected = APIBaseURL + "/MakerSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" 149 | expectedBase := expected 150 | if u != expected { 151 | t.Fatalf("MakerService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 152 | } 153 | if err != nil { 154 | t.Fatalf("MakerService.BuildRequestURL is not expected to have error") 155 | } 156 | 157 | srv.SetLength(-1) 158 | u, err = srv.BuildRequestURL() 159 | if u != "" { 160 | t.Fatalf("MakerService.BuildRequestURL is expected empty if error occurs.") 161 | } 162 | if err == nil { 163 | t.Fatalf("MakerService.BuildRequestURL is expected to return error.") 164 | } 165 | srv.SetLength(0) 166 | 167 | srv.SetOffset(-1) 168 | u, err = srv.BuildRequestURL() 169 | if u != "" { 170 | t.Fatalf("MakerService.BuildRequestURL is expected empty if error occurs.") 171 | } 172 | if err == nil { 173 | t.Fatalf("MakerService.BuildRequestURL is expected to return error.") 174 | } 175 | srv.SetOffset(0) 176 | 177 | srv.SetFloorID("") 178 | u, err = srv.BuildRequestURL() 179 | if u != "" { 180 | t.Fatalf("MakerService.BuildRequestURL is expected empty if error occurs.") 181 | } 182 | if err == nil { 183 | t.Fatalf("MakerService.BuildRequestURL is expected to return error.") 184 | } 185 | srv.SetFloorID("40") 186 | 187 | srv.SetInitial("あ") 188 | expected = expectedBase + "&initial=" + url.QueryEscape("あ") 189 | u, err = srv.BuildRequestURL() 190 | if u != expected { 191 | t.Fatalf("MakerService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 192 | } 193 | if err != nil { 194 | t.Fatalf("MakerService.BuildRequestURL is not expected to have error") 195 | } 196 | srv.SetInitial("") 197 | } 198 | 199 | func TestBuildRequestURLWithoutApiIDInMakerService(t *testing.T) { 200 | srv := dummyMakerService() 201 | srv.ApiID = "" 202 | u, err := srv.BuildRequestURL() 203 | if u != "" { 204 | t.Fatalf("MakerService.BuildRequestURL is expected empty if API ID is not set.") 205 | } 206 | if err == nil { 207 | t.Fatalf("MakerService.BuildRequestURL is expected to return error.") 208 | } 209 | } 210 | 211 | func TestBuildRequestURLWithWrongAffiliateIDInMakerService(t *testing.T) { 212 | srv := dummyMakerService() 213 | srv.AffiliateID = "fizzbizz-100" 214 | u, err := srv.BuildRequestURL() 215 | if u != "" { 216 | t.Fatalf("MakerService.BuildRequestURL is expected empty if wrong Affiliate ID is set.") 217 | } 218 | if err == nil { 219 | t.Fatalf("MakerService.BuildRequestURL is expected to return error.") 220 | } 221 | } 222 | 223 | func TestExcuteRequestMakerAPIToServer(t *testing.T) { 224 | if !RequestAvailable { 225 | t.Skip("Not set valid credentials") 226 | } 227 | 228 | srv := NewMakerService(TestAffiliateID, TestAPIID) 229 | srv.SetFloorID("40") 230 | srv.SetInitial("あ") 231 | srv.SetLength(100) 232 | srv.SetOffset(1) 233 | 234 | rst, err := srv.Execute() 235 | if err != nil { 236 | t.Skip("Maybe, The network is down.") 237 | } 238 | 239 | if reflect.TypeOf(rst).String() != "*api.MakerResponse" { 240 | t.Fatalf("MakerService.Execute is expected to return *api.MakerResponse") 241 | } 242 | } 243 | 244 | func dummyMakerService() *MakerService { 245 | return NewMakerService(DummyAffliateID, DummyAPIID) 246 | } 247 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/product.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/mitchellh/mapstructure" 9 | ) 10 | 11 | const ( 12 | DefaultProductAPILength = 20 13 | DefaultProductMaxLength = 100 14 | DefaultProductMaxOffset = 50000 15 | ) 16 | 17 | type ProductService struct { 18 | ApiID string `mapstructure:"api_id"` 19 | AffiliateID string `mapstructure:"affiliate_id"` 20 | Site string `mapstructure:"site"` 21 | Service string `mapstructure:"service"` 22 | Floor string `mapstructure:"floor"` 23 | Length int64 `mapstructure:"hits"` 24 | Offset int64 `mapstructure:"offset"` 25 | Sort string `mapstructure:"sort"` 26 | Keyword string `mapstructure:"keyword"` 27 | Article string `mapstructure:"article"` 28 | ArticleID string `mapstructure:"article_id"` 29 | GteDate string `mapstructure:"gte_date"` 30 | LteDate string `mapstructure:"lte_date"` 31 | Stock string `mapstructure:"mono_stock"` 32 | } 33 | 34 | type ProductRawResponse struct { 35 | Request ProductService `mapstructure:"request"` 36 | Result ProductResponse `mapstructure:"result"` 37 | } 38 | 39 | type ProductResponse struct { 40 | ResultCount int64 `mapstructure:"result_count"` 41 | TotalCount int64 `mapstructure:"total_count"` 42 | FirstPosition int64 `mapstructure:"first_position"` 43 | Items []Item `mapstructure:"items"` 44 | } 45 | 46 | type Item struct { 47 | AffiliateURL string `mapstructure:"affiliateURL"` 48 | AffiliateURLMobile string `mapstructure:"affiliateURLsp"` 49 | CategoryName string `mapstructure:"category_name"` 50 | Comment string `mapstructure:"comment"` 51 | ContentID string `mapstructure:"content_id"` 52 | Date string `mapstructure:"date"` 53 | FloorName string `mapstructure:"floor_name"` 54 | FloorCode string `mapstructure:"floor_code"` 55 | ISBN string `mapstructure:"isbn"` 56 | JANCode string `mapstructure:"jancode"` 57 | ProductCode string `mapstructure:"maker_product"` 58 | ProductID string `mapstructure:"product_id"` 59 | ServiceName string `mapstructure:"service_name"` 60 | ServiceCode string `mapstructure:"service_code"` 61 | Stock string `mapstructure:"stock"` 62 | Title string `mapstructure:"title"` 63 | URL string `mapstructure:"URL"` 64 | URLMoble string `mapstructure:"URLsp"` 65 | Volume string `mapstructure:"volume"` 66 | ImageURL ImageURLList `mapstructure:"imageURL"` 67 | SampleImageURL SampleImageURLList `mapstructure:"sampleImageURL"` 68 | SampleMovieURL SampleMovieURLList `mapstructure:"sampleMovieURL"` 69 | Review ReviewInformation `mapstructure:"review"` 70 | PriceInformation PriceInformation `mapstructure:"prices"` 71 | ItemInformation ItemInformation `mapstructure:"iteminfo"` 72 | BandaiInformation BandaiInformation `mapstructure:"bandaiinfo"` 73 | CdInformation CdInformation `mapstructure:"cdinfo"` 74 | } 75 | 76 | type ImageURLList struct { 77 | List string `mapstructure:"list"` 78 | Small string `mapstructure:"small"` 79 | Large string `mapstructure:"large"` 80 | } 81 | 82 | type SampleImageURLList struct { 83 | SampleS SmallSampleList `mapstructure:"sample_s"` 84 | } 85 | 86 | type SmallSampleList struct { 87 | Image []string `mapstructure:"image"` 88 | } 89 | 90 | type SampleMovieURLList struct { 91 | Size476_306 string `mapstructure:"size_476_306"` 92 | Size560_360 string `mapstructure:"size_560_360"` 93 | Size644_414 string `mapstructure:"size_644_414"` 94 | Size720_480 string `mapstructure:"size_720_480"` 95 | PCFlag bool `mapstructure:"pc_flag"` 96 | SPFlag bool `mapstructure:"sp_flag"` 97 | } 98 | 99 | type PriceInformation struct { 100 | Price string `mapstructure:"price"` 101 | PriceAll string `mapstructure:"price_all"` 102 | RetailPrice string `mapstructure:"list_price"` 103 | Distributions DistributionList `mapstructure:"deliveries"` 104 | } 105 | 106 | type DistributionList struct { 107 | Distribution []Distribution `mapstructure:"delivery"` 108 | } 109 | 110 | type Distribution struct { 111 | Type string `mapstructure:"type"` 112 | Price string `mapstructure:"price"` 113 | } 114 | 115 | type ItemInformation struct { 116 | Maker []ItemComponent `mapstructure:"maker"` 117 | Label []ItemComponent `mapstructure:"label"` 118 | Series []ItemComponent `mapstructure:"series"` 119 | Keywords []ItemComponent `mapstructure:"keyword"` 120 | Genres []ItemComponent `mapstructure:"genre"` 121 | Actors []ItemComponent `mapstructure:"actor"` 122 | Artists []ItemComponent `mapstructure:"artist"` 123 | Actress []ItemComponent `mapstructure:"actress"` 124 | Authors []ItemComponent `mapstructure:"author"` 125 | Directors []ItemComponent `mapstructure:"director"` 126 | Fighters []ItemComponent `mapstructure:"fighter"` 127 | Colors []ItemComponent `mapstructure:"color"` 128 | Sizes []ItemComponent `mapstructure:"size"` 129 | } 130 | 131 | type ItemComponent struct { 132 | ID string `mapstructure:"id"` 133 | Name string `mapstructure:"name"` 134 | } 135 | 136 | type BandaiInformation struct { 137 | TitleCode string `mapstructure:"titlecode"` 138 | } 139 | 140 | type CdInformation struct { 141 | Kind string `mapstructure:"kind"` 142 | } 143 | 144 | type ReviewInformation struct { 145 | Count int64 `mapstructure:"count"` 146 | Average float64 `mapstructure:"average"` 147 | } 148 | 149 | // NewProductService returns a new service for the given affiliate ID and API ID. 150 | // 151 | // NewProductServiceは渡したアフィリエイトIDとAPI IDを使用して新しい serviceを返します。 152 | func NewProductService(affiliateID, apiID string) *ProductService { 153 | return &ProductService{ 154 | ApiID: apiID, 155 | AffiliateID: affiliateID, 156 | Site: "", 157 | Service: "", 158 | Floor: "", 159 | Length: DefaultProductAPILength, 160 | Offset: DefaultAPIOffset, 161 | Sort: "", 162 | Keyword: "", 163 | Article: "", 164 | ArticleID: "", 165 | Stock: "", 166 | } 167 | } 168 | 169 | // Execute requests a url is created by BuildRequestURL. 170 | // Use ExecuteWeak If you want get this response in interface{}. 171 | // 172 | // BuildRequestURLで生成したURLにリクエストします。 173 | // もし interface{} でこのレスポンスを取得したい場合は ExecuteWeak を使用してください。 174 | func (srv *ProductService) Execute() (*ProductResponse, error) { 175 | result, err := srv.ExecuteWeak() 176 | if err != nil { 177 | return nil, err 178 | } 179 | var raw ProductRawResponse 180 | if err = mapstructure.WeakDecode(result, &raw); err != nil { 181 | return nil, err 182 | } 183 | return &raw.Result, nil 184 | } 185 | 186 | // ExecuteWeak requests a url is created by BuildRequestURL. 187 | // 188 | // BuildRequestURLで生成したURLにリクエストします。 189 | func (srv *ProductService) ExecuteWeak() (interface{}, error) { 190 | reqURL, err := srv.BuildRequestURL() 191 | if err != nil { 192 | return nil, err 193 | } 194 | 195 | return RequestJSON(reqURL) 196 | } 197 | 198 | // SetLength set the specified argument to ProductService.Length 199 | // 200 | // SetLengthはLengthパラメータを設定します。 201 | func (srv *ProductService) SetLength(length int64) *ProductService { 202 | srv.Length = length 203 | return srv 204 | } 205 | 206 | // SetHits set the specified argument to ProductService.Length 207 | // SetHits is the alias for SetLength 208 | // 209 | // SetHitsはLengthパラメータを設定します。 210 | func (srv *ProductService) SetHits(length int64) *ProductService { 211 | srv.SetLength(length) 212 | return srv 213 | } 214 | 215 | // SetOffset set the specified argument to ProductService.Offset 216 | // 217 | // SetOffsetはOffsetパラメータを設定します。 218 | func (srv *ProductService) SetOffset(offset int64) *ProductService { 219 | srv.Offset = offset 220 | return srv 221 | } 222 | 223 | // SetKeyword set the specified argument to ProductService.Keyword 224 | // 225 | // SetKeywordはKeywordパラメータを設定します。 226 | func (srv *ProductService) SetKeyword(keyword string) *ProductService { 227 | srv.Keyword = TrimString(keyword) 228 | return srv 229 | } 230 | 231 | // SetSort set the specified argument to ProductService.Sort 232 | // 233 | // SetSortはSortパラメータを設定します。 234 | func (srv *ProductService) SetSort(sort string) *ProductService { 235 | srv.Sort = TrimString(sort) 236 | return srv 237 | } 238 | 239 | // SetSite set the specified argument to ProductService.Site 240 | // 241 | // SetSiteはSiteパラメータを設定します。 242 | func (srv *ProductService) SetSite(site string) *ProductService { 243 | srv.Site = TrimString(site) 244 | return srv 245 | } 246 | 247 | // SetService set the specified argument to ProductService.Service 248 | // 249 | // SetServiceはServiceパラメータを設定します。 250 | func (srv *ProductService) SetService(service string) *ProductService { 251 | srv.Service = TrimString(service) 252 | return srv 253 | } 254 | 255 | // SetFloor set the specified argument to ProductService.Floor 256 | // 257 | // SetFloorはFloorパラメータを設定します。 258 | func (srv *ProductService) SetFloor(floor string) *ProductService { 259 | srv.Floor = TrimString(floor) 260 | return srv 261 | } 262 | 263 | // SetArticle set the specified argument to ProductService.Article 264 | // 265 | // SetArticleはArticleパラメータを設定します。 266 | func (srv *ProductService) SetArticle(stock string) *ProductService { 267 | srv.Article = TrimString(stock) 268 | return srv 269 | } 270 | 271 | // SetArticleID set the specified argument to ProductService.ArticleID 272 | // 273 | // SetArticleIDはArticleIDパラメータを設定します。 274 | func (srv *ProductService) SetArticleID(stock string) *ProductService { 275 | srv.ArticleID = TrimString(stock) 276 | return srv 277 | } 278 | 279 | // SetGteDate set the specified argument to ProductService.GteDate 280 | // 281 | // SetGteDateはGteDateパラメータを設定します。 282 | func (srv *ProductService) SetGteDate(stock string) *ProductService { 283 | srv.GteDate = TrimString(stock) 284 | return srv 285 | } 286 | 287 | // SetLteDate set the specified argument to ProductService.LteDate 288 | // 289 | // SetLteDateはLteDateパラメータを設定します。 290 | func (srv *ProductService) SetLteDate(stock string) *ProductService { 291 | srv.LteDate = TrimString(stock) 292 | return srv 293 | } 294 | 295 | // SetStock set the specified argument to ProductService.Stock 296 | // 297 | // SetStockはStockパラメータを設定します。 298 | func (srv *ProductService) SetStock(stock string) *ProductService { 299 | srv.Stock = TrimString(stock) 300 | return srv 301 | } 302 | 303 | // ValidateLength validates ProductService.Length within the range (1 <= value <= DefaultProductMaxLength). 304 | // Refer to ValidateRange for more information about the range to validate. 305 | // 306 | // ValidateLengthはProductService.Lengthが範囲内(1 <= value <= DefaultProductMaxLength)にあるか検証します。 307 | // 検証範囲について更に詳しく知りたい方はValidateRangeを参照してください。 308 | func (srv *ProductService) ValidateLength() bool { 309 | return ValidateRange(srv.Length, 1, DefaultProductMaxLength) 310 | } 311 | 312 | // ValidateOffset validates ProductService.Offset within the range (1 <= value <= DefaultProductMaxOffset). 313 | // Refer to ValidateRange for more information about the range to validate. 314 | // 315 | // ValidateOffsetはProductService.Offsetが範囲内(1 <= value <= DefaultProductMaxOffset)にあるか検証します。 316 | // 検証範囲について更に詳しく知りたい方はValidateRangeを参照してください。 317 | func (srv *ProductService) ValidateOffset() bool { 318 | return ValidateRange(srv.Offset, 1, DefaultProductMaxOffset) 319 | } 320 | 321 | // BuildRequestURL creates url to request product API. 322 | // 323 | // BuildRequestURLは商品検索APIにリクエストするためのURLを作成します。 324 | func (srv *ProductService) BuildRequestURL() (string, error) { 325 | if srv.ApiID == "" { 326 | return "", fmt.Errorf("set invalid ApiID parameter") 327 | } 328 | if !ValidateAffiliateID(srv.AffiliateID) { 329 | return "", fmt.Errorf("set invalid AffiliateID parameter") 330 | } 331 | 332 | if !ValidateSite(srv.Site) { 333 | return "", fmt.Errorf("set invalid Site parameter") 334 | } 335 | 336 | queries := url.Values{} 337 | queries.Set("api_id", srv.ApiID) 338 | queries.Set("affiliate_id", srv.AffiliateID) 339 | queries.Set("site", srv.Site) 340 | 341 | if srv.Length != 0 { 342 | if !srv.ValidateLength() { 343 | return "", fmt.Errorf("length out of range: %d", srv.Length) 344 | } 345 | queries.Set("hits", strconv.FormatInt(srv.Length, 10)) 346 | } 347 | 348 | if srv.Offset != 0 { 349 | if !srv.ValidateOffset() { 350 | return "", fmt.Errorf("offset out of range: %d", srv.Offset) 351 | } 352 | queries.Set("offset", strconv.FormatInt(srv.Offset, 10)) 353 | } 354 | 355 | if srv.Service != "" { 356 | queries.Set("service", srv.Service) 357 | } 358 | if srv.Floor != "" { 359 | queries.Set("floor", srv.Floor) 360 | } 361 | if srv.Sort != "" { 362 | queries.Set("sort", srv.Sort) 363 | } 364 | if srv.Keyword != "" { 365 | queries.Set("keyword", srv.Keyword) 366 | } 367 | if srv.Article != "" { 368 | queries.Set("article", srv.Article) 369 | } 370 | if srv.GteDate != "" { 371 | queries.Set("gte_date", srv.GteDate) 372 | } 373 | if srv.LteDate != "" { 374 | queries.Set("lte_date", srv.LteDate) 375 | } 376 | if srv.ArticleID != "" { 377 | queries.Set("article_id", srv.ArticleID) 378 | } 379 | if srv.Stock != "" { 380 | queries.Set("mono_stock", srv.Stock) 381 | } 382 | 383 | u, err := buildAPIEndpoint("ItemList") 384 | if err != nil { 385 | return "", err 386 | } 387 | u.RawQuery = queries.Encode() 388 | 389 | return u.String(), nil 390 | } 391 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/product_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestNewProductService(t *testing.T) { 11 | affiliateID := DummyAffliateID 12 | apiID := DummyAPIID 13 | 14 | srv := NewProductService(affiliateID, apiID) 15 | if srv.AffiliateID != affiliateID { 16 | t.Fatalf("ProductService.AffiliateID is expected to equal the input value(affiliateID)") 17 | } 18 | 19 | if srv.ApiID != apiID { 20 | t.Fatalf("ProductService.ApiID is expected to equal the input value(apiID)") 21 | } 22 | } 23 | 24 | func TestSetLengthInProductService(t *testing.T) { 25 | srv := dummyProductService() 26 | var length int64 = 10 27 | srv.SetLength(length) 28 | 29 | if srv.Length != length { 30 | t.Fatalf("ProductService.Length is expected to equal the input value(length)") 31 | } 32 | } 33 | 34 | func TestSetHitsInProductService(t *testing.T) { 35 | srv := dummyProductService() 36 | var hits int64 = 10 37 | srv.SetHits(hits) 38 | 39 | if srv.Length != hits { 40 | t.Fatalf("ProductService.Length is expected to equal the input value(hits)") 41 | } 42 | } 43 | 44 | func TestSetOffsetInProductService(t *testing.T) { 45 | srv := dummyProductService() 46 | var offset int64 = 10 47 | srv.SetOffset(offset) 48 | 49 | if srv.Offset != offset { 50 | t.Fatalf("ProductService.Offset is expected to equal the input value(offset)") 51 | } 52 | } 53 | 54 | func TestSetKeywordInProductService(t *testing.T) { 55 | srv := dummyProductService() 56 | 57 | keyword1 := "abcdefghijkelmnopqrstuvwxyzABCDEFGHIJKELMNOPQRSTUVWXYZ0123456789" 58 | srv.SetKeyword(keyword1) 59 | if srv.Keyword != keyword1 { 60 | t.Fatalf("ProductService.Keyword is expected to equal the input value(keyword1)") 61 | } 62 | 63 | keyword2 := "" 64 | srv.SetKeyword(keyword2) 65 | if srv.Keyword != keyword2 { 66 | t.Fatalf("ProductService.Keyword is expected to equal the input value(keyword2)") 67 | } 68 | 69 | keyword3 := "つれづれなるまゝに、日暮らし、硯にむかひて、心にうつりゆくよしなし事を、そこはかとなく書きつくれば、あやしうこそものぐるほしけれ。" 70 | srv.SetKeyword(keyword3) 71 | if srv.Keyword != keyword3 { 72 | t.Fatalf("ProductService.Keyword is expected to equal the input value(keyword3)") 73 | } 74 | 75 | keyword4 := " a b c d 0 " 76 | keyword4Expected := "a b c d 0" 77 | srv.SetKeyword(keyword4) 78 | if srv.Keyword != keyword4Expected { 79 | t.Fatalf("ProductService.Keyword is expected to equal keyword4_expected") 80 | } 81 | 82 | keyword5 := " あ ア 化A " 83 | keyword5Expected := "あ ア 化A" 84 | srv.SetKeyword(keyword5) 85 | if srv.Keyword != keyword5Expected { 86 | t.Fatalf("ProductService.Keyword is expected to equal keyword5_expected") 87 | } 88 | } 89 | 90 | func TestSetSiteInProductService(t *testing.T) { 91 | srv := dummyProductService() 92 | 93 | var site string 94 | 95 | site = SiteGeneral 96 | srv.SetSite(site) 97 | if srv.Site != site { 98 | t.Fatalf("ProductService.Site is expected to equal the input value. value:%s", site) 99 | } 100 | 101 | site = SiteAdult 102 | srv.SetSite(site) 103 | if srv.Site != site { 104 | t.Fatalf("ProductService.Site is expected to equal the input value. value:%s", site) 105 | } 106 | } 107 | 108 | func TestSetServiceInProductService(t *testing.T) { 109 | srv := dummyProductService() 110 | 111 | service := "digital" 112 | srv.SetService(service) 113 | if srv.Service != service { 114 | t.Fatalf("ProductService.Service is expected to equal the input value(service)") 115 | } 116 | } 117 | 118 | func TestSetFloorInProductService(t *testing.T) { 119 | srv := dummyProductService() 120 | 121 | floor := "videoa" 122 | srv.SetFloor(floor) 123 | if srv.Floor != floor { 124 | t.Fatalf("ProductService.Floor is expected to equal the input value(floor)") 125 | } 126 | } 127 | 128 | func TestSetGteDateInProductService(t *testing.T) { 129 | srv := dummyProductService() 130 | 131 | gte_date := "2016-04-01T00:00:00" 132 | srv.SetGteDate(gte_date) 133 | if srv.GteDate != gte_date { 134 | t.Fatalf("ProductService.GteDate is expected to equal the input value(gte_date)") 135 | } 136 | } 137 | 138 | func TestSetLteDateInProductService(t *testing.T) { 139 | srv := dummyProductService() 140 | 141 | lte_date := "2016-04-30T23:59:59" 142 | srv.SetGteDate(lte_date) 143 | if srv.GteDate != lte_date { 144 | t.Fatalf("ProductService.LteDate is expected to equal the input value(lte_date)") 145 | } 146 | } 147 | 148 | func TestValidateLengthInProductService(t *testing.T) { 149 | srv := dummyProductService() 150 | 151 | var target int64 152 | 153 | target = 1 154 | srv.SetLength(target) 155 | if srv.ValidateLength() == false { 156 | t.Fatalf("ProductService.ValidateLength is expected TRUE.") 157 | } 158 | 159 | target = DefaultProductAPILength 160 | srv.SetLength(target) 161 | if srv.ValidateLength() == false { 162 | t.Fatalf("ProductService.ValidateLength is expected TRUE.") 163 | } 164 | 165 | target = DefaultProductMaxLength 166 | srv.SetLength(target) 167 | if srv.ValidateLength() == false { 168 | t.Fatalf("ProductService.ValidateLength is expected TRUE.") 169 | } 170 | 171 | target = DefaultProductMaxLength + 1 172 | srv.SetLength(target) 173 | if srv.ValidateLength() == true { 174 | t.Fatalf("ProductService.ValidateLength is expected FALSE.") 175 | } 176 | 177 | target = 0 178 | srv.SetLength(target) 179 | if srv.ValidateLength() == true { 180 | t.Fatalf("ProductService.ValidateLength is expected FALSE.") 181 | } 182 | 183 | target = -1 184 | srv.SetLength(target) 185 | if srv.ValidateLength() == true { 186 | t.Fatalf("ProductService.ValidateLength is expected FALSE.") 187 | } 188 | } 189 | 190 | func TestValidateOffsetInProductService(t *testing.T) { 191 | srv := dummyProductService() 192 | 193 | var target int64 194 | 195 | target = 1 196 | srv.SetOffset(target) 197 | if srv.ValidateOffset() == false { 198 | t.Fatalf("ProductService.ValidateOffset is expected TRUE. target:%d", target) 199 | } 200 | 201 | target = DefaultProductMaxOffset 202 | srv.SetOffset(target) 203 | if srv.ValidateOffset() == false { 204 | t.Fatalf("ProductService.ValidateOffset is expected TRUE. target:%d", target) 205 | } 206 | 207 | target = DefaultProductMaxOffset + 1 208 | srv.SetOffset(target) 209 | if srv.ValidateOffset() == true { 210 | t.Fatalf("ProductService.ValidateOffset is expected FALSE. target:%d", target) 211 | } 212 | 213 | target = 0 214 | srv.SetOffset(target) 215 | if srv.ValidateOffset() == true { 216 | t.Fatalf("ProductService.ValidateOffset is expected FALSE. target:%d", target) 217 | } 218 | 219 | target = -1 220 | srv.SetOffset(target) 221 | if srv.ValidateOffset() == true { 222 | t.Fatalf("ProductService.ValidateOffset is expected FALSE. target:%d", target) 223 | } 224 | } 225 | 226 | func TestBuildRequestURLInProductService(t *testing.T) { 227 | var srv *ProductService 228 | var u string 229 | var err error 230 | var expected string 231 | 232 | srv = dummyProductService() 233 | srv.SetSite(SiteAdult) 234 | u, err = srv.BuildRequestURL() 235 | expected = APIBaseURL + "/ItemList?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&hits=" + strconv.FormatInt(DefaultProductAPILength, 10) + "&offset=" + strconv.FormatInt(DefaultAPIOffset, 10) + "&site=" + SiteAdult 236 | if u != expected { 237 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 238 | } 239 | if err != nil { 240 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 241 | } 242 | 243 | srv = dummyProductService() 244 | srv.SetSite(SiteAdult) 245 | srv.SetLength(0) 246 | srv.SetOffset(0) 247 | u, err = srv.BuildRequestURL() 248 | expected = APIBaseURL + "/ItemList?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID 249 | expectedBase := expected 250 | expected = expectedBase + "&site=" + SiteAdult 251 | if u != expected { 252 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 253 | } 254 | if err != nil { 255 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 256 | } 257 | 258 | srv.SetSite("") 259 | u, err = srv.BuildRequestURL() 260 | if u != "" { 261 | t.Fatalf("ProductService.BuildRequestURL is expected empty if error occurs.") 262 | } 263 | if err == nil { 264 | t.Fatalf("ProductService.BuildRequestURL is expected to return error.") 265 | } 266 | srv.SetSite(SiteAdult) 267 | 268 | srv.SetLength(-1) 269 | u, err = srv.BuildRequestURL() 270 | if u != "" { 271 | t.Fatalf("ProductService.BuildRequestURL is expected empty if error occurs.") 272 | } 273 | if err == nil { 274 | t.Fatalf("ProductService.BuildRequestURL is expected to return error.") 275 | } 276 | srv.SetLength(0) 277 | 278 | srv.SetOffset(-1) 279 | u, err = srv.BuildRequestURL() 280 | if u != "" { 281 | t.Fatalf("ProductService.BuildRequestURL is expected empty if error occurs.") 282 | } 283 | if err == nil { 284 | t.Fatalf("ProductService.BuildRequestURL is expected to return error.") 285 | } 286 | srv.SetOffset(0) 287 | 288 | srv.SetSort("rank") 289 | expected = expectedBase + "&site=" + SiteAdult + "&sort=rank" 290 | u, err = srv.BuildRequestURL() 291 | if u != expected { 292 | t.Fatalf("ActressService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 293 | } 294 | if err != nil { 295 | t.Fatalf("ActressService.BuildRequestURL is not expected to have error") 296 | } 297 | srv.SetSort("") 298 | 299 | srv.SetKeyword("上原亜衣") 300 | expected = expectedBase + "&keyword=" + url.QueryEscape("上原亜衣") + "&site=" + SiteAdult 301 | u, err = srv.BuildRequestURL() 302 | if u != expected { 303 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 304 | } 305 | if err != nil { 306 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 307 | } 308 | srv.SetKeyword("") 309 | 310 | srv.SetService("digital") 311 | expected = expectedBase + "&service=" + url.QueryEscape("digital") + "&site=" + SiteAdult 312 | u, err = srv.BuildRequestURL() 313 | if u != expected { 314 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 315 | } 316 | if err != nil { 317 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 318 | } 319 | srv.SetService("") 320 | 321 | srv.SetFloor("videoa") 322 | expected = expectedBase + "&floor=" + url.QueryEscape("videoa") + "&site=" + SiteAdult 323 | u, err = srv.BuildRequestURL() 324 | if u != expected { 325 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 326 | } 327 | if err != nil { 328 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 329 | } 330 | srv.SetFloor("") 331 | 332 | srv.SetArticle("actress") 333 | expected = expectedBase + "&article=" + url.QueryEscape("actress") + "&site=" + SiteAdult 334 | u, err = srv.BuildRequestURL() 335 | if u != expected { 336 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 337 | } 338 | if err != nil { 339 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 340 | } 341 | srv.SetArticle("") 342 | 343 | srv.SetArticleID("1011199") 344 | expected = expectedBase + "&article_id=" + url.QueryEscape("1011199") + "&site=" + SiteAdult 345 | u, err = srv.BuildRequestURL() 346 | if u != expected { 347 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 348 | } 349 | if err != nil { 350 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 351 | } 352 | srv.SetArticleID("") 353 | 354 | srv.SetStock("mono") 355 | expected = expectedBase + "&mono_stock=" + url.QueryEscape("mono") + "&site=" + SiteAdult 356 | u, err = srv.BuildRequestURL() 357 | if u != expected { 358 | t.Fatalf("ProductService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 359 | } 360 | if err != nil { 361 | t.Fatalf("ProductService.BuildRequestURL is not expected to have error") 362 | } 363 | srv.SetStock("") 364 | } 365 | 366 | func TestBuildRequestURLWithoutApiIDInInProductService(t *testing.T) { 367 | srv := dummyProductService() 368 | srv.ApiID = "" 369 | u, err := srv.BuildRequestURL() 370 | if u != "" { 371 | t.Fatalf("ProductService.BuildRequestURL is expected empty if API ID is not set.") 372 | } 373 | if err == nil { 374 | t.Fatalf("ProductService.BuildRequestURL is expected to return error.") 375 | } 376 | } 377 | 378 | func TestBuildRequestURLWithWrongAffiliateIDInProductService(t *testing.T) { 379 | srv := dummyProductService() 380 | srv.AffiliateID = "fizzbizz-100" 381 | u, err := srv.BuildRequestURL() 382 | if u != "" { 383 | t.Fatalf("ProductService.BuildRequestURL is expected empty if wrong Affiliate ID is set.") 384 | } 385 | if err == nil { 386 | t.Fatalf("ProductService.BuildRequestURL is expected to return error.") 387 | } 388 | } 389 | 390 | func TestExcuteRequestProductAPIToServer(t *testing.T) { 391 | if !RequestAvailable { 392 | t.Skip("Not set valid credentials") 393 | } 394 | 395 | srv := NewProductService(TestAffiliateID, TestAPIID) 396 | srv.SetSite("DMM.R18") 397 | srv.SetService("mono") 398 | srv.SetFloor("dvd") 399 | srv.SetSort("date") 400 | srv.SetLength(1) 401 | 402 | rst, err := srv.Execute() 403 | if err != nil { 404 | t.Skip("Maybe, The network is down.") 405 | } 406 | 407 | if reflect.TypeOf(rst).String() != "*api.ProductResponse" { 408 | t.Fatalf("ProductService.Execute is expected to return *api.ProductResponse") 409 | } 410 | } 411 | 412 | func dummyProductService() *ProductService { 413 | return NewProductService(DummyAffliateID, DummyAPIID) 414 | } 415 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/series.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "strconv" 7 | 8 | "github.com/mitchellh/mapstructure" 9 | ) 10 | 11 | type SeriesService struct { 12 | ApiID string `mapstructure:"api_id"` 13 | AffiliateID string `mapstructure:"affiliate_id"` 14 | FloorID string `mapstructure:"floor_id"` 15 | Initial string `mapstructure:"initial"` 16 | Length int64 `mapstructure:"hits"` 17 | Offset int64 `mapstructure:"offset"` 18 | } 19 | 20 | type SeriesRawResponse struct { 21 | Request SeriesService `mapstructure:"request"` 22 | Result SeriesResponse `mapstructure:"result"` 23 | } 24 | 25 | type SeriesResponse struct { 26 | ResultCount int64 `mapstructure:"result_count"` 27 | TotalCount int64 `mapstructure:"total_count"` 28 | FirstPosition int64 `mapstructure:"first_position"` 29 | SiteName string `mapstructure:"site_name"` 30 | SiteCode string `mapstructure:"site_code"` 31 | ServiceName string `mapstructure:"service_name"` 32 | ServiceCode string `mapstructure:"service_code"` 33 | FloorID string `mapstructure:"floor_id"` 34 | FloorName string `mapstructure:"floor_name"` 35 | FloorCode string `mapstructure:"floor_code"` 36 | SeriesList []Series `mapstructure:"series"` 37 | } 38 | 39 | type Series struct { 40 | SeriesID string `mapstructure:"series_id"` 41 | Name string `mapstructure:"name"` 42 | Ruby string `mapstructure:"ruby"` 43 | ListURL string `mapstructure:"list_url"` 44 | } 45 | 46 | // NewSeriesService returns a new service for the given affiliate ID and API ID. 47 | // 48 | // NewSeriesServiceは渡したアフィリエイトIDとAPI IDを使用して新しい serviceを返します。 49 | func NewSeriesService(affiliateID, apiID string) *SeriesService { 50 | return &SeriesService{ 51 | ApiID: apiID, 52 | AffiliateID: affiliateID, 53 | FloorID: "", 54 | Initial: "", 55 | Length: DefaultAPILength, 56 | Offset: DefaultAPIOffset, 57 | } 58 | } 59 | 60 | // Execute requests a url is created by BuildRequestURL. 61 | // Use ExecuteWeak If you want get this response in interface{}. 62 | // 63 | // BuildRequestURLで生成したURLにリクエストします。 64 | // もし interface{} でこのレスポンスを取得したい場合は ExecuteWeak を使用してください。 65 | func (srv *SeriesService) Execute() (*SeriesResponse, error) { 66 | result, err := srv.ExecuteWeak() 67 | if err != nil { 68 | return nil, err 69 | } 70 | var raw SeriesRawResponse 71 | if err = mapstructure.WeakDecode(result, &raw); err != nil { 72 | return nil, err 73 | } 74 | return &raw.Result, nil 75 | } 76 | 77 | // ExecuteWeak requests a url is created by BuildRequestURL. 78 | // 79 | // BuildRequestURLで生成したURLにリクエストします。 80 | func (srv *SeriesService) ExecuteWeak() (interface{}, error) { 81 | reqURL, err := srv.BuildRequestURL() 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | return RequestJSON(reqURL) 87 | } 88 | 89 | // SetLength set the specified argument to SeriesService.Length 90 | // 91 | // SetLengthはLengthパラメータを設定します。 92 | func (srv *SeriesService) SetLength(length int64) *SeriesService { 93 | srv.Length = length 94 | return srv 95 | } 96 | 97 | // SetHits set the specified argument to SeriesService.Length 98 | // SetHits is the alias for SetLength 99 | // 100 | // SetHitsはLengthパラメータを設定します。 101 | func (srv *SeriesService) SetHits(length int64) *SeriesService { 102 | srv.SetLength(length) 103 | return srv 104 | } 105 | 106 | // SetOffset set the specified argument to SeriesService.Offset 107 | // 108 | // SetOffsetはOffsetパラメータを設定します。 109 | func (srv *SeriesService) SetOffset(offset int64) *SeriesService { 110 | srv.Offset = offset 111 | return srv 112 | } 113 | 114 | // SetInitial sets the specified argument to SeriesService.Initial. 115 | // This argment is series' initial and you can use only hiragana. 116 | // e.g. srv.SetInitial("あ") -> ARIA(ありあ) 117 | // 118 | // SetInitialはInitalパラメータに検索したいシリーズの頭文字をひらがなで設定します。 119 | func (srv *SeriesService) SetInitial(initial string) *SeriesService { 120 | srv.Initial = TrimString(initial) 121 | return srv 122 | } 123 | 124 | // SetFloorID sets the specified argument to SeriesService.FloorID. 125 | // You can retrieve Floor IDs from floor API. 126 | // 127 | // SetFloorIDはFloorIDパラメータを設定します。 128 | // フロアIDはフロアAPIから取得できます。 129 | func (srv *SeriesService) SetFloorID(floorID string) *SeriesService { 130 | srv.FloorID = TrimString(floorID) 131 | return srv 132 | } 133 | 134 | // ValidateLength validates SeriesService.Length within the range (1 <= value <= DefaultMaxLength). 135 | // Refer to ValidateRange for more information about the range to validate. 136 | // 137 | // ValidateLengthはSeriesService.Lengthが範囲内(1 <= value <= DefaultMaxLength)にあるか検証します。 138 | // 検証範囲について更に詳しく知りたい方はValidateRangeを参照してください。 139 | func (srv *SeriesService) ValidateLength() bool { 140 | return ValidateRange(srv.Length, 1, DefaultMaxLength) 141 | } 142 | 143 | // ValidateOffset validates SeriesService.Offset within the range (1 <= value). 144 | // 145 | // ValidateOffsetはSeriesService.Offsetが範囲内(1 <= value)にあるか検証します。 146 | func (srv *SeriesService) ValidateOffset() bool { 147 | return srv.Offset >= 1 148 | } 149 | 150 | // BuildRequestURL creates url to request series API. 151 | // 152 | // BuildRequestURLはシリーズ検索APIにリクエストするためのURLを作成します。 153 | func (srv *SeriesService) BuildRequestURL() (string, error) { 154 | if srv.ApiID == "" { 155 | return "", fmt.Errorf("set invalid ApiID parameter") 156 | } 157 | if !ValidateAffiliateID(srv.AffiliateID) { 158 | return "", fmt.Errorf("set invalid AffiliateID parameter") 159 | } 160 | if srv.FloorID == "" { 161 | return "", fmt.Errorf("set invalid FloorID parameter") 162 | } 163 | 164 | queries := url.Values{} 165 | queries.Set("api_id", srv.ApiID) 166 | queries.Set("affiliate_id", srv.AffiliateID) 167 | queries.Set("floor_id", srv.FloorID) 168 | 169 | if srv.Length != 0 { 170 | if !srv.ValidateLength() { 171 | return "", fmt.Errorf("length out of range: %d", srv.Length) 172 | } 173 | queries.Set("hits", strconv.FormatInt(srv.Length, 10)) 174 | } 175 | 176 | if srv.Offset != 0 { 177 | if !srv.ValidateOffset() { 178 | return "", fmt.Errorf("offset out of range: %d", srv.Offset) 179 | } 180 | queries.Set("offset", strconv.FormatInt(srv.Offset, 10)) 181 | } 182 | 183 | if srv.Initial != "" { 184 | queries.Set("initial", srv.Initial) 185 | } 186 | 187 | u, err := buildAPIEndpoint("SeriesSearch") 188 | if err != nil { 189 | return "", err 190 | } 191 | u.RawQuery = queries.Encode() 192 | 193 | return u.String(), nil 194 | } 195 | -------------------------------------------------------------------------------- /third_party/dmm-go-sdk/api/series_test.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/url" 5 | "reflect" 6 | "strconv" 7 | "testing" 8 | ) 9 | 10 | func TestNewSeriesService(t *testing.T) { 11 | affiliateID := DummyAffliateID 12 | apiID := DummyAPIID 13 | 14 | srv := NewSeriesService(affiliateID, apiID) 15 | if srv.AffiliateID != affiliateID { 16 | t.Fatalf("SeriesService.AffiliateID is expected to equal the input value(affiliateID)") 17 | } 18 | 19 | if srv.ApiID != apiID { 20 | t.Fatalf("SeriesService.ApiID is expected to equal the input value(apiID)") 21 | } 22 | } 23 | 24 | func TestSetLengthInSeriesService(t *testing.T) { 25 | srv := dummySeriesService() 26 | var length int64 = 10 27 | srv.SetLength(length) 28 | 29 | if srv.Length != length { 30 | t.Fatalf("SeriesService.Length is expected to equal the input value(length)") 31 | } 32 | } 33 | 34 | func TestSetHitsInSeriesService(t *testing.T) { 35 | srv := dummySeriesService() 36 | var hits int64 = 10 37 | srv.SetHits(hits) 38 | 39 | if srv.Length != hits { 40 | t.Fatalf("SeriesService.Length is expected to equal the input value(hits)") 41 | } 42 | } 43 | 44 | func TestSetOffsetInSeriesService(t *testing.T) { 45 | srv := dummySeriesService() 46 | var offset int64 = 10 47 | srv.SetOffset(offset) 48 | 49 | if srv.Offset != offset { 50 | t.Fatalf("SeriesService.Offset is expected to equal the input value(offset)") 51 | } 52 | } 53 | 54 | func TestValidateLengthInSeriesService(t *testing.T) { 55 | srv := dummySeriesService() 56 | 57 | var target int64 58 | 59 | target = 1 60 | srv.SetLength(target) 61 | if srv.ValidateLength() == false { 62 | t.Fatalf("SeriesService.ValidateLength is expected TRUE.") 63 | } 64 | 65 | target = DefaultAPILength 66 | srv.SetLength(target) 67 | if srv.ValidateLength() == false { 68 | t.Fatalf("SeriesService.ValidateLength is expected TRUE.") 69 | } 70 | 71 | target = DefaultMaxLength 72 | srv.SetLength(target) 73 | if srv.ValidateLength() == false { 74 | t.Fatalf("SeriesService.ValidateLength is expected TRUE.") 75 | } 76 | 77 | target = DefaultMaxLength + 1 78 | srv.SetLength(target) 79 | if srv.ValidateLength() == true { 80 | t.Fatalf("SeriesService.ValidateLength is expected FALSE.") 81 | } 82 | 83 | target = 0 84 | srv.SetLength(target) 85 | if srv.ValidateLength() == true { 86 | t.Fatalf("SeriesService.ValidateLength is expected FALSE.") 87 | } 88 | 89 | target = -1 90 | srv.SetLength(target) 91 | if srv.ValidateLength() == true { 92 | t.Fatalf("SeriesService.ValidateLength is expected FALSE.") 93 | } 94 | } 95 | 96 | func TestValidateOffsetInSeriesService(t *testing.T) { 97 | srv := dummySeriesService() 98 | 99 | var target int64 100 | 101 | target = 1 102 | srv.SetOffset(target) 103 | if srv.ValidateOffset() == false { 104 | t.Fatalf("SeriesService.ValidateOffset is expected TRUE.") 105 | } 106 | 107 | target = 100 108 | srv.SetOffset(target) 109 | if srv.ValidateOffset() == false { 110 | t.Fatalf("SeriesService.ValidateOffset is expected TRUE.") 111 | } 112 | 113 | target = 0 114 | srv.SetOffset(target) 115 | if srv.ValidateOffset() == true { 116 | t.Fatalf("SeriesService.ValidateOffset is expected FALSE.") 117 | } 118 | 119 | target = -1 120 | srv.SetOffset(target) 121 | if srv.ValidateOffset() == true { 122 | t.Fatalf("SeriesService.ValidateOffset is expected FALSE.") 123 | } 124 | } 125 | 126 | func TestBuildRequestURLInSeriesService(t *testing.T) { 127 | var srv *SeriesService 128 | var u string 129 | var err error 130 | var expected string 131 | 132 | srv = dummySeriesService() 133 | srv.SetFloorID("40") 134 | u, err = srv.BuildRequestURL() 135 | expected = APIBaseURL + "/SeriesSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" + "&hits=" + strconv.FormatInt(DefaultAPILength, 10) + "&offset=" + strconv.FormatInt(DefaultAPIOffset, 10) 136 | if u != expected { 137 | t.Fatalf("SeriesService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 138 | } 139 | if err != nil { 140 | t.Fatalf("SeriesService.BuildRequestURL is not expected to have error") 141 | } 142 | 143 | srv = dummySeriesService() 144 | srv.SetLength(0) 145 | srv.SetOffset(0) 146 | srv.SetFloorID("40") 147 | u, err = srv.BuildRequestURL() 148 | expected = APIBaseURL + "/SeriesSearch?affiliate_id=" + DummyAffliateID + "&api_id=" + DummyAPIID + "&floor_id=40" 149 | expectedBase := expected 150 | if u != expected { 151 | t.Fatalf("SeriesService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 152 | } 153 | if err != nil { 154 | t.Fatalf("SeriesService.BuildRequestURL is not expected to have error") 155 | } 156 | 157 | srv.SetLength(-1) 158 | u, err = srv.BuildRequestURL() 159 | if u != "" { 160 | t.Fatalf("SeriesService.BuildRequestURL is expected empty if error occurs.") 161 | } 162 | if err == nil { 163 | t.Fatalf("SeriesService.BuildRequestURL is expected to return error.") 164 | } 165 | srv.SetLength(0) 166 | 167 | srv.SetOffset(-1) 168 | u, err = srv.BuildRequestURL() 169 | if u != "" { 170 | t.Fatalf("SeriesService.BuildRequestURL is expected empty if error occurs.") 171 | } 172 | if err == nil { 173 | t.Fatalf("SeriesService.BuildRequestURL is expected to return error.") 174 | } 175 | srv.SetOffset(0) 176 | 177 | srv.SetFloorID("") 178 | u, err = srv.BuildRequestURL() 179 | if u != "" { 180 | t.Fatalf("SeriesService.BuildRequestURL is expected empty if error occurs.") 181 | } 182 | if err == nil { 183 | t.Fatalf("SeriesService.BuildRequestURL is expected to return error.") 184 | } 185 | srv.SetFloorID("40") 186 | 187 | srv.SetInitial("あ") 188 | expected = expectedBase + "&initial=" + url.QueryEscape("あ") 189 | u, err = srv.BuildRequestURL() 190 | if u != expected { 191 | t.Fatalf("SeriesService.BuildRequestURL is expected to equal the expected value.\nexpected:%s\nactual: %s", expected, u) 192 | } 193 | if err != nil { 194 | t.Fatalf("SeriesService.BuildRequestURL is not expected to have error") 195 | } 196 | srv.SetInitial("") 197 | } 198 | 199 | func TestBuildRequestURLWithoutApiIDInSeriesService(t *testing.T) { 200 | srv := dummySeriesService() 201 | srv.ApiID = "" 202 | u, err := srv.BuildRequestURL() 203 | if u != "" { 204 | t.Fatalf("SeriesService.BuildRequestURL is expected empty if API ID is not set.") 205 | } 206 | if err == nil { 207 | t.Fatalf("SeriesService.BuildRequestURL is expected to return error.") 208 | } 209 | } 210 | 211 | func TestBuildRequestURLWithWrongAffiliateIDInSeriesService(t *testing.T) { 212 | srv := dummySeriesService() 213 | srv.AffiliateID = "fizzbizz-100" 214 | u, err := srv.BuildRequestURL() 215 | if u != "" { 216 | t.Fatalf("SeriesService.BuildRequestURL is expected empty if wrong Affiliate ID is set.") 217 | } 218 | if err == nil { 219 | t.Fatalf("SeriesService.BuildRequestURL is expected to return error.") 220 | } 221 | } 222 | 223 | func TestExcuteRequestSeriesAPIToServer(t *testing.T) { 224 | if !RequestAvailable { 225 | t.Skip("Not set valid credentials") 226 | } 227 | 228 | srv := NewSeriesService(TestAffiliateID, TestAPIID) 229 | srv.SetFloorID("40") 230 | srv.SetInitial("あ") 231 | srv.SetLength(100) 232 | srv.SetOffset(1) 233 | 234 | rst, err := srv.Execute() 235 | if err != nil { 236 | t.Skip("Maybe, The network is down.") 237 | } 238 | 239 | if reflect.TypeOf(rst).String() != "*api.SeriesResponse" { 240 | t.Fatalf("SeriesService.Execute is expected to return *api.SeriesResponse") 241 | } 242 | } 243 | 244 | func dummySeriesService() *SeriesService { 245 | return NewSeriesService(DummyAffliateID, DummyAPIID) 246 | } 247 | --------------------------------------------------------------------------------