mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-19 15:57:29 -06:00
refining status views etc
This commit is contained in:
parent
3868803ff1
commit
eb9de53776
14 changed files with 184 additions and 90 deletions
|
|
@ -34,6 +34,15 @@ import (
|
||||||
const (
|
const (
|
||||||
// LimitKey is for setting the return amount limit for eg., requesting an account's statuses
|
// LimitKey is for setting the return amount limit for eg., requesting an account's statuses
|
||||||
LimitKey = "limit"
|
LimitKey = "limit"
|
||||||
|
// ExcludeRepliesKey is for specifying whether to exclude replies in a list of returned statuses by an account.
|
||||||
|
ExcludeRepliesKey = "exclude_replies"
|
||||||
|
// PinnedKey is for specifying whether to include pinned statuses in a list of returned statuses by an account.
|
||||||
|
PinnedKey = "pinned"
|
||||||
|
// MaxIDKey is for specifying the maximum ID of the status to retrieve.
|
||||||
|
MaxIDKey = "max_id"
|
||||||
|
// MediaOnlyKey is for specifying that only statuses with media should be returned in a list of returned statuses by an account.
|
||||||
|
MediaOnlyKey = "only_media"
|
||||||
|
|
||||||
// IDKey is the key to use for retrieving account ID in requests
|
// IDKey is the key to use for retrieving account ID in requests
|
||||||
IDKey = "id"
|
IDKey = "id"
|
||||||
// BasePath is the base API path for this module
|
// BasePath is the base API path for this module
|
||||||
|
|
|
||||||
|
|
@ -27,15 +27,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AccountStatusesGETHandler serves the statuses of the requested account, if they're visible to the requester.
|
// AccountStatusesGETHandler serves the statuses of the requested account, if they're visible to the requester.
|
||||||
|
//
|
||||||
|
// Several different filters might be passed into this function in the query:
|
||||||
|
//
|
||||||
|
// limit -- show only limit number of statuses
|
||||||
|
// exclude_replies -- exclude statuses that are a reply to another status
|
||||||
|
// max_id -- the maximum ID of the status to show
|
||||||
|
// pinned -- show only pinned statuses
|
||||||
|
// media_only -- show only statuses that have media attachments
|
||||||
func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
|
l := m.log.WithField("func", "AccountStatusesGETHandler")
|
||||||
|
|
||||||
authed, err := oauth.Authed(c, false, false, false, false)
|
authed, err := oauth.Authed(c, false, false, false, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
l.Debugf("error authing: %s", err)
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
targetAcctID := c.Param(IDKey)
|
targetAcctID := c.Param(IDKey)
|
||||||
if targetAcctID == "" {
|
if targetAcctID == "" {
|
||||||
|
l.Debug("no account id specified in query")
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "no account id specified"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -43,16 +55,60 @@ func (m *Module) AccountStatusesGETHandler(c *gin.Context) {
|
||||||
limit := 30
|
limit := 30
|
||||||
limitString := c.Query(LimitKey)
|
limitString := c.Query(LimitKey)
|
||||||
if limitString != "" {
|
if limitString != "" {
|
||||||
l, err := strconv.ParseInt(limitString, 10, 64)
|
i, err := strconv.ParseInt(limitString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
l.Debugf("error parsing limit string: %s", err)
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse limit query param"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
limit = int(l)
|
limit = int(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses, errWithCode := m.processor.AccountStatusesGet(authed, targetAcctID, limit)
|
excludeReplies := false
|
||||||
|
excludeRepliesString := c.Query(ExcludeRepliesKey)
|
||||||
|
if excludeRepliesString != "" {
|
||||||
|
i, err := strconv.ParseBool(excludeRepliesString)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error parsing replies string: %s", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse exclude replies query param"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
excludeReplies = i
|
||||||
|
}
|
||||||
|
|
||||||
|
maxID := ""
|
||||||
|
maxIDString := c.Query(MaxIDKey)
|
||||||
|
if maxIDString != "" {
|
||||||
|
maxID = maxIDString
|
||||||
|
}
|
||||||
|
|
||||||
|
pinned := false
|
||||||
|
pinnedString := c.Query(PinnedKey)
|
||||||
|
if pinnedString != "" {
|
||||||
|
i, err := strconv.ParseBool(pinnedString)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error parsing pinned string: %s", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse pinned query param"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pinned = i
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaOnly := false
|
||||||
|
mediaOnlyString := c.Query(MediaOnlyKey)
|
||||||
|
if mediaOnlyString != "" {
|
||||||
|
i, err := strconv.ParseBool(mediaOnlyString)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error parsing media only string: %s", err)
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "couldn't parse media only query param"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mediaOnly = i
|
||||||
|
}
|
||||||
|
|
||||||
|
statuses, errWithCode := m.processor.AccountStatusesGet(authed, targetAcctID, limit, excludeReplies, maxID, pinned, mediaOnly)
|
||||||
if errWithCode != nil {
|
if errWithCode != nil {
|
||||||
|
l.Debugf("error from processor account statuses get: %s", errWithCode)
|
||||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ type Status struct {
|
||||||
// Have you bookmarked this status?
|
// Have you bookmarked this status?
|
||||||
Bookmarked bool `json:"bookmarked"`
|
Bookmarked bool `json:"bookmarked"`
|
||||||
// Have you pinned this status? Only appears if the status is pinnable.
|
// Have you pinned this status? Only appears if the status is pinnable.
|
||||||
Pinned bool `json:"pinned"`
|
Pinned bool `json:"pinned,omitempty"`
|
||||||
// HTML-encoded status content.
|
// HTML-encoded status content.
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
// The status being reblogged.
|
// The status being reblogged.
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func (m *Module) UsersGETHandler(c *gin.Context) {
|
||||||
|
|
||||||
// make a copy of the context to pass along so we don't break anything
|
// make a copy of the context to pass along so we don't break anything
|
||||||
cp := c.Copy()
|
cp := c.Copy()
|
||||||
user, err := m.processor.GetFediUser(requestedUsername, cp.Request) // GetAPUser handles auth as well
|
user, err := m.processor.GetFediUser(requestedUsername, cp.Request) // GetFediUser handles auth as well
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Info(err.Error())
|
l.Info(err.Error())
|
||||||
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
c.JSON(err.Code(), gin.H{"error": err.Safe()})
|
||||||
|
|
|
||||||
|
|
@ -160,16 +160,14 @@ type DB interface {
|
||||||
// In case of no entries, a 'no entries' error will be returned
|
// In case of no entries, a 'no entries' error will be returned
|
||||||
GetFavesByAccountID(accountID string, faves *[]gtsmodel.StatusFave) error
|
GetFavesByAccountID(accountID string, faves *[]gtsmodel.StatusFave) error
|
||||||
|
|
||||||
// GetStatusesByAccountID is a shortcut for the common action of fetching a list of statuses produced by accountID.
|
// CountStatusesByAccountID is a shortcut for the common action of counting statuses produced by accountID.
|
||||||
// The given slice 'statuses' will be set to the result of the query, whatever it is.
|
CountStatusesByAccountID(accountID string) (int, error)
|
||||||
// In case of no entries, a 'no entries' error will be returned
|
|
||||||
GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error
|
|
||||||
|
|
||||||
// GetStatusesByTimeDescending is a shortcut for getting the most recent statuses. accountID is optional, if not provided
|
// GetStatusesByTimeDescending is a shortcut for getting the most recent statuses. accountID is optional, if not provided
|
||||||
// then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can
|
// then all statuses will be returned. If limit is set to 0, the size of the returned slice will not be limited. This can
|
||||||
// be very memory intensive so you probably shouldn't do this!
|
// be very memory intensive so you probably shouldn't do this!
|
||||||
// In case of no entries, a 'no entries' error will be returned
|
// In case of no entries, a 'no entries' error will be returned
|
||||||
GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int) error
|
GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) error
|
||||||
|
|
||||||
// GetLastStatusForAccountID simply gets the most recent status by the given account.
|
// GetLastStatusForAccountID simply gets the most recent status by the given account.
|
||||||
// The given slice 'status' pointer will be set to the result of the query, whatever it is.
|
// The given slice 'status' pointer will be set to the result of the query, whatever it is.
|
||||||
|
|
@ -251,9 +249,6 @@ type DB interface {
|
||||||
// StatusBookmarkedBy checks if a given status has been bookmarked by a given account ID
|
// StatusBookmarkedBy checks if a given status has been bookmarked by a given account ID
|
||||||
StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error)
|
StatusBookmarkedBy(status *gtsmodel.Status, accountID string) (bool, error)
|
||||||
|
|
||||||
// StatusPinnedBy checks if a given status has been pinned by a given account ID
|
|
||||||
StatusPinnedBy(status *gtsmodel.Status, accountID string) (bool, error)
|
|
||||||
|
|
||||||
// FaveStatus faves the given status, using accountID as the faver.
|
// FaveStatus faves the given status, using accountID as the faver.
|
||||||
// The returned fave will be nil if the status was already faved.
|
// The returned fave will be nil if the status was already faved.
|
||||||
FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error)
|
FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error)
|
||||||
|
|
|
||||||
|
|
@ -456,23 +456,35 @@ func (ps *postgresService) GetFavesByAccountID(accountID string, faves *[]gtsmod
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *postgresService) GetStatusesByAccountID(accountID string, statuses *[]gtsmodel.Status) error {
|
func (ps *postgresService) CountStatusesByAccountID(accountID string) (int, error) {
|
||||||
if err := ps.conn.Model(statuses).Where("account_id = ?", accountID).Select(); err != nil {
|
count, err := ps.conn.Model(>smodel.Status{}).Where("account_id = ?", accountID).Count()
|
||||||
|
if err != nil {
|
||||||
if err == pg.ErrNoRows {
|
if err == pg.ErrNoRows {
|
||||||
return db.ErrNoEntries{}
|
return 0, nil
|
||||||
}
|
}
|
||||||
return err
|
return 0, err
|
||||||
}
|
}
|
||||||
return nil
|
return count, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int) error {
|
func (ps *postgresService) GetStatusesByTimeDescending(accountID string, statuses *[]gtsmodel.Status, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) error {
|
||||||
q := ps.conn.Model(statuses).Order("created_at DESC")
|
q := ps.conn.Model(statuses).Order("created_at DESC")
|
||||||
|
if accountID != "" {
|
||||||
|
q = q.Where("account_id = ?", accountID)
|
||||||
|
}
|
||||||
if limit != 0 {
|
if limit != 0 {
|
||||||
q = q.Limit(limit)
|
q = q.Limit(limit)
|
||||||
}
|
}
|
||||||
if accountID != "" {
|
if excludeReplies {
|
||||||
q = q.Where("account_id = ?", accountID)
|
q = q.Where("? IS NULL", pg.Ident("in_reply_to_id"))
|
||||||
|
}
|
||||||
|
if pinned {
|
||||||
|
q = q.Where("pinned = ?", true)
|
||||||
|
}
|
||||||
|
if mediaOnly {
|
||||||
|
q = q.WhereGroup(func(q *pg.Query) (*pg.Query, error) {
|
||||||
|
return q.Where("? IS NOT NULL", pg.Ident("attachments")).Where("attachments != '{}'"), nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if err := q.Select(); err != nil {
|
if err := q.Select(); err != nil {
|
||||||
if err == pg.ErrNoRows {
|
if err == pg.ErrNoRows {
|
||||||
|
|
@ -824,8 +836,8 @@ func (ps *postgresService) StatusVisible(targetStatus *gtsmodel.Status, targetAc
|
||||||
return true, nil
|
return true, nil
|
||||||
case gtsmodel.VisibilityDirect:
|
case gtsmodel.VisibilityDirect:
|
||||||
// make sure the requesting account is mentioned in the status
|
// make sure the requesting account is mentioned in the status
|
||||||
for _, menchie := range targetStatus.Mentions {
|
for _, acct := range relevantAccounts.MentionedAccounts {
|
||||||
if menchie == requestingAccount.ID {
|
if acct.ID == requestingAccount.ID {
|
||||||
return true, nil // yep it's mentioned!
|
return true, nil // yep it's mentioned!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -900,10 +912,16 @@ func (ps *postgresService) PullRelevantAccountsFromStatus(targetStatus *gtsmodel
|
||||||
}
|
}
|
||||||
|
|
||||||
// now get all accounts with IDs that are mentioned in the status
|
// now get all accounts with IDs that are mentioned in the status
|
||||||
for _, mentionedAccountID := range targetStatus.Mentions {
|
for _, mentionID := range targetStatus.Mentions {
|
||||||
|
|
||||||
|
mention := >smodel.Mention{}
|
||||||
|
if err := ps.conn.Model(mention).Where("id = ?", mentionID).Select(); err != nil {
|
||||||
|
return accounts, fmt.Errorf("error getting mention with id %s: %s", mentionID, err)
|
||||||
|
}
|
||||||
|
|
||||||
mentionedAccount := >smodel.Account{}
|
mentionedAccount := >smodel.Account{}
|
||||||
if err := ps.conn.Model(mentionedAccount).Where("id = ?", mentionedAccountID).Select(); err != nil {
|
if err := ps.conn.Model(mentionedAccount).Where("id = ?", mention.TargetAccountID).Select(); err != nil {
|
||||||
return accounts, err
|
return accounts, fmt.Errorf("error getting mentioned account: %s", err)
|
||||||
}
|
}
|
||||||
accounts.MentionedAccounts = append(accounts.MentionedAccounts, mentionedAccount)
|
accounts.MentionedAccounts = append(accounts.MentionedAccounts, mentionedAccount)
|
||||||
}
|
}
|
||||||
|
|
@ -939,10 +957,6 @@ func (ps *postgresService) StatusBookmarkedBy(status *gtsmodel.Status, accountID
|
||||||
return ps.conn.Model(>smodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
|
return ps.conn.Model(>smodel.StatusBookmark{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ps *postgresService) StatusPinnedBy(status *gtsmodel.Status, accountID string) (bool, error) {
|
|
||||||
return ps.conn.Model(>smodel.StatusPin{}).Where("status_id = ?", status.ID).Where("account_id = ?", accountID).Exists()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *postgresService) FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) {
|
func (ps *postgresService) FaveStatus(status *gtsmodel.Status, accountID string) (*gtsmodel.StatusFave, error) {
|
||||||
// first check if a fave already exists, we can just return if so
|
// first check if a fave already exists, we can just return if so
|
||||||
existingFave := >smodel.StatusFave{}
|
existingFave := >smodel.StatusFave{}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,6 @@ var models []interface{} = []interface{}{
|
||||||
>smodel.StatusFave{},
|
>smodel.StatusFave{},
|
||||||
>smodel.StatusBookmark{},
|
>smodel.StatusBookmark{},
|
||||||
>smodel.StatusMute{},
|
>smodel.StatusMute{},
|
||||||
>smodel.StatusPin{},
|
|
||||||
>smodel.Tag{},
|
>smodel.Tag{},
|
||||||
>smodel.User{},
|
>smodel.User{},
|
||||||
>smodel.Emoji{},
|
>smodel.Emoji{},
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ type Status struct {
|
||||||
Attachments []string `pg:",array"`
|
Attachments []string `pg:",array"`
|
||||||
// Database IDs of any tags used in this status
|
// Database IDs of any tags used in this status
|
||||||
Tags []string `pg:",array"`
|
Tags []string `pg:",array"`
|
||||||
// Database IDs of any accounts mentioned in this status
|
// Database IDs of any mentions in this status
|
||||||
Mentions []string `pg:",array"`
|
Mentions []string `pg:",array"`
|
||||||
// Database IDs of any emojis used in this status
|
// Database IDs of any emojis used in this status
|
||||||
Emojis []string `pg:",array"`
|
Emojis []string `pg:",array"`
|
||||||
|
|
@ -69,6 +69,8 @@ type Status struct {
|
||||||
ActivityStreamsType ActivityStreamsObject
|
ActivityStreamsType ActivityStreamsObject
|
||||||
// Original text of the status without formatting
|
// Original text of the status without formatting
|
||||||
Text string
|
Text string
|
||||||
|
// Has this status been pinned by its owner?
|
||||||
|
Pinned bool
|
||||||
|
|
||||||
/*
|
/*
|
||||||
INTERNAL MODEL NON-DATABASE FIELDS
|
INTERNAL MODEL NON-DATABASE FIELDS
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
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
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// StatusPin refers to a status 'pinned' to the top of an account
|
|
||||||
type StatusPin struct {
|
|
||||||
// id of this pin in the database
|
|
||||||
ID string `pg:"type:uuid,default:gen_random_uuid(),pk,notnull,unique"`
|
|
||||||
// when was this pin created
|
|
||||||
CreatedAt time.Time `pg:"type:timestamp,notnull,default:now()"`
|
|
||||||
// id of the account that created ('did') the pinning (this should always be the same as the author of the status)
|
|
||||||
AccountID string `pg:",notnull"`
|
|
||||||
// database id of the status that has been pinned
|
|
||||||
StatusID string `pg:",notnull"`
|
|
||||||
}
|
|
||||||
|
|
@ -185,7 +185,7 @@ func (p *processor) AccountUpdate(authed *oauth.Auth, form *apimodel.UpdateCrede
|
||||||
return acctSensitive, nil
|
return acctSensitive, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int) ([]apimodel.Status, ErrorWithCode) {
|
func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode) {
|
||||||
targetAccount := >smodel.Account{}
|
targetAccount := >smodel.Account{}
|
||||||
if err := p.db.GetByID(targetAccountID, targetAccount); err != nil {
|
if err := p.db.GetByID(targetAccountID, targetAccount); err != nil {
|
||||||
if _, ok := err.(db.ErrNoEntries); ok {
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
|
|
@ -196,7 +196,7 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin
|
||||||
|
|
||||||
statuses := []gtsmodel.Status{}
|
statuses := []gtsmodel.Status{}
|
||||||
apiStatuses := []apimodel.Status{}
|
apiStatuses := []apimodel.Status{}
|
||||||
if err := p.db.GetStatusesByTimeDescending(targetAccountID, &statuses, limit); err != nil {
|
if err := p.db.GetStatusesByTimeDescending(targetAccountID, &statuses, limit, excludeReplies, maxID, pinned, mediaOnly); err != nil {
|
||||||
if _, ok := err.(db.ErrNoEntries); ok {
|
if _, ok := err.(db.ErrNoEntries); ok {
|
||||||
return apiStatuses, nil
|
return apiStatuses, nil
|
||||||
}
|
}
|
||||||
|
|
@ -206,12 +206,12 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin
|
||||||
for _, s := range statuses {
|
for _, s := range statuses {
|
||||||
relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(&s)
|
relevantAccounts, err := p.db.PullRelevantAccountsFromStatus(&s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewErrorInternalError(err)
|
return nil, NewErrorInternalError(fmt.Errorf("error getting relevant statuses: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
visible, err := p.db.StatusVisible(&s, targetAccount, authed.Account, relevantAccounts)
|
visible, err := p.db.StatusVisible(&s, targetAccount, authed.Account, relevantAccounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewErrorInternalError(err)
|
return nil, NewErrorInternalError(fmt.Errorf("error checking status visibility: %s", err))
|
||||||
}
|
}
|
||||||
if !visible {
|
if !visible {
|
||||||
continue
|
continue
|
||||||
|
|
@ -221,16 +221,16 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin
|
||||||
if s.BoostOfID != "" {
|
if s.BoostOfID != "" {
|
||||||
bs := >smodel.Status{}
|
bs := >smodel.Status{}
|
||||||
if err := p.db.GetByID(s.BoostOfID, bs); err != nil {
|
if err := p.db.GetByID(s.BoostOfID, bs); err != nil {
|
||||||
return nil, NewErrorInternalError(err)
|
return nil, NewErrorInternalError(fmt.Errorf("error getting boosted status: %s", err))
|
||||||
}
|
}
|
||||||
boostedRelevantAccounts, err := p.db.PullRelevantAccountsFromStatus(bs)
|
boostedRelevantAccounts, err := p.db.PullRelevantAccountsFromStatus(bs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewErrorInternalError(err)
|
return nil, NewErrorInternalError(fmt.Errorf("error getting relevant accounts from boosted status: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
boostedVisible, err := p.db.StatusVisible(bs, relevantAccounts.BoostedAccount, authed.Account, boostedRelevantAccounts)
|
boostedVisible, err := p.db.StatusVisible(bs, relevantAccounts.BoostedAccount, authed.Account, boostedRelevantAccounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewErrorInternalError(err)
|
return nil, NewErrorInternalError(fmt.Errorf("error checking boosted status visibility: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if boostedVisible {
|
if boostedVisible {
|
||||||
|
|
@ -240,7 +240,7 @@ func (p *processor) AccountStatusesGet(authed *oauth.Auth, targetAccountID strin
|
||||||
|
|
||||||
apiStatus, err := p.tc.StatusToMasto(&s, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostedStatus)
|
apiStatus, err := p.tc.StatusToMasto(&s, targetAccount, authed.Account, relevantAccounts.BoostedAccount, relevantAccounts.ReplyToAccount, boostedStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewErrorInternalError(err)
|
return nil, NewErrorInternalError(fmt.Errorf("error converting status to masto: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
apiStatuses = append(apiStatuses, *apiStatus)
|
apiStatuses = append(apiStatuses, *apiStatus)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,9 @@ package message
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||||
|
|
@ -85,7 +87,7 @@ func (p *processor) processFromFederator(federatorMsg gtsmodel.FromFederator) er
|
||||||
// that's up the caller to do.
|
// that's up the caller to do.
|
||||||
func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
||||||
l := p.log.WithFields(logrus.Fields{
|
l := p.log.WithFields(logrus.Fields{
|
||||||
"func": "dereferenceStatusFields",
|
"func": "dereferenceStatusFields",
|
||||||
"status": fmt.Sprintf("%+v", status),
|
"status": fmt.Sprintf("%+v", status),
|
||||||
})
|
})
|
||||||
l.Debug("entering function")
|
l.Debug("entering function")
|
||||||
|
|
@ -99,6 +101,12 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
||||||
return fmt.Errorf("error creating transport: %s", err)
|
return fmt.Errorf("error creating transport: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// the status should have an ID by now, but just in case it doesn't let's generate one here
|
||||||
|
// because we'll need it further down
|
||||||
|
if status.ID == "" {
|
||||||
|
status.ID = uuid.NewString()
|
||||||
|
}
|
||||||
|
|
||||||
// 1. Media attachments.
|
// 1. Media attachments.
|
||||||
//
|
//
|
||||||
// At this point we should know:
|
// At this point we should know:
|
||||||
|
|
@ -132,7 +140,7 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
l.Debugf("dereferenced attachment: %+v", deferencedAttachment)
|
l.Debugf("dereferenced attachment: %+v", deferencedAttachment)
|
||||||
deferencedAttachment.StatusID = deferencedAttachment.ID
|
deferencedAttachment.StatusID = status.ID
|
||||||
if err := p.db.Put(deferencedAttachment); err != nil {
|
if err := p.db.Put(deferencedAttachment); err != nil {
|
||||||
return fmt.Errorf("error inserting dereferenced attachment with remote url %s: %s", a.RemoteURL, err)
|
return fmt.Errorf("error inserting dereferenced attachment with remote url %s: %s", a.RemoteURL, err)
|
||||||
}
|
}
|
||||||
|
|
@ -141,5 +149,60 @@ func (p *processor) dereferenceStatusFields(status *gtsmodel.Status) error {
|
||||||
}
|
}
|
||||||
status.Attachments = attachmentIDs
|
status.Attachments = attachmentIDs
|
||||||
|
|
||||||
|
// 2. Hashtags
|
||||||
|
|
||||||
|
// 3. Emojis
|
||||||
|
|
||||||
|
// 4. Mentions
|
||||||
|
// At this point, mentions should have the namestring and mentionedAccountURI set on them.
|
||||||
|
//
|
||||||
|
// We should dereference any accounts mentioned here which we don't have in our db yet, by their URI.
|
||||||
|
mentions := []string{}
|
||||||
|
for _, m := range status.GTSMentions {
|
||||||
|
uri, err := url.Parse(m.MentionedAccountURI)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error parsing mentioned account uri %s: %s", m.MentionedAccountURI, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
m.StatusID = status.ID
|
||||||
|
m.OriginAccountID = status.GTSAccount.ID
|
||||||
|
m.OriginAccountURI = status.GTSAccount.URI
|
||||||
|
|
||||||
|
targetAccount := >smodel.Account{}
|
||||||
|
if err := p.db.GetWhere("uri", uri.String(), targetAccount); err != nil {
|
||||||
|
// proper error
|
||||||
|
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||||
|
return fmt.Errorf("db error checking for account with uri %s", uri.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// we just don't have it yet, so we should go get it....
|
||||||
|
accountable, err := p.federator.DereferenceRemoteAccount(username, uri)
|
||||||
|
if err != nil {
|
||||||
|
// we can't dereference it so just skip it
|
||||||
|
l.Debugf("error dereferencing remote account with uri %s: %s", uri.String(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
targetAccount, err = p.tc.ASRepresentationToAccount(accountable)
|
||||||
|
if err != nil {
|
||||||
|
l.Debugf("error converting remote account with uri %s into gts model: %s", uri.String(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.db.Put(targetAccount); err != nil {
|
||||||
|
return fmt.Errorf("db error inserting account with uri %s", uri.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// by this point, we know the targetAccount exists in our database with an ID :)
|
||||||
|
m.TargetAccountID = targetAccount.ID
|
||||||
|
if err := p.db.Put(m); err != nil {
|
||||||
|
return fmt.Errorf("error creating mention: %s", err)
|
||||||
|
}
|
||||||
|
mentions = append(mentions, m.ID)
|
||||||
|
}
|
||||||
|
status.Mentions = mentions
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ type Processor interface {
|
||||||
AccountUpdate(authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error)
|
AccountUpdate(authed *oauth.Auth, form *apimodel.UpdateCredentialsRequest) (*apimodel.Account, error)
|
||||||
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
|
// AccountStatusesGet fetches a number of statuses (in time descending order) from the given account, filtered by visibility for
|
||||||
// the account given in authed.
|
// the account given in authed.
|
||||||
AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int) ([]apimodel.Status, ErrorWithCode)
|
AccountStatusesGet(authed *oauth.Auth, targetAccountID string, limit int, excludeReplies bool, maxID string, pinned bool, mediaOnly bool) ([]apimodel.Status, ErrorWithCode)
|
||||||
// AccountFollowersGet
|
// AccountFollowersGet
|
||||||
AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
|
AccountFollowersGet(authed *oauth.Auth, targetAccountID string) ([]apimodel.Account, ErrorWithCode)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,16 +87,12 @@ func (c *converter) AccountToMastoPublic(a *gtsmodel.Account) (*model.Account, e
|
||||||
}
|
}
|
||||||
|
|
||||||
// count statuses
|
// count statuses
|
||||||
statuses := []gtsmodel.Status{}
|
statusesCount, err := c.db.CountStatusesByAccountID(a.ID)
|
||||||
if err := c.db.GetStatusesByAccountID(a.ID, &statuses); err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||||
return nil, fmt.Errorf("error getting last statuses: %s", err)
|
return nil, fmt.Errorf("error getting last statuses: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var statusesCount int
|
|
||||||
if statuses != nil {
|
|
||||||
statusesCount = len(statuses)
|
|
||||||
}
|
|
||||||
|
|
||||||
// check when the last status was
|
// check when the last status was
|
||||||
lastStatus := >smodel.Status{}
|
lastStatus := >smodel.Status{}
|
||||||
|
|
@ -295,7 +291,6 @@ func (c *converter) StatusToMasto(
|
||||||
var faved bool
|
var faved bool
|
||||||
var reblogged bool
|
var reblogged bool
|
||||||
var bookmarked bool
|
var bookmarked bool
|
||||||
var pinned bool
|
|
||||||
var muted bool
|
var muted bool
|
||||||
|
|
||||||
// requestingAccount will be nil for public requests without auth
|
// requestingAccount will be nil for public requests without auth
|
||||||
|
|
@ -320,11 +315,6 @@ func (c *converter) StatusToMasto(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err)
|
return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pinned, err = c.db.StatusPinnedBy(s, requestingAccount.ID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error checking if requesting account has pinned status: %s", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var mastoRebloggedStatus *model.Status
|
var mastoRebloggedStatus *model.Status
|
||||||
|
|
@ -523,7 +513,7 @@ func (c *converter) StatusToMasto(
|
||||||
Reblogged: reblogged,
|
Reblogged: reblogged,
|
||||||
Muted: muted,
|
Muted: muted,
|
||||||
Bookmarked: bookmarked,
|
Bookmarked: bookmarked,
|
||||||
Pinned: pinned,
|
Pinned: s.Pinned,
|
||||||
Content: s.Content,
|
Content: s.Content,
|
||||||
Reblog: mastoRebloggedStatus,
|
Reblog: mastoRebloggedStatus,
|
||||||
Application: mastoApplication,
|
Application: mastoApplication,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,6 @@ var testModels []interface{} = []interface{}{
|
||||||
>smodel.StatusFave{},
|
>smodel.StatusFave{},
|
||||||
>smodel.StatusBookmark{},
|
>smodel.StatusBookmark{},
|
||||||
>smodel.StatusMute{},
|
>smodel.StatusMute{},
|
||||||
>smodel.StatusPin{},
|
|
||||||
>smodel.Tag{},
|
>smodel.Tag{},
|
||||||
>smodel.User{},
|
>smodel.User{},
|
||||||
>smodel.Emoji{},
|
>smodel.Emoji{},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue