diff options
Diffstat (limited to 'service/service.go')
-rw-r--r-- | service/service.go | 266 |
1 files changed, 130 insertions, 136 deletions
diff --git a/service/service.go b/service/service.go index cda42f8..24e3f85 100644 --- a/service/service.go +++ b/service/service.go @@ -1,6 +1,8 @@ package service import ( + "crypto/sha256" + "encoding/base64" "errors" "fmt" "mime/multipart" @@ -27,14 +29,11 @@ type service struct { instance string postFormats []model.PostFormat renderer renderer.Renderer - sessionRepo model.SessionRepo - appRepo model.AppRepo } func NewService(cname string, cscope string, cwebsite string, css string, instance string, postFormats []model.PostFormat, - renderer renderer.Renderer, sessionRepo model.SessionRepo, - appRepo model.AppRepo) *service { + renderer renderer.Renderer) *service { return &service{ cname: cname, cscope: cscope, @@ -43,61 +42,18 @@ func NewService(cname string, cscope string, cwebsite string, instance: instance, postFormats: postFormats, renderer: renderer, - sessionRepo: sessionRepo, - appRepo: appRepo, } } -func (s *service) authenticate(c *client, sid string, csrf string, ref string, t int) (err error) { - var sett *model.Settings - defer func() { - if sett == nil { - sett = model.NewSettings() - } - c.rctx = &renderer.Context{ - HideAttachments: sett.HideAttachments, - MaskNSFW: sett.MaskNSFW, - ThreadInNewTab: sett.ThreadInNewTab, - FluorideMode: sett.FluorideMode, - DarkMode: sett.DarkMode, - CSRFToken: c.s.CSRFToken, - UserID: c.s.UserID, - AntiDopamineMode: sett.AntiDopamineMode, - UserCSS: sett.CSS, - Referrer: ref, - } - }() - if t < SESSION { - return - } - if len(sid) < 1 { - return errInvalidSession - } - c.s, err = s.sessionRepo.Get(sid) - if err != nil { - return errInvalidSession - } - sett = &c.s.Settings - app, err := s.appRepo.Get(c.s.InstanceDomain) - if err != nil { - return err - } - c.Client = mastodon.NewClient(&mastodon.Config{ - Server: app.InstanceURL, - ClientID: app.ClientID, - ClientSecret: app.ClientSecret, - AccessToken: c.s.AccessToken, - }) - if t >= CSRF && (len(csrf) < 1 || csrf != c.s.CSRFToken) { - return errInvalidCSRFToken - } - return -} - func (s *service) cdata(c *client, title string, count int, rinterval int, target string) (data *renderer.CommonData) { + if title == "" { + title = s.cname + } else { + title += " - " + s.cname + } data = &renderer.CommonData{ - Title: title + " - " + s.cname, + Title: title, CustomCSS: s.css, Count: count, RefreshInterval: rinterval, @@ -105,6 +61,7 @@ func (s *service) cdata(c *client, title string, count int, rinterval int, } if c != nil && c.s.IsLoggedIn() { data.CSRFToken = c.s.CSRFToken + data.Title += " - " + c.s.Instance } return } @@ -130,7 +87,7 @@ func (s *service) ErrorPage(c *client, err error, retry bool) error { } func (s *service) SigninPage(c *client) (err error) { - cdata := s.cdata(nil, "signin", 0, 0, "") + cdata := s.cdata(nil, "Signin", 0, 0, "") data := &renderer.SigninData{ CommonData: cdata, } @@ -138,8 +95,9 @@ func (s *service) SigninPage(c *client) (err error) { } func (s *service) RootPage(c *client) (err error) { + cdata := s.cdata(c, "", 0, 0, "") data := &renderer.RootData{ - Title: s.cname, + CommonData: cdata, } return s.renderer.Render(c.rctx, c.w, renderer.RootPage, data) } @@ -154,7 +112,7 @@ func (s *service) NavPage(c *client) (err error) { DefaultFormat: c.s.Settings.DefaultFormat, Formats: s.postFormats, } - cdata := s.cdata(c, "nav", 0, 0, "main") + cdata := s.cdata(c, "Nav", 0, 0, "main") data := &renderer.NavData{ User: u, CommonData: cdata, @@ -251,7 +209,7 @@ func (s *service) TimelinePage(c *client, tType, instance, listId, maxID, nextLink = "/timeline/" + tType + "?" + v.Encode() } - cdata := s.cdata(c, tType+" timeline ", 0, 0, "") + cdata := s.cdata(c, title, 0, 0, "") data := &renderer.TimelineData{ Title: title, Type: tType, @@ -404,7 +362,7 @@ func (s *service) ThreadPage(c *client, id string, reply bool) (err error) { addToReplyMap(replies, statuses[i].InReplyToID, statuses[i].ID, i+1) } - cdata := s.cdata(c, "post by "+status.Account.DisplayName, 0, 0, "") + cdata := s.cdata(c, "Post by "+status.Account.DisplayName, 0, 0, "") data := &renderer.ThreadData{ Statuses: statuses, PostContext: pctx, @@ -460,7 +418,7 @@ func (s *service) QuickReplyPage(c *client, id string) (err error) { }, } - cdata := s.cdata(c, "post by "+status.Account.DisplayName, 0, 0, "") + cdata := s.cdata(c, "Post by "+status.Account.DisplayName, 0, 0, "") data := &renderer.QuickReplyData{ Ancestor: ancestor, Status: status, @@ -475,7 +433,7 @@ func (s *service) LikedByPage(c *client, id string) (err error) { if err != nil { return } - cdata := s.cdata(c, "likes", 0, 0, "") + cdata := s.cdata(c, "Likes", 0, 0, "") data := &renderer.LikedByData{ CommonData: cdata, Users: likers, @@ -488,7 +446,7 @@ func (s *service) RetweetedByPage(c *client, id string) (err error) { if err != nil { return } - cdata := s.cdata(c, "retweets", 0, 0, "") + cdata := s.cdata(c, "Retweets", 0, 0, "") data := &renderer.RetweetedByData{ CommonData: cdata, Users: retweeters, @@ -537,7 +495,7 @@ func (s *service) NotificationPage(c *client, maxID string, nextLink = "/notifications?max_id=" + pg.MaxID } - cdata := s.cdata(c, "notifications", unreadCount, + cdata := s.cdata(c, "Notifications", unreadCount, c.s.Settings.NotificationInterval, "main") data := &renderer.NotificationData{ Notifications: notifications, @@ -560,12 +518,19 @@ func (s *service) UserPage(c *client, id string, pageType string, MinID: minID, Limit: 20, } + isCurrent := c.s.UserID == id - user, err := c.GetAccount(c.ctx, id) + // Some fields like AccountSource are only available in the + // CurrentUser API + var user *mastodon.Account + if isCurrent { + user, err = c.GetAccountCurrentUser(c.ctx) + } else { + user, err = c.GetAccount(c.ctx, id) + } if err != nil { return } - isCurrent := c.s.UserID == user.ID switch pageType { case "": @@ -677,7 +642,6 @@ func (s *service) UserPage(c *client, id string, pageType string, cdata := s.cdata(c, user.DisplayName+" @"+user.Acct, 0, 0, "") data := &renderer.UserData{ User: user, - IsCurrent: isCurrent, Type: pageType, Users: users, Statuses: statuses, @@ -691,7 +655,7 @@ func (s *service) UserSearchPage(c *client, id string, q string, offset int) (err error) { var nextLink string - var title = "search" + var title = "Search" user, err := c.GetAccount(c.ctx, id) if err != nil { @@ -729,8 +693,21 @@ func (s *service) UserSearchPage(c *client, return s.renderer.Render(c.rctx, c.w, renderer.UserSearchPage, data) } +func (s *service) MutePage(c *client, id string) (err error) { + user, err := c.GetAccount(c.ctx, id) + if err != nil { + return + } + cdata := s.cdata(c, "Mute "+user.DisplayName+" @"+user.Acct, 0, 0, "") + data := &renderer.UserData{ + User: user, + CommonData: cdata, + } + return s.renderer.Render(c.rctx, c.w, renderer.MutePage, data) +} + func (s *service) AboutPage(c *client) (err error) { - cdata := s.cdata(c, "about", 0, 0, "") + cdata := s.cdata(c, "About", 0, 0, "") data := &renderer.AboutData{ CommonData: cdata, } @@ -742,7 +719,7 @@ func (s *service) EmojiPage(c *client) (err error) { if err != nil { return } - cdata := s.cdata(c, "emojis", 0, 0, "") + cdata := s.cdata(c, "Emojis", 0, 0, "") data := &renderer.EmojiData{ Emojis: emojis, CommonData: cdata, @@ -754,7 +731,7 @@ func (s *service) SearchPage(c *client, q string, qType string, offset int) (err error) { var nextLink string - var title = "search" + var title = "Search" var results *mastodon.Results if len(q) > 0 { @@ -790,7 +767,7 @@ func (s *service) SearchPage(c *client, } func (s *service) SettingsPage(c *client) (err error) { - cdata := s.cdata(c, "settings", 0, 0, "") + cdata := s.cdata(c, "Settings", 0, 0, "") data := &renderer.SettingsData{ CommonData: cdata, Settings: &c.s.Settings, @@ -804,7 +781,7 @@ func (svc *service) FiltersPage(c *client) (err error) { if err != nil { return } - cdata := svc.cdata(c, "filters", 0, 0, "") + cdata := svc.cdata(c, "Filters", 0, 0, "") data := &renderer.FiltersData{ CommonData: cdata, Filters: filters, @@ -812,6 +789,55 @@ func (svc *service) FiltersPage(c *client) (err error) { return svc.renderer.Render(c.rctx, c.w, renderer.FiltersPage, data) } +func (svc *service) ProfilePage(c *client) (err error) { + u, err := c.GetAccountCurrentUser(c.ctx) + if err != nil { + return + } + // Some instances allow more than 4 fields, but make sure that there are + // at least 4 fields in the slice because the template depends on it. + if u.Source.Fields == nil { + u.Source.Fields = new([]mastodon.Field) + } + for len(*u.Source.Fields) < 4 { + *u.Source.Fields = append(*u.Source.Fields, mastodon.Field{}) + } + cdata := svc.cdata(c, "Edit profile", 0, 0, "") + data := &renderer.ProfileData{ + CommonData: cdata, + User: u, + } + return svc.renderer.Render(c.rctx, c.w, renderer.ProfilePage, data) +} + +func (s *service) ProfileUpdate(c *client, name, bio string, avatar, banner *multipart.FileHeader, + fields []mastodon.Field, locked bool) (err error) { + // Need to pass empty data to clear fields + if len(fields) == 0 { + fields = append(fields, mastodon.Field{}) + } + p := &mastodon.Profile{ + DisplayName: &name, + Note: &bio, + Avatar: avatar, + Header: banner, + Fields: &fields, + Locked: &locked, + } + _, err = c.AccountUpdate(c.ctx, p) + return err +} + +func (s *service) ProfileDelAvatar(c *client) (err error) { + _, err = c.AccountDeleteAvatar(c.ctx) + return +} + +func (s *service) ProfileDelBanner(c *client) (err error) { + _, err = c.AccountDeleteHeader(c.ctx) + return err +} + func (s *service) SingleInstance() (instance string, ok bool) { if len(s.instance) > 0 { instance = s.instance @@ -820,7 +846,7 @@ func (s *service) SingleInstance() (instance string, ok bool) { return } -func (s *service) NewSession(c *client, instance string) (rurl string, sid string, err error) { +func (s *service) NewSession(c *client, instance string) (rurl string, sess *model.Session, err error) { var instanceURL string if strings.HasPrefix(instance, "https://") { instanceURL = instance @@ -829,66 +855,29 @@ func (s *service) NewSession(c *client, instance string) (rurl string, sid strin instanceURL = "https://" + instance } - sid, err = util.NewSessionID() - if err != nil { - return - } csrf, err := util.NewCSRFToken() if err != nil { return } - sess := model.Session{ - ID: sid, - InstanceDomain: instance, - CSRFToken: csrf, - Settings: *model.NewSettings(), - } - err = s.sessionRepo.Add(sess) + app, err := mastodon.RegisterApp(c.ctx, &mastodon.AppConfig{ + Server: instanceURL, + ClientName: s.cname, + Scopes: s.cscope, + Website: s.cwebsite, + RedirectURIs: s.cwebsite + "/oauth_callback", + }) if err != nil { return } - - app, err := s.appRepo.Get(instance) - if err != nil { - if err != model.ErrAppNotFound { - return - } - mastoApp, err := mastodon.RegisterApp(c.ctx, &mastodon.AppConfig{ - Server: instanceURL, - ClientName: s.cname, - Scopes: s.cscope, - Website: s.cwebsite, - RedirectURIs: s.cwebsite + "/oauth_callback", - }) - if err != nil { - return "", "", err - } - app = model.App{ - InstanceDomain: instance, - InstanceURL: instanceURL, - ClientID: mastoApp.ClientID, - ClientSecret: mastoApp.ClientSecret, - } - err = s.appRepo.Add(app) - if err != nil { - return "", "", err - } - } - - u, err := url.Parse("/oauth/authorize") - if err != nil { - return + rurl = app.AuthURI + sess = &model.Session{ + Instance: instance, + ClientID: app.ClientID, + ClientSecret: app.ClientSecret, + CSRFToken: csrf, + Settings: *model.NewSettings(), } - - q := make(url.Values) - q.Set("scope", "read write follow") - q.Set("client_id", app.ClientID) - q.Set("response_type", "code") - q.Set("redirect_uri", s.cwebsite+"/oauth_callback") - u.RawQuery = q.Encode() - - rurl = instanceURL + u.String() return } @@ -907,12 +896,11 @@ func (s *service) Signin(c *client, code string) (err error) { } c.s.AccessToken = c.GetAccessToken(c.ctx) c.s.UserID = u.ID - return s.sessionRepo.Add(c.s) + return c.setSession(c.s) } func (s *service) Signout(c *client) (err error) { - s.sessionRepo.Remove(c.s.ID) - return + return c.RevokeToken(c.ctx) } func (s *service) Post(c *client, content string, replyToID string, @@ -1005,8 +993,8 @@ func (s *service) Reject(c *client, id string) (err error) { return c.FollowRequestReject(c.ctx, id) } -func (s *service) Mute(c *client, id string, notifications *bool) (err error) { - _, err = c.AccountMute(c.ctx, id, notifications) +func (s *service) Mute(c *client, id string, notifications bool, duration int) (err error) { + _, err = c.AccountMute(c.ctx, id, notifications, duration) return } @@ -1041,15 +1029,21 @@ func (s *service) SaveSettings(c *client, settings *model.Settings) (err error) default: return errInvalidArgument } - if len(settings.CSS) > 1<<20 { - return errInvalidArgument - } - sess, err := s.sessionRepo.Get(c.s.ID) - if err != nil { - return + if len(settings.CSS) > 0 { + if len(settings.CSS) > 1<<20 { + return errInvalidArgument + } + // For some reason, browsers convert CRLF to LF before calculating + // the hash of the inline resources. + settings.CSS = strings.Replace(settings.CSS, "\x0d\x0a", "\x0a", -1) + + h := sha256.Sum256([]byte(settings.CSS)) + settings.CSSHash = base64.StdEncoding.EncodeToString(h[:]) + } else { + settings.CSSHash = "" } - sess.Settings = *settings - return s.sessionRepo.Add(sess) + c.s.Settings = *settings + return c.setSession(c.s) } func (s *service) MuteConversation(c *client, id string) (err error) { |