Full Code of WhiteBlue/bilibili-sdk-go for AI

master ec7097f5828e cached
28 files
61.4 KB
20.8k tokens
130 symbols
1 requests
Download .txt
Repository: WhiteBlue/bilibili-sdk-go
Branch: master
Commit: ec7097f5828e
Files: 28
Total size: 61.4 KB

Directory structure:
gitextract_t4bibrws/

├── .gitignore
├── Dockerfile
├── Godeps/
│   ├── Godeps.json
│   └── Readme
├── LICENSE
├── README.md
├── client/
│   ├── bangumi.go
│   ├── base.go
│   ├── cli.go
│   ├── others.go
│   ├── rank.go
│   ├── special.go
│   ├── user.go
│   ├── utils.go
│   └── video.go
├── conf.example.json
├── conf.json
├── deploy.sh
├── docs/
│   └── api_doc.md
├── main.go
├── run.sh
├── service/
│   ├── application.go
│   ├── cache.go
│   ├── config.go
│   ├── corn.go
│   ├── corn_tasks.go
│   └── router.go
└── test/
    └── api_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

*.iml

.idea

test.go

bilibili-service


================================================
FILE: Dockerfile
================================================
FROM golang:onbuild

MAINTAINER whiteblue0616@gmail.com

ADD . $GOPATH/src/github.com/whiteblue/bilibili-go

WORKDIR $GOPATH/src/github.com/whiteblue/bilibili-go


# RUN go get -u github.com/go-playground/log \
#    && go get -u github.com/gin-gonic/gin \
#    && go get -u github.com/gin-gonic/contrib/gzip \
#    && go get -u github.com/valyala/fasthttp

RUN go get github.com/tools/godep

EXPOSE 8080

CMD ["./run.sh"]



================================================
FILE: Godeps/Godeps.json
================================================
{
	"ImportPath": "github.com/whiteblue/bilibili-go",
	"GoVersion": "go1.7",
	"GodepVersion": "v75",
	"Deps": [
		{
			"ImportPath": "github.com/anacrolix/missinggo",
			"Rev": "03b41562f79c09a2bccb139f2a6282dcd14d6ce6"
		},
		{
			"ImportPath": "github.com/anacrolix/sync",
			"Rev": "812602587b72df6a2a4f6e30536adc75394a374b"
		},
		{
			"ImportPath": "github.com/gin-gonic/contrib/gzip",
			"Rev": "547e518040cfb96576b507d2f26779ab9c6fc829"
		},
		{
			"ImportPath": "github.com/gin-gonic/gin",
			"Comment": "v1.0rc1-268-gf931d1e",
			"Rev": "f931d1ea80ae95a6fc739213cdd9399bd2967fb6"
		},
		{
			"ImportPath": "github.com/gin-gonic/gin/binding",
			"Comment": "v1.0rc1-268-gf931d1e",
			"Rev": "f931d1ea80ae95a6fc739213cdd9399bd2967fb6"
		},
		{
			"ImportPath": "github.com/gin-gonic/gin/render",
			"Comment": "v1.0rc1-268-gf931d1e",
			"Rev": "f931d1ea80ae95a6fc739213cdd9399bd2967fb6"
		},
		{
			"ImportPath": "github.com/go-playground/log",
			"Comment": "v4.0.1",
			"Rev": "700a09cca964de69c81ed1d7fa3bb9b94af1fd5f"
		},
		{
			"ImportPath": "github.com/go-playground/log/handlers/console",
			"Comment": "v4.0.1",
			"Rev": "700a09cca964de69c81ed1d7fa3bb9b94af1fd5f"
		},
		{
			"ImportPath": "github.com/golang/protobuf/proto",
			"Rev": "1f49d83d9aa00e6ce4fc8258c71cc7786aec968a"
		},
		{
			"ImportPath": "github.com/klauspost/compress/flate",
			"Comment": "v1.1-2-ge3b7981",
			"Rev": "e3b7981a12dd3cab49afa1d3a50e715846f23732"
		},
		{
			"ImportPath": "github.com/klauspost/compress/gzip",
			"Comment": "v1.1-2-ge3b7981",
			"Rev": "e3b7981a12dd3cab49afa1d3a50e715846f23732"
		},
		{
			"ImportPath": "github.com/klauspost/compress/zlib",
			"Comment": "v1.1-2-ge3b7981",
			"Rev": "e3b7981a12dd3cab49afa1d3a50e715846f23732"
		},
		{
			"ImportPath": "github.com/klauspost/cpuid",
			"Comment": "v1.0",
			"Rev": "09cded8978dc9e80714c4d85b0322337b0a1e5e0"
		},
		{
			"ImportPath": "github.com/klauspost/crc32",
			"Comment": "v1.0-2-gcb6bfca",
			"Rev": "cb6bfca970f6908083f26f39a79009d608efd5cd"
		},
		{
			"ImportPath": "github.com/manucorporat/sse",
			"Rev": "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d"
		},
		{
			"ImportPath": "github.com/valyala/bytebufferpool",
			"Rev": "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
		},
		{
			"ImportPath": "github.com/valyala/fasthttp",
			"Comment": "v20160617-69-g6cd438b",
			"Rev": "6cd438ba896c48caa4194fb7381d91eae33b9461"
		},
		{
			"ImportPath": "github.com/valyala/fasthttp/fasthttputil",
			"Comment": "v20160617-69-g6cd438b",
			"Rev": "6cd438ba896c48caa4194fb7381d91eae33b9461"
		},
		{
			"ImportPath": "github.com/valyala/fasthttp/stackless",
			"Comment": "v20160617-69-g6cd438b",
			"Rev": "6cd438ba896c48caa4194fb7381d91eae33b9461"
		},
		{
			"ImportPath": "golang.org/x/net/context",
			"Rev": "6250b412798208e6c90b03b7c4f226de5aa299e2"
		},
		{
			"ImportPath": "gopkg.in/go-playground/validator.v8",
			"Comment": "v8.18.1",
			"Rev": "5f57d2222ad794d0dffb07e664ea05e2ee07d60c"
		},
		{
			"ImportPath": "gopkg.in/yaml.v2",
			"Rev": "e4d366fc3c7938e2958e662b4258c7a89e1f0e3e"
		}
	]
}


================================================
FILE: Godeps/Readme
================================================
This directory tree is generated automatically by godep.

Please do not edit.

See https://github.com/tools/godep for more information.


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2015 WhiteBlue

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.



================================================
FILE: README.md
================================================
# bilibili-sdk-go

BiliBili Open API & SDK written in Go



## Open API

Docs: [docs](docs/api_doc.md)

* api.bilibilih5.club
* api.prprpr.me/bilibili ( support by [DIYgod](https://github.com/DIYgod))

Deploy:

* ```docker build -t bilibili-go```
* ```docekr run -d -p 80:8080 bilibili-go```


## Progress

* Rank
	* ```SortRank``` (order by danmu/comment/hot)
* Video
	* ```VideoInfo```
	* ```VideoLin``` (mp4/flv)
* User
	* ```UserInfo```
	* ```UserVideos```
* Special
	* ```SpecialInfo```
	* ```SpecialVideos```
* Bangumi
	* ```BangumiList```
	* ```BangumiRecommend```
* Others
	* ```Search```(search user/video/bangumi)



## Install

```
go get github.com/WhiteBlue/bilibili-go
```

## Usage

```
import "github.com/whiteblue/bilibili-go/client"

c := client.NewClient("APPKEY", "SECRET")
back, err := c.Bangumi.GetWeekList("2")

if err != nil {
    log.Error(err)
    return
}

log.Info(result)

```

## Related Projects

* [BiliBili-Html5](http://bilibilih5.club)



## License

MIT License

Copyright (c) 2016 Castaway Consulting LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.




================================================
FILE: client/bangumi.go
================================================
package client

import (
	"encoding/json"
)

type bangumiElement struct {
	Title        string `json:"title"`
	Area         string `json:"area"`
	AreaLimit    int    `json:"arealimit"`
	Attention    int    `json:"attention"`
	BangumiId    int    `json:"bangumi_id"`
	BgmCount     string `json:"bgmcount"`
	Cover        string `json:"cover"`
	SquareCover  string `json:"square_cover"`
	DanmakuCount int    `json:"danmaku_count"`
	Favorites    int    `json:"favorites"`
	IsFinish     int    `json:"is_finish"`
	LastUpdate   string `json:"lastupdate_at"`
	New          bool   `json:"new"`
	PlayCount    int    `json:"play_count"`
	SeasonId     int    `json:"season_id"`
	SpId         int    `json:"spid"`
	Url          string `json:"url"`
	ViewRank     int    `json:"viewRank"`
	Weekday      int    `json:"weekday"`
}

type banner struct {
	Title    string `json:"title"`
	Link     string `json:"link"`
	Img      string `json:"img"`
	SImg     string `json:"simg"`
	Aid      int    `json:"aid"`
	Type     string `json:"type"`
	Platform int    `json:"platform"`
	Pid      int    `json:"pid"`
}

type recommendBangumiVideo struct {
	Aid         string `json:"aid"`
	Title       string `json:"title"`
	Subtitle    string `json:"subtitle"`
	Play        int    `json:"play"`
	Review      int    `json:"review"`
	VideoReview int    `json:"video_review"`
	Favorites   int    `json:"favorites"`
	Mid         int    `json:"mid"`
	Author      string `json:"author"`
	Description string `json:"description"`
	Create      string `json:"create"`
	Pic         string `json:"pic"`
	Coins       int    `json:"coins"`
	Duration    string `json:"duration"`
}

type bangumiActor struct {
	Actor string `json:"actor"`
	Role  string `json:"role"`
}

type bangumiVideo struct {
	Aid        string `json:"av_id"`
	Coins      int    `json:"coins"`
	Cover      string `json:"cover"`
	Danmaku    string `json:"danmaku"`
	Index      string `json:"index"`
	Title      string `json:"index_title"`
	UpdateTime string `json:"update_time"`
}

type bangumiSeason struct {
	Cover        string `json:"cover"`
	IsFinish     string `json:"is_finish"`
	SeasonId     string `json:"season_id"`
	SeasonStatus int    `json:"season_status"`
	Title        string `json:"title"`
	TotalCount   string `json:"total_count"`
}

type bangumiInfoResponse struct {
	Actors       []bangumiActor  `json:"actor"`
	Alias        string          `json:"alias"`
	Area         string          `json:"area"`
	BangumiId    string          `json:"bangumi_id"`
	BangumiTitle string          `json:"bangumi_title"`
	Brief        string          `json:"brief"`
	Coins        string          `json:"coins"`
	CopyRight    string          `json:"copyright"`
	Cover        string          `json:"cover"`
	DanmakuCount string          `json:"danmaku_count"`
	Episodes     []bangumiVideo  `json:"episodes"`
	Evaluate     string          `json:"evaluate"`
	Favorites    string          `json:"favorites"`
	IsFinish     string          `json:"is_finish"`
	JpTitle      string          `json:"jp_title"`
	PlayCount    string          `json:"play_count"`
	PubTime      string          `json:"pub_time"`
	SeasonId     string          `json:"season_id"`
	SeasonStatus int             `json:"season_status"`
	SeasonTitle  string          `json:"season_title"`
	Seasons      []bangumiSeason `json:"seasons"`
	SquareCover  string          `json:"squareCover"`
	Staff        string          `json:"staff"`
	Title        string          `json:"title"`
	TotalCount   string          `json:"total_count"`
}

type weekBangumiResponse struct {
	Count string           `json:"count"`
	List  []bangumiElement `json:"list"`
}

type bangumiIndexResponse struct {
	Banners    []banner                `json:"banners"`
	Recommends []recommendBangumiVideo `json:"recommends"`
}

type BangumiService struct {
	BaseService
}

func (b *BangumiService) GetWeekList(bType string) (*weekBangumiResponse, error) {
	retBody, err := b.doRequest("http://app.bilibili.com/bangumi/timeline_v2", map[string]string{
		"_device":  "iphone",
		"btype":    bType,
		"platform": "ios",
		"type":     "json",
	})
	if err != nil {
		return nil, err
	}
	var ret weekBangumiResponse

	json.Unmarshal(retBody, &ret)

	return &ret, nil
}

func (b *BangumiService) GetIndex() (*bangumiIndexResponse, error) {
	retBody, err := b.doRequest("http://app.bilibili.com/api/region_ios/13.json", map[string]string{
		"platform": "ios",
		"device":   "phone",
	})
	if err != nil {
		return nil, err
	}

	var ret struct {
		Content bangumiIndexResponse `json:"result"`
	}

	json.Unmarshal(retBody, &ret)

	return &ret.Content, nil
}

func (b *BangumiService) GetBangumiInfo(seasonId string) (*bangumiInfoResponse, error) {
	retBody, err := b.doRequest("http://bangumi.bilibili.com/api/season_v4", map[string]string{
		"platform":  "ios",
		"build":     "3940",
		"season_id": seasonId,
		"type":      "bangumi",
	})
	if err != nil {
		return nil, err
	}

	var ret struct {
		Content bangumiInfoResponse `json:"result"`
	}

	json.Unmarshal(retBody, &ret)

	return &ret.Content, nil
}


================================================
FILE: client/base.go
================================================
package client

import (
	"encoding/json"
)

type BaseParam struct {
	Appkey string
	Secret string
}

type BaseService struct {
	Params BaseParam
	Client HttpClient
}

type apiResponse struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Error   string `json:"error"`
}

type ApiError struct {
	Msg string
}

func (a *ApiError) Error() string {
	return a.Msg
}

func (b *BaseService) doRequest(url string, params map[string]string) ([]byte, error) {
	params["appkey"] = b.Params.Appkey
	//generate bilibili sign code
	query, sign := EncodeSign(params, b.Params.Secret)
	reqUrl := url + "?" + query + "&sign=" + sign
	retByte, err := b.Client.Get(reqUrl)
	if err != nil {
		return nil, err
	}
	var badRet apiResponse
	err = json.Unmarshal(retByte, &badRet)
	if err != nil {
		return nil, &ApiError{Msg: "api encode error"}
	}

	if badRet.Code == 0 {
		//api return success
		return retByte, nil
	}

	//api return error
	return nil, &ApiError{Msg: badRet.Message + badRet.Error}
}


================================================
FILE: client/cli.go
================================================
package client

type BCli struct {
	Rank    RankService
	Bangumi BangumiService
	Video   VideoService
	Special SpecialService
	User    UserService
	Others  OthersService
}

func NewClient(appkey, secret string) *BCli {
	params := BaseParam{
		Appkey: appkey,
		Secret: secret,
	}

	client := NewHttpClient()

	base := BaseService{params, client}
	return &BCli{
		Rank:    RankService{base},
		Bangumi: BangumiService{base},
		Video:   VideoService{base},
		Special: SpecialService{base},
		User:    UserService{base},
		Others:  OthersService{base},
	}
}


================================================
FILE: client/others.go
================================================
package client

import (
	"encoding/json"
	"net/url"
	"strconv"
	"strings"
)

type videoTypeInfoElement struct {
	Tid   int    `json:"tid"`
	Name  string `json:"name"`
	Count int    `json:"count"`
}

type searchItem struct {
	Title            string `json:"title"`
	Cover            string `json:"cover"`
	Uri              string `json:"uri"`
	Params           string `json:"param"`
	Goto             string `json:"goto"`
	Desc             string `json:"desc"`

	MovieActors      string `json:"actors"`
	MovieStaff       string `json:"staff"`
	MoviePubDate     string `json:"screen_date"`
	MovieArea        string `json:"area"`
	MovieLength      int    `json:"length"`

	VideoPlay        int    `json:"play"`
	VideoDanmaku     int    `json:"danmaku"`
	VideoAuthor      string `json:"author"`
	VideoDuration    string `json:"duration"`

	SeasonTotalCount int    `json:"total_count"`
	SeasonDesc       string `json:"cat_desc"`

	UserFans         int `json:"fans"`
	UserSign         string `json:"sign"`
}

type searchItems struct {
	Seasons []searchItem `json:"season"`
	Movies  []searchItem  `json:"movie"`
	Vides   []searchItem  `json:"archive"`
}

type searchNavItem struct {
	Name  string `json:"name"`
	Total int    `json:"total"`
	Pages int    `json:"pages"`
	Type  int    `json:"type"`
}

type searchResponse struct {
	Page  int             `json:"page"`
	Navs  []searchNavItem `json:"nav"`
	Items searchItems     `json:"items"`
}

type searchByTypeResponse struct {
	AllPage int `json:"pages"`
	Items   []searchItem `json:"items"`
}

type BannerElement struct {
	Id     int `json:"id"`
	Name   string `json:"name"`
	Pic    string `json:"pic"`
	Url    string `json:"url"`
	PosNum int `json:"pos_num"`
}

type liveBanner struct {
	Title  string `json:"title"`
	Img    string `json:"img"`
	Remark string `json:"remark"`
	Link   string `json:"link"`
}

type liveElement struct {
	User          struct {
			      Face string `json:"face"`
			      Mid  int    `json:"mid"`
			      Name string `json:"name"`
		      } `json:"owner"`
	Cover         struct {
			      Src string `json:"src"`
		      } `json:"cover"`
	Title         string `json:"title"`
	RoomId        int    `json:"room_id"`
	Online        int    `json:"online"`
	Area          string `json:"area"`
	AreaId        int    `json:"area_id"`
	PlayUrl       string `json:"playurl"`
	AcceptQuality string `json:"accept_quality"`
}

type liveAppIndexResponse struct {
	Banners    []liveBanner `json:"banner"`
	Partitions []struct {
		Partition struct {
				  Id      int    `json:"id"`
				  Name    string `json:"name"`
				  Area    string `json:"area"`
				  SubIcon struct {
						  Src string `json:"src"`
					  } `json:"sub_icon"`
			  } `json:"partition"`
		Lives     []liveElement `json:"lives"`
	} `json:"partitions"`
	Recommend  struct {
			   Lives      []liveElement `json:"lives"`
			   BannerData []liveElement `json:"banner_data"`
		   } `json:"recommend_data"`
}

type OthersService struct {
	BaseService
}

/*
	order:
		"totalrank"
		"click"
		"pubdate"
		"dm"

	searchType:
		"all"
*/
func (o *OthersService) Search(keyword string, page, pageSize int, order string) (*searchResponse, error) {
	//url raw encode
	keywordEncode := strings.Replace(url.QueryEscape(keyword), "+", "%20", -1)
	retBody, err := o.doRequest("http://app.bilibili.com/x/v2/search", map[string]string{
		"keyword":     keywordEncode,
		"pn":        strconv.Itoa(page),
		"ps":    strconv.Itoa(pageSize),
		"device":      "phone",
		"main_ver":    "v3",
		"order":       order,
		"platform":    "ios",
	})
	if err != nil {
		return nil, err
	}

	var ret struct {
		Data searchResponse `json:"data"`
	}

	json.Unmarshal(retBody, &ret)

	return &ret.Data, nil
}

func (o *OthersService) SearchByType(keyword string, page, pageSize int, searchType int) (*searchByTypeResponse, error) {
	//url raw encode
	keywordEncode := strings.Replace(url.QueryEscape(keyword), "+", "%20", -1)
	retBody, err := o.doRequest("http://app.bilibili.com/x/v2/search/type", map[string]string{
		"keyword":     keywordEncode,
		"pn":        strconv.Itoa(page),
		"ps":    strconv.Itoa(pageSize),
		"mobi_app":      "iphone",
		"platform":    "ios",
		"device":       "phone",
		"type": strconv.Itoa(searchType),
	})
	if err != nil {
		return nil, err
	}

	var ret struct {
		Data searchByTypeResponse `json:"data"`
	}

	json.Unmarshal(retBody, &ret)

	return &ret.Data, nil
}

func (o *OthersService) AppIndex() (*liveAppIndexResponse, error) {
	retBody, err := o.doRequest("http://live.bilibili.com/AppIndex/home", map[string]string{
		"device":    "phone",
		"platform":  "ios",
		"scale":     "2",
		"actionKey": "appkey",
	})
	if err != nil {
		return nil, err
	}

	var ret struct {
		Data liveAppIndexResponse `json:"data"`
	}

	json.Unmarshal(retBody, &ret)

	return &ret.Data, nil
}

func (o *OthersService) IndexBanner() ([]BannerElement, error) {
	retBody, err := o.doRequest("http://api.bilibili.com/x/web-show/res/loc", map[string]string{
		"jsonp":"jsonp",
		"pf":"0",
		"id":"23",
	})

	if err != nil {
		return nil, err
	}

	var ret struct {
		Data []BannerElement `json:"data"`
	}

	json.Unmarshal(retBody, &ret)

	return ret.Data, nil
}


================================================
FILE: client/rank.go
================================================
package client

import (
	"encoding/json"
	"strconv"
)

type RankService struct {
	BaseService
}

type RankVideoElement struct {
	Title     string `json:"title"`
	Cover     string `json:"cover"`
	Uri       string `json:"uri"`
	Param     string `json:"param"`
	Goto      string `json:"goto"`
	Name      string `json:"name"`
	Play      int `json:"play"`
	Reply     int `json:"reply"`
	Favourite int `json:"favourite"`
	Danmaku   int `json:"danmaku"`
}

/*
	order:
		"view",
		"senddate",
		"reply",
		"danmaku",
		"favorite",
*/
func (r *RankService) SortRank(tid, page, pageSize int, order string) ([]RankVideoElement, error) {
	retBody, err := r.doRequest("http://app.bilibili.com/x/v2/region/show/child/list", map[string]string{
		"build":    "4040",
		"device":      "phone",
		"mobi_app":      "iphone",
		"platform":      "ios",
		"order":    order,
		"pn":     strconv.Itoa(page),
		"ps": strconv.Itoa(pageSize),
		"rid":      strconv.Itoa(tid),
	})
	if err != nil {
		return nil, err
	}
	var ret struct {
		List []RankVideoElement `json:"data"`
	}

	//delete the 'num' key (mdzz)
	json.Unmarshal(retBody, &ret)

	return ret.List, nil
}


================================================
FILE: client/special.go
================================================
package client

import (
	"encoding/json"
	"strconv"
)

type specialVideoElement struct {
	Aid   int    `json:"aid"`
	Cid   int    `json:"cid"`
	Cover string `json:"cover"`
	Title string `json:"title"`
	Click int    `json:"click"`
	Page  int    `json:"page"`
}

type specialVideosResponse struct {
	Count   int                   `json:"count"`
	Results int                   `json:"results"`
	List    []specialVideoElement `json:"list"`
}

type specialInfoResponse struct {
	SpId         int    `json:"spid"`
	Title        string `json:"title"`
	CreateAt     string `json:"create_at"`
	UpdateAt     string `json:"lastupdate_at"`
	Alias        string `json:"alias"`
	Cover        string `json:"cover"`
	IsBangumi    int    `json:"isbangumi"`
	IsBangumiEnd int    `json:"isbangumi_end"`
	BangumiDate  string `json:"bangumi_date"`
	Description  string `json:"description"`
	View         int    `json:"view"`
	VideoView    int    `json:"video_view"`
	Favourite    int    `json:"favourite"`
	Attention    int    `json:"attention"`
}

type SpecialService struct {
	BaseService
}

func (s *SpecialService) GetSpecialInfo(spid int) (*specialInfoResponse, error) {
	retBody, err := s.doRequest("http://api.bilibili.cn/sp", map[string]string{
		"spid": strconv.Itoa(spid),
	})
	if err != nil {
		return nil, err
	}

	var ret specialInfoResponse

	json.Unmarshal(retBody, &ret)

	return &ret, nil
}

/*
	isBangumi:
		the result is "bangumi" or other videos
*/
func (s *SpecialService) GetSpecialVideos(spid int, isBangumi bool) (*specialVideosResponse, error) {
	retType := 0
	if isBangumi {
		retType = 1
	}
	retBody, err := s.doRequest("http://api.bilibili.com/spview", map[string]string{
		"spid":    strconv.Itoa(spid),
		"bangumi": strconv.Itoa(retType),
		"type":    "json",
	})
	if err != nil {
		return nil, err
	}

	var ret specialVideosResponse

	json.Unmarshal(retBody, &ret)

	return &ret, nil
}


================================================
FILE: client/user.go
================================================
package client

import (
	"encoding/json"
	"strconv"
)

type userVideosResponse struct {
	List      []UserVideoElement              `json:"vlist"`
	TypeIndex map[string]videoTypeInfoElement `json:"tlist"`
}

type UserVideoElement struct {
	Aid         int    `json:"aid"`
	Copyright   string `json:"copyright"`
	TypeId      int    `json:"typeid"`
	Title       string `json:"title"`
	Subtitle    string `json:"subtitle"`
	Play        int    `json:"play"`
	Review      int    `json:"review"`
	VideoReview int    `json:"video_review"`
	Favorites   int    `json:"favorites"`
	Mid         int    `json:"mid"`
	Author      string `json:"author"`
	Description string `json:"description"`
	Created     string `json:"created"`
	Pic         string `json:"pic"`
	Comment     int    `json:"comment"`
	Length      string `json:"length"`
}

type userInfoResponse struct {
	Mid          int     `json:"mid"`
	Name         string  `json:"name"`
	Sex          string  `json:"sex"`
	Rank         int     `json:"rank"`
	Face         string  `json:"face"`
	Coins        float32 `json:"coins"`
	RegTime      int     `json:"regtime"`
	Birthday     string  `json:"birthday"`
	Place        string  `json:"place"`
	Description  string  `json:"description"`
	Attentions   []int   `json:"attentions"`
	FansNum      int     `json:"fans"`
	FriendNum    int     `json:"friend"`
	AttentionNum int     `json:"attention"`
	Sign         string  `json:"sign"`
}

type UserVideoResponse struct {
}

type UserService struct {
	BaseService
}

func (u *UserService) GetUserInfo(mid int) (*userInfoResponse, error) {
	retBody, err := u.doRequest("http://api.bilibili.cn/userinfo", map[string]string{
		"mid": strconv.Itoa(mid),
	})
	if err != nil {
		return nil, err
	}

	var ret userInfoResponse

	json.Unmarshal(retBody, &ret)

	return &ret, nil
}

func (u *UserService) GetUserVideos(mid, page, pageSize int) (*userVideosResponse, error) {
	retBody, err := u.doRequest("http://space.bilibili.com/ajax/member/getSubmitVideos", map[string]string{
		"mid":      strconv.Itoa(mid),
		"page":     strconv.Itoa(page),
		"pagesize": strconv.Itoa(pageSize),
	})
	if err != nil {
		return nil, err
	}

	var ret struct {
		Data userVideosResponse `json:"data"`
	}
	json.Unmarshal(retBody, &ret)

	return &ret.Data, nil
}


================================================
FILE: client/utils.go
================================================
package client

import (
	"crypto/md5"
	"encoding/hex"
	"github.com/valyala/fasthttp"
	"time"
	"sort"
	"strings"
	"fmt"
	"sync"
	"errors"
)

func EncodeSign(params map[string]string, secret string) (string, string) {
	queryString := httpBuildQuery(params)
	return queryString, Md5(queryString + secret)
}

func Md5(formal string) string {
	h := md5.New()
	h.Write([]byte(formal))
	return hex.EncodeToString(h.Sum(nil))
}

const (
	HTTP_TIMEOUT     = 2
	HTTP_BUFFER_SIZE = 2 * 1024
)

var (
	bufPool = &sync.Pool{New: func() interface{} {
		return make([]byte, HTTP_BUFFER_SIZE)
	}}
	//transport = http.Transport{
	//	Dial: func(network, addr string) (net.Conn, error) {
	//		deadline := time.Now().Add((HTTP_TIMEOUT + 2) * time.Second)
	//		c, err := net.DialTimeout(network, addr, HTTP_TIMEOUT*time.Second)
	//		if err != nil {
	//			return nil, err
	//		}
	//		c.SetDeadline(deadline)
	//		return c, nil
	//	},
	//	DisableKeepAlives: true,
	//}
)

type HttpClient struct {
	client *fasthttp.Client
}

func NewHttpClient() HttpClient {
	return HttpClient{
		client: &fasthttp.Client{ReadTimeout: HTTP_TIMEOUT * time.Second, WriteTimeout: HTTP_TIMEOUT * time.Second},
	}
}

//map to query string & sort by key
func httpBuildQuery(params map[string]string) string {
	list := make([]string, 0, len(params))
	buffer := make([]string, 0, len(params))
	for key := range params {
		list = append(list, key)
	}
	sort.Strings(list)
	for _, key := range list {
		value := params[key]
		buffer = append(buffer, key)
		buffer = append(buffer, "=")
		buffer = append(buffer, value)
		buffer = append(buffer, "&")
	}
	buffer = buffer[:len(buffer) - 1]
	return strings.Join(buffer, "")
}

func (b *HttpClient) Get(url string) ([]byte, error) {

	buf, _ := bufPool.Get().([]byte)
	defer bufPool.Put(buf)

	code, body, err := b.client.Get(buf, url)
	if err != nil {
		return nil, err
	}

	if code != 200 {
		return nil, errors.New(fmt.Sprintf("server return code %d", code))
	}

	return body, nil
}


================================================
FILE: client/video.go
================================================
package client

import (
	"encoding/json"
	"strconv"
)

type videoElement struct {
	Aid         string `json:"aid"`
	Mid         int    `json:"mid"`
	Copyright   string `json:"copyright"`
	TypeId      int    `json:"typeid"`
	TypeName    string `json:"typename"`
	Title       string `json:"title"`
	SubTitle    string `json:"subtitle"`
	Play        int    `json:"play"`
	Review      int    `json:"review"`
	VideoReview int    `json:"video_review"`
	Favorites   int    `json:"favorites"`
	Author      string `json:"author"`
	Description string `json:"description"`
	Create      string `json:"create"`
	Pic         string `json:"pic"`
	Credit      int    `json:"credit"`
	Coins       int    `json:"coins"`
	Duration    string `json:"duration"`
	Comment     int    `json:"comment"`
	BadGePay    bool   `json:"badgepay"`
}

type videoMidInfo struct {
	Page int    `json:"page"`
	Type string `json:"type"`
	Part string `json:"part"`
	Cid  int    `json:"cid"`
	Vid  int    `json:"vid"`
}

type videoInfoResponse struct {
	Tid         int                     `json:"tid"`
	TypeName    string                  `json:"typename"`
	ArcType     string                  `json:"arctype"`
	Play        string                  `json:"play"`
	Review      string                  `json:"review"`
	VideoReview string                  `json:"video_review"`
	Favorites   string                  `json:"favorites"`
	Title       string                  `json:"title"`
	Description string                  `json:"description"`
	Tag         string                  `json:"tag"`
	Pic         string                  `json:"pic"`
	Author      string                  `json:"author"`
	Mid         string                  `json:"mid"`
	AuthorFace  string                  `json:"face"`
	Pages       int                     `json:"pages"`
	CreatedAt   string                  `json:"created_at"`
	Coins       string                  `json:"coins"`
	PartList    map[string]videoMidInfo `json:"list"`
}

type videoDurl struct {
	Length    int      `json:"length"`
	Size      int      `json:"size"`
	Url       string   `json:"url"`
	BackupUrl []string `json:"backup_url"`
}

type videoPathResponse struct {
	result        string      `json:"result"`
	Format        string      `json:"format"`
	TimeLength    int         `json:"timelength"`
	AcceptFormat  string      `json:"accept_format"`
	AcceptQuality []int       `json:"accept_quality"`
	List          []videoDurl `json:"durl"`
}

type VideoService struct {
	BaseService
}

func (v *VideoService) GetVideoInfo(aid int) (*videoInfoResponse, error) {
	retBody, err := v.doRequest("http://api.bilibili.com/view", map[string]string{
		"batch":      "1",
		"check_area": "1",
		"id":         strconv.Itoa(aid),
		"platform":   "ios",
		"type":       "json",
	})
	if err != nil {
		return nil, err
	}
	var ret videoInfoResponse

	json.Unmarshal(retBody, &ret)

	return &ret, nil
}

/**
videoType:
	"flv"
	"hdmp4"
	"mp4"

quality:
	1,2,3

*/
func (v *VideoService) GetVideoPartPath(cid int, quality int) (*videoPathResponse, error) {
	query, sign := EncodeSign(map[string]string{
		"cid":            strconv.Itoa(cid),
		"from":           "miniplay",
		"player":         "1",
		"otype":          "json",
		"type":           "mp4",
		"quality":        strconv.Itoa(quality),
		"appkey":         "f3bb208b3d081dc8",
	}, "1c15888dc316e05a15fdd0a02ed6584f")

	url := "http://interface.bilibili.com/playurl?&" + query + "&sign=" + sign

	retBody, err := v.Client.Get(url)

	var ret videoPathResponse

	json.Unmarshal(retBody, &ret)

	return &ret, err
}


================================================
FILE: conf.example.json
================================================
{
  "debug": true,
  "appkey": "",
  "secret": ""
}

================================================
FILE: conf.json
================================================
{
  "debug": false,
  "appkey": "4ebafd7c4951b366",
  "secret": "8cb98205e9b2ad3669aad0fce12a4c13",
  "private": false,
  "allow_host": "http://bilibilih5.club"
}


================================================
FILE: deploy.sh
================================================
#!/bin/bash

IMAGENAME="bilibili-go"
TAGENAME="whiteblue/bilibili-go"

sudo -l || ( echo "Error: scripts need run with 'sudo'" && exit -1 )


( cid=`sudo docker ps -a |grep $IMAGENAME | awk '{printf $1" "}'`

if [ "$cid" !=  "" ];then

sudo docker rm -f $cid && echo "delete container $cid"

fi )

( iid=`sudo docker images | grep $IMAGENAME | awk '{printf $3" "}'`

if [ "$iid" !=  "" ];then

sudo docker rmi $iid && echo "delete image $iid"

fi )


sudo docker build -t $TAGENAME . || ( echo "Build image failed....." && exit -1 )

echo "build success"

sudo docker run -d --name $IMAGENAME -p 80:8080 $TAGENAME

echo "run container success"

exit 0


================================================
FILE: docs/api_doc.md
================================================
## 接口地址

URL : ```http://bilibili-service.daoapp.io```

基于DaoCloud免费容器


## 接口文档

* 数据格式: ```application/json```
* 请求方式: ```post/get```


#### 基本状态码(HTTP)约定:

	500: 服务器错误(API返回异常)
	404: 请求的资源不可得
	200: 成功
	400: 参数异常


#### 错误返回格式:

	{
    "code": "PARAM_ERROR",
    "message": "request valitdate error"
	}


#### 分类(sort)约定:
        '1' => '动画',
        '3' => '音乐',
        '4' => '游戏',
        '5' => '娱乐',
        '11' => '电视剧'
        '13' => '番剧',
        '23' => '电影',
        '36' => '科技',
        '119' => '鬼畜',
        '129' => '舞蹈',


### 1. 首页内容获取
---
取得主要分类下的前10个热门视频

* URL: ```/allrank```
* 请求方式: GET
* 示例:
	*  ```curl -X GET -H "Content-Type: application/json" -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/allrank"```

* 参数: 无


成功返回:

```
[
  {
    "sort_name": "动画",
    "videos": [
      {
        "aid": "5697545",
        "mid": 28965086,
        "copyright": "Original",
        "typeid": 27,
        "typename": "综合",
        "title": "【妈的智障】笑到胃疼的动画片段(第七期)片尾洗澡&足控福利",
        "subtitle": "",
        "play": 1398924,
        "review": 3435,
        "video_review": 24134,
        "favorites": 40908,
        "author": "噗汪汪",
        "description": "看你们谁还敢说我短!嗯?不知道怎么抽奖的请看上期视频。番名按照顺序分别是:银魂、超元气三姐妹、男子高中生的日常、妄想学生会、潜行吧奈亚子、濑户的花嫁、我们大家的河合庄和悠哉日常大王,片尾是银魂OAD。谢谢支持!",
        "create": "2016-08-07 19:12",
        "pic": "http://i2.hdslb.com/bfs/archive/7b2aced4ba0e5924b12fcf5a0ad36d8c2728b73d.jpg_320x200.jpg",
        "credit": 0,
        "coins": 6264,
        "duration": "24:28",
        "comment": 24134,
        "badgepay": false
      },
      ...
]
```

### 2. 分类排行获取
---
各分类下的排行

* URL: ```/sort/{tid}```
	* ```tid```为分类id(例如“动画”=>13)
* 请求方式: GET
* 示例: 	
	* ```curl -X GET -H "Content-Type: application/json" -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/sort/13"```

* 参数:

	* ```page```: 页码
	* ```count```: 分页容量
	* ```order```: 排序方式(new,hot)


成功返回:

```
{
  "name": "番剧",
  "list": {
    "0": {
      "aid": "5698105",
      "mid": 21453565,
      "copyright": "Copy",
      "typeid": 33,
      "typename": "连载动画",
      "title": "【4月】Re:从零开始的异世界生活 19",
      "subtitle": "",
      "play": 2493341,
      "review": 42346,
      "video_review": 191673,
      "favorites": 2157,
      "author": "TV-TOKYO",
      "description": "#19 白鲸攻略战",
      "create": "2016-08-07 19:46",
      "pic": "http://i1.hdslb.com/bfs/archive/a0656101763a68a4bcb3fe603496037c253e106d.jpg_320x200.jpg",
      "credit": 0,
      "coins": 15908,
      "duration": "24:35",
      "comment": 191673,
      "badgepay": false
    },
    "1":{...},
    ...
    }
}
```

### 3. 视频信息获取
---
各分类下的排行

* URL: ```/view/{aid}```
 	* aid => av号
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Content-Type: application/json" -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/view/5698105"```

* 参数: 无


成功返回:

```
{
  "tid": 33,
  "typename": "连载动画",
  "arctype": "Copy",
  "play": "2493735",
  "review": "42400",
  "video_review": "191632",
  "favorites": "2159",
  "title": "【4月】Re:从零开始的异世界生活 19",
  "description": "#19 白鲸攻略战",
  "tag": "TV动画,BILIBILI正版,RE:从零开始的异世界生活,从零开始的异世界生活",
  "pic": "http://i0.hdslb.com/bfs/archive/a0656101763a68a4bcb3fe603496037c253e106d.jpg",
  "author": "TV-TOKYO",
  "mid": "21453565",
  "face": "http://i0.hdslb.com/bfs/face/69ef6861067d6ef637b7c73b77d71c3414996745.jpg",
  "pages": 1,
  "created_at": "2016-08-08 01:05",
  "coins": "15911",
  "list": {
    "0": {
      "page": 1,
      "type": "vupload",
      "part": "ReZERO_19",
      "cid": 9253164,
      "vid": 0
    }
  }
}
```


### 4. 视频地址解析
---
mp4/flv视频源取得,(注意某些老视频没有mp4源)

* URL: ```/video/{cid}```
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/video/9253164?quality=2"```
* 参数:
	* ```quality```:清晰度(1~2,根据视频有不同)
	* ```type```: 格式(mp4/flv)


成功返回:

```
{
  "format": "hdmp4",
  "timelength": 1476160,
  "accept_format": "mp4,hdmp4",
  "accept_quality": [
    2,
    1
  ],
  "durl": [
    {
      "length": 1476160,
      "size": 206950377,
      "url": "http://cn-tj1-cu.acgvideo.com/vg123/3/2e/9253164-1-hd.mp4?expires=1471021200&ssig=1NyYrtPpFZmm4zHVClIHzA&oi=2067479167&rate=0",
      "backup_url": [
        "http://cn-sddz2-cu.acgvideo.com/vg1/3/6d/9253164-1-hd.mp4?expires=1471021200&ssig=actJTZUGft5yN6fSQGL9Kw&oi=2067479167&rate=0",
        "http://cn-sdjn-cu-v-01.acgvideo.com/vg6/e/d5/9253164-1-hd.mp4?expires=1471021200&ssig=7I1sNpH6szF5CtNP4bwfXA&oi=2067479167&rate=0"
      ]
    }
  ]
}
```


### 5. 番剧更新列表
---
目前B站版权二次元新番

* URL: ```/bangumi```
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/bangumi"```

* 参数: 无


成功返回:

```
{
  "0": [
    {
      "area": "日本",
      "arealimit": 0,
      "attention": 471585,
      "bangumi_id": 1070,
      "bgmcount": "24",
      "brief": null,
      "cover": "http://i1.hdslb.com/u_user/e6835e74ce9d6f63bc44d4f42dfc82e4.jpg",
      "danmaku_count": 439806,
      "favorites": 471585,
      "is_finish": 0,
      "lastupdate": 1451152800,
      "lastupdate_at": "2015-12-27 02:00:00",
      "new": true,
      "play_count": 9646746,
      "pub_time": "",
      "season_id": 2760,
      "spid": 56749,
      "square_cover": "http://i0.hdslb.com/sp/1e/1e21c6a6e17f5419eb1e10fadc53e6eb.jpg",
      "title": "终结的炽天使 第二季",
      "url": "/bangumi/i/2760/",
      "weekday": 0
    },
    ...
 ],
 ...
}
```


### 6. 专题信息查看
---
例如番剧专题

* URL:
    * ```/spinfo/{spid}```
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/spinfo/56749"```

* 参数: 无


成功返回:

```
{
    "alias": "终わりのセラフ",
    "alias_spid": 41465,
    "attention": 367438,
    "bangumi_date": "2015-10-01",
    "count": 40,
    "cover": "http://i0.hdslb.com/sp/1e/1e21c6a6e17f5419eb1e10fadc53e6eb.jpg",
    "create_at": "2014-12-12 21:19",
    "description": "电视动画《终结的炽天使》改编自日本轻小说家镜贵也原作、漫画家山本大和作画的同名漫画。\r\n2014年8月28日,发表了《终结的炽天使》电视动画化的决定。\r\n2014年12月20日,在日本千叶县幕张展览馆开幕的“Jump Festa 2015”会场上,宣布电视动画《终结的炽天使》会被分割成两个季度播出。\r\n第1期的播送时间为2015年4月4日-6月20日。\r\n第2期则是同年的10月至12月。",
    "favourite": 172114,
    "isbangumi": 1,
    "isbangumi_end": 1,
    "lastupdate": 1450364026,
    "lastupdate_at": "2015-12-17 22:53",
    "pubdate": 1418390386,
    "season": [
        {
            "default": false,
            "index_cover": "http://i2.hdslb.com/sp/5c/5c7dbad52d522b6a5cbc8fe383ed92fe.jpg",
            "last_episode": null,
            "season_id": 2052,
            "season_name": "第一季",
            "video_view": 826087
        },
        {
            "default": false,
            "index_cover": "http://i1.hdslb.com/sp/87/87d650e53a8d50302a369365063c45a4.jpg",
            "last_episode": null,
            "season_id": 2053,
            "season_name": "第二季",
            "video_view": 4314354
        }
    ],
    "season_id": 2053,
    "spid": 56749,
    "title": "终结的炽天使 第二季",
    "video_view": 28723627,
    "view": 1350787
}
```

### 7. 专题视频获取
---
取得专题下的所有视频

* URL: ```/spvideos/{spid}```
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/spvideos/56749?bangumi=0"```

* 参数:
	* ```bangumi```: 取得番剧视频:1,其他视频:0


成功返回:

```
{
    "code": 0,
    "count": 17,
    "list": [
        {
            "aid": 2330598,
            "cid": 3638258,
            "click": 498347,
            "cover": "http://i2.hdslb.com/video/51/512fc7fce5bb04a42fe116eb5500af20.jpg",
            "from": "vupload",
            "page": 0,
            "title": "「终结的炽天使」OP ED专辑"
        },
        {
            "aid": 2425245,
            "cid": 3796297,
            "click": 101788,
            "cover": "http://i0.hdslb.com/video/4c/4cf6be151a858c200e4aa5ac07c2ccd1.jpg",
            "from": "vupload",
            "page": 0,
            "title": "让我们的炽天使燃起来吧Answer is near【MAD】"
        },
				...
    ],
    "results": 17,
    "spid": 56749
}
```


### 8. 全站搜索
---

* URL: ```/search```
* 请求方式: POST
* 示例:
	* ```curl -X POST -H "Cache-Control: no-cache" -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "content=Fate" "http://bilibili-service.daoapp.io/search"```

* 参数:
	* ```content```: 搜索内容
	* ```page```: 页码
	* ```count```: 分页大小

成功返回:

```
{
  "page": 1,
  "pagesize": 20,
  "pageinfo": {
    "bangumi": {
      "total": 11,
      "numResults": 11,
      "pages": 4
    },
    "movie": {
      "total": 2,
      "numResults": 2,
      "pages": 1
    },
    "pgc": {
      "total": 2,
      "numResults": 2,
      "pages": 1
    },
    "special": {
      "total": 27,
      "numResults": 24,
      "pages": 8
    },
    "topic": {
      "total": 10,
      "numResults": 10,
      "pages": 4
    },
    "tvplay": {
      "total": 0,
      "numResults": 0,
      "pages": 1
    },
    "upuser": {
      "total": 86,
      "numResults": 86,
      "pages": 29
    },
    "video": {
      "total": 24742,
      "numResults": 999,
      "pages": 50
    }
  },
  "result": {
    "video": [
      {
        "aid": "4912937",
        "mid": 777536,
        "copyright": "",
        "typeid": 0,
        "typename": "综合",
        "title": "【灵魂配音】10分钟演完fate stay night UBW",
        "subtitle": "",
        "play": 1121120,
        "review": 6922,
        "video_review": 18656,
        "favorites": 32058,
        "author": "LexBurner",
        "description": "自制 试水作,感谢新月冰冰配以及小鹤儿的帮忙,希望以后参与的人能越来越多,做的越来越好玩,这次还不是很到位,以后继续努力啦\r\nlex的零食铺:http://lexzhils.taobao.com\r\nlex的新浪微博:http://weibo.com/lexburner\r\n新月冰冰视频空间:http://space.bilibili.com/3295/#!/index\r\n小鹤儿视频空间:http://space.bilibili.com/6719190/#!/index",
        "create": "",
        "pic": "http://i0.hdslb.com/bfs/archive/7edf866255ae2d9a8f31c176c0873769d6451243.jpg_320x200.jpg",
        "credit": 0,
        "coins": 0,
        "duration": "10:33",
        "comment": 0,
        "badgepay": false
      },
      ...
      ]
  }   
}
```

### 9. 用户信息
---
* URL: ```/user/{mid}```
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/user/116683"```

* 参数: 无

成功返回:

```
{
  "mid": 116683,
  "name": "=咬人猫=",
  "sex": "女",
  "rank": 10000,
  "face": "http://i1.hdslb.com/bfs/face/8fad84a4470f3d894d8f0dc95555ab8f2cb10a83.jpg",
  "coins": 63211.8,
  "regtime": 1301718879,
  "birthday": "0000-00-00",
  "place": "",
  "description": "bilibili 知名舞见",
  "attentions": [
    179628,
    271126,
    622863,
    5055,
    433715,
    6870383,
    4350178,
    8084905
  ],
  "fans": 627933,
  "friend": 8,
  "attention": 8,
  "sign": "面瘫女仆酱~小粗腿~事业线什么的!!吐槽你就输了!喵~"
}
```

### 10. 用户视频
---
* URL: ```/uservideos/{mid}```
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/uservideos/116683"```

* 参数:
	* ```page```: 页码
	* ```count```: 分页容量


成功返回:

```
{
  "vlist": [
    {
      "aid": 5682645,
      "copyright": "Original",
      "typeid": 154,
      "title": "【咬人猫】落花情❤ o(*≧▽≦)ツ",
      "subtitle": "",
      "play": 752435,
      "review": 6402,
      "video_review": 9549,
      "favorites": 25734,
      "mid": 116683,
      "author": "=咬人猫=",
      "description": "也许是有史以来最忐忑的一次投稿,这次尝试的风格对我来说挑战很大,完全没有这类舞蹈的基础,所以当时这支舞挖坑填了一段时间因为实在是能力有限,被我搁置了很久~后来因为确实是太喜欢这种感觉的舞蹈了,又重新鼓起劲去学,也尝试了不同的服装版本录制了很多次,非常想完成这一支舞,虽然还有很多地方不够好,也希望大家多多包涵,谢谢大家的支持和等待~\n服装:七秀萝莉定国套\n舞蹈歌曲:七朵组合(一代)的作品《落花情》。",
      "created": "2016-08-06 20:20:45",
      "pic": "http://i0.hdslb.com/bfs/archive/a96729ac9e0a14544d1fcdb1471d23cf7ac1e61b.jpg",
      "comment": 9549,
      "length": "03:46"
    },
    ...
   ]
}
```



### 11. 新番推荐
---

* URL: ```/bangumiindex```
* 请求方式: GET
* 示例:
	* ```curl -X GET -H "Cache-Control: no-cache" "http://bilibili-service.daoapp.io/bangumiindex"```

参数: 无


成功返回:

```
{
  "banners": [
    {
      "title": "美术社大有问题",
      "link": "http://www.bilibili.com/bangumi/i/5043/",
      "img": "http://i0.hdslb.com/bfs/archive/c69c196266bfe6fb52e05d5752f4687e501d83e8.jpg",
      "simg": "",
      "aid": 0,
      "type": "link",
      "platform": 0,
      "pid": 0
    },
    {
      "title": "魔法战争",
      "link": "http://www.bilibili.com/bangumi/i/4367/",
      "img": "http://i0.hdslb.com/bfs/archive/3324a9fc99275ef22d0364bd3221e009020d9567.jpg",
      "simg": "",
      "aid": 0,
      "type": "link",
      "platform": 0,
      "pid": 0
    },
    {
      "title": "月歌",
      "link": "http://bangumi.bilibili.com/anime/5038",
      "img": "http://i0.hdslb.com/bfs/archive/dfbc9098218c7deffed53dd9c14619d88ac8f180.jpg",
      "simg": "",
      "aid": 0,
      "type": "link",
      "platform": 0,
      "pid": 0
    },
    {
      "title": "灵能百分百",
      "link": "http://bangumi.bilibili.com/anime/5058",
      "img": "http://i0.hdslb.com/bfs/archive/35e322a660aa83ada2a6ae94923c27510f40fd26.jpg",
      "simg": "",
      "aid": 0,
      "type": "link",
      "platform": 0,
      "pid": 0
    }
  ],
  "recommends": [
    {
      "aid": "5753187",
      "title": "【蒼氏甜品坊】初恋怪兽「…这是我的广播、要怎么办呢?」",
      "subtitle": "",
      "play": 631,
      "review": 0,
      "video_review": 82,
      "favorites": 139,
      "mid": 628114,
      "author": "祈妹",
      "description": "TV动画初恋怪兽的应援广播,配信日为每周五,主持人为苍井翔太。\n一个与动画内容相比在污和hentai的层面毫不逊色的广播节目。\n这是甜品坊第一次做广播,由于人手、经验和水平的不足不能保证翻译完全准确,有错误的地方欢迎指正~",
      "create": "2016-08-10 22:24",
      "pic": "http://i0.hdslb.com/bfs/archive/8770dd5682edb0170b5d3bfe08b8763aff9f7e4d.jpg_320x200.jpg",
      "coins": 40,
      "duration": "128:36"
    },
    ...
  ]
}
```


================================================
FILE: main.go
================================================
package main

import (
	"github.com/go-playground/log"
	"github.com/whiteblue/bilibili-go/service"
)

func main() {
	app, err := service.NewApplication("conf.json")
	if err != nil {
		log.Fatal(err)
	}

	app.Router.Run(":8080")
}


================================================
FILE: run.sh
================================================
#!/bin/sh


godep go build && ./bilibili-go


================================================
FILE: service/application.go
================================================
package service

import (
	"github.com/gin-gonic/contrib/gzip"
	"github.com/gin-gonic/gin"
	"github.com/go-playground/log"
	"github.com/go-playground/log/handlers/console"
	"github.com/whiteblue/bilibili-go/client"
	"time"
)

const (
	INDEX_CACHE = "index"
	ALL_RANK_CACHE = "all_rank"
	BANGUMI_CACHE = "bangumi"
	BANGUMI_LIST_CACHE = "bangumi_list"
	SORT_TOP_CACHE = "sort-"
	LIVE_INDEX_CACHE = "live_index"
	INDEX_BANNER_CACHE = "index_banner"
)

var (
	ProdLevels = []log.Level{
		log.InfoLevel,
		log.NoticeLevel,
		log.WarnLevel,
		log.ErrorLevel,
		log.PanicLevel,
		log.AlertLevel,
		log.FatalLevel,
	}
)

type BiliBiliApplication struct {
	Router *gin.Engine
	Corn   *CornService
	Conf   *Config
	Client *client.BCli
	Cache  *CacheManager
}

func NewApplication(configFile string) (*BiliBiliApplication, error) {
	conf, err := ReadConfigFromFile(configFile)

	if err != nil {
		return nil, err
	}

	cLog := console.New()

	if conf.Debug {
		log.RegisterHandler(cLog, log.AllLevels...)
		gin.SetMode(gin.DebugMode)
	} else {
		log.RegisterHandler(cLog, ProdLevels...)
		gin.SetMode(gin.ReleaseMode)
	}

	log.Info("conform config file")

	r := gin.New()

	//use gzip
	r.Use(gin.Recovery())
	r.Use(gzip.Gzip(gzip.BestCompression))

	//corn service
	corn := NewCornService()

	//bilibili client
	cli := client.NewClient(conf.Appkey, conf.Secret)

	cache := NewCacheManager()

	app := &BiliBiliApplication{Router: r, Corn: corn, Conf: conf, Client: cli, Cache: cache}

	ConformRoute(app)

	log.Info("conform route")

	conformTask(app)
	corn.Start()

	log.Info("conform task")

	log.Info("init complete, start listen...")

	return app, nil
}

func conformTask(app *BiliBiliApplication) {
	app.Corn.RegisterTask(&IndexInfoTask{CornTask: CornTask{Name: "index_info", Duration: 2 * time.Hour}, app: app})
	app.Corn.RegisterTask(&BangumiInfoTask{CornTask: CornTask{Name: "bangumi_info", Duration: 6 * time.Hour}, app: app})
	app.Corn.RegisterTask(&BangumiListTask{CornTask: CornTask{Name: "bangumi_list", Duration: 6 * time.Hour}, app: app})
	app.Corn.RegisterTask(&TopRankTask{CornTask: CornTask{Name: "top_rank", Duration: 2 * time.Hour}, app: app})
	app.Corn.RegisterTask(&LiveIndexTask{CornTask: CornTask{Name: "alive_index", Duration: 2 * time.Hour}, app: app})
	app.Corn.RegisterTask(&BannerTask{CornTask: CornTask{Name: "index_banner", Duration: 6 * time.Hour}, app: app})
}


================================================
FILE: service/cache.go
================================================
package service

import "sync"

type CacheManager struct {
	cacheMap map[string]interface{}
	lock     *sync.RWMutex
}

func (c *CacheManager) GetCache(key string) interface{} {
	c.lock.RLock()
	defer c.lock.RUnlock()
	elem, ok := c.cacheMap[key]
	if !ok {
		return nil
	}
	return elem
}

func (c *CacheManager) SetCache(key string, value interface{}) {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.cacheMap[key] = value
}

func NewCacheManager() *CacheManager {
	return &CacheManager{cacheMap: make(map[string]interface{}), lock: &sync.RWMutex{}}
}


================================================
FILE: service/config.go
================================================
package service

import (
	"encoding/json"
	"io/ioutil"
	"os"
)

type Config struct {
	Debug     bool   `json:"debug"`
	Appkey    string `json:"appkey"`
	Secret    string `json:"secret"`
	AllowHost string `json:"allow_host"`
	IsPrivate bool   `json:"private"`
}

func ReadConfigFromFile(filename string) (*Config, error) {
	if _, err := os.Stat(filename); os.IsNotExist(err) {
		return nil, err
	}
	bytes, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, err
	}
	var conf Config
	err = json.Unmarshal(bytes, &conf)
	if err != nil {
		return nil, err
	}
	return &conf, nil
}


================================================
FILE: service/corn.go
================================================
package service

import (
	"github.com/anacrolix/sync"
	"github.com/go-playground/log"
	"time"
)

type CornTaskImpl interface {
	Run() error
	Success()
	Failure(error)
	GetName() string
	GetDuration() time.Duration
	GetLastRun() time.Time
	SyncLastRunTime()
}

type CornTask struct {
	Name     string
	Duration time.Duration
	LastRun  time.Time
}

func (t *CornTask) Run() error {
	return nil
}

func (t *CornTask) Success() {

}

func (t *CornTask) Failure(err error) {

}

func (t *CornTask) GetName() string {
	return t.Name
}

func (t *CornTask) GetDuration() time.Duration {
	return t.Duration
}

func (t *CornTask) GetLastRun() time.Time {
	return t.LastRun
}

func (t *CornTask) SyncLastRunTime() {
	t.LastRun = time.Now()
}

//execute task
func exec(f CornTaskImpl) {
	log.Info("invoke task, taskname: ", f.GetName())
	defer func() {
		if r := recover(); r != nil {
			log.Error(r)
		}
	}()

	if err := f.Run(); err != nil {
		f.Failure(err)
	} else {
		f.Success()
	}
	log.Info("run task end, taskname: ", f.GetName())
}

type CornService struct {
	ticker *time.Ticker
	tasks  []CornTaskImpl
	lock   sync.Mutex
	done   chan struct{}
}

func (c *CornService) RegisterTask(task CornTaskImpl) {
	task.SyncLastRunTime()
	exec(task)
	c.tasks = append(c.tasks, task)
}

func (c *CornService) syncTaskList(nowTime time.Time) {
	for _, task := range c.tasks {
		//Unix timestamp => duration
		between := time.Duration(nowTime.Unix()-task.GetLastRun().Unix()) * time.Second
		if between >= task.GetDuration() {
			task.SyncLastRunTime()
			exec(task)
		}
	}
}

func (c *CornService) loop() {
	for {
		select {
		case <-c.done:
			log.Info("corn loop stopped....")
			return
		case nowTime := <-c.ticker.C:
			go c.syncTaskList(nowTime)
		}
	}
}

func (c *CornService) Start() {
	go c.loop()
}

func (c *CornService) Stop() {
	c.ticker.Stop()
	close(c.done)
}

func NewCornService() *CornService {
	return &CornService{
		ticker: time.NewTicker(time.Minute),
		tasks:  []CornTaskImpl{},
		lock:   sync.Mutex{},
		done:   make(chan struct{}, 1),
	}
}


================================================
FILE: service/corn_tasks.go
================================================
package service

import (
	"strconv"
)

var (
	_INDEX_SORTS = []int{
		24, 33, 31, 20, 17, 36, 119,
	}
)

type SortRankInfo struct {
	SortId int        `json:"sort_id"`
	Videos []interface{} `json:"videos"`
}

type IndexInfoTask struct {
	CornTask
	app *BiliBiliApplication
}

func (i *IndexInfoTask) Run() error {
	retInfo := make([]SortRankInfo, 0, len(_INDEX_SORTS))
	for _, sortId := range _INDEX_SORTS {
		back, err := i.app.Client.Rank.SortRank(sortId, 1, 10, "view")
		if err != nil {
			return err
		}
		videos := make([]interface{}, 0, len(back))

		for _, v := range (back) {
			videos = append(videos, v)
		}

		sortRank := SortRankInfo{SortId: sortId, Videos: videos}

		retInfo = append(retInfo, sortRank)

		sortCacheName := SORT_TOP_CACHE + strconv.Itoa(sortId)

		i.app.Cache.SetCache(sortCacheName, sortRank)

	}
	i.app.Cache.SetCache(INDEX_CACHE, retInfo)
	return nil
}

type BangumiInfoTask struct {
	CornTask
	app *BiliBiliApplication
}

func (i *BangumiInfoTask) Run() error {
	ret, err := i.app.Client.Bangumi.GetIndex()
	if err != nil {
		return err
	}
	i.app.Cache.SetCache(BANGUMI_CACHE, ret)
	return nil
}

type BangumiListTask struct {
	CornTask
	app *BiliBiliApplication
}

func (i *BangumiListTask) Run() error {
	ret, err := i.app.Client.Bangumi.GetWeekList("2")
	if err != nil {
		return err
	}
	i.app.Cache.SetCache(BANGUMI_LIST_CACHE, ret)
	return nil
}

type TopRankTask struct {
	CornTask
	app *BiliBiliApplication
}

func (i *TopRankTask) Run() error {
	ret, err := i.app.Client.Rank.SortRank(0, 1, 8, "hot")
	if err != nil {
		return err
	}
	i.app.Cache.SetCache(ALL_RANK_CACHE, ret)
	return nil
}

type LiveIndexTask struct {
	CornTask
	app *BiliBiliApplication
}

func (i *LiveIndexTask) Run() error {
	ret, err := i.app.Client.Others.AppIndex()
	if err != nil {
		return err
	}
	i.app.Cache.SetCache(LIVE_INDEX_CACHE, ret)
	return nil
}

type BannerTask struct {
	CornTask
	app *BiliBiliApplication
}

func (b *BannerTask) Run() error {
	ret, err := b.app.Client.Others.IndexBanner()
	if err != nil {
		return err
	}
	b.app.Cache.SetCache(INDEX_BANNER_CACHE, ret)
	return nil
}


================================================
FILE: service/router.go
================================================
package service

import (
	"github.com/gin-gonic/gin"
	"strconv"
	"strings"
)

func MakeFailedJsonMap(code string, message string) map[string]string {
	return map[string]string{
		"code":    code,
		"message": message,
	}
}

func ConformRoute(app *BiliBiliApplication) {
	allowOrigin := "*"

	if app.Conf.IsPrivate {
		allowOrigin = app.Conf.AllowHost
	}

	app.Router.Use(func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", allowOrigin)
		c.Header("Access-Control-Allow-Headers", "Content-Type")
		c.Header("Access-Control-Max-Age", "7200")
	})

	app.Router.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{"message": "BiliBili-Service 2.1"})
	})

	app.Router.GET("/allrank", func(c *gin.Context) {
		back := app.Cache.GetCache(INDEX_CACHE)
		c.JSON(200, back)
	})

	app.Router.GET("/toprank", func(c *gin.Context) {
		back := app.Cache.GetCache(ALL_RANK_CACHE)
		c.JSON(200, back)
	})

	app.Router.GET("/view/:aid", func(c *gin.Context) {
		aid := c.Param("aid")
		aidNum, err := strconv.Atoi(aid)
		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM ERROR", err.Error()))
			return
		}
		list, err := app.Client.Video.GetVideoInfo(aidNum)
		if err != nil {
			c.JSON(404, MakeFailedJsonMap("VIDEO_NOT_FOUND", err.Error()))
			return
		}
		c.JSON(200, list)
	})

	app.Router.GET("/video/:cid", func(c *gin.Context) {
		cid := c.Param("cid")
		quality := c.DefaultQuery("quality", "1")
		cidNum, err := strconv.Atoi(cid)
		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM ERROR", err.Error()))
			return
		}
		qualityNum, err := strconv.Atoi(quality)
		if err != nil {
			qualityNum = 1
		}
		list, err := app.Client.Video.GetVideoPartPath(cidNum, qualityNum)
		if err != nil {
			c.JSON(404, MakeFailedJsonMap("VIDEO_NOT_FOUND", err.Error()))
			return
		}
		c.JSON(200, list)
	})

	app.Router.GET("/user/:mid", func(c *gin.Context) {
		mid := c.Param("mid")
		midNum, err := strconv.Atoi(mid)
		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM ERROR", err.Error()))
			return
		}
		list, err := app.Client.User.GetUserInfo(midNum)
		if err != nil {
			c.JSON(404, MakeFailedJsonMap("USER_NOT_FOUND", err.Error()))
			return
		}
		c.JSON(200, list)
	})

	app.Router.GET("/uservideos/:mid", func(c *gin.Context) {
		mid := c.Param("mid")
		page := c.DefaultQuery("page", "1")
		pageSize := c.DefaultQuery("page_size", "20")

		midNum, err := strconv.Atoi(mid)
		pageNum, err := strconv.Atoi(page)
		pageSizeNum, err := strconv.Atoi(pageSize)

		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM ERROR", err.Error()))
			return
		}
		list, err := app.Client.User.GetUserVideos(midNum, pageNum, pageSizeNum)
		if err != nil {
			c.JSON(404, MakeFailedJsonMap("USER_NOT_FOUND", err.Error()))
			return
		}
		c.JSON(200, list)
	})

	app.Router.GET("/search", func(c *gin.Context) {
		content := c.Query("content")
		page := c.DefaultQuery("page", "1")
		pageSize := c.DefaultQuery("page_size", "20")
		order := c.DefaultQuery("order", "totalrank")

		var err error
		pageNum, err := strconv.Atoi(page)
		pageSizeNum, err := strconv.Atoi(pageSize)

		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM_ERROR", ""))
			return
		}

		if strings.TrimSpace(content) == "" {
			c.JSON(400, MakeFailedJsonMap("PARAM 'content' is '' or not set", ""))
			return
		}

		list, err := app.Client.Others.Search(content, pageNum, pageSizeNum, order)
		if err != nil {
			c.JSON(500, MakeFailedJsonMap("API_RETURN_ERROR", err.Error()))
			return
		}

		c.JSON(200, list)
	})

	//type :
	//	bangumi => 1
	//	user =>2
	//	movie=>3
	//	sp=>4

	app.Router.GET("/searchbytype", func(c *gin.Context) {
		content := c.Query("content")
		page := c.DefaultQuery("page", "1")
		pageSize := c.DefaultQuery("page_size", "20")
		searchType := c.Query("type")

		var err error
		pageNum, err := strconv.Atoi(page)
		pageSizeNum, err := strconv.Atoi(pageSize)

		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM_ERROR", ""))
			return
		}

		if strings.TrimSpace(content) == "" {
			c.JSON(400, MakeFailedJsonMap("PARAM 'content' is '' or not set", ""))
			return
		}

		if strings.TrimSpace(searchType) == "" {
			c.JSON(400, MakeFailedJsonMap("PARAM 'type' is '' or not set", ""))
			return
		}

		typeInt := 1
		switch searchType {
		case "user":typeInt = 2
		case "movie":typeInt = 3
		case "sp":typeInt = 4
		}

		list, err := app.Client.Others.SearchByType(content, pageNum, pageSizeNum, typeInt)
		if err != nil {
			c.JSON(500, MakeFailedJsonMap("API_RETURN_ERROR", err.Error()))
			return
		}

		c.JSON(200, list)
	})

	app.Router.GET("/top/:tid", func(c *gin.Context) {
		tid := c.Param("tid")
		var err error
		tidNum, err := strconv.Atoi(tid)
		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM_ERROR", err.Error()))
			return
		}

		cacheName := SORT_TOP_CACHE + strconv.Itoa(tidNum)
		target := app.Cache.GetCache(cacheName)

		if target == nil {
			c.JSON(404, MakeFailedJsonMap("SORT_NOT_FOUND", ""))
			return
		}

		c.JSON(200, target)
	})

	app.Router.GET("/sort/:tid", func(c *gin.Context) {
		page := c.DefaultQuery("page", "1")
		pageSize := c.DefaultQuery("count", "20")
		tid := c.Param("tid")
		order := c.DefaultQuery("order", "hot")

		var err error
		tidNum, err := strconv.Atoi(tid)
		pageNum, err := strconv.Atoi(page)
		pageSizeNum, err := strconv.Atoi(pageSize)

		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM_ERROR", err.Error()))
			return
		}
		list, err := app.Client.Rank.SortRank(tidNum, pageNum, pageSizeNum, order)
		if err != nil {
			c.JSON(500, MakeFailedJsonMap("API_RETURN_ERROR", err.Error()))
			return
		}
		c.JSON(200, list)
	})

	app.Router.GET("/spinfo/:spid", func(c *gin.Context) {
		spid := c.Param("spid")
		spidNum, err := strconv.Atoi(spid)

		if err != nil {
			c.JSON(400, MakeFailedJsonMap("PARAM_ERROR", ""))
		}

		list, err := app.Client.Special.GetSpecialInfo(spidNum)
		if err != nil {
			c.JSON(500, MakeFailedJsonMap("API_RETURN_ERROR", err.Error()))
			return
		}
		c.JSON(200, list)
	})

	app.Router.GET("/bangumi", func(c *gin.Context) {
		back := app.Cache.GetCache(BANGUMI_LIST_CACHE)

		c.JSON(200, back)
	})

	app.Router.GET("/bangumiinfo/:seasonid", func(c *gin.Context) {
		seasonId := c.Param("seasonid")

		back, err := app.Client.Bangumi.GetBangumiInfo(seasonId)

		if err != nil {
			c.JSON(500, MakeFailedJsonMap("API_RETURN_ERROR", err.Error()))
			return
		}

		c.JSON(200, back)
	})

	app.Router.GET("/bangumiindex", func(c *gin.Context) {
		back := app.Cache.GetCache(BANGUMI_CACHE)

		c.JSON(200, back)
	})

	app.Router.GET("/liveindex", func(c *gin.Context) {
		back := app.Cache.GetCache(LIVE_INDEX_CACHE)

		c.JSON(200, back)
	})

	app.Router.GET("/banner", func(c *gin.Context) {
		back := app.Cache.GetCache(INDEX_BANNER_CACHE)

		c.JSON(200, back)
	})
}


