weibo

package module
v1.0.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 28, 2020 License: MIT Imports: 26 Imported by: 1

README

weibo

新浪微博 Golang 版 SDK

微博API: https://open.weibo.com/wiki/微博API

功能

代码组织结构已按新浪微博提供的接口拆分,已支持的功能列表:

亮点

模拟微博登录自动获取授权码并取得token

使用账号密码模拟登录微博后获取授权码,从url中取得授权码后再获取token,过程中无需人工干预。

支持登录时验证码识别

默认触发验证码时,将验证码保存在临时目录中,提示用户人工处理,手动输入验证码后继续后续逻辑,期间会尝试显示验证码图片,若失败则需要人工去提示路径下打开图片。

支持注册破解验证码的函数,注册后触发验证码时,优先使用注册的函数识别验证码,如果识别失败则仍然采用提示用户手动输入。

破解函数的声明为 func(io.Reader) (string, error) ,只要符合此签名的函数就可以调用 RegisterCrackPinFunc 方法注册。RegisterCrackPinFunc 可以传入多个破解函数,会逐个尝试。

安装

go get -u -v github.com/axiaoxin-com/weibo

在线文档

https://pkg.go.dev/github.com/axiaoxin-com/weibo?tab=doc

使用示例

发送纯文本内容的微博

example/text.go

// 发送文本内容示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/axiaoxin-com/weibo"
)

func main() {
	// 从环境变量获取必须的账号信息
	appkey := os.Getenv("weibo_app_key")
	appsecret := os.Getenv("weibo_app_secret")
	username := os.Getenv("weibo_username")
	passwd := os.Getenv("weibo_passwd")
	redirecturi := os.Getenv("weibo_redirect_uri")
	securitydomain := os.Getenv("weibo_security_domain")

	// 初始化客户端
	weibo := weibo.New(appkey, appsecret, username, passwd, redirecturi)

	// 登录微博
	if err := weibo.PCLogin(); err != nil {
		log.Fatal(err)
	}

	// 获取授权码
	code, err := weibo.Authorize()
	if err != nil {
		log.Fatal(err)
	}

	// 获取access token
	token, err := weibo.AccessToken(code)
	if err != nil {
		log.Fatal(err)
	}

	// 发送微博,必须带有安全域名链接
	status := fmt.Sprintf("文本内容 http://%s", securitydomain)
	resp, err := weibo.StatusesShare(token.AccessToken, status, nil)
	if err != nil {
		log.Println(err)
	}
	log.Println("微博发送成功 详情点击 http://weibo.com/" + resp.User.ProfileURL)
}
发送文字内容带图片的微博

example/text_pic.go

// 发送带图片的文本内容示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/axiaoxin-com/weibo"
)

func main() {
	// 从环境变量获取必须的账号信息
	appkey := os.Getenv("weibo_app_key")
	appsecret := os.Getenv("weibo_app_secret")
	username := os.Getenv("weibo_username")
	passwd := os.Getenv("weibo_passwd")
	redirecturi := os.Getenv("weibo_redirect_uri")
	securitydomain := os.Getenv("weibo_security_domain")

	// 初始化客户端
	weibo := weibo.New(appkey, appsecret, username, passwd, redirecturi)

	// 登录微博
	if err := weibo.PCLogin(); err != nil {
		log.Fatal(err)
	}

	// 获取授权码
	code, err := weibo.Authorize()
	if err != nil {
		log.Fatal(err)
	}

	// 获取access token
	token, err := weibo.AccessToken(code)
	if err != nil {
		log.Fatal(err)
	}

	// 发送微博,必须带有安全域名链接
	status := fmt.Sprintf("文字带图片示例 http://%s", securitydomain)
	// 加载要发送的图片,加载方式只要是返回io.Reader都可以
	pic, err := os.Open("./pic.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer pic.Close()
	resp, err := weibo.StatusesShare(token.AccessToken, status, pic)
	if err != nil {
		log.Println(err)
	}
	log.Println("微博发送成功 详情点击 http://weibo.com/" + resp.User.ProfileURL)
}
注册破解函数

example/crackfunc.go

// 注册验证码破解函数示例
// 登录遇到验证码时
// 如果有注册你自己的破解函数则会尝试使用你注册的函数进行验证码破解
// 破解失败则采用默认的人工手动处理的方式手工输入保存在临时目录中的weibo_pin.png中的验证码

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/axiaoxin-com/chaojiying"
	"github.com/axiaoxin-com/weibo"
)

