lots of stufffffffffffffffff
This commit is contained in:
parent
dc06e71b76
commit
8832784778
|
@ -57,6 +57,8 @@ const (
|
|||
GetStatusesPath = BasePathWithID + "/statuses"
|
||||
// GetFollowersPath is for showing an account's followers
|
||||
GetFollowersPath = BasePathWithID + "/followers"
|
||||
// GetRelationshipsPath is for showing an account's relationship with other accounts
|
||||
GetRelationshipsPath = BasePath + "/relationships"
|
||||
)
|
||||
|
||||
// Module implements the ClientAPIModule interface for account-related actions
|
||||
|
@ -82,6 +84,7 @@ func (m *Module) Route(r router.Router) error {
|
|||
r.AttachHandler(http.MethodPatch, BasePathWithID, m.muxHandler)
|
||||
r.AttachHandler(http.MethodGet, GetStatusesPath, m.AccountStatusesGETHandler)
|
||||
r.AttachHandler(http.MethodGet, GetFollowersPath, m.AccountFollowersGETHandler)
|
||||
r.AttachHandler(http.MethodGet, GetRelationshipsPath, m.AccountRelationshipsGETHandler)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
package account
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// AccountRelationshipsGETHandler serves the relationship of the requesting account with one or more requested account IDs.
|
||||
func (m *Module) AccountRelationshipsGETHandler(c *gin.Context) {
|
||||
l := m.log.WithField("func", "AccountRelationshipsGETHandler")
|
||||
|
||||
authed, err := oauth.Authed(c, true, true, true, true)
|
||||
if err != nil {
|
||||
l.Debugf("error authing: %s", err)
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||
return
|
||||
}
|
||||
|
||||
targetAccountIDs := c.QueryArray("id[]")
|
||||
if len(targetAccountIDs) == 0 {
|
||||
l.Debug("no account id specified in query")
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
||||
return
|
||||
}
|
||||
|
||||
relationships := []model.Relationship{}
|
||||
|
||||
for _, targetAccountID := range targetAccountIDs {
|
||||
r, errWithCode := m.processor.AccountRelationshipGet(authed, targetAccountID)
|
||||
if err != nil {
|
||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||
return
|
||||
}
|
||||
relationships = append(relationships, *r)
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, relationships)
|
||||
}
|
|
@ -48,10 +48,11 @@ func (m *Module) FollowRequestAcceptPOSTHandler(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
|
||||
if errWithCode := m.processor.FollowRequestAccept(authed, originAccountID); errWithCode != nil {
|
||||
r, errWithCode := m.processor.FollowRequestAccept(authed, originAccountID)
|
||||
if errWithCode != nil {
|
||||
l.Debug(errWithCode.Error())
|
||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusOK)
|
||||
c.JSON(http.StatusOK, r)
|
||||
}
|
||||
|
|
|
@ -77,18 +77,18 @@ type Account struct {
|
|||
// See https://docs.joinmastodon.org/methods/accounts/
|
||||
type AccountCreateRequest struct {
|
||||
// Text that will be reviewed by moderators if registrations require manual approval.
|
||||
Reason string `form:"reason"`
|
||||
Reason string `form:"reason" json:"reason" xml:"reason"`
|
||||
// The desired username for the account
|
||||
Username string `form:"username" binding:"required"`
|
||||
Username string `form:"username" json:"username" xml:"username" binding:"required"`
|
||||
// The email address to be used for login
|
||||
Email string `form:"email" binding:"required"`
|
||||
Email string `form:"email" json:"email" xml:"email" binding:"required"`
|
||||
// The password to be used for login
|
||||
Password string `form:"password" binding:"required"`
|
||||
Password string `form:"password" json:"password" xml:"password" binding:"required"`
|
||||
// Whether the user agrees to the local rules, terms, and policies.
|
||||
// These should be presented to the user in order to allow them to consent before setting this parameter to TRUE.
|
||||
Agreement bool `form:"agreement" binding:"required"`
|
||||
Agreement bool `form:"agreement" json:"agreement" xml:"agreement" binding:"required"`
|
||||
// The language of the confirmation email that will be sent
|
||||
Locale string `form:"locale" binding:"required"`
|
||||
Locale string `form:"locale" json:"locale" xml:"locale" binding:"required"`
|
||||
// The IP of the sign up request, will not be parsed from the form but must be added manually
|
||||
IP net.IP `form:"-"`
|
||||
}
|
||||
|
@ -97,33 +97,33 @@ type AccountCreateRequest struct {
|
|||
// See https://docs.joinmastodon.org/methods/accounts/
|
||||
type UpdateCredentialsRequest struct {
|
||||
// Whether the account should be shown in the profile directory.
|
||||
Discoverable *bool `form:"discoverable"`
|
||||
Discoverable *bool `form:"discoverable" json:"discoverable" xml:"discoverable"`
|
||||
// Whether the account has a bot flag.
|
||||
Bot *bool `form:"bot"`
|
||||
Bot *bool `form:"bot" json:"bot" xml:"bot"`
|
||||
// The display name to use for the profile.
|
||||
DisplayName *string `form:"display_name"`
|
||||
DisplayName *string `form:"display_name" json:"display_name" xml:"display_name"`
|
||||
// The account bio.
|
||||
Note *string `form:"note"`
|
||||
Note *string `form:"note" json:"note" xml:"note"`
|
||||
// Avatar image encoded using multipart/form-data
|
||||
Avatar *multipart.FileHeader `form:"avatar"`
|
||||
Avatar *multipart.FileHeader `form:"avatar" json:"avatar" xml:"avatar"`
|
||||
// Header image encoded using multipart/form-data
|
||||
Header *multipart.FileHeader `form:"header"`
|
||||
Header *multipart.FileHeader `form:"header" json:"header" xml:"header"`
|
||||
// Whether manual approval of follow requests is required.
|
||||
Locked *bool `form:"locked"`
|
||||
Locked *bool `form:"locked" json:"locked" xml:"locked"`
|
||||
// New Source values for this account
|
||||
Source *UpdateSource `form:"source"`
|
||||
Source *UpdateSource `form:"source" json:"source" xml:"source"`
|
||||
// Profile metadata name and value
|
||||
FieldsAttributes *[]UpdateField `form:"fields_attributes"`
|
||||
FieldsAttributes *[]UpdateField `form:"fields_attributes" json:"fields_attributes" xml:"fields_attributes"`
|
||||
}
|
||||
|
||||
// UpdateSource is to be used specifically in an UpdateCredentialsRequest.
|
||||
type UpdateSource struct {
|
||||
// Default post privacy for authored statuses.
|
||||
Privacy *string `form:"privacy"`
|
||||
Privacy *string `form:"privacy" json:"privacy" xml:"privacy"`
|
||||
// Whether to mark authored statuses as sensitive by default.
|
||||
Sensitive *bool `form:"sensitive"`
|
||||
Sensitive *bool `form:"sensitive" json:"sensitive" xml:"sensitive"`
|
||||
// Default language to use for authored statuses. (ISO 6391)
|
||||
Language *string `form:"language"`
|
||||
Language *string `form:"language" json:"language" xml:"language"`
|
||||
}
|
||||
|
||||
// UpdateField is to be used specifically in an UpdateCredentialsRequest.
|
||||
|
|
|
@ -43,5 +43,6 @@ func New(config *config.Config, log *logrus.Logger) api.ClientModule {
|
|||
func (m *Module) Route(s router.Router) error {
|
||||
s.AttachMiddleware(m.FlocBlock)
|
||||
s.AttachMiddleware(m.ExtraHeaders)
|
||||
s.AttachMiddleware(m.UserAgentBlock)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package security
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UserAgentBlock is a middleware that prevents google chrome cohort tracking by
|
||||
// writing the Permissions-Policy header after all other parts of the request have been completed.
|
||||
// See: https://plausible.io/blog/google-floc
|
||||
func (m *Module) UserAgentBlock(c *gin.Context) {
|
||||
|
||||
ua := c.Request.UserAgent()
|
||||
if ua == "" {
|
||||
c.AbortWithStatus(http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(c.Request.UserAgent()), strings.ToLower("friendica")) {
|
||||
c.AbortWithStatus(http.StatusTeapot)
|
||||
return
|
||||
}
|
||||
}
|
|
@ -117,7 +117,9 @@ type DB interface {
|
|||
|
||||
// AcceptFollowRequest moves a follow request in the database from the follow_requests table to the follows table.
|
||||
// In other words, it should create the follow, and delete the existing follow request.
|
||||
AcceptFollowRequest(originAccountID string, targetAccountID string) error
|
||||
//
|
||||
// It will return the newly created follow for further processing.
|
||||
AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, error)
|
||||
|
||||
// CreateInstanceAccount creates an account in the database with the same username as the instance host value.
|
||||
// Ie., if the instance is hosted at 'example.org' the instance user will have a username of 'example.org'.
|
||||
|
@ -204,6 +206,9 @@ type DB interface {
|
|||
// That is, it returns true if account1 blocks account2, OR if account2 blocks account1.
|
||||
Blocked(account1 string, account2 string) (bool, error)
|
||||
|
||||
// GetRelationship retrieves the relationship of the targetAccount to the requestingAccount.
|
||||
GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, error)
|
||||
|
||||
// StatusVisible returns true if targetStatus is visible to requestingAccount, based on the
|
||||
// privacy settings of the status, and any blocks/mutes that might exist between the two accounts
|
||||
// or account domains.
|
||||
|
|
|
@ -307,30 +307,34 @@ func (ps *postgresService) DeleteWhere(key string, value interface{}, i interfac
|
|||
HANDY SHORTCUTS
|
||||
*/
|
||||
|
||||
func (ps *postgresService) AcceptFollowRequest(originAccountID string, targetAccountID string) error {
|
||||
func (ps *postgresService) AcceptFollowRequest(originAccountID string, targetAccountID string) (*gtsmodel.Follow, error) {
|
||||
// make sure the original follow request exists
|
||||
fr := >smodel.FollowRequest{}
|
||||
if err := ps.conn.Model(fr).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Select(); err != nil {
|
||||
if err == pg.ErrMultiRows {
|
||||
return db.ErrNoEntries{}
|
||||
return nil, db.ErrNoEntries{}
|
||||
}
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create a new follow to 'replace' the request with
|
||||
follow := >smodel.Follow{
|
||||
AccountID: originAccountID,
|
||||
TargetAccountID: targetAccountID,
|
||||
URI: fr.URI,
|
||||
}
|
||||
|
||||
if _, err := ps.conn.Model(follow).Insert(); err != nil {
|
||||
return err
|
||||
// if the follow already exists, just update the URI -- we don't need to do anything else
|
||||
if _, err := ps.conn.Model(follow).OnConflict("ON CONSTRAINT follows_account_id_target_account_id_key DO UPDATE set uri = ?", follow.URI).Insert(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// now remove the follow request
|
||||
if _, err := ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", originAccountID).Where("target_account_id = ?", targetAccountID).Delete(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil
|
||||
return follow, nil
|
||||
}
|
||||
|
||||
func (ps *postgresService) CreateInstanceAccount() error {
|
||||
|
@ -681,6 +685,60 @@ func (ps *postgresService) Blocked(account1 string, account2 string) (bool, erro
|
|||
return blocked, nil
|
||||
}
|
||||
|
||||
func (ps *postgresService) GetRelationship(requestingAccount string, targetAccount string) (*gtsmodel.Relationship, error) {
|
||||
r := >smodel.Relationship{
|
||||
ID: targetAccount,
|
||||
}
|
||||
|
||||
// check if the requesting account follows the target account
|
||||
follow := >smodel.Follow{}
|
||||
if err := ps.conn.Model(follow).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Select(); err != nil {
|
||||
if err != pg.ErrNoRows {
|
||||
// a proper error
|
||||
return nil, fmt.Errorf("getrelationship: error checking follow existence: %s", err)
|
||||
}
|
||||
// no follow exists so these are all false
|
||||
r.Following = false
|
||||
r.ShowingReblogs = false
|
||||
r.Notifying = false
|
||||
} else {
|
||||
// follow exists so we can fill these fields out...
|
||||
r.Following = true
|
||||
r.ShowingReblogs = follow.ShowReblogs
|
||||
r.Notifying = follow.Notify
|
||||
}
|
||||
|
||||
// check if the target account follows the requesting account
|
||||
followedBy, err := ps.conn.Model(>smodel.Follow{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getrelationship: error checking followed_by existence: %s", err)
|
||||
}
|
||||
r.FollowedBy = followedBy
|
||||
|
||||
// check if the requesting account blocks the target account
|
||||
blocking, err := ps.conn.Model(>smodel.Block{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getrelationship: error checking blocking existence: %s", err)
|
||||
}
|
||||
r.Blocking = blocking
|
||||
|
||||
// check if the target account blocks the requesting account
|
||||
blockedBy, err := ps.conn.Model(>smodel.Block{}).Where("account_id = ?", targetAccount).Where("target_account_id = ?", requestingAccount).Exists()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err)
|
||||
}
|
||||
r.BlockedBy = blockedBy
|
||||
|
||||
// check if there's a pending following request from requesting account to target account
|
||||
requested, err := ps.conn.Model(>smodel.FollowRequest{}).Where("account_id = ?", requestingAccount).Where("target_account_id = ?", targetAccount).Exists()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getrelationship: error checking blocked existence: %s", err)
|
||||
}
|
||||
r.Requested = requested
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account, relevantAccounts *gtsmodel.RelevantAccounts) (bool, error) {
|
||||
l := ps.log.WithField("func", "StatusVisible")
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package federation
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -371,24 +372,37 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
"asType": asType.GetTypeName(),
|
||||
},
|
||||
)
|
||||
l.Debugf("received CREATE asType %+v", asType)
|
||||
m, err := streams.Serialize(asType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Debugf("received CREATE asType %s", string(b))
|
||||
|
||||
targetAcctI := ctx.Value(util.APAccount)
|
||||
if targetAcctI == nil {
|
||||
l.Error("target account wasn't set on context")
|
||||
return nil
|
||||
}
|
||||
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
l.Error("target account was set on context but couldn't be parsed")
|
||||
return nil
|
||||
}
|
||||
|
||||
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
|
||||
if fromFederatorChanI == nil {
|
||||
l.Error("from federator channel wasn't set on context")
|
||||
return nil
|
||||
}
|
||||
fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator)
|
||||
if !ok {
|
||||
l.Error("from federator channel was set on context but couldn't be parsed")
|
||||
return nil
|
||||
}
|
||||
|
||||
switch gtsmodel.ActivityStreamsActivity(asType.GetTypeName()) {
|
||||
|
@ -433,7 +447,7 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
}
|
||||
|
||||
if !targetAcct.Locked {
|
||||
if err := f.db.AcceptFollowRequest(followRequest.AccountID, followRequest.TargetAccountID); err != nil {
|
||||
if _, err := f.db.AcceptFollowRequest(followRequest.AccountID, followRequest.TargetAccountID); err != nil {
|
||||
return fmt.Errorf("database error accepting follow request: %s", err)
|
||||
}
|
||||
}
|
||||
|
@ -450,14 +464,87 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
|||
// the entire value.
|
||||
//
|
||||
// The library makes this call only after acquiring a lock first.
|
||||
func (f *federatingDB) Update(c context.Context, asType vocab.Type) error {
|
||||
func (f *federatingDB) Update(ctx context.Context, asType vocab.Type) error {
|
||||
l := f.log.WithFields(
|
||||
logrus.Fields{
|
||||
"func": "Update",
|
||||
"asType": asType.GetTypeName(),
|
||||
},
|
||||
)
|
||||
l.Debugf("received UPDATE asType %+v", asType)
|
||||
m, err := streams.Serialize(asType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
l.Debugf("received UPDATE asType %s", string(b))
|
||||
|
||||
receivingAcctI := ctx.Value(util.APAccount)
|
||||
if receivingAcctI == nil {
|
||||
l.Error("receiving account wasn't set on context")
|
||||
}
|
||||
receivingAcct, ok := receivingAcctI.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
l.Error("receiving account was set on context but couldn't be parsed")
|
||||
}
|
||||
|
||||
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
|
||||
if fromFederatorChanI == nil {
|
||||
l.Error("from federator channel wasn't set on context")
|
||||
}
|
||||
fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator)
|
||||
if !ok {
|
||||
l.Error("from federator channel was set on context but couldn't be parsed")
|
||||
}
|
||||
|
||||
switch gtsmodel.ActivityStreamsActivity(asType.GetTypeName()) {
|
||||
case gtsmodel.ActivityStreamsUpdate:
|
||||
update, ok := asType.(vocab.ActivityStreamsCreate)
|
||||
if !ok {
|
||||
return errors.New("could not convert type to create")
|
||||
}
|
||||
object := update.GetActivityStreamsObject()
|
||||
for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() {
|
||||
switch objectIter.GetType().GetTypeName() {
|
||||
case string(gtsmodel.ActivityStreamsPerson):
|
||||
person := objectIter.GetActivityStreamsPerson()
|
||||
updatedAcct, err := f.typeConverter.ASRepresentationToAccount(person)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting person to account: %s", err)
|
||||
}
|
||||
if err := f.db.Put(updatedAcct); err != nil {
|
||||
return fmt.Errorf("database error inserting updated account: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsUpdate,
|
||||
GTSModel: updatedAcct,
|
||||
ReceivingAccount: receivingAcct,
|
||||
}
|
||||
|
||||
case string(gtsmodel.ActivityStreamsApplication):
|
||||
application := objectIter.GetActivityStreamsApplication()
|
||||
updatedAcct, err := f.typeConverter.ASRepresentationToAccount(application)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting person to account: %s", err)
|
||||
}
|
||||
if err := f.db.Put(updatedAcct); err != nil {
|
||||
return fmt.Errorf("database error inserting updated account: %s", err)
|
||||
}
|
||||
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsUpdate,
|
||||
GTSModel: updatedAcct,
|
||||
ReceivingAccount: receivingAcct,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -490,7 +577,7 @@ func (f *federatingDB) GetOutbox(c context.Context, outboxIRI *url.URL) (inbox v
|
|||
)
|
||||
l.Debug("entering GETOUTBOX function")
|
||||
|
||||
return nil, nil
|
||||
return streams.NewActivityStreamsOrderedCollectionPage(), nil
|
||||
}
|
||||
|
||||
// SetOutbox saves the outbox value given from GetOutbox, with new items
|
||||
|
@ -522,9 +609,18 @@ func (f *federatingDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err
|
|||
"asType": t.GetTypeName(),
|
||||
},
|
||||
)
|
||||
l.Debugf("received NEWID request for asType %+v", t)
|
||||
m, err := streams.Serialize(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return url.Parse(fmt.Sprintf("%s://%s/", f.config.Protocol, uuid.NewString()))
|
||||
l.Debugf("received NEWID request for asType %s", string(b))
|
||||
|
||||
return url.Parse(fmt.Sprintf("%s://%s/%s", f.config.Protocol, f.config.Host, uuid.NewString()))
|
||||
}
|
||||
|
||||
// Followers obtains the Followers Collection for an actor with the
|
||||
|
|
|
@ -20,12 +20,14 @@ package federation
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
|
@ -146,6 +148,22 @@ func (f *federator) AuthenticatePostInbox(ctx context.Context, w http.ResponseWr
|
|||
}
|
||||
|
||||
requestingAccount = a
|
||||
|
||||
// send the newly dereferenced account into the processor channel for further async processing
|
||||
fromFederatorChanI := ctx.Value(util.APFromFederatorChanKey)
|
||||
if fromFederatorChanI == nil {
|
||||
l.Error("from federator channel wasn't set on context")
|
||||
}
|
||||
fromFederatorChan, ok := fromFederatorChanI.(chan gtsmodel.FromFederator)
|
||||
if !ok {
|
||||
l.Error("from federator channel was set on context but couldn't be parsed")
|
||||
}
|
||||
|
||||
fromFederatorChan <- gtsmodel.FromFederator{
|
||||
APObjectType: gtsmodel.ActivityStreamsProfile,
|
||||
APActivityType: gtsmodel.ActivityStreamsCreate,
|
||||
GTSModel: requestingAccount,
|
||||
}
|
||||
}
|
||||
|
||||
withRequester := context.WithValue(ctx, util.APRequestingAccount, requestingAccount)
|
||||
|
@ -228,17 +246,19 @@ func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.Federa
|
|||
"func": "FederatingCallbacks",
|
||||
})
|
||||
|
||||
targetAcctI := ctx.Value(util.APAccount)
|
||||
if targetAcctI == nil {
|
||||
l.Error("target account wasn't set on context")
|
||||
receivingAcctI := ctx.Value(util.APAccount)
|
||||
if receivingAcctI == nil {
|
||||
l.Error("receiving account wasn't set on context")
|
||||
return
|
||||
}
|
||||
targetAcct, ok := targetAcctI.(*gtsmodel.Account)
|
||||
receivingAcct, ok := receivingAcctI.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
l.Error("target account was set on context but couldn't be parsed")
|
||||
l.Error("receiving account was set on context but couldn't be parsed")
|
||||
return
|
||||
}
|
||||
|
||||
var onFollow pub.OnFollowBehavior = pub.OnFollowAutomaticallyAccept
|
||||
if targetAcct.Locked {
|
||||
if receivingAcct.Locked {
|
||||
onFollow = pub.OnFollowDoNothing
|
||||
}
|
||||
|
||||
|
@ -248,6 +268,13 @@ func (f *federator) FederatingCallbacks(ctx context.Context) (wrapped pub.Federa
|
|||
OnFollow: onFollow,
|
||||
}
|
||||
|
||||
// override default undo behavior
|
||||
other = []interface{}{
|
||||
func(ctx context.Context, undo vocab.ActivityStreamsUndo) error {
|
||||
return f.typeConverter.
|
||||
},
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -76,13 +76,13 @@ type Account struct {
|
|||
*/
|
||||
|
||||
// Does this account need an approval for new followers?
|
||||
Locked bool `pg:",default:'true'"`
|
||||
Locked bool
|
||||
// Should this account be shown in the instance's profile directory?
|
||||
Discoverable bool
|
||||
// Default post privacy for this account
|
||||
Privacy Visibility
|
||||
// Set posts from this account to sensitive by default?
|
||||
Sensitive bool `pg:",default:'false'"`
|
||||
Sensitive bool
|
||||
// What language does this account post in?
|
||||
Language string `pg:",default:'en'"`
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ type FromClientAPI struct {
|
|||
|
||||
// FromFederator wraps a message that travels from the federator into the processor
|
||||
type FromFederator struct {
|
||||
APObjectType ActivityStreamsObject
|
||||
APActivityType ActivityStreamsActivity
|
||||
GTSModel interface{}
|
||||
APObjectType ActivityStreamsObject
|
||||
APActivityType ActivityStreamsActivity
|
||||
GTSModel interface{}
|
||||
ReceivingAccount *Account
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
GoToSocial
|
||||
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package gtsmodel
|
||||
|
||||
// Relationship describes a requester's relationship with another account.
|
||||
type Relationship struct {
|
||||
// The account id.
|
||||
ID string
|
||||
// Are you following this user?
|
||||
Following bool
|
||||
// Are you receiving this user's boosts in your home timeline?
|
||||
ShowingReblogs bool
|
||||
// Have you enabled notifications for this user?
|
||||
Notifying bool
|
||||
// Are you followed by this user?
|
||||
FollowedBy bool
|
||||
// Are you blocking this user?
|
||||
Blocking bool
|
||||
// Is this user blocking you?
|
||||
BlockedBy bool
|
||||
// Are you muting this user?
|
||||
Muting bool
|
||||
// Are you muting notifications from this user?
|
||||
MutingNotifications bool
|
||||
// Do you have a pending follow request for this user?
|
||||
Requested bool
|
||||
// Are you blocking this user's domain?
|
||||
DomainBlocking bool
|
||||
// Are you featuring this user on your profile?
|
||||
Endorsed bool
|
||||
// Your note on this account.
|
||||
Note string
|
||||
}
|
|
@ -67,7 +67,7 @@ type Handler interface {
|
|||
// ProcessHeaderOrAvatar takes a new header image for an account, checks it out, removes exif data from it,
|
||||
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,
|
||||
// and then returns information to the caller about the new header.
|
||||
ProcessHeaderOrAvatar(img []byte, accountID string, mediaType Type) (*gtsmodel.MediaAttachment, error)
|
||||
ProcessHeaderOrAvatar(attachment []byte, accountID string, mediaType Type, remoteURL string) (*gtsmodel.MediaAttachment, error)
|
||||
|
||||
// ProcessLocalAttachment takes a new attachment and the requesting account, checks it out, removes exif data from it,
|
||||
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new media,
|
||||
|
@ -86,6 +86,8 @@ type Handler interface {
|
|||
// information to the caller about the new attachment. It's the caller's responsibility to put the returned struct
|
||||
// in the database.
|
||||
ProcessRemoteAttachment(t transport.Transport, currentAttachment *gtsmodel.MediaAttachment, accountID string) (*gtsmodel.MediaAttachment, error)
|
||||
|
||||
ProcessRemoteHeaderOrAvatar(t transport.Transport, currentAttachment *gtsmodel.MediaAttachment, accountID string) (*gtsmodel.MediaAttachment, error)
|
||||
}
|
||||
|
||||
type mediaHandler struct {
|
||||
|
@ -112,7 +114,7 @@ func New(config *config.Config, database db.DB, storage storage.Storage, log *lo
|
|||
// ProcessHeaderOrAvatar takes a new header image for an account, checks it out, removes exif data from it,
|
||||
// puts it in whatever storage backend we're using, sets the relevant fields in the database for the new image,
|
||||
// and then returns information to the caller about the new header.
|
||||
func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID string, mediaType Type) (*gtsmodel.MediaAttachment, error) {
|
||||
func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID string, mediaType Type, remoteURL string) (*gtsmodel.MediaAttachment, error) {
|
||||
l := mh.log.WithField("func", "SetHeaderForAccountID")
|
||||
|
||||
if mediaType != Header && mediaType != Avatar {
|
||||
|
@ -134,7 +136,7 @@ func (mh *mediaHandler) ProcessHeaderOrAvatar(attachment []byte, accountID strin
|
|||
l.Tracef("read %d bytes of file", len(attachment))
|
||||
|
||||
// process it
|
||||
ma, err := mh.processHeaderOrAvi(attachment, contentType, mediaType, accountID)
|
||||
ma, err := mh.processHeaderOrAvi(attachment, contentType, mediaType, accountID, remoteURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error processing %s: %s", mediaType, err)
|
||||
}
|
||||
|
@ -315,3 +317,43 @@ func (mh *mediaHandler) ProcessRemoteAttachment(t transport.Transport, currentAt
|
|||
|
||||
return mh.ProcessAttachment(attachmentBytes, accountID, currentAttachment.RemoteURL)
|
||||
}
|
||||
|
||||
func (mh *mediaHandler) ProcessRemoteHeaderOrAvatar(t transport.Transport, currentAttachment *gtsmodel.MediaAttachment, accountID string) (*gtsmodel.MediaAttachment, error) {
|
||||
|
||||
if !currentAttachment.Header && !currentAttachment.Avatar {
|
||||
return nil, errors.New("provided attachment was set to neither header nor avatar")
|
||||
}
|
||||
|
||||
if currentAttachment.Header && currentAttachment.Avatar {
|
||||
return nil, errors.New("provided attachment was set to both header and avatar")
|
||||
}
|
||||
|
||||
var headerOrAvi Type
|
||||
if currentAttachment.Header {
|
||||
headerOrAvi = Header
|
||||
} else if currentAttachment.Avatar {
|
||||
headerOrAvi = Avatar
|
||||
}
|
||||
|
||||
if currentAttachment.RemoteURL == "" {
|
||||
return nil, errors.New("no remote URL on media attachment to dereference")
|
||||
}
|
||||
remoteIRI, err := url.Parse(currentAttachment.RemoteURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing attachment url %s: %s", currentAttachment.RemoteURL, err)
|
||||
}
|
||||
|
||||
// for content type, we assume we don't know what to expect...
|
||||
expectedContentType := "*/*"
|
||||
if currentAttachment.File.ContentType != "" {
|
||||
// ... and then narrow it down if we do
|
||||
expectedContentType = currentAttachment.File.ContentType
|
||||
}
|
||||
|
||||
attachmentBytes, err := t.DereferenceMedia(context.Background(), remoteIRI, expectedContentType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("dereferencing remote media with url %s: %s", remoteIRI.String(), err)
|
||||
}
|
||||
|
||||
return mh.ProcessHeaderOrAvatar(attachmentBytes, accountID, headerOrAvi, currentAttachment.RemoteURL)
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ func (suite *MediaTestSuite) TestSetHeaderOrAvatarForAccountID() {
|
|||
f, err := ioutil.ReadFile("./test/test-jpeg.jpg")
|
||||
assert.Nil(suite.T(), err)
|
||||
|
||||
ma, err := suite.mediaHandler.ProcessHeaderOrAvatar(f, "weeeeeee", "header")
|
||||
ma, err := suite.mediaHandler.ProcessHeaderOrAvatar(f, "weeeeeee", "header", "")
|
||||
assert.Nil(suite.T(), err)
|
||||
suite.log.Debugf("%+v", ma)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, mediaType Type, accountID string) (*gtsmodel.MediaAttachment, error) {
|
||||
func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string, mediaType Type, accountID string, remoteURL string) (*gtsmodel.MediaAttachment, error) {
|
||||
var isHeader bool
|
||||
var isAvatar bool
|
||||
|
||||
|
@ -96,7 +96,7 @@ func (mh *mediaHandler) processHeaderOrAvi(imageBytes []byte, contentType string
|
|||
ID: newMediaID,
|
||||
StatusID: "",
|
||||
URL: originalURL,
|
||||
RemoteURL: "",
|
||||
RemoteURL: remoteURL,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
Type: gtsmodel.FileTypeImage,
|
||||
|
|
|
@ -78,6 +78,15 @@ func (p *processor) AccountGet(authed *oauth.Auth, targetAccountID string) (*api
|
|||
return nil, fmt.Errorf("db error: %s", err)
|
||||
}
|
||||
|
||||
// lazily dereference things on the account if it hasn't been done yet
|
||||
var requestingUsername string
|
||||
if authed.Account != nil {
|
||||
requestingUsername = authed.Account.Username
|
||||
}
|
||||
if err := p.dereferenceAccountFields(targetAccount, requestingUsername); err != nil {
|
||||
p.log.WithField("func", "AccountGet").Debugf("dereferencing account: %s", err)
|
||||
}
|
||||
|
||||
var mastoAccount *apimodel.Account
|
||||
var err error
|
||||
if authed.Account != nil && targetAccount.ID == authed.Account.ID {
|
||||
|
@ -285,6 +294,12 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
|
|||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// derefence account fields in case we haven't done it already
|
||||
if err := p.dereferenceAccountFields(a, authed.Account.Username); err != nil {
|
||||
// don't bail if we can't fetch them, we'll try another time
|
||||
p.log.WithField("func", "AccountFollowersGet").Debugf("error dereferencing account fields: %s", err)
|
||||
}
|
||||
|
||||
account, err := p.tc.AccountToMastoPublic(a)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
|
@ -293,3 +308,21 @@ func (p *processor) AccountFollowersGet(authed *oauth.Auth, targetAccountID stri
|
|||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (p *processor) AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode) {
|
||||
if authed == nil || authed.Account == nil {
|
||||
return nil, NewErrorForbidden(errors.New("not authed"))
|
||||
}
|
||||
|
||||
gtsR, err := p.db.GetRelationship(authed.Account.ID, targetAccountID)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(fmt.Errorf("error getting relationship: %s", err))
|
||||
}
|
||||
|
||||
r, err := p.tc.RelationshipToMasto(gtsR)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(fmt.Errorf("error converting relationship: %s", err))
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
|
|
@ -19,30 +19,48 @@
|
|||
package message
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error {
|
||||
switch clientMsg.APObjectType {
|
||||
case gtsmodel.ActivityStreamsNote:
|
||||
status, ok := clientMsg.GTSModel.(*gtsmodel.Status)
|
||||
switch clientMsg.APActivityType {
|
||||
case gtsmodel.ActivityStreamsCreate:
|
||||
// CREATE
|
||||
switch clientMsg.APObjectType {
|
||||
case gtsmodel.ActivityStreamsNote:
|
||||
// CREATE NOTE
|
||||
status, ok := clientMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return errors.New("note was not parseable as *gtsmodel.Status")
|
||||
}
|
||||
|
||||
if err := p.notifyStatus(status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status.VisibilityAdvanced.Federated {
|
||||
return p.federateStatus(status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case gtsmodel.ActivityStreamsUpdate:
|
||||
// UPDATE
|
||||
case gtsmodel.ActivityStreamsAccept:
|
||||
// ACCEPT
|
||||
follow, ok := clientMsg.GTSModel.(*gtsmodel.Follow)
|
||||
if !ok {
|
||||
return errors.New("note was not parseable as *gtsmodel.Status")
|
||||
return errors.New("accept was not parseable as *gtsmodel.Follow")
|
||||
}
|
||||
|
||||
if err := p.notifyStatus(status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if status.VisibilityAdvanced.Federated {
|
||||
return p.federateStatus(status)
|
||||
}
|
||||
return nil
|
||||
return p.federateAcceptFollowRequest(follow)
|
||||
}
|
||||
return fmt.Errorf("message type unprocessable: %+v", clientMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *processor) federateStatus(status *gtsmodel.Status) error {
|
||||
|
@ -71,3 +89,74 @@ func (p *processor) federateStatus(status *gtsmodel.Status) error {
|
|||
// _, err = p.federator.FederatingActor().Send(context.Background(), outboxURI, note)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow) error {
|
||||
|
||||
followAccepter := >smodel.Account{}
|
||||
if err := p.db.GetByID(follow.TargetAccountID, followAccepter); err != nil {
|
||||
return fmt.Errorf("error federating follow accept: %s", err)
|
||||
}
|
||||
followAccepterIRI, err := url.Parse(followAccepter.URI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing URL: %s", err)
|
||||
}
|
||||
followAccepterOutboxIRI, err := url.Parse(followAccepter.OutboxURI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing URL: %s", err)
|
||||
}
|
||||
me := streams.NewActivityStreamsActorProperty()
|
||||
me.AppendIRI(followAccepterIRI)
|
||||
|
||||
followRequester := >smodel.Account{}
|
||||
if err := p.db.GetByID(follow.AccountID, followRequester); err != nil {
|
||||
return fmt.Errorf("error federating follow accept: %s", err)
|
||||
}
|
||||
requesterIRI, err := url.Parse(followRequester.URI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing URL: %s", err)
|
||||
}
|
||||
them := streams.NewActivityStreamsActorProperty()
|
||||
them.AppendIRI(requesterIRI)
|
||||
|
||||
// prepare the follow
|
||||
ASFollow := streams.NewActivityStreamsFollow()
|
||||
// set the follow requester as the actor
|
||||
ASFollow.SetActivityStreamsActor(them)
|
||||
// set the ID from the follow
|
||||
ASFollowURI, err := url.Parse(follow.URI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing URL: %s", err)
|
||||
}
|
||||
ASFollowIDProp := streams.NewJSONLDIdProperty()
|
||||
ASFollowIDProp.SetIRI(ASFollowURI)
|
||||
ASFollow.SetJSONLDId(ASFollowIDProp)
|
||||
|
||||
// set the object as the accepter URI
|
||||
ASFollowObjectProp := streams.NewActivityStreamsObjectProperty()
|
||||
ASFollowObjectProp.AppendIRI(followAccepterIRI)
|
||||
|
||||
// Prepare the response.
|
||||
ASAccept := streams.NewActivityStreamsAccept()
|
||||
// Set us as the 'actor'.
|
||||
ASAccept.SetActivityStreamsActor(me)
|
||||
|
||||
// Set the Follow as the 'object' property.
|
||||
ASAcceptObject := streams.NewActivityStreamsObjectProperty()
|
||||
ASAcceptObject.AppendActivityStreamsFollow(ASFollow)
|
||||
ASAccept.SetActivityStreamsObject(ASAcceptObject)
|
||||
|
||||
// Add all actors on the original Follow to the 'to' property.
|
||||
ASAcceptTo := streams.NewActivityStreamsToProperty()
|
||||
followActors := ASFollow.GetActivityStreamsActor()
|
||||
for iter := followActors.Begin(); iter != followActors.End(); iter = iter.Next() {
|
||||
id, err := pub.ToId(iter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ASAcceptTo.AppendIRI(id)
|
||||
}
|
||||
ASAccept.SetActivityStreamsTo(ASAcceptTo)
|
||||
|
||||
_, err = p.federator.FederatingActor().Send(context.Background(), followAccepterOutboxIRI, ASAccept)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -38,24 +38,60 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
|
|||
|
||||
l.Debug("entering function PROCESS FROM FEDERATOR")
|
||||
|
||||
switch federatorMsg.APObjectType {
|
||||
case gtsmodel.ActivityStreamsNote:
|
||||
switch federatorMsg.APActivityType {
|
||||
case gtsmodel.ActivityStreamsCreate:
|
||||
// CREATE
|
||||
switch federatorMsg.APObjectType {
|
||||
case gtsmodel.ActivityStreamsNote:
|
||||
// CREATE A STATUS
|
||||
incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return errors.New("note was not parseable as *gtsmodel.Status")
|
||||
}
|
||||
|
||||
incomingStatus, ok := federatorMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return errors.New("note was not parseable as *gtsmodel.Status")
|
||||
}
|
||||
l.Debug("will now derefence incoming status")
|
||||
if err := p.dereferenceStatusFields(incomingStatus); err != nil {
|
||||
return fmt.Errorf("error dereferencing status from federator: %s", err)
|
||||
}
|
||||
if err := p.db.UpdateByID(incomingStatus.ID, incomingStatus); err != nil {
|
||||
return fmt.Errorf("error updating dereferenced status in the db: %s", err)
|
||||
}
|
||||
|
||||
l.Debug("will now derefence incoming status")
|
||||
if err := p.dereferenceStatusFields(incomingStatus); err != nil {
|
||||
return fmt.Errorf("error dereferencing status from federator: %s", err)
|
||||
}
|
||||
if err := p.db.UpdateByID(incomingStatus.ID, incomingStatus); err != nil {
|
||||
return fmt.Errorf("error updating dereferenced status in the db: %s", err)
|
||||
}
|
||||
if err := p.notifyStatus(incomingStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
case gtsmodel.ActivityStreamsProfile:
|
||||
// CREATE AN ACCOUNT
|
||||
incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return errors.New("profile was not parseable as *gtsmodel.Account")
|
||||
}
|
||||
|
||||
if err := p.notifyStatus(incomingStatus); err != nil {
|
||||
return err
|
||||
l.Debug("will now derefence incoming account")
|
||||
if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
|
||||
return fmt.Errorf("error dereferencing account from federator: %s", err)
|
||||
}
|
||||
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
|
||||
return fmt.Errorf("error updating dereferenced account in the db: %s", err)
|
||||
}
|
||||
}
|
||||
case gtsmodel.ActivityStreamsUpdate:
|
||||
// UPDATE
|
||||
switch federatorMsg.APObjectType {
|
||||
case gtsmodel.ActivityStreamsProfile:
|
||||
// UPDATE AN ACCOUNT
|
||||
incomingAccount, ok := federatorMsg.GTSModel.(*gtsmodel.Account)
|
||||
if !ok {
|
||||
return errors.New("profile was not parseable as *gtsmodel.Account")
|
||||
}
|
||||
|
||||
l.Debug("will now derefence incoming account")
|
||||
if err := p.dereferenceAccountFields(incomingAccount, ""); err != nil {
|
||||
return fmt.Errorf("error dereferencing account from federator: %s", err)
|
||||
}
|
||||
if err := p.db.UpdateByID(incomingAccount.ID, incomingAccount); err != nil {
|
||||
return fmt.Errorf("error updating dereferenced account in the db: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -206,3 +242,27 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *processor) dereferenceAccountFields(account *gtsmodel.Account, requestingUsername string) error {
|
||||
l := p.log.WithFields(logrus.Fields{
|
||||
"func": "dereferenceAccountFields",
|
||||
"requestingUsername": requestingUsername,
|
||||
})
|
||||
|
||||
t, err := p.federator.GetTransportForUser(requestingUsername)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting transport for user: %s", err)
|
||||
}
|
||||
|
||||
// fetch the header and avatar
|
||||
if err := p.fetchHeaderAndAviForAccount(account, t); err != nil {
|
||||
// if this doesn't work, just skip it -- we can do it later
|
||||
l.Debugf("error fetching header/avi for account: %s", err)
|
||||
}
|
||||
|
||||
if err := p.db.UpdateByID(account.ID, account); err != nil {
|
||||
return fmt.Errorf("error updating account in database: %s", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -48,11 +48,28 @@ func (p *processor) FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, Err
|
|||
return accts, nil
|
||||
}
|
||||
|
||||
func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode {
|
||||
if err := p.db.AcceptFollowRequest(accountID, auth.Account.ID); err != nil {
|
||||
return NewErrorNotFound(err)
|
||||
func (p *processor) FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode) {
|
||||
follow, err := p.db.AcceptFollowRequest(accountID, auth.Account.ID)
|
||||
if err != nil {
|
||||
return nil, NewErrorNotFound(err)
|
||||
}
|
||||
return nil
|
||||
|
||||
p.fromClientAPI <- gtsmodel.FromClientAPI{
|
||||
APActivityType: gtsmodel.ActivityStreamsAccept,
|
||||
GTSModel: follow,
|
||||
}
|
||||
|
||||
gtsR, err := p.db.GetRelationship(auth.Account.ID, accountID)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
r, err := p.tc.RelationshipToMasto(gtsR)
|
||||
if err != nil {
|
||||
return nil, NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (p *processor) FollowRequestDeny(auth *oauth.Auth) ErrorWithCode {
|
||||
|
|
|
@ -73,6 +73,8 @@ type Processor interface {
|
|||
AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode)
|
||||
// AccountFollowersGet
|
||||
AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
|
||||
// AccountRelationshipGet
|
||||
AccountRelationshipGet(authed *oauth.Auth, targetAccountID string) (*apimodel.Relationship, ErrorWithCode)
|
||||
|
||||
// AdminEmojiCreate handles the creation of a new instance emoji by an admin, using the given form.
|
||||
AdminEmojiCreate(authed *oauth.Auth, form *apimodel.EmojiCreateRequest) (*apimodel.Emoji, error)
|
||||
|
@ -86,7 +88,7 @@ type Processor interface {
|
|||
// FollowRequestsGet handles the getting of the authed account's incoming follow requests
|
||||
FollowRequestsGet(auth *oauth.Auth) ([]apimodel.Account, ErrorWithCode)
|
||||
// FollowRequestAccept handles the acceptance of a follow request from the given account ID
|
||||
FollowRequestAccept(auth *oauth.Auth, accountID string) ErrorWithCode
|
||||
FollowRequestAccept(auth *oauth.Auth, accountID string) (*apimodel.Relationship, ErrorWithCode)
|
||||
|
||||
// InstanceGet retrieves instance information for serving at api/v1/instance
|
||||
InstanceGet(domain string) (*apimodel.Instance, ErrorWithCode)
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/media"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/transport"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
)
|
||||
|
||||
|
@ -280,7 +281,7 @@ func (p *processor) updateAccountAvatar(avatar *multipart.FileHeader, accountID
|
|||
}
|
||||
|
||||
// do the setting
|
||||
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar)
|
||||
avatarInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Avatar, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error processing avatar: %s", err)
|
||||
}
|
||||
|
@ -313,10 +314,42 @@ func (p *processor) updateAccountHeader(header *multipart.FileHeader, accountID
|
|||
}
|
||||
|
||||
// do the setting
|
||||
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header)
|
||||
headerInfo, err := p.mediaHandler.ProcessHeaderOrAvatar(buf.Bytes(), accountID, media.Header, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error processing header: %s", err)
|
||||
}
|
||||
|
||||
return headerInfo, f.Close()
|
||||
}
|
||||
|
||||
// fetchHeaderAndAviForAccount fetches the header and avatar for a remote account, using a transport
|
||||
// on behalf of requestingUsername.
|
||||
//
|
||||
// targetAccount's AvatarMediaAttachmentID and HeaderMediaAttachmentID will be updated as necessary.
|
||||
//
|
||||
// SIDE EFFECTS: remote header and avatar will be stored in local storage, and the database will be updated
|
||||
// to reflect the creation of these new attachments.
|
||||
func (p *processor) fetchHeaderAndAviForAccount(targetAccount *gtsmodel.Account, t transport.Transport) error {
|
||||
if targetAccount.AvatarRemoteURL != "" && targetAccount.AvatarMediaAttachmentID == "" {
|
||||
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, >smodel.MediaAttachment{
|
||||
RemoteURL: targetAccount.AvatarRemoteURL,
|
||||
Avatar: true,
|
||||
}, targetAccount.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing avatar for user: %s", err)
|
||||
}
|
||||
targetAccount.AvatarMediaAttachmentID = a.ID
|
||||
}
|
||||
|
||||
if targetAccount.HeaderRemoteURL != "" && targetAccount.HeaderMediaAttachmentID == "" {
|
||||
a, err := p.mediaHandler.ProcessRemoteHeaderOrAvatar(t, >smodel.MediaAttachment{
|
||||
RemoteURL: targetAccount.HeaderRemoteURL,
|
||||
Header: true,
|
||||
}, targetAccount.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error processing header for user: %s", err)
|
||||
}
|
||||
targetAccount.HeaderMediaAttachmentID = a.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ type controller struct {
|
|||
clock pub.Clock
|
||||
client pub.HttpClient
|
||||
appAgent string
|
||||
log *logrus.Logger
|
||||
}
|
||||
|
||||
// NewController returns an implementation of the Controller interface for creating new transports
|
||||
|
@ -48,6 +49,7 @@ func NewController(config *config.Config, clock pub.Clock, client pub.HttpClient
|
|||
clock: clock,
|
||||
client: client,
|
||||
appAgent: fmt.Sprintf("%s %s", config.ApplicationName, config.Host),
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,5 +82,6 @@ func (c *controller) NewTransport(pubKeyID string, privkey crypto.PrivateKey) (T
|
|||
sigTransport: sigTransport,
|
||||
getSigner: getSigner,
|
||||
getSignerMu: &sync.Mutex{},
|
||||
log: c.log,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/go-fed/activity/pub"
|
||||
"github.com/go-fed/httpsig"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Transport wraps the pub.Transport interface with some additional
|
||||
|
@ -31,6 +32,7 @@ type transport struct {
|
|||
sigTransport *pub.HttpSigTransport
|
||||
getSigner httpsig.Signer
|
||||
getSignerMu *sync.Mutex
|
||||
log *logrus.Logger
|
||||
}
|
||||
|
||||
func (t *transport) BatchDeliver(c context.Context, b []byte, recipients []*url.URL) error {
|
||||
|
@ -38,14 +40,20 @@ func (t *transport) BatchDeliver(c context.Context, b []byte, recipients []*url.
|
|||
}
|
||||
|
||||
func (t *transport) Deliver(c context.Context, b []byte, to *url.URL) error {
|
||||
l := t.log.WithField("func", "Deliver")
|
||||
l.Debugf("performing POST to %s", to.String())
|
||||
return t.sigTransport.Deliver(c, b, to)
|
||||
}
|
||||
|
||||
func (t *transport) Dereference(c context.Context, iri *url.URL) ([]byte, error) {
|
||||
l := t.log.WithField("func", "Dereference")
|
||||
l.Debugf("performing GET to %s", iri.String())
|
||||
return t.sigTransport.Dereference(c, iri)
|
||||
}
|
||||
|
||||
func (t *transport) DereferenceMedia(c context.Context, iri *url.URL, expectedContentType string) ([]byte, error) {
|
||||
l := t.log.WithField("func", "DereferenceMedia")
|
||||
l.Debugf("performing GET to %s", iri.String())
|
||||
req, err := http.NewRequest("GET", iri.String(), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -77,6 +77,9 @@ type TypeConverter interface {
|
|||
// InstanceToMasto converts a gts instance into its mastodon equivalent for serving at /api/v1/instance
|
||||
InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, error)
|
||||
|
||||
// RelationshipToMasto converts a gts relationship into its mastodon equivalent for serving in various places
|
||||
RelationshipToMasto(r *gtsmodel.Relationship) (*model.Relationship, error)
|
||||
|
||||
/*
|
||||
FRONTEND (mastodon) MODEL TO INTERNAL (gts) MODEL
|
||||
*/
|
||||
|
|
|
@ -572,3 +572,21 @@ func (c *converter) InstanceToMasto(i *gtsmodel.Instance) (*model.Instance, erro
|
|||
|
||||
return mi, nil
|
||||
}
|
||||
|
||||
func (c *converter) RelationshipToMasto(r *gtsmodel.Relationship) (*model.Relationship, error) {
|
||||
return &model.Relationship{
|
||||
ID: r.ID,
|
||||
Following: r.Following,
|
||||
ShowingReblogs: r.ShowingReblogs,
|
||||
Notifying: r.Notifying,
|
||||
FollowedBy: r.FollowedBy,
|
||||
Blocking: r.Blocking,
|
||||
BlockedBy: r.BlockedBy,
|
||||
Muting: r.Muting,
|
||||
MutingNotifications: r.MutingNotifications,
|
||||
Requested: r.Requested,
|
||||
DomainBlocking: r.DomainBlocking,
|
||||
Endorsed: r.Endorsed,
|
||||
Note: r.Note,
|
||||
}, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue