aboutsummaryrefslogtreecommitdiff
path: root/service/service.go
diff options
context:
space:
mode:
Diffstat (limited to 'service/service.go')
-rw-r--r--service/service.go266
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) {