func main() {
	// 从环境变量获取必须的账号信息
	appkey := os.Getenv("weibo_app_key")
	appsecret := os.Getenv("weibo_app_secret")
	username := os.Getenv("weibo_username")
	passwd := os.Getenv("weibo_passwd")
	redirecturi := os.Getenv("weibo_redirect_uri")
	securitydomain := os.Getenv("weibo_security_domain")

	// 初始化客户端
	weibo := weibo.New(appkey, appsecret, username, passwd, redirecturi)

	// 使用超级鹰破解验证码
	// 初始化超级鹰客户端
	chaojiyingUser := os.Getenv("chaojiying_user")
	chaojiyingPass := os.Getenv("chaojiying_pass")
	chaojiyingAccount := chaojiying.Account{User: chaojiyingUser, Pass: chaojiyingPass}
	cracker, err := chaojiying.New([]chaojiying.Account{chaojiyingAccount})
	if err != nil {
		log.Println(err)
	}
	// 将破解函数注册到微博客户端
	// 破解函数的声明为 func(io.Reader) (string, error),只要符合此签名的函数就可以注册
	// RegisterCrackPinFunc 可以传入多个破解函数,会逐个尝试
	// 这里的Cr4ck即为chaojiying中的破解函数
	weibo.RegisterCrackPinFunc(cracker.Cr4ck)
	fmt.Println("验证码破解方法注册成功")

	// 登录微博 遇到验证码将自动识别
	if err := weibo.PCLogin(); err != nil {
		log.Fatal(err)
	}

	// 获取授权码
	code, err := weibo.Authorize()
	if err != nil {
		log.Fatal(err)
	}

	// 获取access token
	token, err := weibo.AccessToken(code)
	if err != nil {
		log.Fatal(err)
	}

	// 发送微博,必须带有安全域名链接
	status := fmt.Sprintf("文本内容 http://%s", securitydomain)
	resp, err := weibo.StatusesShare(token.AccessToken, status, nil)
	if err != nil {
		log.Println(err)
	}
	log.Println("微博发送成功 详情点击 http://weibo.com/" + resp.User.ProfileURL)
}

难点

模拟登录

要想代码层面直接获取到授权码,必须要在微博应用的授权页面进行模拟浏览器登录。 登录参数会被js代码处理,需要翻译对应的 js 代码为 go ,crypto 包不熟,这里花了一些时间。

如何处理模拟登录时遇到的验证码

最开始的方案是客户端实例化的时候可以接收授权码参数,如果直接传入了授权码则跳过登录逻辑直接去获取 token 就不会触发验证码。 但是这个接口使用体验太差,每次需要先去浏览器中手工授权获取 URL 中的授权码,再将其写入配置中运行时加载,过程相对麻烦; 而且如果为多个微博账号配置各自的授权码时极易忘记退出上一次登录的账号导致获取到的是其他账号的 token 。

在无法自己实现验证码自动识别的事实上,不如将验证码保存到本地,触发验证码时本地直接打开后手动输入即可,只需一次人工干预, 实际应用中通常也只有服务启动时需要登录授权操作,因此这样可以接受。

在调研后发现有超级鹰等类似的验证码识别平台,提供 http 接口破解验证码,于是实现了注册破解函数的模式。 用户可以实现自己的破解方法,只需按约定的出入参数实现即可,注册后当遇到验证码会自动调用处理,失败时再使用人工识别手动输入的方案兜底。

遇到的问题

https://github.com/axiaoxin-com/weibo/issues?q=label%3Anote

Documentation

Overview

Package weibo 新浪微博 SDK

Example (LoginWithCrackFunc)

注册验证码破解函数示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/axiaoxin-com/chaojiying"
	"github.com/axiaoxin-com/weibo"
)

