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)
}
}
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
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.