deletes, unboosts, docs updates
This commit is contained in:
parent
efbd839181
commit
ef3d38240b
|
@ -78,7 +78,7 @@
|
|||
* [x] /api/v1/statuses/:id/favourite POST (Fave a status)
|
||||
* [x] /api/v1/statuses/:id/unfavourite POST (Unfave a status)
|
||||
* [x] /api/v1/statuses/:id/reblog POST (Reblog a status)
|
||||
* [ ] /api/v1/statuses/:id/unreblog POST (Undo a reblog)
|
||||
* [x] /api/v1/statuses/:id/unreblog POST (Undo a reblog)
|
||||
* [ ] /api/v1/statuses/:id/bookmark POST (Bookmark a status)
|
||||
* [ ] /api/v1/statuses/:id/unbookmark POST (Undo a bookmark)
|
||||
* [ ] /api/v1/statuses/:id/mute POST (Mute notifications on a status)
|
||||
|
|
53
README.md
53
README.md
|
@ -8,31 +8,46 @@ Federated social media software.
|
|||
|
||||
GoToSocial is a Fediverse server project, written in Golang. It provides an alternative to existing projects such as [Mastodon](https://joinmastodon.org/), [Pleroma](https://pleroma.social/), [Friendica](https://friendica.net), [PixelFed](https://pixelfed.org/) etc.
|
||||
|
||||
One of the key differences between GoToSocial and those other projects is that GoToSocial doesn't include an integrated front-end (ie., a webapp). Instead, like the Matrix.org's [Synapse](https://github.com/matrix-org/synapse) project, it provides only a server implementation and a well-documented API. On this API, developers are free to build any front-end implementation or mobile application that they wish.
|
||||
One of the key differences between GoToSocial and those other projects is that GoToSocial doesn't include an integrated client front-end (ie., a webapp). Instead, like the Matrix.org's [Synapse](https://github.com/matrix-org/synapse) project, it provides only a server implementation, some static web pages for profiles and posts, and a well-documented API. On this API, developers are free to build any front-end implementation or mobile application that they wish.
|
||||
|
||||
Because the server implementation is as generic and flexible/configurable as possible, GoToSocial provides the basis for many different types of social media experience, whether Tumblr-like, Facebook-like, or Twitter-like.
|
||||
|
||||
## Goals
|
||||
## Features Wishlist
|
||||
|
||||
The first goal of the project is to implement a feature set comparable to Mastodon: server logic, federation logic, and a client API that's a superset of the Mastodon API described [here](https://docs.joinmastodon.org/).
|
||||
A grab-bag of things that are already included or will be included in the project if time allows:
|
||||
|
||||
Once the client API is implemented, it should allow existing Mastodon apps like [Tusky](https://tusky.app/) and [Whalebird](https://whalebird.social/en/desktop/contents) to work with GoToSocial.
|
||||
|
||||
After that, custom features will be added that will necessitate expanding the API.
|
||||
|
||||
## Wishlist
|
||||
|
||||
Among other things:
|
||||
|
||||
* Reputation-based 'slow' federation.
|
||||
* Granular post settings.
|
||||
* Local-only posting.
|
||||
* Easily-configurable character limit.
|
||||
* Groups and group posting.
|
||||
* Various federation modes, including reputation-based 'slow' federation, 'normal' federation, and zero federation.
|
||||
* Local-only posting, and granular post settings including 'rebloggable/boostable', 'likeable', 'replyable'.
|
||||
* Character limit for posts that's easy for admins to configure (no messing around in the source code).
|
||||
* Groups and group posting!
|
||||
* Built-in, automatic LetsEncrypt support (no messing around with Nginx or Certbot).
|
||||
* Good performance on lower-powered machines like Raspberry Pi, old laptops, tiny VPSes (the test VPS has 1gb of ram and 1 cpu core).
|
||||
|
||||
## Implementation Status
|
||||
|
||||
For an up-to-date view on progress made towards a v1.0.0 release, see [here](./PROGRESS.md).
|
||||
Things are moving on the project! As of June 2021 you can now:
|
||||
|
||||
* Build and deploy GoToSocial as a binary, with automatic LetsEncrypt certificate support built-in.
|
||||
* Connect to the running instance via Tusky or Pinafore, using email address and password (stored encrypted).
|
||||
* Post/delete posts.
|
||||
* Reply/delete replies.
|
||||
* Fave/unfave posts.
|
||||
* Post images and gifs.
|
||||
* Boost stuff/unboost stuff.
|
||||
* Set your profile info (including header and avatar).
|
||||
* Follow people/unfollow people.
|
||||
* Accept follow requests from people.
|
||||
* Post followers only/direct/public/unlocked.
|
||||
* Customize posts with further flags: federated (y/n), replyable (y/n), likeable (y/n), boostable (y/n) -- not supported through Pinafore/Tusky yet.
|
||||
* Get notifications for mentions/replies/likes/boosts.
|
||||
* View local timeline.
|
||||
* View and scroll home timeline (with ~10ms latency hell yeah).
|
||||
* Stream new posts, notifications and deletes through a websockets connection via Pinafore.
|
||||
* Federation support and interoperability with Mastodon and others.
|
||||
|
||||
In other words, a deployed GoToSocial instance is already pretty useable!
|
||||
|
||||
For a detailed view on progress made towards a v0.1.0 (beta) release, see [here](./PROGRESS.md).
|
||||
|
||||
## Contact
|
||||
|
||||
|
@ -44,7 +59,9 @@ Currently, this project is funded using Liberapay, to put bread on the table whi
|
|||
|
||||
### Sponsors
|
||||
|
||||
None yet! [Go For It](https://liberapay.com/dumpsterqueer/)
|
||||
Only anonymous donations so far!
|
||||
|
||||
[Donate](https://liberapay.com/dumpsterqueer/)
|
||||
|
||||
### Image Attribution
|
||||
|
||||
|
|
|
@ -98,6 +98,7 @@ func (m *Module) Route(r router.Router) error {
|
|||
r.AttachHandler(http.MethodGet, FavouritedPath, m.StatusFavedByGETHandler)
|
||||
|
||||
r.AttachHandler(http.MethodPost, ReblogPath, m.StatusBoostPOSTHandler)
|
||||
r.AttachHandler(http.MethodPost, UnreblogPath, m.StatusUnboostPOSTHandler)
|
||||
r.AttachHandler(http.MethodGet, RebloggedPath, m.StatusBoostedByGETHandler)
|
||||
|
||||
r.AttachHandler(http.MethodGet, ContextPath, m.StatusContextGETHandler)
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
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 status
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth"
|
||||
)
|
||||
|
||||
// StatusUnboostPOSTHandler handles unboost requests against a given status ID
|
||||
func (m *Module) StatusUnboostPOSTHandler(c *gin.Context) {
|
||||
l := m.log.WithFields(logrus.Fields{
|
||||
"func": "StatusUnboostPOSTHandler",
|
||||
"request_uri": c.Request.RequestURI,
|
||||
"user_agent": c.Request.UserAgent(),
|
||||
"origin_ip": c.ClientIP(),
|
||||
})
|
||||
l.Debugf("entering function")
|
||||
|
||||
authed, err := oauth.Authed(c, true, false, true, true) // we don't really need an app here but we want everything else
|
||||
if err != nil {
|
||||
l.Debug("not authed so can't unboost status")
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "not authorized"})
|
||||
return
|
||||
}
|
||||
|
||||
targetStatusID := c.Param(IDKey)
|
||||
if targetStatusID == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no status id provided"})
|
||||
return
|
||||
}
|
||||
|
||||
mastoStatus, errWithCode := m.processor.StatusUnboost(authed, targetStatusID)
|
||||
if errWithCode != nil {
|
||||
l.Debugf("error processing status unboost: %s", errWithCode.Error())
|
||||
c.JSON(errWithCode.Code(), gin.H{"error": errWithCode.Safe()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, mastoStatus)
|
||||
}
|
|
@ -138,6 +138,18 @@ func (p *processor) processFromClientAPI(clientMsg gtsmodel.FromClientAPI) error
|
|||
return errors.New("undo was not parseable as *gtsmodel.StatusFave")
|
||||
}
|
||||
return p.federateUnfave(fave, clientMsg.OriginAccount, clientMsg.TargetAccount)
|
||||
case gtsmodel.ActivityStreamsAnnounce:
|
||||
// UNDO ANNOUNCE/BOOST
|
||||
boost, ok := clientMsg.GTSModel.(*gtsmodel.Status)
|
||||
if !ok {
|
||||
return errors.New("undo was not parseable as *gtsmodel.Status")
|
||||
}
|
||||
|
||||
if err := p.deleteStatusFromTimelines(boost); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.federateUnannounce(boost, clientMsg.OriginAccount, clientMsg.TargetAccount)
|
||||
}
|
||||
case gtsmodel.ActivityStreamsDelete:
|
||||
// DELETE
|
||||
|
@ -313,6 +325,36 @@ func (p *processor) federateUnfave(fave *gtsmodel.StatusFave, originAccount *gts
|
|||
return err
|
||||
}
|
||||
|
||||
func (p *processor) federateUnannounce(boost *gtsmodel.Status, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error {
|
||||
asAnnounce, err := p.tc.BoostToAS(boost, originAccount, targetAccount)
|
||||
if err != nil {
|
||||
return fmt.Errorf("federateUnannounce: error converting status to announce: %s", err)
|
||||
}
|
||||
|
||||
// create an Undo and set the appropriate actor on it
|
||||
undo := streams.NewActivityStreamsUndo()
|
||||
undo.SetActivityStreamsActor(asAnnounce.GetActivityStreamsActor())
|
||||
|
||||
// Set the boost as the 'object' property.
|
||||
undoObject := streams.NewActivityStreamsObjectProperty()
|
||||
undoObject.AppendActivityStreamsAnnounce(asAnnounce)
|
||||
undo.SetActivityStreamsObject(undoObject)
|
||||
|
||||
// set the to
|
||||
undo.SetActivityStreamsTo(asAnnounce.GetActivityStreamsTo())
|
||||
|
||||
// set the cc
|
||||
undo.SetActivityStreamsCc(asAnnounce.GetActivityStreamsCc())
|
||||
|
||||
outboxIRI, err := url.Parse(originAccount.OutboxURI)
|
||||
if err != nil {
|
||||
return fmt.Errorf("federateUnannounce: error parsing outboxURI %s: %s", originAccount.OutboxURI, err)
|
||||
}
|
||||
|
||||
_, err = p.federator.FederatingActor().Send(context.Background(), outboxIRI, undo)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *processor) federateAcceptFollowRequest(follow *gtsmodel.Follow, originAccount *gtsmodel.Account, targetAccount *gtsmodel.Account) error {
|
||||
// if both accounts are local there's nothing to do here
|
||||
if originAccount.Domain == "" && targetAccount.Domain == "" {
|
||||
|
|
|
@ -401,5 +401,9 @@ func (p *processor) timelineStatusForAccount(status *gtsmodel.Status, accountID
|
|||
}
|
||||
|
||||
func (p *processor) deleteStatusFromTimelines(status *gtsmodel.Status) error {
|
||||
return p.timelineManager.WipeStatusFromAllTimelines(status.ID)
|
||||
if err := p.timelineManager.WipeStatusFromAllTimelines(status.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.streamingProcessor.StreamDelete(status.ID)
|
||||
}
|
||||
|
|
|
@ -117,6 +117,8 @@ type Processor interface {
|
|||
StatusFave(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, error)
|
||||
// StatusBoost processes the boost/reblog of a given status, returning the newly-created boost if all is well.
|
||||
StatusBoost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
|
||||
// StatusUnboost processes the unboost/unreblog of a given status, returning the status if all is well.
|
||||
StatusUnboost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
|
||||
// StatusBoostedBy returns a slice of accounts that have boosted the given status, filtered according to privacy settings.
|
||||
StatusBoostedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode)
|
||||
// StatusFavedBy returns a slice of accounts that have liked the given status, filtered according to privacy settings.
|
||||
|
|
|
@ -40,6 +40,10 @@ func (p *processor) StatusBoost(authed *oauth.Auth, targetStatusID string) (*api
|
|||
return p.statusProcessor.Boost(authed.Account, authed.Application, targetStatusID)
|
||||
}
|
||||
|
||||
func (p *processor) StatusUnboost(authed *oauth.Auth, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
|
||||
return p.statusProcessor.Unboost(authed.Account, authed.Application, targetStatusID)
|
||||
}
|
||||
|
||||
func (p *processor) StatusBoostedBy(authed *oauth.Auth, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode) {
|
||||
return p.statusProcessor.BoostedBy(authed.Account, targetStatusID)
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ type Processor interface {
|
|||
Fave(account *gtsmodel.Account, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
|
||||
// Boost processes the boost/reblog of a given status, returning the newly-created boost if all is well.
|
||||
Boost(account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
|
||||
// Unboost processes the unboost/unreblog of a given status, returning the status if all is well.
|
||||
Unboost(account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode)
|
||||
// BoostedBy returns a slice of accounts that have boosted the given status, filtered according to privacy settings.
|
||||
BoostedBy(account *gtsmodel.Account, targetStatusID string) ([]*apimodel.Account, gtserror.WithCode)
|
||||
// FavedBy returns a slice of accounts that have liked the given status, filtered according to privacy settings.
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
package status
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (p *processor) Unboost(account *gtsmodel.Account, application *gtsmodel.Application, targetStatusID string) (*apimodel.Status, gtserror.WithCode) {
|
||||
l := p.log.WithField("func", "Unboost")
|
||||
|
||||
l.Tracef("going to search for target status %s", targetStatusID)
|
||||
targetStatus := >smodel.Status{}
|
||||
if err := p.db.GetByID(targetStatusID, targetStatus); err != nil {
|
||||
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching status %s: %s", targetStatusID, err))
|
||||
}
|
||||
|
||||
l.Tracef("going to search for target account %s", targetStatus.AccountID)
|
||||
targetAccount := >smodel.Account{}
|
||||
if err := p.db.GetByID(targetStatus.AccountID, targetAccount); err != nil {
|
||||
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error fetching target account %s: %s", targetStatus.AccountID, err))
|
||||
}
|
||||
|
||||
l.Trace("going to see if status is visible")
|
||||
visible, err := p.filter.StatusVisible(targetStatus, account)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorNotFound(fmt.Errorf("error seeing if status %s is visible: %s", targetStatus.ID, err))
|
||||
}
|
||||
|
||||
if !visible {
|
||||
return nil, gtserror.NewErrorNotFound(errors.New("status is not visible"))
|
||||
}
|
||||
|
||||
// check if we actually have a boost for this status
|
||||
var toUnboost bool
|
||||
|
||||
gtsBoost := >smodel.Status{}
|
||||
where := []db.Where{
|
||||
{
|
||||
Key: "boost_of_id",
|
||||
Value: targetStatusID,
|
||||
},
|
||||
{
|
||||
Key: "account_id",
|
||||
Value: account.ID,
|
||||
},
|
||||
}
|
||||
err = p.db.GetWhere(where, gtsBoost)
|
||||
if err == nil {
|
||||
// we have a boost
|
||||
toUnboost = true
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// something went wrong in the db finding the boost
|
||||
if _, ok := err.(db.ErrNoEntries); !ok {
|
||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error fetching existing boost from database: %s", err))
|
||||
}
|
||||
// we just don't have a boost
|
||||
toUnboost = false
|
||||
}
|
||||
|
||||
if toUnboost {
|
||||
// we had a boost, so take some action to get rid of it
|
||||
if err := p.db.DeleteWhere(where, >smodel.Status{}); err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error unboosting status: %s", err))
|
||||
}
|
||||
|
||||
// pin some stuff onto the boost while we have it out of the db
|
||||
gtsBoost.GTSBoostedStatus = targetStatus
|
||||
gtsBoost.GTSBoostedStatus.GTSAuthorAccount = targetAccount
|
||||
gtsBoost.GTSBoostedAccount = targetAccount
|
||||
gtsBoost.GTSAuthorAccount = account
|
||||
|
||||
// send it back to the processor for async processing
|
||||
p.fromClientAPI <- gtsmodel.FromClientAPI{
|
||||
APObjectType: gtsmodel.ActivityStreamsAnnounce,
|
||||
APActivityType: gtsmodel.ActivityStreamsUndo,
|
||||
GTSModel: gtsBoost,
|
||||
OriginAccount: account,
|
||||
TargetAccount: targetAccount,
|
||||
}
|
||||
}
|
||||
|
||||
mastoStatus, err := p.tc.StatusToMasto(targetStatus, account)
|
||||
if err != nil {
|
||||
return nil, gtserror.NewErrorInternalError(fmt.Errorf("error converting status %s to frontend representation: %s", targetStatus.ID, err))
|
||||
}
|
||||
|
||||
return mastoStatus, nil
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package streaming
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
func (p *processor) StreamDelete(statusID string) error {
|
||||
errs := []string{}
|
||||
|
||||
// we want to range through ALL streams for ALL accounts here to make sure it's very clear to everyone that the status has been deleted
|
||||
p.streamMap.Range(func(k interface{}, v interface{}) bool {
|
||||
// the key of this map should be an accountID (string)
|
||||
accountID, ok := k.(string)
|
||||
if !ok {
|
||||
errs = append(errs, "key in streamMap was not a string!")
|
||||
return false
|
||||
}
|
||||
|
||||
// the value of the map should be a buncha streams
|
||||
streamsForAccount, ok := v.(*gtsmodel.StreamsForAccount)
|
||||
if !ok {
|
||||
errs = append(errs, fmt.Sprintf("stream map error for account stream %s", accountID))
|
||||
}
|
||||
|
||||
// lock the streams while we work on them
|
||||
streamsForAccount.Lock()
|
||||
defer streamsForAccount.Unlock()
|
||||
for _, stream := range streamsForAccount.Streams {
|
||||
// lock each individual stream as we work on it
|
||||
stream.Lock()
|
||||
defer stream.Unlock()
|
||||
if stream.Connected {
|
||||
stream.Messages <- >smodel.Message{
|
||||
Stream: []string{stream.Type},
|
||||
Event: "delete",
|
||||
Payload: statusID,
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if len(errs) != 0 {
|
||||
return fmt.Errorf("one or more errors streaming status delete: %s", strings.Join(errs, ";"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -18,9 +18,14 @@ import (
|
|||
type Processor interface {
|
||||
// AuthorizeStreamingRequest returns an oauth2 token info in response to an access token query from the streaming API
|
||||
AuthorizeStreamingRequest(accessToken string) (*gtsmodel.Account, error)
|
||||
// OpenStreamForAccount returns a new Stream for the given account, which will contain a channel for passing messages back to the caller.
|
||||
OpenStreamForAccount(account *gtsmodel.Account, streamType string) (*gtsmodel.Stream, gtserror.WithCode)
|
||||
// StreamStatusToAccount streams the given status to any open, appropriate streams belonging to the given account.
|
||||
StreamStatusToAccount(s *apimodel.Status, account *gtsmodel.Account) error
|
||||
// StreamNotificationToAccount streams the given notification to any open, appropriate streams belonging to the given account.
|
||||
StreamNotificationToAccount(n *apimodel.Notification, account *gtsmodel.Account) error
|
||||
// StreamDelete streams the delete of the given statusID to *ALL* open streams.
|
||||
StreamDelete(statusID string) error
|
||||
}
|
||||
|
||||
type processor struct {
|
||||
|
|
|
@ -74,11 +74,9 @@ type Manager interface {
|
|||
GetOldestIndexedID(timelineAccountID string) (string, error)
|
||||
// PrepareXFromTop prepares limit n amount of posts, based on their indexed representations, from the top of the index.
|
||||
PrepareXFromTop(timelineAccountID string, limit int) error
|
||||
// WipeStatusFromTimeline completely removes a status and from the index and prepared posts of the given account ID
|
||||
//
|
||||
// The returned int indicates how many entries were removed.
|
||||
WipeStatusFromTimeline(timelineAccountID string, statusID string) (int, error)
|
||||
// WipeStatusFromAllTimelines removes the status from the index and prepared posts of all timelines
|
||||
// Remove removes one status from the timeline of the given timelineAccountID
|
||||
Remove(statusID string, timelineAccountID string) (int, error)
|
||||
// WipeStatusFromAllTimelines removes one status from the index and prepared posts of all timelines
|
||||
WipeStatusFromAllTimelines(statusID string) error
|
||||
}
|
||||
|
||||
|
@ -177,12 +175,6 @@ func (m *manager) PrepareXFromTop(timelineAccountID string, limit int) error {
|
|||
return t.PrepareFromTop(limit)
|
||||
}
|
||||
|
||||
func (m *manager) WipeStatusFromTimeline(timelineAccountID string, statusID string) (int, error) {
|
||||
t := m.getOrCreateTimeline(timelineAccountID)
|
||||
|
||||
return t.Remove(statusID)
|
||||
}
|
||||
|
||||
func (m *manager) WipeStatusFromAllTimelines(statusID string) error {
|
||||
errors := []string{}
|
||||
m.accountTimelines.Range(func(k interface{}, i interface{}) bool {
|
||||
|
@ -195,7 +187,7 @@ func (m *manager) WipeStatusFromAllTimelines(statusID string) error {
|
|||
errors = append(errors, err.Error())
|
||||
}
|
||||
|
||||
return false
|
||||
return true
|
||||
})
|
||||
|
||||
var err error
|
||||
|
|
|
@ -3,9 +3,16 @@ package timeline
|
|||
import (
|
||||
"container/list"
|
||||
"errors"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (t *timeline) Remove(statusID string) (int, error) {
|
||||
l := t.log.WithFields(logrus.Fields{
|
||||
"func": "Remove",
|
||||
"accountTimeline": t.accountID,
|
||||
"statusID": statusID,
|
||||
})
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
var removed int
|
||||
|
@ -19,6 +26,7 @@ func (t *timeline) Remove(statusID string) (int, error) {
|
|||
return removed, errors.New("Remove: could not parse e as a postIndexEntry")
|
||||
}
|
||||
if entry.statusID == statusID {
|
||||
l.Debug("found status in postIndex")
|
||||
removeIndexes = append(removeIndexes, e)
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +45,7 @@ func (t *timeline) Remove(statusID string) (int, error) {
|
|||
return removed, errors.New("Remove: could not parse e as a preparedPostsEntry")
|
||||
}
|
||||
if entry.statusID == statusID {
|
||||
l.Debug("found status in preparedPosts")
|
||||
removePrepared = append(removePrepared, e)
|
||||
}
|
||||
}
|
||||
|
@ -46,5 +55,6 @@ func (t *timeline) Remove(statusID string) (int, error) {
|
|||
removed = removed + 1
|
||||
}
|
||||
|
||||
l.Debugf("removed %d entries", removed)
|
||||
return removed, nil
|
||||
}
|
||||
|
|
|
@ -649,7 +649,7 @@ func (c *converter) BoostToAS(boostWrapperStatus *gtsmodel.Status, boostingAccou
|
|||
if err := c.db.GetByID(boostWrapperStatus.BoostOfID, b); err != nil {
|
||||
return nil, fmt.Errorf("BoostToAS: error getting status with ID %s from the db: %s", boostWrapperStatus.BoostOfID, err)
|
||||
}
|
||||
boostWrapperStatus = b
|
||||
boostWrapperStatus.GTSBoostedStatus = b
|
||||
}
|
||||
|
||||
// create the announce
|
||||
|
|
Loading…
Reference in New Issue