func main() {
	// 登录遇到验证码时
	// 如果有注册你自己的破解函数则会尝试使用你注册的函数进行验证码破解
	// 破解失败则采用默认的人工手动处理的方式手工输入保存在临时目录中的weibo_pin.png中的验证码

	// 从环境变量获取必须的账号信息
	appkey := os.Getenv("weibo_app_key")
	appsecret := os.Getenv("weibo_app_secret")
	username := os.Getenv("weibo_username")
	passwd := os.Getenv("weibo_passwd")
	redirecturi := os.Getenv("weibo_redirect_uri")
	securitydomain := os.Getenv("weibo_security_domain")

	// 初始化客户端
	weibo := weibo.New(appkey, appsecret, username, passwd, redirecturi)

	// 使用超级鹰破解验证码
	// 初始化超级鹰客户端
	chaojiyingUser := os.Getenv("chaojiying_user")
	chaojiyingPass := os.Getenv("chaojiying_pass")
	chaojiyingAccount := chaojiying.Account{User: chaojiyingUser, Pass: chaojiyingPass}
	cracker, err := chaojiying.New([]chaojiying.Account{chaojiyingAccount})
	if err != nil {
		log.Println(err)
	}
	// 将破解函数注册到微博客户端
	// 破解函数的声明为 func(io.Reader) (string, error),只要符合此签名的函数就可以注册
	// RegisterCrackPinFunc 可以传入多个破解函数,会逐个尝试
	// 这里的Cr4ck即为chaojiying中的破解函数
	weibo.RegisterCrackPinFunc(cracker.Cr4ck)
	fmt.Println("验证码破解方法注册成功")

	// 登录微博 遇到验证码将自动识别
	if err := weibo.PCLogin(); err != nil {
		log.Fatal(err)
	}

	// 获取授权码
	code, err := weibo.Authorize()
	if err != nil {
		log.Fatal(err)
	}

	// 获取access token
	token, err := weibo.AccessToken(code)
	if err != nil {
		log.Fatal(err)
	}

	// 发送微博,必须带有安全域名链接
	status := fmt.Sprintf("文本内容 http://%s", securitydomain)
	resp, err := weibo.StatusesShare(token.AccessToken, status, nil)
	if err != nil {
		log.Println(err)
	}
	log.Println("微博发送成功 详情点击 http://weibo.com/" + resp.User.ProfileURL)
}
Output:

Example (SendPicWeibo)

发送带图片的微博示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/axiaoxin-com/weibo"
)

