TODO\color{#EE2C2C}{TODO}TODO
−优化命令,增加url缓存\color{#6495ED}{-优化命令,增加url缓存}−优化命令,增加url缓存
−开放随机setu和检索功能\color{#6495ED}{-开放随机setu和检索功能}−开放随机setu和检索功能

根据分析好的安卓App Pivix用户登录和获取图片api的response来编写解析json需要的结构体

package fvckPixivtype UserProfileImageUrls struct {Px16x16   string `json:"px_16x16"`Px170x170 string `json:"px_170x170"`Px50x50   string `json:"px_50x50"`
}type PixOAuthHandler struct {Account                 string                 `json:"account"`Id                      string                 `json:"id"`IsMailAuthorized        bool                   `json:"is_mail_authorized"`IsPremium               bool                   `json:"is_premium"`MailAddress             string                 `json:"mail_address"`Name                    string                 `json:"name"`ProfileImageUrls        UserProfileImageUrls   `json:"profile_image_urls"`RequirePolicyAgreement  bool                   `json:"require_policy_agreement"`XRestrict               int                    `json:"x_restrict"`
}type PixLoginResponseHandler struct {AccessToken  string          `json:"access_token"`DeviceToken  string          `json:"device_token"`ExpiresIn    int             `json:"expires_in"`RefreshToken string          `json:"refresh_token"`Scope        string          `json:"scope"`TokenType    string          `json:"token_type"`UserOAuth    PixOAuthHandler `json:"user"`
}type PixLoginResponse struct {Response PixLoginResponseHandler `json:"response"`
}//-------------------------Illustration Info----------------------------//type ImageUrls struct {Large           string `json:"large"`Medium          string `json:"medium"`SquareMedium    string `json:"square_medium"`
}type MetaPages struct {}type IllustrationInfo struct {CreateDate      string      `json:"create_date"`Height          int         `json:"height"`Id              int         `json:"id"`ImageUrl        ImageUrls   `json:"image_urls"`
}type Illustration struct {Illusts         []IllustrationInfo  `json:"illusts"`NextUrl         string              `json:"next_url"`
}

生成构建post请求的参数需要的参数X-Client-Hash和X-Client-Time

package fvckPixiv
import ("crypto/md5""encoding/hex""strings""time"
)var HashSalt string = "28c1fdd170a5204386cb1313c7077b34f83e4aaf4aa829ce78c231e05b0bae2c"func getXClientHashAndTime() (string, string)  {var result  stringvar curTime string = getCurFormatTime()data        := []byte(curTime+HashSalt)encodedData := md5.Sum(data)for _, b := range encodedData {temp    := make([]byte, 1)temp[0] = byte(int(b) & 0xff)hexString := hex.EncodeToString(temp)if len(hexString) < 2 {hexString = "0" + hexString}result = result+hexString}return result, curTime
}func getCurFormatTime() string {rawTime := time.Now().Format("2006-01-02 15:04:05")var curTime stringfor index, timeMsg := range strings.Split(rawTime, " ") {if index == 0 {curTime = timeMsg} else if index == 1 {curTime = curTime + "T" + timeMsg + "+08:00"}}return curTime
}

模拟登录&发送请求

package fvckPixivimport ("crypto/tls""encoding/json""fmt""io""io/ioutil""log""net""net/http""net/url""strconv""strings""time"
)type PixClient struct {clnt             http.ClientpixLoginResponse PixLoginResponseR18illustration	 Illustration
}func NewPixivClient() *PixClient {proxyUrl, err := url.Parse("http://127.0.0.1:8118/")if err != nil {panic(err)}transport := &http.Transport {TLSClientConfig: &tls.Config{InsecureSkipVerify: false},Dial: func(netw, addr string) (net.Conn, error) {deadline := time.Now().Add(60 * time.Second)c, err := net.DialTimeout(netw, addr, time.Second*60)if err != nil {panic(err)}err = c.SetDeadline(deadline)return c, err},ResponseHeaderTimeout: time.Second*60,Proxy: http.ProxyURL(proxyUrl),}return &PixClient {clnt:             http.Client{Transport: transport},pixLoginResponse: PixLoginResponse{},}
}func (client *PixClient) PixLogin() {info := "password=fanfan2000&client_id=MOBrBDS8blbauoSck0ZfDbtuzpyT&get_secure_url=true&username=1264625484%40qq.com&include_policy=true&client_secret=lsACyCD94FhDUtGTXi3QzcFE2uU1hqtDaKeqrdwj&device_token=pixiv&grant_type=password"request, err := http.NewRequest("POST","https://oauth.secure.pixiv.net/auth/token",strings.NewReader(info))if err != nil {log.Println(err)}clientHash, curTime := getXClientHashAndTime()request.Header.Set("User-Agent", "PixivAndroidApp/5.0.155 (Android 5.1.1; OPPO R17)")request.Header.Set("Content-Type", "application/x-www-form-urlencoded")request.Header.Set("App-OS", "Android")request.Header.Set("App-OS-Version", "5.1.1")request.Header.Set("App-Version", "5.0.166")request.Header.Set("X-Client-Hash", clientHash)request.Header.Set("X-Client-Time", curTime)request.Header.Set("Connection", "Keep-Alive")request.Header.Set("Content-Length", strconv.Itoa(len(info)))for key, value := range request.Header {fmt.Println(key, value)}resp, err := client.clnt.Do(request)if err != nil {panic(err)}data, err := ioutil.ReadAll(resp.Body)defer resp.Body.Close()if err != nil {panic(err)} else {log.Println(string(data))}err = json.Unmarshal(data, &client.pixLoginResponse)if err != nil {panic(err)}}func (client *PixClient) PixivGetR18Daily() {req, err := http.NewRequest("GET","https://app-api.pixiv.net/v1/illust/ranking?filter=for_android&mode=day_r18",strings.NewReader(""))if err != nil {panic(err)}req.Header.Set("Content-Type", "application/x-www-form-urlencoded")req.Header.Set("Authorization", "Bearer " + client.pixLoginResponse.Response.AccessToken)req.Header.Set("User-Agent", "PixivAndroidApp/5.0.155 (Android 5.1.1; OPPO R17)")req.Header.Set("Accept", "*/*")req.Header.Set("Cache-Control", "no-cache")resp, err := client.clnt.Do(req)if err != nil {log.Println(err)}if resp.StatusCode == 400 {client.PixLogin()client.PixivGetR18Daily()return}content, err := ioutil.ReadAll(resp.Body)if err != nil {panic(err)}defer resp.Body.Close()err = json.Unmarshal(content, &client.R18illustration)if err != nil {log.Println(err)}}func (client *PixClient) HSGet(url string) (*http.Response, error) {resp, err := client.clnt.Get(url)if err != nil {return nil, err}return resp, nil
}func (client *PixClient) HSPost(url string, args string) (*http.Response, error) {var rsp io.Readerresp, err := client.clnt.Post(url, args, rsp)if err != nil {log.Println(err)}return resp, nil
}

下载文件到本地

package downloaderimport ("errors""fmt""io""log""net/http""os""strconv"
)func IsFileExist(filename string, fileSize int64) bool {info, err := os.Stat(filename)if os.IsNotExist(err) {log.Println(info)return false}if fileSize == info.Size() {log.Println("文件已存在!", info.Name(), info.Size(), info.ModTime())return true}del := os.Remove(filename)if del != nil {log.Println(del)}return false}func DownloadFile(url string, localPath string,feedbacks ...func(length, downLen int64)) error {var (fsize   int64buf   = make([]byte, 128*1024)written int64)tmpFilePath := localPath + ".download"log.Println(tmpFilePath)client := new(http.Client)resp, err := client.Get(url)if err != nil {return err}fsize, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 32)if err != nil {log.Println(err)}if IsFileExist(localPath, fsize) {return err}log.Println("fsize", fsize)file, err := os.Create(tmpFilePath)if err != nil {return err}defer file.Close()if resp.Body == nil {return errors.New("body is null")}defer resp.Body.Close()for {nr, er := resp.Body.Read(buf)if nr > 0 {nw, ew := file.Write(buf[0:nr])if nw > 0 {written += int64(nw)}if ew != nil {err = ewbreak}if nr != nw {err = io.ErrShortWritebreak}}if er != nil {if er != io.EOF {err = er}break}for _, feedback := range feedbacks {feedback(fsize, written)}}log.Println(err)if err == nil {file.Close()err = os.Rename(tmpFilePath, localPath)fmt.Println(err)}return err
}

机器人检测消息

package mainimport ("log""net/url""regexp""strconv""strings""FuckPixiv/src/fvckPixiv""github.com/catsworld/qq-bot-api"
)var bot  *qqbotapi.BotAPI
var pix  *fvckPixiv.PixClientconst Host = "127.0.0.1:6700"func main() {var err errorbot, err = qqbotapi.NewBotAPI("", "ws://"+Host, "CQHTTP_SECRET")if err != nil {log.Fatal(err)}bot.Debug = falseconf := qqbotapi.NewUpdate(0)conf.PreloadUserInfo = trueupdates, err := bot.GetUpdatesChan(conf)pix = fvckPixiv.NewPixivClient()pix.PixLogin()for update := range updates {if update.Message == nil {continue}log.Println(update.Message)handleMsg(update)}
}func handleMsg(update qqbotapi.Update) {if msgSlice := strings.Split(update.Message.Text, " ")len(msgSlice) == 2 {if msgSlice[0] == "KKP" || msgSlice[0] == "kkp" {re := regexp.MustCompile(`[0-9]{1,2}`)if re.MatchString(msgSlice[1]) {index, _ := strconv.Atoi(msgSlice[1])picUrl, err := url.Parse(pix.R18illustration.Illusts[index - 1].ImageUrl.Large)if err != nil {log.Println(err)}if update.MessageType == "group" {bot.NewMessage(update.GroupID, "group").ImageWeb(picUrl).Send()} else if update.MessageType == "private" {bot.NewMessage(update.Message.From.ID, "private").ImageWeb(picUrl).Send()}} else {return}}} else if len(msgSlice) == 1 {if msgSlice[0] == "色图GKD" {pix.PixivGetR18Daily()}}
}