-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
481823b
commit 9c01430
Showing
6 changed files
with
581 additions
and
110 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,170 @@ | ||
package oppo | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"net/url" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/KevinGong2013/apkgo/cmd/shared" | ||
"github.com/go-rod/rod" | ||
"github.com/go-rod/rod/lib/proto" | ||
"github.com/go-resty/resty/v2" | ||
) | ||
|
||
type OppoClient string | ||
// https://open.oppomobile.com/new/developmentDoc/info?id=10998 | ||
|
||
var DefaultClient = OppoClient("oppo") | ||
// 1300699476073808945 | ||
// 2ebf891579110cfdd7d1095adab056f716c1ecda029132f98bde73b00d220b31 | ||
|
||
func (oc OppoClient) Identifier() string { | ||
return string(oc) | ||
type Client struct { | ||
restyClient *resty.Client | ||
accessToken string | ||
clientSecret string | ||
} | ||
|
||
func (oc OppoClient) Name() string { | ||
return "oppo应用商店" | ||
func (c *Client) Name() string { | ||
return "oppo开放平台" | ||
} | ||
|
||
func (oc OppoClient) CheckAuth(browser *rod.Browser, reAuth bool) (*rod.Page, error) { | ||
page, err := browser.Page(proto.TargetCreateTarget{ | ||
URL: "https://open.oppomobile.com/new/ecological/app", | ||
}) | ||
func NewClient(clientId, clientSecret string) (*Client, error) { | ||
|
||
restyClient := resty.New() | ||
|
||
restyClient.SetDebugBodyLimit(10000) | ||
|
||
restyClient.SetBaseURL("https://oop-openapi-cn.heytapmobi.com") | ||
restyClient.SetHeader("Content-Type", "application/json") | ||
restyClient.SetDebug(true) | ||
|
||
// 获取token | ||
var result struct { | ||
Errno int `json:"errno"` | ||
Data struct { | ||
AccessToken string `json:"access_token"` | ||
ExpireIn int `json:"expire_in"` | ||
} `json:"data"` | ||
} | ||
_, err := restyClient.R().SetResult(&result).SetQueryParams(map[string]string{ | ||
"client_id": clientId, | ||
"client_secret": clientSecret, | ||
}).Get("/developer/v1/token") | ||
if err != nil { | ||
return nil, err | ||
} | ||
// oppo 环境检测非常慢 | ||
time.Sleep(time.Second * 15) | ||
_, err = page.Race().ElementR("h1", "Sign in").Handle(func(e *rod.Element) error { | ||
if !reAuth { | ||
return errors.New("登陆态失效") | ||
} | ||
fmt.Println("show login alert") | ||
if _, err := page.Eval("(msg) => { alert(msg) }", "登录完成以后会自动同步到apkgo"); err != nil { | ||
return err | ||
} | ||
return page.WaitElementsMoreThan(".service-item-open", 0) | ||
}).Element(".service-item-open").MustHandle(func(e *rod.Element) { | ||
// Click this element | ||
// | ||
|
||
fmt.Println("oppo login succeed") | ||
}).Do() | ||
|
||
return page, err | ||
} | ||
|
||
func (oc OppoClient) Do(page *rod.Page, req shared.PublishRequest) error { | ||
return do(page, req) | ||
|
||
token := result.Data.AccessToken | ||
|
||
c := &Client{restyClient: restyClient, accessToken: token, clientSecret: clientSecret} | ||
|
||
return c, nil | ||
} | ||
|
||
// 签名 | ||
func (c *Client) sign(data url.Values) url.Values { | ||
// 公共参数 | ||
data.Set("access_token", c.accessToken) | ||
data.Set("timestamp", strconv.FormatInt(time.Now().Unix(), 10)) | ||
// 计算签名 | ||
api_sign := calSign(c.clientSecret, data) | ||
data.Set("api_sign", api_sign) | ||
|
||
return data | ||
} | ||
|
||
// 查询普通包详情 | ||
func (c *Client) query(pkgName string) (*app, error) { | ||
data := make(url.Values) | ||
data.Set("pkg_name", pkgName) | ||
var result struct { | ||
Errno int `json:"errno"` | ||
Data *app `json:"data"` | ||
} | ||
if _, err := c.restyClient.R().SetResult(&result). | ||
SetQueryParamsFromValues(c.sign(data)).Get("/resource/v1/app/info"); err != nil { | ||
return nil, err | ||
} | ||
if result.Errno != 0 { | ||
return nil, fmt.Errorf("查询失败基础信息失败 code: %d", result.Errno) | ||
} | ||
|
||
return result.Data, nil | ||
} | ||
|
||
// 上传文件 | ||
func (c *Client) uploadAPK(file string) (*uploadResult, error) { | ||
var result struct { | ||
Errno int `json:"errno"` | ||
Data struct { | ||
UploadUrl string `json:"upload_url"` | ||
Sign string `json:"sign"` | ||
} `json:"data"` | ||
} | ||
// 1. 获取上传url | ||
if _, err := c.restyClient.R(). | ||
SetResult(&result). | ||
SetQueryParamsFromValues(c.sign(make(url.Values))). | ||
Get("/resource/v1/upload/get-upload-url"); err != nil { | ||
return nil, err | ||
} | ||
|
||
var uploadResult struct { | ||
Errno int `json:"errno"` | ||
Data uploadResult `json:"data"` | ||
} | ||
|
||
if _, err := c.restyClient.R().SetResult(&uploadResult). | ||
SetFormData(map[string]string{ | ||
"sign": result.Data.Sign, | ||
"type": "apk", | ||
}). | ||
SetFile("file", file). | ||
Post(result.Data.UploadUrl); err != nil { | ||
return nil, err | ||
} | ||
|
||
return &uploadResult.Data, nil | ||
} | ||
|
||
func (c *Client) publish(req publishRequestParameter) error { | ||
|
||
var result struct { | ||
Errno int `json:"errno"` | ||
Data struct { | ||
Success bool `json:"success"` | ||
Message string `json:"message"` | ||
LogId int `json:"logid"` | ||
} `json:"data"` | ||
} | ||
|
||
if _, err := c.restyClient.R(). | ||
SetResult(&result). | ||
SetFormDataFromValues(c.sign(req.toValues())). | ||
Post("/resource/v1/app/upd"); err != nil { | ||
return err | ||
} | ||
if result.Errno != 0 { | ||
return fmt.Errorf("发布失败 message: %s logid: %d", result.Data.Message, result.Data.LogId) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// 获取任务状态 | ||
|
||
func (c *Client) taskState(packageName string, versionCode string) (*taskBody, error) { | ||
data := make(url.Values) | ||
data.Set("pkg_name", packageName) | ||
data.Set("version_code", versionCode) | ||
var result struct { | ||
Errno int `json:"errno"` | ||
Data *taskBody `json:"data"` | ||
} | ||
if _, err := c.restyClient.R().SetResult(&result). | ||
SetFormDataFromValues(c.sign(data)). | ||
Post("/resource/v1/app/task-state"); err != nil { | ||
return nil, err | ||
} | ||
if result.Errno != 0 { | ||
return nil, fmt.Errorf("查询任务状态失败 code: %d", result.Errno) | ||
} | ||
|
||
return result.Data, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,95 +1,106 @@ | ||
package oppo | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"errors" | ||
"strconv" | ||
"time" | ||
|
||
"github.com/KevinGong2013/apkgo/cmd/shared" | ||
"github.com/go-rod/rod" | ||
"github.com/go-rod/rod/lib/proto" | ||
"github.com/ysmood/gson" | ||
) | ||
|
||
func do(page *rod.Page, req shared.PublishRequest) error { | ||
func (c *Client) Do(req shared.PublishRequest) error { | ||
|
||
js := `async function postData() { | ||
var formData = new FormData() | ||
formData.append('type', 0) | ||
formData.append('limit', 100) | ||
formData.append('offset', 0) | ||
const response = await fetch('https://open.oppomobile.com/resource/list/index.json', { | ||
method: 'POST', | ||
body: formData | ||
}) | ||
return response.text() | ||
}` | ||
|
||
obj, err := page.Evaluate(&rod.EvalOptions{ | ||
ByValue: true, | ||
AwaitPromise: true, | ||
JS: js, | ||
UserGesture: true, | ||
}) | ||
param := publishRequestParameter{} | ||
|
||
param.PkgName = req.PackageName | ||
param.VersionCode = strconv.Itoa(int(req.VersionCode)) | ||
|
||
// 1. 获取一批基础信息 | ||
app, err := c.query(req.PackageName) | ||
if err != nil { | ||
fmt.Println(err) | ||
return err | ||
} | ||
param.AppName = app.AppName | ||
|
||
r := gson.NewFrom(obj.Value.Str()) | ||
secondCategoryId, _ := strconv.Atoi(app.SecondCategoryID) | ||
param.SecondCategoryId = secondCategoryId | ||
|
||
errno := r.Get("errno").Int() | ||
if errno != 0 { | ||
return fmt.Errorf("auth failed. %s", r.Raw()) | ||
} | ||
thirdCategoryId, _ := strconv.Atoi(app.ThirdCategoryID) | ||
param.ThirdCategoryId = thirdCategoryId | ||
|
||
var appid string | ||
for _, row := range r.Get("data").Get("rows").Arr() { | ||
if row.Get("pkg_name").Str() == req.PackageName { | ||
appid = row.Get("app_id").Str() | ||
break | ||
} | ||
param.Summary = "[寓小二]公寓系统定制专家" //app.Summary | ||
param.DetailDesc = app.DetailDesc | ||
param.UpdateDesc = req.UpdateDesc | ||
param.PrivacySourceUrl = app.PrivacySourceURL | ||
param.IconUrl = app.IconURL | ||
param.PicUrl = app.PicURL | ||
param.OnlineType = 1 | ||
param.TestDesc = "submit by apkgo tool." | ||
param.CopyrightUrl = app.CopyrightURL | ||
param.BusinessUsername = app.BusinessUsername | ||
param.BusinessMobile = app.BusinessMobile | ||
param.BusinessEmail = app.BusinessEmail | ||
|
||
ageLevel, _ := strconv.Atoi(app.AgeLevel) | ||
param.AgeLevel = ageLevel | ||
|
||
param.AdaptiveType = app.AdaptiveType | ||
param.AdaptiveEquipment = app.AdaptiveEquipment | ||
|
||
param.CustomerContact = app.CustomerContact | ||
|
||
// 2. 上传apk包 | ||
uploadResult, err := c.uploadAPK(req.ApkFile) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(appid) == 0 { | ||
return fmt.Errorf("unsupported package. %s", req.PackageName) | ||
cpucode := 0 | ||
|
||
if len(req.SecondApkFile) > 0 { | ||
cpucode = 32 | ||
} | ||
|
||
return rod.Try(func() { | ||
page.MustNavigate(fmt.Sprintf("https://open.oppomobile.com/new/mcom#/home/management/app-admin#/resource/update/index?app_id=%s&is_gray=2", appid)) | ||
iframe := page.MustElement(`iframe[id="menu_service_main_iframe"]`).MustFrame() | ||
// 判断一下如果有弹窗,先关掉弹窗 | ||
time.Sleep(time.Second * 3) | ||
exist, noButton, _ := iframe.Has(`#save-the-draft > div > div > div.modal-body > div:nth-child(2) > button.btn.no`) | ||
if exist { | ||
noButton.MustClick() | ||
} | ||
param.ApkUrl = append(param.ApkUrl, apkInfo{ | ||
Url: uploadResult.URL, | ||
Md5: uploadResult.MD5, | ||
CpuCode: cpucode, | ||
}) | ||
|
||
iframe.MustElement(`textarea[name="update_desc"`).MustSelectAllText().MustInput(req.UpdateDesc) | ||
|
||
wait := page.EachEvent(func(e *proto.NetworkResponseReceived) bool { | ||
url := e.Response.URL | ||
if strings.Contains(url, "verify-info.json") { | ||
m := proto.NetworkGetResponseBody{RequestID: e.RequestID} | ||
if r, err := m.Call(page); err == nil { | ||
body := gson.NewFrom(r.Body) | ||
errno := body.Get("errono").Int() | ||
if errno == 0 { | ||
return true | ||
} | ||
|
||
if errno == 911046 { | ||
return true | ||
} | ||
|
||
} | ||
return false | ||
} | ||
return false | ||
if len(req.SecondApkFile) > 0 { | ||
secondResult, err := c.uploadAPK(req.SecondApkFile) | ||
if err != nil { | ||
return err | ||
} | ||
param.ApkUrl = append(param.ApkUrl, apkInfo{ | ||
Url: secondResult.URL, | ||
Md5: secondResult.MD5, | ||
CpuCode: 64, | ||
}) | ||
} | ||
|
||
go iframe.MustElement(`#auditphasedbuttonclick`).MustClick() | ||
// 3. 发布 | ||
if err := c.publish(param); err != nil { | ||
return err | ||
} | ||
|
||
wait() | ||
}) | ||
times := 0 | ||
for { | ||
if times > 10 { | ||
return errors.New("发布失败") | ||
} | ||
time.Sleep(time.Second * 10) | ||
state, err := c.taskState(req.PackageName, strconv.Itoa(int(req.VersionCode))) | ||
if err != nil { | ||
return err | ||
} | ||
// 成功 | ||
if state.TaskState == "2" { | ||
return nil | ||
} | ||
if state.TaskState == "3" { | ||
return errors.New(state.ErrMsg) | ||
} | ||
times += 1 | ||
} | ||
} |
Oops, something went wrong.