Feat: bilibili sms login

pull/31/head
zijiren233 2 years ago
parent 92e29eb3a7
commit d02fa8362c

@ -147,9 +147,17 @@ func Init(e *gin.Engine) {
{
bilibili := vendor.Group("/bilibili")
bilibili.GET("/qr", Vbilibili.QRCode)
login := bilibili.Group("/login")
bilibili.POST("/login", Vbilibili.Login)
login.GET("/qr", Vbilibili.NewQRCode)
login.POST("/qr", Vbilibili.LoginWithQR)
login.GET("/captcha", Vbilibili.NewCaptcha)
login.POST("/sms/send", Vbilibili.NewSMS)
login.POST("/sms/login", Vbilibili.LoginWithSMS)
bilibili.POST("/parse", Vbilibili.Parse)

@ -518,7 +518,10 @@ func parse2VendorMovie(userID string, movie *dbModel.Movie, getUrl bool) (err er
if err != nil {
return err
}
cli := bilibili.NewClient(vendor.Cookies)
cli, err := bilibili.NewClient(vendor.Cookies)
if err != nil {
return err
}
if getUrl {
var mu *bilibili.VideoURL

@ -14,54 +14,6 @@ import (
"github.com/synctv-org/synctv/vendors/bilibili"
)
func QRCode(ctx *gin.Context) {
r, err := bilibili.NewQRCode()
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(r))
}
type LoginReq struct {
Key string `json:"key"`
}
func (r *LoginReq) Validate() error {
if r.Key == "" {
return errors.New("key is empty")
}
return nil
}
func (r *LoginReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(r)
}
func Login(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
req := LoginReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
cookie, err := bilibili.Login(req.Key)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
_, err = db.AssignFirstOrCreateVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili, db.WithCookie([]*http.Cookie{cookie}))
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
type ParseReq struct {
URL string `json:"url"`
}
@ -97,7 +49,11 @@ func Parse(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
cli := bilibili.NewClient(vendor.Cookies)
cli, err := bilibili.NewClient(vendor.Cookies)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
switch matchType {
case "bv":

@ -0,0 +1,157 @@
package Vbilibili
import (
"errors"
"net/http"
"github.com/gin-gonic/gin"
json "github.com/json-iterator/go"
"github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/server/model"
"github.com/synctv-org/synctv/vendors/bilibili"
)
func NewQRCode(ctx *gin.Context) {
r, err := bilibili.NewQRCode()
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(r))
}
type QRCodeLoginReq struct {
Key string `json:"key"`
}
func (r *QRCodeLoginReq) Validate() error {
if r.Key == "" {
return errors.New("key is empty")
}
return nil
}
func (r *QRCodeLoginReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(r)
}
func LoginWithQR(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
req := QRCodeLoginReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
cookie, err := bilibili.LoginWithQRCode(req.Key)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
_, err = db.AssignFirstOrCreateVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili, db.WithCookie([]*http.Cookie{cookie}))
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func NewCaptcha(ctx *gin.Context) {
r, err := bilibili.NewCaptcha()
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(r))
}
type SMSReq struct {
Token string `json:"token"`
Challenge string `json:"challenge"`
Validate_ string `json:"validate"`
Telephone string `json:"telephone"`
}
func (r *SMSReq) Validate() error {
if r.Token == "" {
return errors.New("token is empty")
}
if r.Challenge == "" {
return errors.New("challenge is empty")
}
if r.Validate_ == "" {
return errors.New("validate is empty")
}
if r.Telephone == "" {
return errors.New("telephone is empty")
}
return nil
}
func (r *SMSReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(r)
}
func NewSMS(ctx *gin.Context) {
var req SMSReq
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
r, err := bilibili.NewSMS(req.Telephone, req.Token, req.Challenge, req.Validate_)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"captchaKey": r,
}))
}
type SMSLoginReq struct {
Telephone string `json:"telephone"`
CaptchaKey string `json:"captchaKey"`
Code string `json:"code"`
}
func (r *SMSLoginReq) Validate() error {
if r.Telephone == "" {
return errors.New("telephone is empty")
}
if r.CaptchaKey == "" {
return errors.New("captchaKey is empty")
}
if r.Code == "" {
return errors.New("code is empty")
}
return nil
}
func (r *SMSLoginReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(r)
}
func LoginWithSMS(ctx *gin.Context) {
var req SMSLoginReq
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
c, err := bilibili.LoginWithSMS(req.Telephone, req.Code, req.CaptchaKey)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
user := ctx.MustGet("user").(*op.User)
_, err = db.AssignFirstOrCreateVendorByUserIDAndVendor(user.ID, dbModel.StreamingVendorBilibili, db.WithCookie([]*http.Cookie{c}))
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}

@ -31,7 +31,12 @@ func Me(ctx *gin.Context) {
}))
return
}
cli := bilibili.NewClient(vendor.Cookies)
cli, err := bilibili.NewClient(vendor.Cookies)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
nav, err := cli.UserInfo()
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))

@ -1,6 +1,7 @@
package bilibili
import (
"errors"
"io"
"net/http"
@ -10,6 +11,7 @@ import (
type Client struct {
httpClient *http.Client
cookies []*http.Cookie
buvid3 *http.Cookie
}
type ClientConfig func(*Client)
@ -20,15 +22,43 @@ func WithHttpClient(httpClient *http.Client) ClientConfig {
}
}
func NewClient(cookies []*http.Cookie, conf ...ClientConfig) *Client {
c := &Client{
func NewClient(cookies []*http.Cookie, conf ...ClientConfig) (*Client, error) {
cli := &Client{
httpClient: http.DefaultClient,
cookies: cookies,
}
for _, v := range conf {
v(c)
v(cli)
}
return c
c, err := newBuvid3()
if err != nil {
return nil, err
}
cli.buvid3 = c
return cli, nil
}
func newBuvid3() (*http.Cookie, error) {
req, err := http.NewRequest(http.MethodGet, "https://www.bilibili.com/", nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", utils.UA)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
for _, c := range resp.Cookies() {
if c.Name == "buvid3" {
return c, nil
}
}
return nil, errors.New("no buvid3 cookie")
}
func (c *Client) SetCookies(cookies []*http.Cookie) {
c.cookies = cookies
}
type RequestConfig struct {
@ -64,6 +94,7 @@ func (c *Client) NewRequest(method, url string, body io.Reader, conf ...RequestO
if err != nil {
return nil, err
}
req.AddCookie(c.buvid3)
for _, cookie := range c.cookies {
req.AddCookie(cookie)
}

@ -3,8 +3,11 @@ package bilibili
import (
"fmt"
"net/http"
"net/url"
"strings"
json "github.com/json-iterator/go"
"github.com/synctv-org/synctv/utils"
)
type RQCode struct {
@ -13,7 +16,11 @@ type RQCode struct {
}
func NewQRCode() (*RQCode, error) {
resp, err := http.Get("https://passport.bilibili.com/x/passport-login/web/qrcode/generate")
req, err := http.NewRequest(http.MethodGet, "https://passport.bilibili.com/x/passport-login/web/qrcode/generate", nil)
if err != nil {
return nil, err
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
@ -31,8 +38,13 @@ func NewQRCode() (*RQCode, error) {
}
// return SESSDATA cookie
func Login(key string) (*http.Cookie, error) {
resp, err := http.Get(fmt.Sprintf("https://passport.bilibili.com/x/passport-login/web/qrcode/poll?qrcode_key=%s", key))
func LoginWithQRCode(key string) (*http.Cookie, error) {
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("https://passport.bilibili.com/x/passport-login/web/qrcode/auth?oauthKey=%s", key), nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", utils.UA)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
@ -44,3 +56,116 @@ func Login(key string) (*http.Cookie, error) {
}
return nil, fmt.Errorf("no SESSDATA cookie")
}
type CaptchaResp struct {
Token string `json:"token"`
Gt string `json:"gt"`
Challenge string `json:"challenge"`
}
func NewCaptcha() (*CaptchaResp, error) {
req, err := http.NewRequest(http.MethodGet, "https://passport.bilibili.com/x/passport-login/captcha", nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", utils.UA)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
var captcha captcha
err = json.NewDecoder(resp.Body).Decode(&captcha)
if err != nil {
return nil, err
}
return &CaptchaResp{
Token: captcha.Data.Token,
Gt: captcha.Data.Geetest.Gt,
Challenge: captcha.Data.Geetest.Challenge,
}, nil
}
type captcha struct {
Code int `json:"code"`
Message string `json:"message"`
TTL int `json:"ttl"`
Data struct {
Type string `json:"type"`
Token string `json:"token"`
Geetest struct {
Challenge string `json:"challenge"`
Gt string `json:"gt"`
} `json:"geetest"`
Tencent struct {
Appid string `json:"appid"`
} `json:"tencent"`
} `json:"data"`
}
type sms struct {
Code int `json:"code"`
Message string `json:"message"`
Data struct {
CaptchaKey string `json:"captcha_key"`
} `json:"data"`
}
func NewSMS(tel, token, challenge, validate string) (captchaKey string, err error) {
buvid3, err := newBuvid3()
if err != nil {
return "", err
}
data := url.Values{}
data.Set("cid", "86")
data.Set("tel", tel)
data.Set("source", "main-fe-header")
data.Set("token", token)
data.Set("challenge", challenge)
data.Set("validate", validate)
data.Set("seccode", fmt.Sprintf("%s|jordan", validate))
req, err := http.NewRequest(http.MethodPost, "https://passport.bilibili.com/x/passport-login/web/sms/send", strings.NewReader(data.Encode()))
if err != nil {
return "", err
}
req.Header.Set("User-Agent", utils.UA)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.AddCookie(buvid3)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
var sms sms
err = json.NewDecoder(resp.Body).Decode(&sms)
if err != nil {
return "", err
}
return sms.Data.CaptchaKey, nil
}
func LoginWithSMS(tel, code, captchaKey string) (*http.Cookie, error) {
data := url.Values{}
data.Set("cid", "86")
data.Set("tel", tel)
data.Set("code", code)
data.Set("source", "main-fe-header")
data.Set("captcha_key", captchaKey)
req, err := http.NewRequest(http.MethodPost, "https://passport.bilibili.com/x/passport-login/web/login/sms", strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", utils.UA)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
for _, cookie := range resp.Cookies() {
if cookie.Name == "SESSDATA" {
return cookie, nil
}
}
return nil, fmt.Errorf("no SESSDATA cookie")
}

Loading…
Cancel
Save