487 lines
13 KiB
Go
487 lines
13 KiB
Go
// novel
|
|
package pixiv
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/tidwall/gjson"
|
|
"golang.org/x/net/proxy"
|
|
)
|
|
|
|
type PixivAPI struct {
|
|
accessToken string
|
|
refreshToken string
|
|
httpClient *http.Client
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// 初始化
|
|
func NewApp() *PixivAPI {
|
|
Client := &http.Client{}
|
|
Client.Timeout = 5000 * time.Millisecond
|
|
return &PixivAPI{httpClient: Client}
|
|
|
|
}
|
|
|
|
// 设置http超时时间 毫秒
|
|
func (p *PixivAPI) SetHttpTimeout(t int) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.httpClient.Timeout = time.Duration(t) * time.Millisecond
|
|
}
|
|
|
|
// 设置http代理 http://127.0.0.1:8080
|
|
func (p *PixivAPI) SetProxy_Http(address string, user string, pwd string) error {
|
|
proxyURL, err := url.Parse(address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
proxyURL.User = url.UserPassword(user, pwd)
|
|
transport := &http.Transport{Proxy: http.ProxyURL(proxyURL)}
|
|
p.httpClient.Transport = transport
|
|
return nil
|
|
}
|
|
|
|
// 设置socks5代理 socks5://proxy.example.com:1080
|
|
func (p *PixivAPI) SetProxy_Socks5(address string, user string, pwd string) error {
|
|
proxyURL, err := url.Parse(address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
auth := proxy.Auth{
|
|
User: user, // Socks5账号
|
|
Password: pwd, // Socks5密码
|
|
}
|
|
dialer, err := proxy.SOCKS5("tcp", proxyURL.Host, &auth, proxy.Direct)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
transport := &http.Transport{
|
|
Dial: dialer.Dial,
|
|
}
|
|
p.httpClient.Transport = transport
|
|
return nil
|
|
}
|
|
|
|
// 设置代理
|
|
func (p *PixivAPI) SetProxy(address string, user string, pwd string) error {
|
|
u, err := url.Parse(address)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if u.Scheme == "http" || u.Scheme == "https" {
|
|
return p.SetProxy_Http(address, user, pwd)
|
|
}
|
|
if u.Scheme == "socks5" {
|
|
return p.SetProxy_Socks5(address, user, pwd)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 设置Refresh_Token
|
|
func (p *PixivAPI) SetRefresh_Token(r string) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.refreshToken = r
|
|
|
|
}
|
|
|
|
// 获取Access_Token
|
|
func (p *PixivAPI) GetAccess_Token() error {
|
|
URL := "https://oauth.secure.pixiv.net/auth/token"
|
|
from := url.Values{}
|
|
from.Add("grant_type", "refresh_token")
|
|
from.Add("client_id", "MOBrBDS8blbauoSck0ZfDbtuzpyT")
|
|
from.Add("client_secret", "lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj")
|
|
from.Add("include_policy", "true")
|
|
from.Add("refresh_token", p.refreshToken)
|
|
req, err := http.NewRequest("POST", URL, bytes.NewBufferString(from.Encode()))
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234")
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
body := GetBody(resp.Body)
|
|
if resp.StatusCode != 200 {
|
|
return errors.New(body)
|
|
}
|
|
AccessToken := gjson.Get(body, "access_token").String()
|
|
p.mu.Lock()
|
|
p.accessToken = AccessToken
|
|
p.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// 打印Access_Token
|
|
func (p *PixivAPI) PrintAccess_Token() string {
|
|
return p.accessToken
|
|
}
|
|
|
|
// 设置Access_Token
|
|
func (p *PixivAPI) SetAccess_Token(a string) {
|
|
p.mu.Lock()
|
|
defer p.mu.Unlock()
|
|
p.accessToken = a
|
|
}
|
|
|
|
// 检测Access_Token是否有效
|
|
func (p *PixivAPI) TestAccess_token() bool {
|
|
URL := "https://app-api.pixiv.net/v1/illust/recommended?include_privacy_policy=true&filter=for_android&include_ranking_illusts=true"
|
|
req, err := http.NewRequest("GET", URL, nil)
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234 ")
|
|
req.Header.Set("Authorization", "Bearer "+p.accessToken)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != 200 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 获取body内容
|
|
func GetBody(body io.Reader) string {
|
|
b, err := ioutil.ReadAll(body)
|
|
if err != nil {
|
|
return err.Error()
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
// 转换到北京时间格式 2022-09-20T23:50:04+09:00 to 2022-09-20 22:50:04
|
|
func ToBeiJing(timeStr string) string {
|
|
// 解析时间字符串
|
|
t, err := time.Parse(time.RFC3339, timeStr)
|
|
if err != nil {
|
|
return timeStr
|
|
}
|
|
// 将时间转换为 UTC+8
|
|
utc8 := t.UTC().Add(8 * time.Hour)
|
|
// 格式化为指定的时间字符串
|
|
utc8Str := utc8.Format("2006-01-02 15:04:05")
|
|
|
|
return utc8Str
|
|
}
|
|
|
|
// 获取系列小说
|
|
func (p *PixivAPI) GetNovelSeries(SeriesID string) (Series, error, int) {
|
|
var (
|
|
last_order int
|
|
title string
|
|
message string
|
|
user_id int64
|
|
count int64
|
|
caption string
|
|
|
|
list []NovelList
|
|
series Series
|
|
user UserInfo
|
|
)
|
|
for {
|
|
URL := "https://app-api.pixiv.net/v2/novel/series?series_id=" + SeriesID + "&last_order=" + strconv.Itoa(last_order)
|
|
req, err := http.NewRequest("GET", URL, nil)
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234 ")
|
|
req.Header.Set("Authorization", "Bearer "+p.accessToken)
|
|
if err != nil {
|
|
return series, err, 0
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return series, err, 0
|
|
}
|
|
body := GetBody(resp.Body)
|
|
defer resp.Body.Close()
|
|
message = gjson.Get(body, "error.message").String()
|
|
if message != "" {
|
|
return series, errors.New(message), resp.StatusCode
|
|
}
|
|
message = gjson.Get(body, "error.user_message").String()
|
|
if message != "" {
|
|
return series, errors.New(message), resp.StatusCode
|
|
}
|
|
if len(title) == 0 {
|
|
title = gjson.Get(body, "novel_series_detail.title").String()
|
|
count = gjson.Get(body, "novel_series_detail.content_count").Int()
|
|
caption = gjson.Get(body, "novel_series_detail.caption").String()
|
|
user_id = gjson.Get(body, "novel_series_detail.user.id").Int()
|
|
}
|
|
novels := gjson.Get(body, "novels")
|
|
novels.ForEach(func(key, value gjson.Result) bool {
|
|
var tagArry []string
|
|
tags := value.Get("tags")
|
|
tags.ForEach(func(key2, value2 gjson.Result) bool {
|
|
tag := value2.Get("name").String()
|
|
tagArry = append(tagArry, tag)
|
|
return true
|
|
})
|
|
list = append(list, NovelList{ID: value.Get("id").String(), Title: value.Get("title").String(), Length: value.Get("text_length").String(), Date: ToBeiJing(value.Get("create_date").String()), Caption: value.Get("caption").String(), Tags: tagArry})
|
|
return true
|
|
})
|
|
if len(novels.Array()) != 30 {
|
|
break
|
|
}
|
|
last_order = last_order + 30
|
|
|
|
}
|
|
|
|
user, _ = p.GetUserInfo(strconv.FormatInt(user_id, 10))
|
|
|
|
series.Title = title
|
|
series.Caption = caption
|
|
series.Novels = list
|
|
series.ContentCount = count
|
|
series.User = user
|
|
return series, nil, 200
|
|
}
|
|
|
|
// 获取用户小说列表
|
|
func (p *PixivAPI) GetUserNovel(UserID string) (UserNovel, error, int) {
|
|
var (
|
|
last_order int
|
|
message string
|
|
novel UserNovel
|
|
list []NovelList
|
|
user UserInfo
|
|
)
|
|
for {
|
|
URL := "https://app-api.pixiv.net/v1/user/novels?user_id=" + UserID + "&offset=" + strconv.Itoa(last_order)
|
|
|
|
req, err := http.NewRequest("GET", URL, nil)
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234 ")
|
|
req.Header.Set("Authorization", "Bearer "+p.accessToken)
|
|
if err != nil {
|
|
return novel, err, 0
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return novel, err, 0
|
|
}
|
|
body := GetBody(resp.Body)
|
|
defer resp.Body.Close()
|
|
|
|
message = gjson.Get(body, "error.message").String()
|
|
if message != "" {
|
|
return novel, errors.New(message), resp.StatusCode
|
|
}
|
|
message = gjson.Get(body, "error.user_message").String()
|
|
if message != "" {
|
|
return novel, errors.New(message), resp.StatusCode
|
|
}
|
|
|
|
novels := gjson.Get(body, "novels")
|
|
// 遍历json数组
|
|
novels.ForEach(func(key, value gjson.Result) bool {
|
|
list = append(list, NovelList{ID: value.Get("id").String(), Title: value.Get("title").String(), Length: value.Get("text_length").String(), Date: ToBeiJing(value.Get("create_date").String())})
|
|
|
|
return true
|
|
})
|
|
if len(novels.Array()) != 30 {
|
|
break
|
|
}
|
|
last_order = last_order + 30
|
|
}
|
|
user, _ = p.GetUserInfo(UserID)
|
|
novel.Novels = list
|
|
|
|
novel.User = user
|
|
|
|
return novel, nil, 200
|
|
}
|
|
|
|
// 获取系列简介
|
|
func (p *PixivAPI) GetNovelSeriesDetail(SeriesID string) (Series, error, int) {
|
|
var (
|
|
title string
|
|
message string
|
|
user_id int64
|
|
count int64
|
|
caption string
|
|
series Series
|
|
user UserInfo
|
|
)
|
|
|
|
URL := "https://app-api.pixiv.net/v2/novel/series?series_id=" + SeriesID + "&last_order=0"
|
|
req, err := http.NewRequest("GET", URL, nil)
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234 ")
|
|
req.Header.Set("Authorization", "Bearer "+p.accessToken)
|
|
if err != nil {
|
|
return series, err, 0
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return series, err, 0
|
|
}
|
|
body := GetBody(resp.Body)
|
|
defer resp.Body.Close()
|
|
message = gjson.Get(body, "error.message").String()
|
|
if message != "" {
|
|
return series, errors.New(message), resp.StatusCode
|
|
}
|
|
message = gjson.Get(body, "error.user_message").String()
|
|
if message != "" {
|
|
return series, errors.New(message), resp.StatusCode
|
|
}
|
|
|
|
title = gjson.Get(body, "novel_series_detail.title").String()
|
|
count = gjson.Get(body, "novel_series_detail.content_count").Int()
|
|
caption = gjson.Get(body, "novel_series_detail.caption").String()
|
|
user_id = gjson.Get(body, "novel_series_detail.user.id").Int()
|
|
|
|
user, _ = p.GetUserInfo(strconv.FormatInt(user_id, 10))
|
|
|
|
series.Title = title
|
|
series.Caption = caption
|
|
series.ContentCount = count
|
|
series.User = user
|
|
return series, nil, 200
|
|
}
|
|
|
|
// 获取小说简介
|
|
func (p *PixivAPI) GetNovelCaption(NovelID string) (NovelInfo, error) {
|
|
var (
|
|
message string
|
|
novel NovelInfo
|
|
user UserInfo
|
|
)
|
|
url := "https://app-api.pixiv.net/v2/novel/detail?novel_id=" + NovelID
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234 ")
|
|
req.Header.Set("Authorization", "Bearer "+p.accessToken)
|
|
|
|
if err != nil {
|
|
return novel, err
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return novel, err
|
|
}
|
|
defer resp.Body.Close()
|
|
body := GetBody(resp.Body)
|
|
|
|
message = gjson.Get(body, "error.message").String()
|
|
if message != "" {
|
|
return novel, errors.New(message)
|
|
}
|
|
message = gjson.Get(body, "error.user_message").String()
|
|
if message != "" {
|
|
return novel, errors.New(message)
|
|
|
|
}
|
|
// 简介
|
|
novel.Caption = gjson.Get(body, "novel.caption").String()
|
|
// id
|
|
novel.ID = NovelID
|
|
// 字数
|
|
novel.Length = gjson.Get(body, "novel.text_length").String()
|
|
// 发布时间
|
|
novel.Title = ToBeiJing(gjson.Get(body, "novel.create_date").String())
|
|
// 标题
|
|
novel.Title = gjson.Get(body, "novel.title").String()
|
|
// 用户名
|
|
user.Account = gjson.Get(body, "novel.user.account").String()
|
|
// 用户简介
|
|
user.Caption = ""
|
|
// 用户id
|
|
user.UserId = gjson.Get(body, "novel.user.id").Int()
|
|
// 用户名
|
|
user.UserName = gjson.Get(body, "novel.user.name").String()
|
|
novel.User = user
|
|
|
|
return novel, nil
|
|
}
|
|
|
|
// 获取小说内容
|
|
func (p *PixivAPI) GetNovelContent(NovelID string) (string, error, int) {
|
|
var (
|
|
message string
|
|
)
|
|
url := "https://app-api.pixiv.net/v1/novel/text?novel_id=" + NovelID
|
|
req, err := http.NewRequest("GET", url, nil)
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234 ")
|
|
req.Header.Set("Authorization", "Bearer "+p.accessToken)
|
|
if err != nil {
|
|
return "", err, 0
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return "", err, 0
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
body := GetBody(resp.Body)
|
|
|
|
message = gjson.Get(body, "error.message").String()
|
|
if message != "" {
|
|
return "", errors.New(message), resp.StatusCode
|
|
}
|
|
message = gjson.Get(body, "error.user_message").String()
|
|
if message != "" {
|
|
return "", errors.New(message), resp.StatusCode
|
|
}
|
|
return gjson.Get(body, "novel_text").String(), nil, resp.StatusCode
|
|
}
|
|
|
|
// 获取用户信息
|
|
func (p *PixivAPI) GetUserInfo(UserId string) (UserInfo, error) {
|
|
var (
|
|
message string
|
|
user UserInfo
|
|
)
|
|
URL := "https://app-api.pixiv.net/v1/user/detail?filter=for_android&user_id=" + UserId
|
|
req, err := http.NewRequest("GET", URL, nil)
|
|
req.Header.Set("accept-language", "zh_CN")
|
|
req.Header.Set("User-Agent", "PixivAndroidApp/5.0.234")
|
|
req.Header.Set("Authorization", "Bearer "+p.accessToken)
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
resp, err := p.httpClient.Do(req)
|
|
if err != nil {
|
|
return user, err
|
|
}
|
|
body := GetBody(resp.Body)
|
|
defer resp.Body.Close()
|
|
|
|
message = gjson.Get(body, "error.message").String()
|
|
if message != "" {
|
|
return user, errors.New(message)
|
|
}
|
|
message = gjson.Get(body, "error.user_message").String()
|
|
if message != "" {
|
|
return user, errors.New(message)
|
|
}
|
|
user.Account = gjson.Get(body, "user.account").String()
|
|
user.Caption = gjson.Get(body, "user.comment").String()
|
|
user.TotalNovels = gjson.Get(body, "profile.total_novels").Int()
|
|
user.UserId = gjson.Get(body, "user.id").Int()
|
|
user.UserName = gjson.Get(body, "user.name").String()
|
|
return user, nil
|
|
}
|