|
|
|
package douyin
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"github.com/mvdan/xurls"
|
|
|
|
"go.dtapp.net/gorequest"
|
|
|
|
"net/http"
|
|
|
|
"regexp"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
type AnalysisResponse struct {
|
|
|
|
StatusCode int `json:"status_code"`
|
|
|
|
ItemList []struct {
|
|
|
|
AwemePoiInfo struct {
|
|
|
|
Tag string `json:"tag"`
|
|
|
|
Icon struct {
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
} `json:"icon"`
|
|
|
|
PoiName string `json:"poi_name"`
|
|
|
|
TypeName string `json:"type_name"`
|
|
|
|
} `json:"aweme_poi_info"`
|
|
|
|
Images interface{} `json:"images"`
|
|
|
|
Author struct {
|
|
|
|
Geofencing interface{} `json:"geofencing"`
|
|
|
|
CardEntries interface{} `json:"card_entries"`
|
|
|
|
ShortId string `json:"short_id"`
|
|
|
|
Nickname string `json:"nickname"`
|
|
|
|
FollowStatus int `json:"follow_status"`
|
|
|
|
UniqueId string `json:"unique_id"`
|
|
|
|
PlatformSyncInfo interface{} `json:"platform_sync_info"`
|
|
|
|
Uid string `json:"uid"`
|
|
|
|
AvatarLarger struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"avatar_larger"`
|
|
|
|
PolicyVersion interface{} `json:"policy_version"`
|
|
|
|
MixInfo interface{} `json:"mix_info"`
|
|
|
|
Signature string `json:"signature"`
|
|
|
|
AvatarThumb struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"avatar_thumb"`
|
|
|
|
AvatarMedium struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"avatar_medium"`
|
|
|
|
FollowersDetail interface{} `json:"followers_detail"`
|
|
|
|
TypeLabel interface{} `json:"type_label"`
|
|
|
|
} `json:"author"`
|
|
|
|
ChaList []struct {
|
|
|
|
ViewCount int `json:"view_count"`
|
|
|
|
HashTagProfile string `json:"hash_tag_profile"`
|
|
|
|
Cid string `json:"cid"`
|
|
|
|
CoverItem struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"cover_item"`
|
|
|
|
UserCount int `json:"user_count"`
|
|
|
|
ConnectMusic interface{} `json:"connect_music"`
|
|
|
|
Type int `json:"type"`
|
|
|
|
IsCommerce bool `json:"is_commerce"`
|
|
|
|
ChaName string `json:"cha_name"`
|
|
|
|
Desc string `json:"desc"`
|
|
|
|
} `json:"cha_list"`
|
|
|
|
Duration int `json:"duration"`
|
|
|
|
LongVideo interface{} `json:"long_video"`
|
|
|
|
Desc string `json:"desc"`
|
|
|
|
AuthorUserId int64 `json:"author_user_id"`
|
|
|
|
LabelTopText interface{} `json:"label_top_text"`
|
|
|
|
IsPreview int `json:"is_preview"`
|
|
|
|
CreateTime int `json:"create_time"`
|
|
|
|
ShareUrl string `json:"share_url"`
|
|
|
|
RiskInfos struct {
|
|
|
|
Warn bool `json:"warn"`
|
|
|
|
Type int `json:"type"`
|
|
|
|
Content string `json:"content"`
|
|
|
|
ReflowUnplayable int `json:"reflow_unplayable"`
|
|
|
|
} `json:"risk_infos"`
|
|
|
|
Promotions interface{} `json:"promotions"`
|
|
|
|
Music struct {
|
|
|
|
Duration int `json:"duration"`
|
|
|
|
Id int64 `json:"id"`
|
|
|
|
Mid string `json:"mid"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
CoverHd struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"cover_hd"`
|
|
|
|
CoverLarge struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"cover_large"`
|
|
|
|
CoverMedium struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"cover_medium"`
|
|
|
|
CoverThumb struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"cover_thumb"`
|
|
|
|
Author string `json:"author"`
|
|
|
|
PlayUrl struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"play_url"`
|
|
|
|
Position interface{} `json:"position"`
|
|
|
|
Status int `json:"status"`
|
|
|
|
} `json:"music"`
|
|
|
|
CommentList interface{} `json:"comment_list"`
|
|
|
|
ForwardId string `json:"forward_id"`
|
|
|
|
GroupIdStr string `json:"group_id_str"`
|
|
|
|
Video struct {
|
|
|
|
OriginCover struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"origin_cover"`
|
|
|
|
HasWatermark bool `json:"has_watermark"`
|
|
|
|
Duration int `json:"duration"`
|
|
|
|
Height int `json:"height"`
|
|
|
|
DynamicCover struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"dynamic_cover"`
|
|
|
|
Width int `json:"width"`
|
|
|
|
Ratio string `json:"ratio"`
|
|
|
|
BitRate interface{} `json:"bit_rate"`
|
|
|
|
Vid string `json:"vid"`
|
|
|
|
PlayAddr struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"` // 真实去水印地址
|
|
|
|
} `json:"play_addr"`
|
|
|
|
Cover struct {
|
|
|
|
Uri string `json:"uri"`
|
|
|
|
UrlList []string `json:"url_list"`
|
|
|
|
} `json:"cover"`
|
|
|
|
} `json:"video"`
|
|
|
|
TextExtra []struct {
|
|
|
|
Start int `json:"start"`
|
|
|
|
End int `json:"end"`
|
|
|
|
Type int `json:"type"`
|
|
|
|
HashtagName string `json:"hashtag_name"`
|
|
|
|
HashtagId int64 `json:"hashtag_id"`
|
|
|
|
} `json:"text_extra"`
|
|
|
|
VideoLabels interface{} `json:"video_labels"`
|
|
|
|
VideoText interface{} `json:"video_text"`
|
|
|
|
AwemeType int `json:"aweme_type"`
|
|
|
|
ImageInfos interface{} `json:"image_infos"`
|
|
|
|
AwemeId string `json:"aweme_id"`
|
|
|
|
Statistics struct {
|
|
|
|
CommentCount int `json:"comment_count"`
|
|
|
|
DiggCount int `json:"digg_count"`
|
|
|
|
PlayCount int `json:"play_count"`
|
|
|
|
ShareCount int `json:"share_count"`
|
|
|
|
AwemeId string `json:"aweme_id"`
|
|
|
|
} `json:"statistics"`
|
|
|
|
IsLiveReplay bool `json:"is_live_replay"`
|
|
|
|
ShareInfo struct {
|
|
|
|
ShareWeiboDesc string `json:"share_weibo_desc"`
|
|
|
|
ShareDesc string `json:"share_desc"`
|
|
|
|
ShareTitle string `json:"share_title"`
|
|
|
|
} `json:"share_info"`
|
|
|
|
Geofencing interface{} `json:"geofencing"`
|
|
|
|
GroupId int64 `json:"group_id"`
|
|
|
|
} `json:"item_list"`
|
|
|
|
FilterList []interface{} `json:"filter_list"`
|
|
|
|
Extra struct {
|
|
|
|
Now int64 `json:"now"`
|
|
|
|
Logid string `json:"logid"`
|
|
|
|
} `json:"extra"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type AnalysisResult struct {
|
|
|
|
Result AnalysisResponse // 结果
|
|
|
|
Body []byte // 内容
|
|
|
|
Http gorequest.Response // 请求
|
|
|
|
Err error // 错误
|
|
|
|
}
|
|
|
|
|
|
|
|
func newAnalysisResult(result AnalysisResponse, body []byte, http gorequest.Response, err error) *AnalysisResult {
|
|
|
|
return &AnalysisResult{Result: result, Body: body, Http: http, Err: err}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Analysis 抖音解析
|
|
|
|
func (c *Client) Analysis(ctx context.Context, content string) *AnalysisResult {
|
|
|
|
|
|
|
|
// 提取url
|
|
|
|
var url string
|
|
|
|
if strings.Contains(content, "douyin.com") {
|
|
|
|
url = xurls.Relaxed.FindString(content)
|
|
|
|
} else if strings.Contains(content, "iesdouyin.com") {
|
|
|
|
url = xurls.Relaxed.FindString(content)
|
|
|
|
} else {
|
|
|
|
return newAnalysisResult(AnalysisResponse{}, nil, gorequest.Response{}, errors.New("url为空"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 重定向信息
|
|
|
|
request302, err := c.request302(url)
|
|
|
|
if err != nil {
|
|
|
|
return newAnalysisResult(AnalysisResponse{}, nil, gorequest.Response{}, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 提取编号
|
|
|
|
itemIds := regexp.MustCompile(`\d+`).FindStringSubmatch(request302)
|
|
|
|
if len(itemIds) < 1 {
|
|
|
|
return newAnalysisResult(AnalysisResponse{}, nil, gorequest.Response{}, errors.New("参数错误"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// 请求
|
|
|
|
request, err := c.request(ctx, "https://www.iesdouyin.com/web/api/v2/aweme/iteminfo/?item_ids="+itemIds[0], map[string]interface{}{}, http.MethodGet)
|
|
|
|
|
|
|
|
// 定义
|
|
|
|
var response AnalysisResponse
|
|
|
|
err = json.Unmarshal(request.ResponseBody, &response)
|
|
|
|
return newAnalysisResult(response, request.ResponseBody, request, err)
|
|
|
|
}
|