Skip to content

Commit 9fdba3a

Browse files
PIKACHUIMxrgzsSuyunmeng
authored
feat(drivers/123_open): support 123 official app api (#2293)
* feat(driver): support 123 official app api * fix(123_open): migrate api refresh to token.go Signed-off-by: MadDogOwner <xiaoran@xrgzs.top> * fix(drivers/123_open): trigger proactive refresh with client credentials * fix(drivers/123_open): use client-credential token endpoint for local refresh Keep renewapi parsing for expires_in and map it to internal expiry time handling. * fix(drivers/123_open): limit proactive refresh to client credentials * fix(drivers/123_open): allow renewapi refresh token proactive init * fix(drivers/123_open): update API address to use renewapi endpoint * fix(drivers/123_open): simplify token refresh parsing * fix(drivers/123_open): unify token expiration to expiredAt --------- Signed-off-by: MadDogOwner <xiaoran@xrgzs.top> Co-authored-by: MadDogOwner <xiaoran@xrgzs.top> Co-authored-by: Suyunmeng <Susus0175@proton.me> Co-authored-by: Suyunjing <suyunmeng@oplist.org.cn>
1 parent 7bea29c commit 9fdba3a

File tree

4 files changed

+94
-73
lines changed

4 files changed

+94
-73
lines changed

drivers/123_open/driver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ func (d *Open123) Init(ctx context.Context) error {
3434
d.UploadThread = 3
3535
}
3636

37-
if d.RefreshToken != "" {
38-
// refresh token 直接主动刷新
37+
if (d.UseOnlineAPI && d.RefreshToken != "" && len(d.APIAddress) > 0) || (d.ClientID != "" && d.ClientSecret != "") {
38+
// proactive refresh by renewapi or client credentials
3939
d.AccessToken = ""
4040
d.tm = &tokenManager{}
4141
} else {

drivers/123_open/meta.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,20 @@ import (
66
)
77

88
type Addition struct {
9-
// refresh_token方式的AccessToken 【对个人开发者暂未开放】
10-
RefreshToken string `json:"RefreshToken" required:"false"`
11-
129
// 通过 https://www.123pan.com/developer 申请
1310
ClientID string `json:"ClientID" required:"false"`
1411
ClientSecret string `json:"ClientSecret" required:"false"`
1512

1613
// 直接写入AccessToken, AccessToken有过期时间,不建议直接填写
1714
AccessToken string `json:"AccessToken" required:"false"`
1815

16+
// refresh_token方式的AccessToken 【对个人开发者暂未开放】
17+
RefreshToken string `json:"RefreshToken" required:"false"`
18+
19+
// 使用在线API
20+
UseOnlineAPI bool `json:"use_online_api" default:"true"`
21+
APIAddress string `json:"api_url_address" default:"https://api.oplist.org/123cloud/renewapi"`
22+
1923
// 用户名+密码方式登录的AccessToken可以兼容
2024
//Username string `json:"username" required:"false"`
2125
//Password string `json:"password" required:"false"`

drivers/123_open/token.go

Lines changed: 77 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package _123_open
22

33
import (
4-
"encoding/json"
54
"errors"
65
"fmt"
76
"net/http"
@@ -13,10 +12,16 @@ import (
1312
)
1413

1514
var (
16-
AccessToken = "https://open-api.123pan.com/api/v1/access_token"
17-
RefreshToken = "https://open-api.123pan.com/api/v1/oauth2/access_token"
15+
AccessToken = "https://open-api.123pan.com/api/v1/access_token"
1816
)
1917

18+
func expiresInToExpiredAt(expiresIn int64) (time.Time, error) {
19+
if expiresIn <= 0 {
20+
return time.Time{}, errors.New("invalid expires_in from official API")
21+
}
22+
return time.Now().UTC().Add(time.Duration(expiresIn) * time.Second), nil
23+
}
24+
2025
type tokenManager struct {
2126
// accessToken string
2227
expiredAt time.Time
@@ -43,73 +48,82 @@ func (d *Open123) getAccessToken(forceRefresh bool) (string, error) {
4348
}
4449

4550
func (d *Open123) flushAccessToken() error {
46-
// directly send request to avoid deadlock
47-
req := base.RestyClient.R()
48-
req.SetHeaders(map[string]string{
49-
"authorization": "Bearer " + d.AccessToken,
50-
"platform": "open_platform",
51-
"Content-Type": "application/json",
52-
})
51+
// Official app renewapi response contains access_token, refresh_token and expires_in.
52+
if d.UseOnlineAPI && d.RefreshToken != "" && len(d.APIAddress) > 0 {
53+
var resp RefreshTokenResp
54+
_, err := base.RestyClient.R().
55+
SetResult(&resp).
56+
SetQueryParams(map[string]string{
57+
"refresh_ui": d.RefreshToken,
58+
"server_use": "true",
59+
"driver_txt": "123cloud_oa",
60+
}).
61+
Get(d.APIAddress)
62+
if err != nil {
63+
return err
64+
}
5365

54-
if d.ClientID != "" {
55-
if d.RefreshToken != "" {
56-
var resp RefreshTokenResp
57-
req.SetQueryParam("client_id", d.ClientID)
58-
if d.ClientSecret != "" {
59-
req.SetQueryParam("client_secret", d.ClientSecret)
66+
if resp.AccessToken == "" || resp.RefreshToken == "" {
67+
errMessage := resp.ErrorDescription
68+
if errMessage == "" {
69+
errMessage = resp.Text
6070
}
61-
req.SetQueryParam("grant_type", "refresh_token")
62-
req.SetQueryParam("refresh_token", d.RefreshToken)
63-
req.SetResult(&resp)
64-
res, err := req.Execute(http.MethodPost, RefreshToken)
65-
if err != nil {
66-
return err
71+
if errMessage == "" {
72+
errMessage = resp.Message
6773
}
68-
body := res.Body()
69-
var baseResp BaseResp
70-
if err = json.Unmarshal(body, &baseResp); err != nil {
71-
return err
74+
if errMessage == "" {
75+
errMessage = resp.Error
7276
}
73-
if baseResp.Code != 0 {
74-
return fmt.Errorf("get access token failed: %s", baseResp.Message)
77+
if errMessage != "" {
78+
return fmt.Errorf("failed to refresh token: %s", errMessage)
7579
}
80+
return fmt.Errorf("empty access_token or refresh_token returned from official API")
81+
}
82+
expiredAt, err := expiresInToExpiredAt(resp.ExpiresIn)
83+
if err != nil {
84+
return err
85+
}
7686

77-
d.AccessToken = resp.AccessToken
78-
// add token expire time
79-
d.tm.expiredAt = time.Now().Add(time.Duration(resp.ExpiresIn) * time.Second)
80-
d.RefreshToken = resp.RefreshToken
81-
op.MustSaveDriverStorage(d)
82-
d.tm.blockRefresh = false
83-
return nil
84-
} else if d.ClientSecret != "" {
85-
var resp AccessTokenResp
86-
req.SetBody(base.Json{
87-
"clientID": d.ClientID,
88-
"clientSecret": d.ClientSecret,
89-
})
90-
req.SetResult(&resp)
91-
res, err := req.Execute(http.MethodPost, AccessToken)
92-
if err != nil {
93-
return err
94-
}
95-
body := res.Body()
96-
var baseResp BaseResp
97-
if err = json.Unmarshal(body, &baseResp); err != nil {
98-
return err
99-
}
100-
if baseResp.Code != 0 {
101-
return fmt.Errorf("get access token failed: %s", baseResp.Message)
102-
}
103-
d.AccessToken = resp.Data.AccessToken
104-
// parse token expire time
105-
d.tm.expiredAt, err = time.Parse(time.RFC3339, resp.Data.ExpiredAt)
106-
if err != nil {
107-
return fmt.Errorf("parse expire time failed: %w", err)
108-
}
109-
op.MustSaveDriverStorage(d)
110-
d.tm.blockRefresh = false
111-
return nil
87+
d.AccessToken = resp.AccessToken
88+
d.RefreshToken = resp.RefreshToken
89+
d.tm.expiredAt = expiredAt
90+
op.MustSaveDriverStorage(d)
91+
d.tm.blockRefresh = false
92+
return nil
93+
}
94+
95+
// Developer API response contains code/message/data(accessToken, expiredAt).
96+
if d.ClientID != "" && d.ClientSecret != "" {
97+
req := base.RestyClient.R()
98+
req.SetHeaders(map[string]string{
99+
"platform": "open_platform",
100+
"Content-Type": "application/json",
101+
})
102+
var resp AccessTokenResp
103+
req.SetBody(base.Json{
104+
"clientID": d.ClientID,
105+
"clientSecret": d.ClientSecret,
106+
})
107+
req.SetResult(&resp)
108+
_, err := req.Execute(http.MethodPost, AccessToken)
109+
if err != nil {
110+
return err
111+
}
112+
if resp.Code != 0 {
113+
return fmt.Errorf("get access token failed: %s", resp.Message)
114+
}
115+
if resp.Data.AccessToken == "" || resp.Data.ExpiredAt == "" {
116+
return errors.New("invalid token payload from developer API")
117+
}
118+
expiredAt, err := time.Parse(time.RFC3339, resp.Data.ExpiredAt)
119+
if err != nil {
120+
return fmt.Errorf("parse expire time failed: %w", err)
112121
}
122+
d.AccessToken = resp.Data.AccessToken
123+
d.tm.expiredAt = expiredAt.UTC()
124+
op.MustSaveDriverStorage(d)
125+
d.tm.blockRefresh = false
126+
return nil
113127
}
114128
return errors.New("no valid authentication method available")
115129
}

drivers/123_open/types.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,14 @@ type AccessTokenResp struct {
125125
}
126126

127127
type RefreshTokenResp struct {
128-
AccessToken string `json:"access_token"`
129-
ExpiresIn int `json:"expires_in"`
130-
RefreshToken string `json:"refresh_token"`
131-
Scope string `json:"scope"`
132-
TokenType string `json:"token_type"`
128+
AccessToken string `json:"access_token"`
129+
RefreshToken string `json:"refresh_token"`
130+
ExpiresIn int64 `json:"expires_in"`
131+
Code int `json:"code"`
132+
Message string `json:"message"`
133+
ErrorDescription string `json:"error_description"`
134+
Error string `json:"error"`
135+
Text string `json:"text"`
133136
}
134137

135138
type UserInfoResp struct {

0 commit comments

Comments
 (0)