================================================
FILE: test/api_test.go
================================================
package test

import (
	"encoding/json"
	"github.com/whiteblue/bilibili-go/client"
	"os"
	"strconv"
	"testing"
)

const (
	APPKEY = "4ebafd7c4951b366"
	SECRET = "8cb98205e9b2ad3669aad0fce12a4c13"
)

func TestApiSortRank(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Rank.SortRank(1, 1, 10, "hot")
	if err != nil {
		t.Error(err.Error())
		t.Fail()
	} else {
		length := len(back.List)
		if length == 0 {
			t.Error("return length is 0")
		}
		for i := 0; i < length; i++ {
			index := strconv.Itoa(i)
			if back.List[index].Title == "" {
				t.Error("api return nil")
			}
			t.Log(back.List[index])
		}
	}
}

func TestWeekBangumi(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Bangumi.GetWeekList("2")
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if len(back.List) == 0 {
			t.Error("return length is 0")
		}
		for _, element := range back.List {
			if element.Title == "" {
				t.Error("api return nil")
			}
			t.Log(element)
		}
	}
}

func TestBangumiIndex(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Bangumi.GetIndex()
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if len(back.Banners) == 0 {
			t.Error("return banner length is 0")
		}
		for _, banner := range back.Banners {
			if banner.Title == "" {
				t.Error("api return nil")
			}
			t.Log(banner.Title)
		}
		if len(back.Recommends) == 0 {
			t.Error("return recommends length is 0")
		}
		for _, ele := range back.Recommends {
			if ele.Title == "" {
				t.Error("api return nil")
			}
			t.Log(ele)
		}

		f, err := os.Create("test.json")
		if err != nil {
			t.Error(err)
		}
		defer f.Close()

		jsonByte, _ := json.Marshal(back)

		f.WriteString(string(jsonByte))

	}
}

func TestVideoInfo(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Video.GetVideoInfo(5495647)
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if back.Title == "" {
			t.Error("return title is nil")
		}
		if len(back.PartList) == 0 {
			t.Error("return partlist length is 0")
		}
		t.Log(back)
	}
}

func TestVideoPath(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Video.GetVideoPartPath(8932442, 1, "mp4")
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if len(back.List) == 0 {
			t.Error("return list length is 0")
		}
		for _, dUrl := range back.List {
			t.Log(dUrl)
		}
	}
}

func TestSearch(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Others.Search("fate", 1, 10, "totalrank", "all")
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if len(back.PageInfo) == 0 {
			t.Error("return list length is 0")
		}

		for _, video := range back.Result.Videos {
			t.Log(video)
		}
	}
}

func TestSpInfo(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Special.GetSpecialInfo(158)
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if back.Title == "" {
			t.Error("return title is nil")
		}
		t.Log(back)
	}
}

func TestSpVideos(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.Special.GetSpecialVideos(158, true)
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if len(back.List) == 0 {
			t.Error("return list is nil")
		}
		for _, ele := range back.List {
			t.Log(ele)
		}
	}
}

func TestUserInfo(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.User.GetUserInfo(591635)
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if back.Name == "" {
			t.Error("api return user name nil")
		}
		t.Log(back)
	}
}

func TestUserVideos(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	back, err := c.User.GetUserVideos(591635, 1, 10)
	if err != nil {
		t.Error(err.Error())
		t.Failed()
	} else {
		if len(back.List) == 0 {
			t.Error("api return empty list")
		}
		if len(back.TypeIndex) == 0 {
			t.Error("type list is empty")
		}
		t.Log(back)
	}
}

func TestAppIndex(t *testing.T) {
	c := client.NewClient(APPKEY, SECRET)
	_, err := c.Others.AppIndex()
	if err != nil {
		t.Error(err)
	}

	if err != nil {
		t.Error(err)
	}

}
Download .txt
gitextract_t4bibrws/

├── .gitignore
├── Dockerfile
├── Godeps/
│   ├── Godeps.json
│   └── Readme
├── LICENSE
├── README.md
├── client/
│   ├── bangumi.go
│   ├── base.go
│   ├── cli.go
│   ├── others.go
│   ├── rank.go
│   ├── special.go
│   ├── user.go
│   ├── utils.go
│   └── video.go
├── conf.example.json
├── conf.json
├── deploy.sh
├── docs/
│   └── api_doc.md
├── main.go
├── run.sh
├── service/
│   ├── application.go
│   ├── cache.go
│   ├── config.go
│   ├── corn.go
│   ├── corn_tasks.go
│   └── router.go
└── test/
    └── api_test.go