func main() {
	// 从环境变量获取必须的账号信息
	appkey := os.Getenv("weibo_app_key")
	appsecret := os.Getenv("weibo_app_secret")
	username := os.Getenv("weibo_username")
	passwd := os.Getenv("weibo_passwd")
	redirecturi := os.Getenv("weibo_redirect_uri")
	securitydomain := os.Getenv("weibo_security_domain")

	// 初始化客户端
	weibo := weibo.New(appkey, appsecret, username, passwd, redirecturi)

	// 登录微博
	if err := weibo.PCLogin(); err != nil {
		log.Fatal(err)
	}

	// 获取授权码
	code, err := weibo.Authorize()
	if err != nil {
		log.Fatal(err)
	}

	// 获取access token
	token, err := weibo.AccessToken(code)
	if err != nil {
		log.Fatal(err)
	}

	// 发送微博,必须带有安全域名链接
	status := fmt.Sprintf("文字带图片示例 http://%s", securitydomain)
	// 加载要发送的图片,加载方式只要是返回io.Reader都可以
	pic, err := os.Open("./example/pic.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer pic.Close()
	resp, err := weibo.StatusesShare(token.AccessToken, status, pic)
	if err != nil {
		log.Println(err)
	}
	log.Println("微博发送成功 详情点击 http://weibo.com/" + resp.User.ProfileURL)
}
Output:

Example (SendTextWeibo)

发送存文本内容微博示例

package main

import (
	"fmt"
	"log"
	"os"

	"github.com/axiaoxin-com/weibo"
)

func main() {
	// 从环境变量获取必须的账号信息
	appkey := os.Getenv("weibo_app_key")
	appsecret := os.Getenv("weibo_app_secret")
	username := os.Getenv("weibo_username")
	passwd := os.Getenv("weibo_passwd")
	redirecturi := os.Getenv("weibo_redirect_uri")
	securitydomain := os.Getenv("weibo_security_domain")

	// 初始化客户端
	weibo := weibo.New(appkey, appsecret, username, passwd, redirecturi)

	// 登录微博
	if err := weibo.PCLogin(); err != nil {
		log.Fatal(err)
	}

	// 获取授权码
	code, err := weibo.Authorize()
	if err != nil {
		log.Fatal(err)
	}

	// 获取access token
	token, err := weibo.AccessToken(code)
	if err != nil {
		log.Fatal(err)
	}

	// 发送微博,必须带有安全域名链接
	status := fmt.Sprintf("文本内容 http://%s", securitydomain)
	resp, err := weibo.StatusesShare(token.AccessToken, status, nil)
	if err != nil {
		log.Println(err)
	}
	log.Println("微博发送成功 详情点击 http://weibo.com/" + resp.User.ProfileURL)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var UserAgents []string = []string{
	"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Safari/605.1.15",
	"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
	"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.109 Safari/537.36",
}

UserAgents 模拟登录时随机选择其中的 User-Agent 设置请求头

Functions

func RandInt

func RandInt(min int, max int) int

RandInt 产生指定数字范围内的随机数

func RandUA

func RandUA() string

RandUA 随机返回一个 UserAgents 中的元素

func RealIP

func RealIP() string

RealIP 获取 IP 地址

func TerminalOpen

func TerminalOpen(filePath string) error

TerminalOpen 调用终端打开文件命令

Types

type CrackPinFunc

type CrackPinFunc func(io.Reader) (string, error)

CrackPinFunc 验证码破解方法类型声明 验证码图片以 io.Reader 类型传入,返回破解结果字符串

type MobileLoginResp

type MobileLoginResp struct {
	Retcode int                    `json:"retcode"`
	Msg     string                 `json:"msg"`
	Data    map[string]interface{} `json:"data"`
}

MobileLoginResp 移动端登录的返回结果

type StatusesShareResp

type StatusesShareResp struct {
	Visible struct {
		Type   int `json:"type"`
		ListID int `json:"list_id"`
	} `json:"visible"`
	CreatedAt                string        `json:"created_at"`
	ID                       int64         `json:"id"`
	Idstr                    string        `json:"idstr"`
	Mid                      string        `json:"mid"`
	CanEdit                  bool          `json:"can_edit"`
	ShowAdditionalIndication int           `json:"show_additional_indication"`
	Text                     string        `json:"text"`
	TextLength               int           `json:"textLength"`
	SourceAllowclick         int           `json:"source_allowclick"`
	SourceType               int           `json:"source_type"`
	Source                   string        `json:"source"`
	Favorited                bool          `json:"favorited"`
	Truncated                bool          `json:"truncated"`
	InReplyToStatusID        string        `json:"in_reply_to_status_id"`
	InReplyToUserID          string        `json:"in_reply_to_user_id"`
	InReplyToScreenName      string        `json:"in_reply_to_screen_name"`
	PicUrls                  []interface{} `json:"pic_urls"`
	Geo                      interface{}   `json:"geo"`
	IsPaid                   bool          `json:"is_paid"`
	MblogVipType             int           `json:"mblog_vip_type"`
	User                     struct {
		ID               int64  `json:"id"`
		Idstr            string `json:"idstr"`
		Class            int    `json:"class"`
		ScreenName       string `json:"screen_name"`
		Name             string `json:"name"`
		Province         string `json:"province"`
		City             string `json:"city"`
		Location         string `json:"location"`
		Description      string `json:"description"`
		URL              string `json:"url"`
		ProfileImageURL  string `json:"profile_image_url"`
		ProfileURL       string `json:"profile_url"`
		Domain           string `json:"domain"`
		Weihao           string `json:"weihao"`
		Gender           string `json:"gender"`
		FollowersCount   int    `json:"followers_count"`
		FriendsCount     int    `json:"friends_count"`
		PagefriendsCount int    `json:"pagefriends_count"`
		StatusesCount    int    `json:"statuses_count"`
		VideoStatusCount int    `json:"video_status_count"`
		FavouritesCount  int    `json:"favourites_count"`
		CreatedAt        string `json:"created_at"`
		Following        bool   `json:"following"`
		AllowAllActMsg   bool   `json:"allow_all_act_msg"`
		GeoEnabled       bool   `json:"geo_enabled"`
		Verified         bool   `json:"verified"`
		VerifiedType     int    `json:"verified_type"`
		Remark           string `json:"remark"`
		Insecurity       struct {
			SexualContent bool `json:"sexual_content"`
		} `json:"insecurity"`
		Ptype             int    `json:"ptype"`
		AllowAllComment   bool   `json:"allow_all_comment"`
		AvatarLarge       string `json:"avatar_large"`
		AvatarHd          string `json:"avatar_hd"`
		VerifiedReason    string `json:"verified_reason"`
		VerifiedTrade     string `json:"verified_trade"`
		VerifiedReasonURL string `json:"verified_reason_url"`
		VerifiedSource    string `json:"verified_source"`
		VerifiedSourceURL string `json:"verified_source_url"`
		FollowMe          bool   `json:"follow_me"`
		Like              bool   `json:"like"`
		LikeMe            bool   `json:"like_me"`
		OnlineStatus      int    `json:"online_status"`
		BiFollowersCount  int    `json:"bi_followers_count"`
		Lang              string `json:"lang"`
		Star              int    `json:"star"`
		Mbtype            int    `json:"mbtype"`
		Mbrank            int    `json:"mbrank"`
		BlockWord         int    `json:"block_word"`
		BlockApp          int    `json:"block_app"`
		CreditScore       int    `json:"credit_score"`
		UserAbility       int    `json:"user_ability"`
		Urank             int    `json:"urank"`
		StoryReadState    int    `json:"story_read_state"`
		VclubMember       int    `json:"vclub_member"`
		IsTeenager        int    `json:"is_teenager"`
		IsGuardian        int    `json:"is_guardian"`
		IsTeenagerList    int    `json:"is_teenager_list"`
		SpecialFollow     bool   `json:"special_follow"`
		TabManage         string `json:"tab_manage"`
	} `json:"user"`
	RepostsCount         int           `json:"reposts_count"`
	CommentsCount        int           `json:"comments_count"`
	AttitudesCount       int           `json:"attitudes_count"`
	PendingApprovalCount int           `json:"pending_approval_count"`
	IsLongText           bool          `json:"isLongText"`
	RewardExhibitionType int           `json:"reward_exhibition_type"`
	HideFlag             int           `json:"hide_flag"`
	Mlevel               int           `json:"mlevel"`
	BizFeature           int           `json:"biz_feature"`
	HasActionTypeCard    int           `json:"hasActionTypeCard"`
	DarwinTags           []interface{} `json:"darwin_tags"`
	HotWeiboTags         []interface{} `json:"hot_weibo_tags"`
	TextTagTips          []interface{} `json:"text_tag_tips"`
	Mblogtype            int           `json:"mblogtype"`
	UserType             int           `json:"userType"`
	MoreInfoType         int           `json:"more_info_type"`
	PositiveRecomFlag    int           `json:"positive_recom_flag"`
	ContentAuth          int           `json:"content_auth"`
	GifIds               interface{}   `json:"gif_ids"`
	IsShowBulletin       int           `json:"is_show_bulletin"`
	CommentManageInfo    struct {
		CommentManageButton   int `json:"comment_manage_button"`
		CommentPermissionType int `json:"comment_permission_type"`
		ApprovalCommentType   int `json:"approval_comment_type"`
	} `json:"comment_manage_info"`
	PicNum int `json:"pic_num"`
}

StatusesShareResp 微博成功发送后的返回结构

type TokenInfoResp

type TokenInfoResp struct {
	UID      string `json:"uid"`
	Appkey   string `json:"appkey"`
	Scope    string `json:"scope"`
	CreateAt string `json:"create_at"`
	ExpireIn string `json:"expire_in"`
}

TokenInfoResp 查询 token 信息接口的返回结果

type TokenResp

type TokenResp struct {
	AccessToken string `json:"access_token"` // access token
	ExpiresIn   int64  `json:"expires_in"`   // ExpiresIn 秒之后token过期
	UID         string `json:"uid"`
	IsRealName  string `json:"isRealName"`
}

TokenResp 获取 access token 接口的返回结果

type Weibo

type Weibo struct {
	// contains filtered or unexported fields
}

Weibo 实例,在其上实现各类接口

func New

func New(appkey, appsecret, username, passwd, redirecturi string) *Weibo

New 创建Weibo实例

appkey 微博开放平台应用的 appkey

appsecret 微博开放平台应用的 appsecret

username 需要发微博的微博登录账号,用于模拟登录直接获取授权码

password 需要发微博的微博登录密码,用于模拟登录直接获取授权码

redirecturi 微博开发平台应用的回调 URL

func (*Weibo) AccessToken

func (w *Weibo) AccessToken(code string) (*TokenResp, error)

AccessToken 传入授权码请求 access_token 接口,返回 TokenResp 对象

func (*Weibo) Authorize

func (w *Weibo) Authorize() (string, error)

Authorize 请求微博授权,返回授权码

func (*Weibo) MobileLogin

func (w *Weibo) MobileLogin() error

MobileLogin 模拟移动端登录微博 (该登录无法通过调用 Authorize 方法获取开放平台的 token)

func (*Weibo) PCLogin

func (w *Weibo) PCLogin() error

PCLogin 模拟电脑 web 登录 登录后才能成功获取开放平台授权码和 token

func (*Weibo) RegisterCrackPinFunc

func (w *Weibo) RegisterCrackPinFunc(f ...CrackPinFunc)

RegisterCrackPinFunc 注册验证码破解方法到 Weibo 实例 触发验证码时自动调用注册的方法进行破解后模拟登录

func (*Weibo) StatusesShare

func (w *Weibo) StatusesShare(token, status string, pic io.Reader) (*StatusesShareResp, error)

StatusesShare 第三方分享一条链接到微博

token 为获取到的access_token内容

status 为微博文字内容

pic 为附带的一张图片,传nil则只发文字

func (*Weibo) TokenInfo

func (w *Weibo) TokenInfo(token string) (*TokenInfoResp, error)

TokenInfo 获取用户 access_token 的授权相关信息,包括授权时间,过期时间和 scope 权限

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL