cloud_service.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  1. // SiYuan - Refactor your thinking
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package model
  17. import (
  18. "encoding/hex"
  19. "errors"
  20. "fmt"
  21. "net/http"
  22. "os"
  23. "path/filepath"
  24. "strconv"
  25. "strings"
  26. "time"
  27. "github.com/88250/gulu"
  28. "github.com/siyuan-note/httpclient"
  29. "github.com/siyuan-note/logging"
  30. "github.com/siyuan-note/siyuan/kernel/conf"
  31. "github.com/siyuan-note/siyuan/kernel/util"
  32. )
  33. var ErrFailedToConnectCloudServer = errors.New("failed to connect cloud server")
  34. func CloudChatGPT(msg string, contextMsgs []string) (ret string, stop bool, err error) {
  35. if nil == Conf.User {
  36. return
  37. }
  38. payload := map[string]interface{}{}
  39. var messages []map[string]interface{}
  40. for _, contextMsg := range contextMsgs {
  41. messages = append(messages, map[string]interface{}{
  42. "role": "user",
  43. "content": contextMsg,
  44. })
  45. }
  46. messages = append(messages, map[string]interface{}{
  47. "role": "user",
  48. "content": msg,
  49. })
  50. payload["messages"] = messages
  51. requestResult := gulu.Ret.NewResult()
  52. request := httpclient.NewCloudRequest30s()
  53. _, err = request.
  54. SetSuccessResult(requestResult).
  55. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  56. SetBody(payload).
  57. Post(util.GetCloudServer() + "/apis/siyuan/ai/chatGPT")
  58. if nil != err {
  59. logging.LogErrorf("chat gpt failed: %s", err)
  60. err = ErrFailedToConnectCloudServer
  61. return
  62. }
  63. if 0 != requestResult.Code {
  64. err = errors.New(requestResult.Msg)
  65. stop = true
  66. return
  67. }
  68. data := requestResult.Data.(map[string]interface{})
  69. choices := data["choices"].([]interface{})
  70. if 1 > len(choices) {
  71. stop = true
  72. return
  73. }
  74. choice := choices[0].(map[string]interface{})
  75. message := choice["message"].(map[string]interface{})
  76. ret = message["content"].(string)
  77. if nil != choice["finish_reason"] {
  78. finishReason := choice["finish_reason"].(string)
  79. if "length" == finishReason {
  80. stop = false
  81. } else {
  82. stop = true
  83. }
  84. } else {
  85. stop = true
  86. }
  87. return
  88. }
  89. func StartFreeTrial() (err error) {
  90. if nil == Conf.User {
  91. return errors.New(Conf.Language(31))
  92. }
  93. requestResult := gulu.Ret.NewResult()
  94. request := httpclient.NewCloudRequest30s()
  95. _, err = request.
  96. SetSuccessResult(requestResult).
  97. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  98. Post(util.GetCloudServer() + "/apis/siyuan/user/startFreeTrial")
  99. if nil != err {
  100. logging.LogErrorf("start free trial failed: %s", err)
  101. return ErrFailedToConnectCloudServer
  102. }
  103. if 0 != requestResult.Code {
  104. return errors.New(requestResult.Msg)
  105. }
  106. return
  107. }
  108. func DeactivateUser() (err error) {
  109. requestResult := gulu.Ret.NewResult()
  110. request := httpclient.NewCloudRequest30s()
  111. resp, err := request.
  112. SetSuccessResult(requestResult).
  113. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  114. Post(util.GetCloudServer() + "/apis/siyuan/user/deactivate")
  115. if nil != err {
  116. logging.LogErrorf("deactivate user failed: %s", err)
  117. return ErrFailedToConnectCloudServer
  118. }
  119. if 401 == resp.StatusCode {
  120. err = errors.New(Conf.Language(31))
  121. return
  122. }
  123. if 0 != requestResult.Code {
  124. logging.LogErrorf("deactivate user failed: %s", requestResult.Msg)
  125. return errors.New(requestResult.Msg)
  126. }
  127. return
  128. }
  129. func SetCloudBlockReminder(id, data string, timed int64) (err error) {
  130. requestResult := gulu.Ret.NewResult()
  131. payload := map[string]interface{}{"dataId": id, "data": data, "timed": timed}
  132. request := httpclient.NewCloudRequest30s()
  133. resp, err := request.
  134. SetSuccessResult(requestResult).
  135. SetBody(payload).
  136. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  137. Post(util.GetCloudServer() + "/apis/siyuan/calendar/setBlockReminder")
  138. if nil != err {
  139. logging.LogErrorf("set block reminder failed: %s", err)
  140. return ErrFailedToConnectCloudServer
  141. }
  142. if 401 == resp.StatusCode {
  143. err = errors.New(Conf.Language(31))
  144. return
  145. }
  146. if 0 != requestResult.Code {
  147. logging.LogErrorf("set block reminder failed: %s", requestResult.Msg)
  148. return errors.New(requestResult.Msg)
  149. }
  150. return
  151. }
  152. var uploadToken = ""
  153. var uploadTokenTime int64
  154. func LoadUploadToken() (err error) {
  155. now := time.Now().Unix()
  156. if 3600 >= now-uploadTokenTime {
  157. return
  158. }
  159. requestResult := gulu.Ret.NewResult()
  160. request := httpclient.NewCloudRequest30s()
  161. resp, err := request.
  162. SetSuccessResult(requestResult).
  163. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  164. Post(util.GetCloudServer() + "/apis/siyuan/upload/token")
  165. if nil != err {
  166. logging.LogErrorf("get upload token failed: %s", err)
  167. return ErrFailedToConnectCloudServer
  168. }
  169. if 401 == resp.StatusCode {
  170. err = errors.New(Conf.Language(31))
  171. return
  172. }
  173. if 0 != requestResult.Code {
  174. logging.LogErrorf("get upload token failed: %s", requestResult.Msg)
  175. return
  176. }
  177. resultData := requestResult.Data.(map[string]interface{})
  178. uploadToken = resultData["uploadToken"].(string)
  179. uploadTokenTime = now
  180. return
  181. }
  182. var (
  183. subscriptionExpirationReminded bool
  184. )
  185. func RefreshCheckJob() {
  186. go refreshSubscriptionExpirationRemind()
  187. go refreshUser()
  188. go refreshAnnouncement()
  189. go refreshCheckDownloadInstallPkg()
  190. }
  191. func refreshSubscriptionExpirationRemind() {
  192. if subscriptionExpirationReminded {
  193. return
  194. }
  195. subscriptionExpirationReminded = true
  196. if "ios" == util.Container {
  197. return
  198. }
  199. defer logging.Recover()
  200. if IsSubscriber() && -1 != Conf.User.UserSiYuanProExpireTime {
  201. expired := int64(Conf.User.UserSiYuanProExpireTime)
  202. now := time.Now().UnixMilli()
  203. if now >= expired { // 已经过期
  204. if now-expired <= 1000*60*60*24*2 { // 2 天内提醒 https://github.com/siyuan-note/siyuan/issues/7816
  205. time.Sleep(time.Second * 30)
  206. util.PushErrMsg(Conf.Language(128), 0)
  207. }
  208. return
  209. }
  210. remains := int((expired - now) / 1000 / 60 / 60 / 24)
  211. expireDay := 15 // 付费订阅提前 15 天提醒
  212. if 2 == Conf.User.UserSiYuanSubscriptionPlan {
  213. expireDay = 3 // 试用订阅提前 3 天提醒
  214. }
  215. if 0 < remains && expireDay > remains {
  216. util.WaitForUILoaded()
  217. time.Sleep(time.Second * 3)
  218. util.PushErrMsg(fmt.Sprintf(Conf.Language(127), remains), 0)
  219. return
  220. }
  221. }
  222. }
  223. func refreshUser() {
  224. defer logging.Recover()
  225. if nil != Conf.User {
  226. time.Sleep(2 * time.Minute)
  227. if nil != Conf.User {
  228. RefreshUser(Conf.User.UserToken)
  229. }
  230. subscriptionExpirationReminded = false
  231. }
  232. }
  233. func refreshCheckDownloadInstallPkg() {
  234. defer logging.Recover()
  235. time.Sleep(3 * time.Minute)
  236. checkDownloadInstallPkg()
  237. if "" != getNewVerInstallPkgPath() {
  238. util.PushMsg(Conf.Language(62), 15*1000)
  239. }
  240. }
  241. func refreshAnnouncement() {
  242. defer logging.Recover()
  243. time.Sleep(1 * time.Minute)
  244. announcementConf := filepath.Join(util.HomeDir, ".config", "siyuan", "announcement.json")
  245. var existingAnnouncements, newAnnouncements []*Announcement
  246. if gulu.File.IsExist(announcementConf) {
  247. data, err := os.ReadFile(announcementConf)
  248. if nil != err {
  249. logging.LogErrorf("read announcement conf failed: %s", err)
  250. return
  251. }
  252. if err = gulu.JSON.UnmarshalJSON(data, &existingAnnouncements); nil != err {
  253. logging.LogErrorf("unmarshal announcement conf failed: %s", err)
  254. os.Remove(announcementConf)
  255. return
  256. }
  257. }
  258. for _, announcement := range GetAnnouncements() {
  259. var exist bool
  260. for _, existingAnnouncement := range existingAnnouncements {
  261. if announcement.Id == existingAnnouncement.Id {
  262. exist = true
  263. break
  264. }
  265. }
  266. if !exist {
  267. existingAnnouncements = append(existingAnnouncements, announcement)
  268. if Conf.CloudRegion == announcement.Region {
  269. newAnnouncements = append(newAnnouncements, announcement)
  270. }
  271. }
  272. }
  273. data, err := gulu.JSON.MarshalJSON(existingAnnouncements)
  274. if nil != err {
  275. logging.LogErrorf("marshal announcement conf failed: %s", err)
  276. return
  277. }
  278. if err = os.WriteFile(announcementConf, data, 0644); nil != err {
  279. logging.LogErrorf("write announcement conf failed: %s", err)
  280. return
  281. }
  282. for _, newAnnouncement := range newAnnouncements {
  283. util.PushMsg(fmt.Sprintf(Conf.Language(11), newAnnouncement.URL, newAnnouncement.Title), 0)
  284. }
  285. }
  286. func RefreshUser(token string) error {
  287. threeDaysAfter := util.CurrentTimeMillis() + 1000*60*60*24*3
  288. if "" == token {
  289. if "" != Conf.UserData {
  290. Conf.User = loadUserFromConf()
  291. }
  292. if nil == Conf.User {
  293. return errors.New(Conf.Language(19))
  294. }
  295. var tokenExpireTime int64
  296. tokenExpireTime, err := strconv.ParseInt(Conf.User.UserTokenExpireTime+"000", 10, 64)
  297. if nil != err {
  298. logging.LogErrorf("convert token expire time [%s] failed: %s", Conf.User.UserTokenExpireTime, err)
  299. return errors.New(Conf.Language(19))
  300. }
  301. if threeDaysAfter > tokenExpireTime {
  302. token = Conf.User.UserToken
  303. goto Net
  304. }
  305. return nil
  306. }
  307. Net:
  308. start := time.Now()
  309. user, err := getUser(token)
  310. if err != nil {
  311. if nil == Conf.User || errInvalidUser == err {
  312. return errors.New(Conf.Language(19))
  313. }
  314. var tokenExpireTime int64
  315. tokenExpireTime, err = strconv.ParseInt(Conf.User.UserTokenExpireTime+"000", 10, 64)
  316. if nil != err {
  317. logging.LogErrorf("convert token expire time [%s] failed: %s", Conf.User.UserTokenExpireTime, err)
  318. return errors.New(Conf.Language(19))
  319. }
  320. if threeDaysAfter > tokenExpireTime {
  321. return errors.New(Conf.Language(19))
  322. }
  323. return nil
  324. }
  325. Conf.User = user
  326. data, _ := gulu.JSON.MarshalJSON(user)
  327. Conf.UserData = util.AESEncrypt(string(data))
  328. Conf.Save()
  329. if elapsed := time.Now().Sub(start).Milliseconds(); 3000 < elapsed {
  330. logging.LogInfof("get cloud user elapsed [%dms]", elapsed)
  331. }
  332. return nil
  333. }
  334. func loadUserFromConf() *conf.User {
  335. if "" == Conf.UserData {
  336. return nil
  337. }
  338. data := util.AESDecrypt(Conf.UserData)
  339. data, _ = hex.DecodeString(string(data))
  340. user := &conf.User{}
  341. if err := gulu.JSON.UnmarshalJSON(data, &user); nil == err {
  342. return user
  343. }
  344. return nil
  345. }
  346. func RemoveCloudShorthands(ids []string) (err error) {
  347. result := map[string]interface{}{}
  348. request := httpclient.NewCloudRequest30s()
  349. body := map[string]interface{}{
  350. "ids": ids,
  351. }
  352. resp, err := request.
  353. SetSuccessResult(&result).
  354. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  355. SetBody(body).
  356. Post(util.GetCloudServer() + "/apis/siyuan/inbox/removeCloudShorthands")
  357. if nil != err {
  358. logging.LogErrorf("remove cloud shorthands failed: %s", err)
  359. err = ErrFailedToConnectCloudServer
  360. return
  361. }
  362. if 401 == resp.StatusCode {
  363. err = errors.New(Conf.Language(31))
  364. return
  365. }
  366. code := result["code"].(float64)
  367. if 0 != code {
  368. logging.LogErrorf("remove cloud shorthands failed: %s", result["msg"])
  369. err = errors.New(result["msg"].(string))
  370. return
  371. }
  372. return
  373. }
  374. func GetCloudShorthand(id string) (ret map[string]interface{}, err error) {
  375. result := map[string]interface{}{}
  376. request := httpclient.NewCloudRequest30s()
  377. resp, err := request.
  378. SetSuccessResult(&result).
  379. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  380. Post(util.GetCloudServer() + "/apis/siyuan/inbox/getCloudShorthand?id=" + id)
  381. if nil != err {
  382. logging.LogErrorf("get cloud shorthand failed: %s", err)
  383. err = ErrFailedToConnectCloudServer
  384. return
  385. }
  386. if 401 == resp.StatusCode {
  387. err = errors.New(Conf.Language(31))
  388. return
  389. }
  390. code := result["code"].(float64)
  391. if 0 != code {
  392. logging.LogErrorf("get cloud shorthand failed: %s", result["msg"])
  393. err = errors.New(result["msg"].(string))
  394. return
  395. }
  396. ret = result["data"].(map[string]interface{})
  397. t, _ := strconv.ParseInt(id, 10, 64)
  398. hCreated := util.Millisecond2Time(t)
  399. ret["hCreated"] = hCreated.Format("2006-01-02 15:04")
  400. return
  401. }
  402. func GetCloudShorthands(page int) (result map[string]interface{}, err error) {
  403. result = map[string]interface{}{}
  404. request := httpclient.NewCloudRequest30s()
  405. resp, err := request.
  406. SetSuccessResult(&result).
  407. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  408. Post(util.GetCloudServer() + "/apis/siyuan/inbox/getCloudShorthands?p=" + strconv.Itoa(page))
  409. if nil != err {
  410. logging.LogErrorf("get cloud shorthands failed: %s", err)
  411. err = ErrFailedToConnectCloudServer
  412. return
  413. }
  414. if 401 == resp.StatusCode {
  415. err = errors.New(Conf.Language(31))
  416. return
  417. }
  418. code := result["code"].(float64)
  419. if 0 != code {
  420. logging.LogErrorf("get cloud shorthands failed: %s", result["msg"])
  421. err = errors.New(result["msg"].(string))
  422. return
  423. }
  424. shorthands := result["data"].(map[string]interface{})["shorthands"].([]interface{})
  425. for _, item := range shorthands {
  426. shorthand := item.(map[string]interface{})
  427. id := shorthand["oId"].(string)
  428. t, _ := strconv.ParseInt(id, 10, 64)
  429. hCreated := util.Millisecond2Time(t)
  430. shorthand["hCreated"] = hCreated.Format("2006-01-02 15:04")
  431. }
  432. return
  433. }
  434. var errInvalidUser = errors.New("invalid user")
  435. func getUser(token string) (*conf.User, error) {
  436. result := map[string]interface{}{}
  437. request := httpclient.NewCloudRequest30s()
  438. _, err := request.
  439. SetSuccessResult(&result).
  440. SetBody(map[string]string{"token": token}).
  441. Post(util.GetCloudServer() + "/apis/siyuan/user")
  442. if nil != err {
  443. logging.LogErrorf("get community user failed: %s", err)
  444. return nil, errors.New(Conf.Language(18))
  445. }
  446. code := result["code"].(float64)
  447. if 0 != code {
  448. if 255 == code {
  449. return nil, errInvalidUser
  450. }
  451. logging.LogErrorf("get community user failed: %s", result["msg"])
  452. return nil, errors.New(Conf.Language(18))
  453. }
  454. dataStr := result["data"].(string)
  455. data := util.AESDecrypt(dataStr)
  456. user := &conf.User{}
  457. if err = gulu.JSON.UnmarshalJSON(data, &user); nil != err {
  458. logging.LogErrorf("get community user failed: %s", err)
  459. return nil, errors.New(Conf.Language(18))
  460. }
  461. return user, nil
  462. }
  463. func UseActivationcode(code string) (err error) {
  464. code = strings.TrimSpace(code)
  465. code = gulu.Str.RemoveInvisible(code)
  466. requestResult := gulu.Ret.NewResult()
  467. request := httpclient.NewCloudRequest30s()
  468. _, err = request.
  469. SetSuccessResult(requestResult).
  470. SetBody(map[string]string{"data": code}).
  471. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  472. Post(util.GetCloudServer() + "/apis/siyuan/useActivationcode")
  473. if nil != err {
  474. logging.LogErrorf("check activation code failed: %s", err)
  475. return ErrFailedToConnectCloudServer
  476. }
  477. if 0 != requestResult.Code {
  478. return errors.New(requestResult.Msg)
  479. }
  480. return
  481. }
  482. func CheckActivationcode(code string) (retCode int, msg string) {
  483. code = strings.TrimSpace(code)
  484. code = gulu.Str.RemoveInvisible(code)
  485. retCode = 1
  486. requestResult := gulu.Ret.NewResult()
  487. request := httpclient.NewCloudRequest30s()
  488. _, err := request.
  489. SetSuccessResult(requestResult).
  490. SetBody(map[string]string{"data": code}).
  491. SetCookies(&http.Cookie{Name: "symphony", Value: Conf.User.UserToken}).
  492. Post(util.GetCloudServer() + "/apis/siyuan/checkActivationcode")
  493. if nil != err {
  494. logging.LogErrorf("check activation code failed: %s", err)
  495. msg = ErrFailedToConnectCloudServer.Error()
  496. return
  497. }
  498. if 0 == requestResult.Code {
  499. retCode = 0
  500. }
  501. msg = requestResult.Msg
  502. return
  503. }
  504. func Login(userName, password, captcha string, cloudRegion int) (ret *gulu.Result) {
  505. Conf.CloudRegion = cloudRegion
  506. Conf.Save()
  507. util.CurrentCloudRegion = cloudRegion
  508. result := map[string]interface{}{}
  509. request := httpclient.NewCloudRequest30s()
  510. _, err := request.
  511. SetSuccessResult(&result).
  512. SetBody(map[string]string{"userName": userName, "userPassword": password, "captcha": captcha}).
  513. Post(util.GetCloudServer() + "/apis/siyuan/login")
  514. if nil != err {
  515. logging.LogErrorf("login failed: %s", err)
  516. ret = gulu.Ret.NewResult()
  517. ret.Code = -1
  518. ret.Msg = Conf.Language(18) + ": " + err.Error()
  519. return
  520. }
  521. ret = &gulu.Result{
  522. Code: int(result["code"].(float64)),
  523. Msg: result["msg"].(string),
  524. Data: map[string]interface{}{
  525. "userName": result["userName"],
  526. "token": result["token"],
  527. "needCaptcha": result["needCaptcha"],
  528. },
  529. }
  530. if -1 == ret.Code {
  531. ret.Code = 1
  532. }
  533. return
  534. }
  535. func Login2fa(token, code string) (map[string]interface{}, error) {
  536. result := map[string]interface{}{}
  537. request := httpclient.NewCloudRequest30s()
  538. _, err := request.
  539. SetSuccessResult(&result).
  540. SetBody(map[string]string{"twofactorAuthCode": code}).
  541. SetHeader("token", token).
  542. Post(util.GetCloudServer() + "/apis/siyuan/login/2fa")
  543. if nil != err {
  544. logging.LogErrorf("login 2fa failed: %s", err)
  545. return nil, errors.New(Conf.Language(18))
  546. }
  547. return result, nil
  548. }
  549. func LogoutUser() {
  550. Conf.UserData = ""
  551. Conf.User = nil
  552. Conf.Save()
  553. }