Download .txt
SYMBOL INDEX (130 symbols across 17 files)

FILE: client/bangumi.go
  type bangumiElement (line 7) | type bangumiElement struct
  type banner (line 29) | type banner struct
  type recommendBangumiVideo (line 40) | type recommendBangumiVideo struct
  type bangumiActor (line 57) | type bangumiActor struct
  type bangumiVideo (line 62) | type bangumiVideo struct
  type bangumiSeason (line 72) | type bangumiSeason struct
  type bangumiInfoResponse (line 81) | type bangumiInfoResponse struct
  type weekBangumiResponse (line 109) | type weekBangumiResponse struct
  type bangumiIndexResponse (line 114) | type bangumiIndexResponse struct
  type BangumiService (line 119) | type BangumiService struct
    method GetWeekList (line 123) | func (b *BangumiService) GetWeekList(bType string) (*weekBangumiRespon...
    method GetIndex (line 140) | func (b *BangumiService) GetIndex() (*bangumiIndexResponse, error) {
    method GetBangumiInfo (line 158) | func (b *BangumiService) GetBangumiInfo(seasonId string) (*bangumiInfo...

FILE: client/base.go
  type BaseParam (line 7) | type BaseParam struct
  type BaseService (line 12) | type BaseService struct
    method doRequest (line 31) | func (b *BaseService) doRequest(url string, params map[string]string) ...
  type apiResponse (line 17) | type apiResponse struct
  type ApiError (line 23) | type ApiError struct
    method Error (line 27) | func (a *ApiError) Error() string {

FILE: client/cli.go
  type BCli (line 3) | type BCli struct
  function NewClient (line 12) | func NewClient(appkey, secret string) *BCli {

FILE: client/others.go
  type videoTypeInfoElement (line 10) | type videoTypeInfoElement struct
  type searchItem (line 16) | type searchItem struct
  type searchItems (line 42) | type searchItems struct
  type searchNavItem (line 48) | type searchNavItem struct
  type searchResponse (line 55) | type searchResponse struct
  type searchByTypeResponse (line 61) | type searchByTypeResponse struct
  type BannerElement (line 66) | type BannerElement struct
  type liveBanner (line 74) | type liveBanner struct
  type liveElement (line 81) | type liveElement struct
  type liveAppIndexResponse (line 99) | type liveAppIndexResponse struct
  type OthersService (line 118) | type OthersService struct
    method Search (line 132) | func (o *OthersService) Search(keyword string, page, pageSize int, ord...
    method SearchByType (line 157) | func (o *OthersService) SearchByType(keyword string, page, pageSize in...
    method AppIndex (line 182) | func (o *OthersService) AppIndex() (*liveAppIndexResponse, error) {
    method IndexBanner (line 202) | func (o *OthersService) IndexBanner() ([]BannerElement, error) {

FILE: client/rank.go
  type RankService (line 8) | type RankService struct
    method SortRank (line 33) | func (r *RankService) SortRank(tid, page, pageSize int, order string) ...
  type RankVideoElement (line 12) | type RankVideoElement struct

FILE: client/special.go
  type specialVideoElement (line 8) | type specialVideoElement struct
  type specialVideosResponse (line 17) | type specialVideosResponse struct
  type specialInfoResponse (line 23) | type specialInfoResponse struct
  type SpecialService (line 40) | type SpecialService struct
    method GetSpecialInfo (line 44) | func (s *SpecialService) GetSpecialInfo(spid int) (*specialInfoRespons...
    method GetSpecialVideos (line 63) | func (s *SpecialService) GetSpecialVideos(spid int, isBangumi bool) (*...

FILE: client/user.go
  type userVideosResponse (line 8) | type userVideosResponse struct
  type UserVideoElement (line 13) | type UserVideoElement struct
  type userInfoResponse (line 32) | type userInfoResponse struct
  type UserVideoResponse (line 50) | type UserVideoResponse struct
  type UserService (line 53) | type UserService struct
    method GetUserInfo (line 57) | func (u *UserService) GetUserInfo(mid int) (*userInfoResponse, error) {
    method GetUserVideos (line 72) | func (u *UserService) GetUserVideos(mid, page, pageSize int) (*userVid...

FILE: client/utils.go
  function EncodeSign (line 15) | func EncodeSign(params map[string]string, secret string) (string, string) {
  function Md5 (line 20) | func Md5(formal string) string {
  constant HTTP_TIMEOUT (line 27) | HTTP_TIMEOUT     = 2
  constant HTTP_BUFFER_SIZE (line 28) | HTTP_BUFFER_SIZE = 2 * 1024
  type HttpClient (line 49) | type HttpClient struct
    method Get (line 78) | func (b *HttpClient) Get(url string) ([]byte, error) {
  function NewHttpClient (line 53) | func NewHttpClient() HttpClient {
  function httpBuildQuery (line 60) | func httpBuildQuery(params map[string]string) string {

FILE: client/video.go
  type videoElement (line 8) | type videoElement struct
  type videoMidInfo (line 31) | type videoMidInfo struct
  type videoInfoResponse (line 39) | type videoInfoResponse struct
  type videoDurl (line 60) | type videoDurl struct
  type videoPathResponse (line 67) | type videoPathResponse struct
  type VideoService (line 76) | type VideoService struct
    method GetVideoInfo (line 80) | func (v *VideoService) GetVideoInfo(aid int) (*videoInfoResponse, erro...
    method GetVideoPartPath (line 108) | func (v *VideoService) GetVideoPartPath(cid int, quality int) (*videoP...

FILE: main.go
  function main (line 8) | func main() {

FILE: service/application.go
  constant INDEX_CACHE (line 13) | INDEX_CACHE = "index"
  constant ALL_RANK_CACHE (line 14) | ALL_RANK_CACHE = "all_rank"
  constant BANGUMI_CACHE (line 15) | BANGUMI_CACHE = "bangumi"
  constant BANGUMI_LIST_CACHE (line 16) | BANGUMI_LIST_CACHE = "bangumi_list"
  constant SORT_TOP_CACHE (line 17) | SORT_TOP_CACHE = "sort-"
  constant LIVE_INDEX_CACHE (line 18) | LIVE_INDEX_CACHE = "live_index"
  constant INDEX_BANNER_CACHE (line 19) | INDEX_BANNER_CACHE = "index_banner"
  type BiliBiliApplication (line 34) | type BiliBiliApplication struct
  function NewApplication (line 42) | func NewApplication(configFile string) (*BiliBiliApplication, error) {
  function conformTask (line 91) | func conformTask(app *BiliBiliApplication) {

FILE: service/cache.go
  type CacheManager (line 5) | type CacheManager struct
    method GetCache (line 10) | func (c *CacheManager) GetCache(key string) interface{} {
    method SetCache (line 20) | func (c *CacheManager) SetCache(key string, value interface{}) {
  function NewCacheManager (line 26) | func NewCacheManager() *CacheManager {

FILE: service/config.go
  type Config (line 9) | type Config struct
  function ReadConfigFromFile (line 17) | func ReadConfigFromFile(filename string) (*Config, error) {

FILE: service/corn.go
  type CornTaskImpl (line 9) | type CornTaskImpl interface
  type CornTask (line 19) | type CornTask struct
    method Run (line 25) | func (t *CornTask) Run() error {
    method Success (line 29) | func (t *CornTask) Success() {
    method Failure (line 33) | func (t *CornTask) Failure(err error) {
    method GetName (line 37) | func (t *CornTask) GetName() string {
    method GetDuration (line 41) | func (t *CornTask) GetDuration() time.Duration {
    method GetLastRun (line 45) | func (t *CornTask) GetLastRun() time.Time {
    method SyncLastRunTime (line 49) | func (t *CornTask) SyncLastRunTime() {
  function exec (line 54) | func exec(f CornTaskImpl) {
  type CornService (line 70) | type CornService struct
    method RegisterTask (line 77) | func (c *CornService) RegisterTask(task CornTaskImpl) {
    method syncTaskList (line 83) | func (c *CornService) syncTaskList(nowTime time.Time) {
    method loop (line 94) | func (c *CornService) loop() {
    method Start (line 106) | func (c *CornService) Start() {
    method Stop (line 110) | func (c *CornService) Stop() {
  function NewCornService (line 115) | func NewCornService() *CornService {

FILE: service/corn_tasks.go
  type SortRankInfo (line 13) | type SortRankInfo struct
  type IndexInfoTask (line 18) | type IndexInfoTask struct
    method Run (line 23) | func (i *IndexInfoTask) Run() error {
  type BangumiInfoTask (line 49) | type BangumiInfoTask struct
    method Run (line 54) | func (i *BangumiInfoTask) Run() error {
  type BangumiListTask (line 63) | type BangumiListTask struct
    method Run (line 68) | func (i *BangumiListTask) Run() error {
  type TopRankTask (line 77) | type TopRankTask struct
    method Run (line 82) | func (i *TopRankTask) Run() error {
  type LiveIndexTask (line 91) | type LiveIndexTask struct
    method Run (line 96) | func (i *LiveIndexTask) Run() error {
  type BannerTask (line 105) | type BannerTask struct
    method Run (line 110) | func (b *BannerTask) Run() error {

FILE: service/router.go
  function MakeFailedJsonMap (line 9) | func MakeFailedJsonMap(code string, message string) map[string]string {
  function ConformRoute (line 16) | func ConformRoute(app *BiliBiliApplication) {

FILE: test/api_test.go
  constant APPKEY (line 12) | APPKEY = "4ebafd7c4951b366"
  constant SECRET (line 13) | SECRET = "8cb98205e9b2ad3669aad0fce12a4c13"
  function TestApiSortRank (line 16) | func TestApiSortRank(t *testing.T) {
  function TestWeekBangumi (line 37) | func TestWeekBangumi(t *testing.T) {
  function TestBangumiIndex (line 56) | func TestBangumiIndex(t *testing.T) {
  function TestVideoInfo (line 95) | func TestVideoInfo(t *testing.T) {
  function TestVideoPath (line 112) | func TestVideoPath(t *testing.T) {
  function TestSearch (line 128) | func TestSearch(t *testing.T) {
  function TestSpInfo (line 145) | func TestSpInfo(t *testing.T) {
  function TestSpVideos (line 159) | func TestSpVideos(t *testing.T) {
  function TestUserInfo (line 175) | func TestUserInfo(t *testing.T) {
  function TestUserVideos (line 189) | func TestUserVideos(t *testing.T) {
  function TestAppIndex (line 206) | func TestAppIndex(t *testing.T) {
Condensed preview — 28 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (74K chars).
[
  {
    "path": ".gitignore",
    "chars": 307,
    "preview": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Folders\n_obj\n_test\n\n# Architecture spe"
  },
  {
    "path": "Dockerfile",
    "chars": 423,
    "preview": "FROM golang:onbuild\n\nMAINTAINER whiteblue0616@gmail.com\n\nADD . $GOPATH/src/github.com/whiteblue/bilibili-go\n\nWORKDIR $GO"
  },
  {
    "path": "Godeps/Godeps.json",
    "chars": 3073,
    "preview": "{\n\t\"ImportPath\": \"github.com/whiteblue/bilibili-go\",\n\t\"GoVersion\": \"go1.7\",\n\t\"GodepVersion\": \"v75\",\n\t\"Deps\": [\n\t\t{\n\t\t\t\"I"
  },
  {
    "path": "Godeps/Readme",
    "chars": 136,
    "preview": "This directory tree is generated automatically by godep.\n\nPlease do not edit.\n\nSee https://github.com/tools/godep for mo"
  },
  {
    "path": "LICENSE",
    "chars": 1077,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2015 WhiteBlue\n\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "README.md",
    "chars": 2068,
    "preview": "# bilibili-sdk-go\n\nBiliBili Open API & SDK written in Go\n\n\n\n## Open API\n\nDocs: [docs](docs/api_doc.md)\n\n* api.bilibilih5"
  },
  {
    "path": "client/bangumi.go",
    "chars": 5050,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n)\n\ntype bangumiElement struct {\n\tTitle        string `json:\"title\"`\n\tArea     "
  },
  {
    "path": "client/base.go",
    "chars": 999,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n)\n\ntype BaseParam struct {\n\tAppkey string\n\tSecret string\n}\n\ntype BaseService s"
  },
  {
    "path": "client/cli.go",
    "chars": 555,
    "preview": "package client\n\ntype BCli struct {\n\tRank    RankService\n\tBangumi BangumiService\n\tVideo   VideoService\n\tSpecial SpecialSe"
  },
  {
    "path": "client/others.go",
    "chars": 5178,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n)\n\ntype videoTypeInfoElement struct {\n\tTid   "
  },
  {
    "path": "client/rank.go",
    "chars": 1142,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"strconv\"\n)\n\ntype RankService struct {\n\tBaseService\n}\n\ntype RankVideoElement "
  },
  {
    "path": "client/special.go",
    "chars": 1897,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"strconv\"\n)\n\ntype specialVideoElement struct {\n\tAid   int    `json:\"aid\"`\n\tCi"
  },
  {
    "path": "client/user.go",
    "chars": 2274,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"strconv\"\n)\n\ntype userVideosResponse struct {\n\tList      []UserVideoElement  "
  },
  {
    "path": "client/utils.go",
    "chars": 1983,
    "preview": "package client\n\nimport (\n\t\"crypto/md5\"\n\t\"encoding/hex\"\n\t\"github.com/valyala/fasthttp\"\n\t\"time\"\n\t\"sort\"\n\t\"strings\"\n\t\"fmt\"\n"
  },
  {
    "path": "client/video.go",
    "chars": 3561,
    "preview": "package client\n\nimport (\n\t\"encoding/json\"\n\t\"strconv\"\n)\n\ntype videoElement struct {\n\tAid         string `json:\"aid\"`\n\tMid"
  },
  {
    "path": "conf.example.json",
    "chars": 51,
    "preview": "{\n  \"debug\": true,\n  \"appkey\": \"\",\n  \"secret\": \"\"\n}"
  },
  {
    "path": "conf.json",
    "chars": 163,
    "preview": "{\n  \"debug\": false,\n  \"appkey\": \"4ebafd7c4951b366\",\n  \"secret\": \"8cb98205e9b2ad3669aad0fce12a4c13\",\n  \"private\": false,\n"
  },
  {
    "path": "deploy.sh",
    "chars": 652,
    "preview": "#!/bin/bash\n\nIMAGENAME=\"bilibili-go\"\nTAGENAME=\"whiteblue/bilibili-go\"\n\nsudo -l || ( echo \"Error: scripts need run with '"
  },
  {
    "path": "docs/api_doc.md",
    "chars": 13396,
    "preview": "## 接口地址\n\nURL : ```http://bilibili-service.daoapp.io```\n\n基于DaoCloud免费容器\n\n\n## 接口文档\n\n* 数据格式: ```application/json```\n* 请求方式:"
  },
  {
    "path": "main.go",
    "chars": 230,
    "preview": "package main\n\nimport (\n\t\"github.com/go-playground/log\"\n\t\"github.com/whiteblue/bilibili-go/service\"\n)\n\nfunc main() {\n\tapp"
  },
  {
    "path": "run.sh",
    "chars": 44,
    "preview": "#!/bin/sh\n\n\ngodep go build && ./bilibili-go\n"
  },
  {
    "path": "service/application.go",
    "chars": 2380,
    "preview": "package service\n\nimport (\n\t\"github.com/gin-gonic/contrib/gzip\"\n\t\"github.com/gin-gonic/gin\"\n\t\"github.com/go-playground/lo"
  },
  {
    "path": "service/cache.go",
    "chars": 545,
    "preview": "package service\n\nimport \"sync\"\n\ntype CacheManager struct {\n\tcacheMap map[string]interface{}\n\tlock     *sync.RWMutex\n}\n\nf"
  },
  {
    "path": "service/config.go",
    "chars": 589,
    "preview": "package service\n\nimport (\n\t\"encoding/json\"\n\t\"io/ioutil\"\n\t\"os\"\n)\n\ntype Config struct {\n\tDebug     bool   `json:\"debug\"`\n\t"
  },
  {
    "path": "service/corn.go",
    "chars": 2049,
    "preview": "package service\n\nimport (\n\t\"github.com/anacrolix/sync\"\n\t\"github.com/go-playground/log\"\n\t\"time\"\n)\n\ntype CornTaskImpl inte"
  },
  {
    "path": "service/corn_tasks.go",
    "chars": 2118,
    "preview": "package service\n\nimport (\n\t\"strconv\"\n)\n\nvar (\n\t_INDEX_SORTS = []int{\n\t\t24, 33, 31, 20, 17, 36, 119,\n\t}\n)\n\ntype SortRankI"
  },
  {
    "path": "service/router.go",
    "chars": 6782,
    "preview": "package service\n\nimport (\n\t\"github.com/gin-gonic/gin\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc MakeFailedJsonMap(code string, messa"
  },
  {
    "path": "test/api_test.go",
    "chars": 4118,
    "preview": "package test\n\nimport (\n\t\"encoding/json\"\n\t\"github.com/whiteblue/bilibili-go/client\"\n\t\"os\"\n\t\"strconv\"\n\t\"testing\"\n)\n\nconst "
  }
]

About this extraction

This page contains the full source code of the WhiteBlue/bilibili-sdk-go GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 28 files (61.4 KB), approximately 20.8k tokens, and a symbol index with 130 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!