thread.go (4767B)
1 // GoToSocial 2 // Copyright (C) GoToSocial Authors admin@gotosocial.org 3 // SPDX-License-Identifier: AGPL-3.0-or-later 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package web 19 20 import ( 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "net/http" 26 "strings" 27 28 "github.com/gin-gonic/gin" 29 apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" 30 apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" 31 "github.com/superseriousbusiness/gotosocial/internal/config" 32 "github.com/superseriousbusiness/gotosocial/internal/gtserror" 33 "github.com/superseriousbusiness/gotosocial/internal/oauth" 34 ) 35 36 func (m *Module) threadGETHandler(c *gin.Context) { 37 ctx := c.Request.Context() 38 39 authed, err := oauth.Authed(c, false, false, false, false) 40 if err != nil { 41 apiutil.WebErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) 42 return 43 } 44 45 // usernames on our instance will always be lowercase 46 username := strings.ToLower(c.Param(usernameKey)) 47 if username == "" { 48 err := errors.New("no account username specified") 49 apiutil.WebErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) 50 return 51 } 52 53 // status ids will always be uppercase 54 statusID := strings.ToUpper(c.Param(statusIDKey)) 55 if statusID == "" { 56 err := errors.New("no status id specified") 57 apiutil.WebErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) 58 return 59 } 60 61 instance, err := m.processor.InstanceGetV1(ctx) 62 if err != nil { 63 apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) 64 return 65 } 66 67 instanceGet := func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) { 68 return instance, nil 69 } 70 71 // do this check to make sure the status is actually from a local account, 72 // we shouldn't render threads from statuses that don't belong to us! 73 if _, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, username); errWithCode != nil { 74 apiutil.WebErrorHandler(c, errWithCode, instanceGet) 75 return 76 } 77 78 status, errWithCode := m.processor.Status().Get(ctx, authed.Account, statusID) 79 if errWithCode != nil { 80 apiutil.WebErrorHandler(c, errWithCode, instanceGet) 81 return 82 } 83 84 if !strings.EqualFold(username, status.Account.Username) { 85 err := gtserror.NewErrorNotFound(errors.New("path username not equal to status author username")) 86 apiutil.WebErrorHandler(c, gtserror.NewErrorNotFound(err), instanceGet) 87 return 88 } 89 90 // if we're getting an AP request on this endpoint we 91 // should render the status's AP representation instead 92 accept := apiutil.NegotiateFormat(c, string(apiutil.TextHTML), string(apiutil.AppActivityJSON), string(apiutil.AppActivityLDJSON)) 93 if accept == string(apiutil.AppActivityJSON) || accept == string(apiutil.AppActivityLDJSON) { 94 m.returnAPStatus(c, username, statusID, accept) 95 return 96 } 97 98 context, errWithCode := m.processor.Status().ContextGet(ctx, authed.Account, statusID) 99 if errWithCode != nil { 100 apiutil.WebErrorHandler(c, errWithCode, instanceGet) 101 return 102 } 103 104 stylesheets := []string{ 105 assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css", 106 distPathPrefix + "/status.css", 107 } 108 if config.GetAccountsAllowCustomCSS() { 109 stylesheets = append(stylesheets, "/@"+username+"/custom.css") 110 } 111 112 c.HTML(http.StatusOK, "thread.tmpl", gin.H{ 113 "instance": instance, 114 "status": status, 115 "context": context, 116 "ogMeta": ogBase(instance).withStatus(status), 117 "stylesheets": stylesheets, 118 "javascript": []string{distPathPrefix + "/frontend.js"}, 119 }) 120 } 121 122 func (m *Module) returnAPStatus(c *gin.Context, username string, statusID string, accept string) { 123 status, errWithCode := m.processor.Fedi().StatusGet(c.Request.Context(), username, statusID) 124 if errWithCode != nil { 125 apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1) 126 return 127 } 128 129 b, mErr := json.Marshal(status) 130 if mErr != nil { 131 err := fmt.Errorf("could not marshal json: %s", mErr) 132 apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1) 133 return 134 } 135 136 c.Data(http.StatusOK, accept, b) 137 }