Skip to content

Commit 2e91f5f

Browse files
authored
feat: support 189 family cloud (close AlistGo#612)
1 parent 8f19c45 commit 2e91f5f

5 files changed

Lines changed: 1448 additions & 0 deletions

File tree

drivers/189pc/189.go

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
package _189
2+
3+
import (
4+
"bytes"
5+
"crypto/tls"
6+
"encoding/json"
7+
"errors"
8+
"fmt"
9+
"net/http"
10+
"net/http/cookiejar"
11+
"net/url"
12+
"regexp"
13+
"sync"
14+
15+
"github.com/Xhofe/alist/conf"
16+
"github.com/Xhofe/alist/drivers/base"
17+
"github.com/Xhofe/alist/model"
18+
"github.com/go-resty/resty/v2"
19+
"github.com/google/uuid"
20+
jsoniter "github.com/json-iterator/go"
21+
log "github.com/sirupsen/logrus"
22+
)
23+
24+
var userStateCache = struct {
25+
sync.Mutex
26+
States map[string]*State
27+
}{States: make(map[string]*State)}
28+
29+
func GetState(account *model.Account) *State {
30+
userStateCache.Lock()
31+
defer userStateCache.Unlock()
32+
if v, ok := userStateCache.States[account.Username]; ok && v != nil {
33+
return v
34+
}
35+
state := &State{client: resty.New().
36+
SetProxy("http://192.168.0.30:8888").SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true}).
37+
SetHeaders(map[string]string{
38+
"Accept": "application/json;charset=UTF-8",
39+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/76.0",
40+
}),
41+
}
42+
userStateCache.States[account.Username] = state
43+
return state
44+
}
45+
46+
type State struct {
47+
sync.Mutex
48+
client *resty.Client
49+
50+
RsaPublicKey string
51+
52+
SessionKey string
53+
SessionSecret string
54+
FamilySessionKey string
55+
FamilySessionSecret string
56+
57+
AccessToken string
58+
59+
//怎么刷新的???
60+
RefreshToken string
61+
}
62+
63+
func (s *State) login(account *model.Account) error {
64+
// 清除cookie
65+
jar, _ := cookiejar.New(nil)
66+
s.client.SetCookieJar(jar)
67+
68+
var err error
69+
var res *resty.Response
70+
defer func() {
71+
account.Status = "work"
72+
if err != nil {
73+
account.Status = err.Error()
74+
}
75+
model.SaveAccount(account)
76+
if res != nil {
77+
log.Debug(res.String())
78+
}
79+
}()
80+
81+
var param *LoginParam
82+
param, err = s.getLoginParam()
83+
if err != nil {
84+
return err
85+
}
86+
87+
// 提交登录
88+
s.RsaPublicKey = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", param.jRsaKey)
89+
res, err = s.client.R().
90+
SetHeaders(map[string]string{
91+
"Referer": AUTH_URL,
92+
"REQID": param.ReqId,
93+
"lt": param.Lt,
94+
}).
95+
SetFormData(map[string]string{
96+
"appKey": APP_ID,
97+
"accountType": "02",
98+
"userName": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Username),
99+
"password": "{RSA}" + rsaEncrypt(s.RsaPublicKey, account.Password),
100+
"validateCode": param.vCodeRS,
101+
"captchaToken": param.CaptchaToken,
102+
"returnUrl": RETURN_URL,
103+
"mailSuffix": "@189.cn",
104+
"dynamicCheck": "FALSE",
105+
"clientType": CLIENT_TYPE,
106+
"cb_SaveName": "1",
107+
"isOauth2": "false",
108+
"state": "",
109+
"paramId": param.ParamId,
110+
}).
111+
Post(AUTH_URL + "/api/logbox/oauth2/loginSubmit.do")
112+
if err != nil {
113+
return err
114+
}
115+
toUrl := jsoniter.Get(res.Body(), "toUrl").ToString()
116+
if toUrl == "" {
117+
log.Error(res.String())
118+
return fmt.Errorf(res.String())
119+
}
120+
121+
// 获取Session
122+
var erron Erron
123+
var sessionResp appSessionResp
124+
res, err = s.client.R().
125+
SetResult(&sessionResp).SetError(&erron).
126+
SetQueryParams(clientSuffix()).
127+
SetQueryParam("redirectURL", url.QueryEscape(toUrl)).
128+
Post(API_URL + "/getSessionForPC.action")
129+
if err != nil {
130+
return err
131+
}
132+
133+
if erron.ResCode != "" {
134+
err = fmt.Errorf(erron.ResMessage)
135+
return err
136+
}
137+
if sessionResp.ResCode != 0 {
138+
err = fmt.Errorf(sessionResp.ResMessage)
139+
return err
140+
}
141+
s.SessionKey = sessionResp.SessionKey
142+
s.SessionSecret = sessionResp.SessionSecret
143+
s.FamilySessionKey = sessionResp.FamilySessionKey
144+
s.FamilySessionSecret = sessionResp.FamilySessionSecret
145+
s.AccessToken = sessionResp.AccessToken
146+
s.RefreshToken = sessionResp.RefreshToken
147+
return err
148+
}
149+
150+
func (s *State) getLoginParam() (*LoginParam, error) {
151+
res, err := s.client.R().
152+
SetQueryParams(map[string]string{
153+
"appId": APP_ID,
154+
"clientType": CLIENT_TYPE,
155+
"returnURL": RETURN_URL,
156+
"timeStamp": fmt.Sprint(timestamp()),
157+
}).
158+
Get(WEB_URL + "/api/portal/unifyLoginForPC.action")
159+
log.Debug(res.String())
160+
if err != nil {
161+
return nil, err
162+
}
163+
164+
param := &LoginParam{
165+
CaptchaToken: regexp.MustCompile(`'captchaToken' value='(.+?)'`).FindStringSubmatch(res.String())[1],
166+
Lt: regexp.MustCompile(`lt = "(.+?)"`).FindStringSubmatch(res.String())[1],
167+
ParamId: regexp.MustCompile(`paramId = "(.+?)"`).FindStringSubmatch(res.String())[1],
168+
ReqId: regexp.MustCompile(`reqId = "(.+?)"`).FindStringSubmatch(res.String())[1],
169+
jRsaKey: regexp.MustCompile(`"j_rsaKey" value="(.+?)"`).FindStringSubmatch(res.String())[1],
170+
171+
vCodeID: regexp.MustCompile(`token=([A-Za-z0-9&=]+)`).FindStringSubmatch(res.String())[1],
172+
}
173+
174+
imgRes, err := s.client.R().Get(fmt.Sprint(AUTH_URL, "/api/logbox/oauth2/picCaptcha.do?token=", param.vCodeID, timestamp()))
175+
if err != nil {
176+
return nil, err
177+
}
178+
if len(imgRes.Body()) > 0 {
179+
vRes, err := resty.New().R().
180+
SetMultipartField("image", "validateCode.png", "image/png", bytes.NewReader(imgRes.Body())).
181+
Post(conf.GetStr("ocr api"))
182+
if err != nil {
183+
return nil, err
184+
}
185+
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
186+
return nil, errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
187+
}
188+
param.vCodeRS = jsoniter.Get(vRes.Body(), "result").ToString()
189+
log.Debugln("code: ", param.vCodeRS)
190+
}
191+
return param, nil
192+
}
193+
194+
func (s *State) refreshSession(account *model.Account) error {
195+
var erron Erron
196+
var userSessionResp UserSessionResp
197+
res, err := s.client.R().
198+
SetResult(&userSessionResp).SetError(&erron).
199+
SetQueryParams(clientSuffix()).
200+
SetQueryParams(map[string]string{
201+
"appId": APP_ID,
202+
"accessToken": s.AccessToken,
203+
}).
204+
SetHeader("X-Request-ID", uuid.NewString()).
205+
Get("https://api.cloud.189.cn/getSessionForPC.action")
206+
log.Debug(res.String())
207+
if err != nil {
208+
return err
209+
}
210+
if erron.ResCode != "" {
211+
return fmt.Errorf(erron.ResMessage)
212+
}
213+
214+
switch userSessionResp.ResCode {
215+
case 0:
216+
s.SessionKey = userSessionResp.SessionKey
217+
s.SessionSecret = userSessionResp.SessionSecret
218+
s.FamilySessionKey = userSessionResp.FamilySessionKey
219+
s.FamilySessionSecret = userSessionResp.FamilySessionSecret
220+
case 11, 18:
221+
return s.login(account)
222+
default:
223+
account.Status = userSessionResp.ResMessage
224+
model.SaveAccount(account)
225+
return fmt.Errorf(userSessionResp.ResMessage)
226+
}
227+
return nil
228+
}
229+
230+
func (s *State) IsLogin() bool {
231+
_, err := s.Request("GET", API_URL+"/getUserInfo.action", nil, func(r *resty.Request) {
232+
r.SetQueryParams(clientSuffix())
233+
}, nil)
234+
return err == nil
235+
}
236+
237+
func (s *State) Login(account *model.Account) error {
238+
s.Lock()
239+
defer s.Unlock()
240+
return s.login(account)
241+
}
242+
243+
func (s *State) RefreshSession(account *model.Account) error {
244+
s.Lock()
245+
defer s.Unlock()
246+
return s.refreshSession(account)
247+
}
248+
249+
func (s *State) Request(method string, fullUrl string, params url.Values, callback func(*resty.Request), account *model.Account) (*resty.Response, error) {
250+
s.Lock()
251+
dateOfGmt := getHttpDateStr()
252+
sessionKey := s.SessionKey
253+
sessionSecret := s.SessionSecret
254+
if account != nil && isFamily(account) {
255+
sessionKey = s.FamilySessionKey
256+
sessionSecret = s.FamilySessionSecret
257+
}
258+
259+
req := s.client.R()
260+
req.SetHeaders(map[string]string{
261+
"Date": dateOfGmt,
262+
"SessionKey": sessionKey,
263+
"X-Request-ID": uuid.NewString(),
264+
})
265+
266+
// 设置params
267+
var paramsData string
268+
if params != nil {
269+
paramsData = AesECBEncrypt(params.Encode(), s.SessionSecret[:16])
270+
req.SetQueryParam("params", paramsData)
271+
}
272+
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, fullUrl, dateOfGmt, paramsData))
273+
274+
callback(req)
275+
s.Unlock()
276+
277+
var err error
278+
var res *resty.Response
279+
switch method {
280+
case "GET":
281+
res, err = req.Get(fullUrl)
282+
case "POST":
283+
res, err = req.Post(fullUrl)
284+
case "DELETE":
285+
res, err = req.Delete(fullUrl)
286+
case "PATCH":
287+
res, err = req.Patch(fullUrl)
288+
case "PUT":
289+
res, err = req.Put(fullUrl)
290+
default:
291+
return nil, base.ErrNotSupport
292+
}
293+
if err != nil {
294+
return nil, err
295+
}
296+
log.Debug(res.String())
297+
298+
var erron Erron
299+
json.Unmarshal(res.Body(), &erron)
300+
if erron.ResCode != "" {
301+
return nil, fmt.Errorf(erron.ResMessage)
302+
}
303+
if erron.Code != "" && erron.Code != "SUCCESS" {
304+
if erron.Msg == "" {
305+
return nil, fmt.Errorf(erron.Message)
306+
}
307+
return nil, fmt.Errorf(erron.Msg)
308+
}
309+
if erron.ErrorCode != "" {
310+
return nil, fmt.Errorf(erron.ErrorMsg)
311+
}
312+
313+
if account != nil {
314+
switch jsoniter.Get(res.Body(), "res_code").ToInt64() {
315+
case 11, 18:
316+
if err := s.refreshSession(account); err != nil {
317+
return nil, err
318+
}
319+
return s.Request(method, fullUrl, params, callback, account)
320+
case 0:
321+
if res.StatusCode() == http.StatusOK {
322+
return res, nil
323+
}
324+
fallthrough
325+
default:
326+
return nil, fmt.Errorf(res.String())
327+
}
328+
}
329+
330+
if jsoniter.Get(res.Body(), "res_code").ToInt64() != 0 {
331+
return res, fmt.Errorf(jsoniter.Get(res.Body(), "res_message").ToString())
332+
}
333+
return res, nil
334+
}

0 commit comments

Comments
 (0)