Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693// SiYuan - Refactor your thinking// Copyright (c) 2020-present, b3log.org//// This program is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program. If not, see <https://www.gnu.org/licenses/>.
package model
import ( "encoding/hex" "errors" "fmt" "net/http" "os" "path/filepath" "regexp" "strconv" "strings" "time"
"github.com/88250/gulu" "github.com/88250/lute/parse" "github.com/siyuan-note/httpclient" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/conf" "github.com/siyuan-note/siyuan/kernel/task" "github.com/siyuan-note/siyuan/kernel/util")
var ErrFailedToConnectCloudServer = errors.New("failed to connect cloud server")
func CloudChatGPT(msg string, contextMsgs []string) (ret string, stop bool, err error) { if nil == Conf.GetUser() { return }
payload := map[string]interface{}{} var messages []map[string]interface{} for _, contextMsg := range contextMsgs { messages = append(messages, map[string]interface{}{ "role": "user", "content": contextMsg, }) } messages = append(messages, map[string]interface{}{ "role": "user", "content": msg, }) payload["messages"] = messages
requestResult := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() _, err = request. SetSuccessResult(requestResult). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). SetBody(payload). Post(util.GetCloudServer() + "/apis/siyuan/ai/chatGPT") if err != nil { logging.LogErrorf("chat gpt failed: %s", err) err = ErrFailedToConnectCloudServer return } if 0 != requestResult.Code { err = errors.New(requestResult.Msg) stop = true return }
data := requestResult.Data.(map[string]interface{}) choices := data["choices"].([]interface{}) if 1 > len(choices) { stop = true return } choice := choices[0].(map[string]interface{}) message := choice["message"].(map[string]interface{}) ret = message["content"].(string)
if nil != choice["finish_reason"] { finishReason := choice["finish_reason"].(string) if "length" == finishReason { stop = false } else { stop = true } } else { stop = true } return}
func StartFreeTrial() (err error) { if nil == Conf.GetUser() { return errors.New(Conf.Language(31)) }
requestResult := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(requestResult). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/user/startFreeTrial") if err != nil { logging.LogErrorf("start free trial failed: %s", err) return ErrFailedToConnectCloudServer } if http.StatusOK != resp.StatusCode { logging.LogErrorf("start free trial failed: %d", resp.StatusCode) return ErrFailedToConnectCloudServer } if 0 != requestResult.Code { return errors.New(requestResult.Msg) } return}
func DeactivateUser() (err error) { requestResult := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(requestResult). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/user/deactivate") if err != nil { logging.LogErrorf("deactivate user failed: %s", err) return ErrFailedToConnectCloudServer }
if 401 == resp.StatusCode { err = errors.New(Conf.Language(31)) return }
if 0 != requestResult.Code { logging.LogErrorf("deactivate user failed: %s", requestResult.Msg) return errors.New(requestResult.Msg) } return}
func SetCloudBlockReminder(id, data string, timed int64) (err error) { requestResult := gulu.Ret.NewResult() payload := map[string]interface{}{"dataId": id, "data": data, "timed": timed} request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(requestResult). SetBody(payload). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/calendar/setBlockReminder") if err != nil { logging.LogErrorf("set block reminder failed: %s", err) return ErrFailedToConnectCloudServer }
if 401 == resp.StatusCode { err = errors.New(Conf.Language(31)) return }
if 0 != requestResult.Code { logging.LogErrorf("set block reminder failed: %s", requestResult.Msg) return errors.New(requestResult.Msg) } return}
var uploadToken = ""var uploadTokenTime int64
func LoadUploadToken() (err error) { now := time.Now().Unix() if 3600 >= now-uploadTokenTime { return }
requestResult := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(requestResult). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/upload/token") if err != nil { logging.LogErrorf("get upload token failed: %s", err) return ErrFailedToConnectCloudServer }
if 401 == resp.StatusCode { err = errors.New(Conf.Language(31)) return }
if 0 != requestResult.Code { logging.LogErrorf("get upload token failed: %s", requestResult.Msg) return }
resultData := requestResult.Data.(map[string]interface{}) uploadToken = resultData["uploadToken"].(string) uploadTokenTime = now return}
var ( subscriptionExpirationReminded bool)
func RefreshCheckJob2H() { go refreshSubscriptionExpirationRemind() go refreshUser() go refreshAnnouncement()}
func RefreshCheckJob6H() { go refreshCheckDownloadInstallPkg()}
func refreshSubscriptionExpirationRemind() { if subscriptionExpirationReminded { return } subscriptionExpirationReminded = true
if "ios" == util.Container { return }
defer logging.Recover()
if IsSubscriber() && -1 != Conf.GetUser().UserSiYuanProExpireTime { expired := int64(Conf.GetUser().UserSiYuanProExpireTime) now := time.Now().UnixMilli() if now >= expired { // 已经过期 if now-expired <= 1000*60*60*24*2 { // 2 天内提醒 https://github.com/siyuan-note/siyuan/issues/7816 task.AppendAsyncTaskWithDelay(task.PushMsg, 30*time.Second, util.PushErrMsg, Conf.Language(128), 0) } return } remains := int((expired - now) / 1000 / 60 / 60 / 24) expireDay := 15 // 付费订阅提前 15 天提醒 if 2 == Conf.GetUser().UserSiYuanSubscriptionPlan { expireDay = 3 // 试用订阅提前 3 天提醒 }
if 0 < remains && expireDay > remains { task.AppendAsyncTaskWithDelay(task.PushMsg, 7*time.Second, util.PushErrMsg, fmt.Sprintf(Conf.Language(127), remains), 0) return } }}
func refreshUser() { defer logging.Recover()
if nil != Conf.GetUser() { time.Sleep(2 * time.Minute) if nil != Conf.GetUser() { RefreshUser(Conf.GetUser().UserToken) } subscriptionExpirationReminded = false }}
func refreshCheckDownloadInstallPkg() { defer logging.Recover()
time.Sleep(3 * time.Minute) checkDownloadInstallPkg()}
func refreshAnnouncement() { defer logging.Recover()
time.Sleep(1 * time.Minute) announcementConf := filepath.Join(util.HomeDir, ".config", "siyuan", "announcement.json") var existingAnnouncements, newAnnouncements []*Announcement if gulu.File.IsExist(announcementConf) { data, err := os.ReadFile(announcementConf) if err != nil { logging.LogErrorf("read announcement conf failed: %s", err) return } if err = gulu.JSON.UnmarshalJSON(data, &existingAnnouncements); err != nil { logging.LogErrorf("unmarshal announcement conf failed: %s", err) os.Remove(announcementConf) return } }
for _, announcement := range getAnnouncements() { var exist bool for _, existingAnnouncement := range existingAnnouncements { if announcement.Id == existingAnnouncement.Id { exist = true break } } if !exist { existingAnnouncements = append(existingAnnouncements, announcement) if Conf.CloudRegion == announcement.Region { newAnnouncements = append(newAnnouncements, announcement) } } }
data, err := gulu.JSON.MarshalJSON(existingAnnouncements) if err != nil { logging.LogErrorf("marshal announcement conf failed: %s", err) return } if err = os.WriteFile(announcementConf, data, 0644); err != nil { logging.LogErrorf("write announcement conf failed: %s", err) return }
for _, newAnnouncement := range newAnnouncements { util.PushMsg(fmt.Sprintf(Conf.Language(11), newAnnouncement.URL, newAnnouncement.Title), 0) }}
func RefreshUser(token string) { threeDaysAfter := util.CurrentTimeMillis() + 1000*60*60*24*3 if "" == token { user := Conf.GetUser() if nil == user && "" != Conf.UserData { user = loadUserFromConf() if nil != user { Conf.SetUser(user) } } if nil == user { return }
var tokenExpireTime int64 tokenExpireTime, err := strconv.ParseInt(user.UserTokenExpireTime+"000", 10, 64) if err != nil { logging.LogErrorf("convert token expire time [%s] failed: %s", user.UserTokenExpireTime, err) util.PushErrMsg(Conf.Language(19), 5000) return }
if threeDaysAfter > tokenExpireTime { token = user.UserToken goto Net } return }
Net: start := time.Now() user, err := getUser(token) if err != nil { if nil == Conf.GetUser() || errors.Is(err, errInvalidUser) { util.PushErrMsg(Conf.Language(19), 5000) return }
if errors.Is(err, errRequestUserFailed) { util.PushErrMsg(Conf.Language(18), 5000) return }
var tokenExpireTime int64 tokenExpireTime, err = strconv.ParseInt(Conf.GetUser().UserTokenExpireTime+"000", 10, 64) if err != nil { logging.LogErrorf("convert token expire time [%s] failed: %s", Conf.GetUser().UserTokenExpireTime, err) util.PushErrMsg(Conf.Language(19), 5000) return }
if threeDaysAfter > tokenExpireTime { util.PushErrMsg(Conf.Language(19), 5000) return } return }
Conf.SetUser(user) data, _ := gulu.JSON.MarshalJSON(user) Conf.UserData = util.AESEncrypt(string(data)) Conf.Save()
if elapsed := time.Now().Sub(start).Milliseconds(); 3000 < elapsed { logging.LogInfof("get cloud user elapsed [%dms]", elapsed) } return}
func loadUserFromConf() *conf.User { if "" == Conf.UserData { return nil }
data := util.AESDecrypt(Conf.UserData) data, _ = hex.DecodeString(string(data)) user := &conf.User{} if err := gulu.JSON.UnmarshalJSON(data, &user); err == nil { return user } return nil}
func RemoveCloudShorthands(ids []string) (err error) { result := map[string]interface{}{} request := httpclient.NewCloudRequest30s() body := map[string]interface{}{ "ids": ids, } resp, err := request. SetSuccessResult(&result). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). SetBody(body). Post(util.GetCloudServer() + "/apis/siyuan/inbox/removeCloudShorthands") if err != nil { logging.LogErrorf("remove cloud shorthands failed: %s", err) err = ErrFailedToConnectCloudServer return }
if 401 == resp.StatusCode { err = errors.New(Conf.Language(31)) return }
code := result["code"].(float64) if 0 != code { logging.LogErrorf("remove cloud shorthands failed: %s", result["msg"]) err = errors.New(result["msg"].(string)) return } return}
func GetCloudShorthand(id string) (ret map[string]interface{}, err error) { result := map[string]interface{}{} request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(&result). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/inbox/getCloudShorthand?id=" + id) if err != nil { logging.LogErrorf("get cloud shorthand failed: %s", err) err = ErrFailedToConnectCloudServer return }
if 401 == resp.StatusCode { err = errors.New(Conf.Language(31)) return }
code := result["code"].(float64) if 0 != code { logging.LogErrorf("get cloud shorthand failed: %s", result["msg"]) err = errors.New(result["msg"].(string)) return } ret = result["data"].(map[string]interface{}) t, _ := strconv.ParseInt(id, 10, 64) hCreated := util.Millisecond2Time(t) ret["hCreated"] = hCreated.Format("2006-01-02 15:04")
md := ret["shorthandContent"].(string) ret["shorthandMd"] = md
luteEngine := NewLute() luteEngine.SetFootnotes(true) tree := parse.Parse("", []byte(md), luteEngine.ParseOptions) luteEngine.RenderOptions.ProtyleMarkNetImg = false content := luteEngine.ProtylePreview(tree, luteEngine.RenderOptions, luteEngine.ParseOptions) ret["shorthandContent"] = content return}
func GetCloudShorthands(page int) (result map[string]interface{}, err error) { result = map[string]interface{}{} request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(&result). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/inbox/getCloudShorthands?p=" + strconv.Itoa(page)) if err != nil { logging.LogErrorf("get cloud shorthands failed: %s", err) err = ErrFailedToConnectCloudServer return }
if 401 == resp.StatusCode { err = errors.New(Conf.Language(31)) return }
code := result["code"].(float64) if 0 != code { logging.LogErrorf("get cloud shorthands failed: %s", result["msg"]) err = errors.New(result["msg"].(string)) return }
luteEngine := NewLute() audioRegexp := regexp.MustCompile("<audio.*>.*</audio>") videoRegexp := regexp.MustCompile("<video.*>.*</video>") fileRegexp := regexp.MustCompile("\\[文件]\\(.*\\)") shorthands := result["data"].(map[string]interface{})["shorthands"].([]interface{}) for _, item := range shorthands { shorthand := item.(map[string]interface{}) id := shorthand["oId"].(string) t, _ := strconv.ParseInt(id, 10, 64) hCreated := util.Millisecond2Time(t) shorthand["hCreated"] = hCreated.Format("2006-01-02 15:04")
desc := shorthand["shorthandDesc"].(string) desc = audioRegexp.ReplaceAllString(desc, " 语音 ") desc = videoRegexp.ReplaceAllString(desc, " 视频 ") desc = fileRegexp.ReplaceAllString(desc, " 文件 ") desc = strings.ReplaceAll(desc, "\n\n", "") desc = strings.TrimSpace(desc) shorthand["shorthandDesc"] = desc
md := shorthand["shorthandContent"].(string) shorthand["shorthandMd"] = md tree := parse.Parse("", []byte(md), luteEngine.ParseOptions) luteEngine.RenderOptions.ProtyleMarkNetImg = false content := luteEngine.ProtylePreview(tree, luteEngine.RenderOptions, luteEngine.ParseOptions) shorthand["shorthandContent"] = content } return}
var ( errInvalidUser = errors.New("invalid user") errRequestUserFailed = errors.New("request user failed"))
func getUser(token string) (*conf.User, error) { result := map[string]interface{}{} request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(&result). SetBody(map[string]string{"token": token}). Post(util.GetCloudServer() + "/apis/siyuan/user") if err != nil { logging.LogErrorf("get community user failed: %s", err) return nil, errRequestUserFailed } if http.StatusOK != resp.StatusCode { logging.LogErrorf("get community user failed: %d", resp.StatusCode) return nil, errRequestUserFailed }
code := result["code"].(float64) if 0 != code { if 255 == code { return nil, errInvalidUser } logging.LogErrorf("get community user failed: %s", result["msg"]) return nil, errRequestUserFailed }
dataStr := result["data"].(string) data := util.AESDecrypt(dataStr) user := &conf.User{} if err = gulu.JSON.UnmarshalJSON(data, &user); err != nil { logging.LogErrorf("get community user failed: %s", err) return nil, errRequestUserFailed } return user, nil}
func UseActivationcode(code string) (err error) { code = strings.TrimSpace(code) code = util.RemoveInvalid(code) requestResult := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(requestResult). SetBody(map[string]string{"data": code}). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/useActivationcode") if err != nil { logging.LogErrorf("check activation code failed: %s", err) return ErrFailedToConnectCloudServer } if http.StatusOK != resp.StatusCode { logging.LogErrorf("check activation code failed: %d", resp.StatusCode) return ErrFailedToConnectCloudServer } if 0 != requestResult.Code { return errors.New(requestResult.Msg) } return}
func CheckActivationcode(code string) (retCode int, msg string) { code = strings.TrimSpace(code) code = util.RemoveInvalid(code) retCode = 1 requestResult := gulu.Ret.NewResult() request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(requestResult). SetBody(map[string]string{"data": code}). SetCookies(&http.Cookie{Name: "symphony", Value: Conf.GetUser().UserToken}). Post(util.GetCloudServer() + "/apis/siyuan/checkActivationcode") if err != nil { logging.LogErrorf("check activation code failed: %s", err) msg = ErrFailedToConnectCloudServer.Error() return } if http.StatusOK != resp.StatusCode { logging.LogErrorf("check activation code failed: %d", resp.StatusCode) msg = ErrFailedToConnectCloudServer.Error() return } if 0 == requestResult.Code { retCode = 0 } msg = requestResult.Msg return}
func Login(userName, password, captcha string, cloudRegion int) (ret *gulu.Result) { Conf.CloudRegion = cloudRegion Conf.Save() util.CurrentCloudRegion = cloudRegion
result := map[string]interface{}{} request := httpclient.NewCloudRequest30s() resp, err := request. SetSuccessResult(&result). SetBody(map[string]string{"userName": userName, "userPassword": password, "captcha": captcha}). Post(util.GetCloudServer() + "/apis/siyuan/login") if err != nil { logging.LogErrorf("login failed: %s", err) ret = gulu.Ret.NewResult() ret.Code = -1 ret.Msg = Conf.Language(18) + ": " + err.Error() return } if http.StatusOK != resp.StatusCode { logging.LogErrorf("login failed: %d", resp.StatusCode) ret = gulu.Ret.NewResult() ret.Code = -1 ret.Msg = Conf.Language(18) return }
ret = &gulu.Result{ Code: int(result["code"].(float64)), Msg: result["msg"].(string), Data: map[string]interface{}{ "userName": result["userName"], "token": result["token"], "needCaptcha": result["needCaptcha"], }, } if -1 == ret.Code { ret.Code = 1 } return}
func Login2fa(token, code string) (map[string]interface{}, error) { result := map[string]interface{}{} request := httpclient.NewCloudRequest30s() _, err := request. SetSuccessResult(&result). SetBody(map[string]string{"twofactorAuthCode": code}). SetHeader("token", token). Post(util.GetCloudServer() + "/apis/siyuan/login/2fa") if err != nil { logging.LogErrorf("login 2fa failed: %s", err) return nil, errors.New(Conf.Language(18)) } return result, nil}
func LogoutUser() { Conf.UserData = "" Conf.SetUser(nil) Conf.Save()}