Compare commits

...

7 commits

Author SHA1 Message Date
Victor Dyotte e07f0ec928
Merge 40c33ccc49 into 7978d88a01 2024-09-28 17:57:08 +02:00
tobi 7978d88a01
[chore] Update apparmor example file (#3368) 2024-09-28 16:58:39 +02:00
vdyotte 40c33ccc49
Fix: update swagger doc 2024-09-24 16:13:49 -04:00
Victor Dyotte 90b773ae2a
Merge branch 'main' into profile-boosts 2024-09-24 15:51:41 -04:00
vdyotte 4b7d7f9b8b
Feat: document new hide boots setting 2024-09-24 15:49:56 -04:00
vdyotte af5a766f62
Feat: display boosts on public profile 2024-09-24 15:22:10 -04:00
S0yKaf d9e59820ed Feat: add "HideBoots" option to account settings 2024-09-23 12:53:21 -04:00
20 changed files with 225 additions and 41 deletions

View file

@ -24,7 +24,7 @@ $ sudo apparmor_parser -Kr /etc/apparmor.d/gotosocial
```
!!! tip
If you're using SQLite, the AppArmor profile expects the database in `/gotosocial/db/` so you'll need to adjust your configuration paths or the policy accordingly.
The provided AppArmor example is just intended to get you started. It will still need to be edited depending on your exact setup; consult the comments in the example profile file for more information.
With the policy installed, you'll need to configure your system to use it to constrain the permissions GoToSocial has.

View file

@ -284,6 +284,12 @@ definitions:
example: https://example.org/media/some_user/header/static/header.png
type: string
x-go-name: HeaderStatic
hide_boosts:
description: |-
Account has opted to hide boosts from their profile.
Key/value omitted if false.
type: boolean
x-go-name: HideBoosts
hide_collections:
description: |-
Account has opted to hide their followers/following collections.
@ -2284,6 +2290,12 @@ definitions:
example: https://example.org/media/some_user/header/static/header.png
type: string
x-go-name: HeaderStatic
hide_boosts:
description: |-
Account has opted to hide boosts from their profile.
Key/value omitted if false.
type: boolean
x-go-name: HideBoosts
hide_collections:
description: |-
Account has opted to hide their followers/following collections.

View file

@ -134,6 +134,11 @@ This feed only includes posts set as 'Public' (see [Privacy Settings](./posts.md
!!! warning
Exposing your RSS feed allows *anyone* to subscribe to updates on your Public posts anonymously, bypassing follows and follow requests.
#### Hide boosts from your public page
By default, GoToSocial will display posts boosted by you on your public web profile. If you do not wish to display them, You can hide them by checking this box.
#### Hide Who You Follow / Are Followed By
By default, GoToSocial shows your following/followers counts on your public web profile, and allows others to see who you follow and are followed by. This can be useful for account discovery purposes. However, for privacy + safety reasons you may wish to hide these counts, and to hide your following/followers lists from other accounts. You can do this by checking this box.

View file

@ -7,23 +7,44 @@ profile gotosocial flags=(attach_disconnected, mediate_deleted) {
include <abstractions/nameservice>
include <abstractions/user-tmp>
# Allow common binary install paths.
#
# You can change or remove these depending on
# where you've installed your GoToSocial binary.
/gotosocial/gotosocial mrix,
/usr/local/bin/gotosocial mrix,
/usr/bin/gotosocial mrix,
/usr/sbin/gotosocial mrix,
# Allow access to GoToSocial's storage and database paths.
# Change these depending on your db + storage locations.
owner /gotosocial/{,**} r,
owner /gotosocial/db/* wk,
owner /gotosocial/storage/** wk,
# Allow GoToSocial to write logs
# NOTE: you only need to allow write permissions to /var/log/syslog if you've
# enabled logging to syslog.
# Embedded ffmpeg needs read
# permission on /dev/urandom.
owner /dev/ r,
owner /dev/urandom r,
# Temp dir access is needed for storing
# files briefly during media processing.
owner /tmp/ r,
owner /tmp/* rwk,
# If running with GTS_WAZERO_COMPILATION_CACHE set,
# change + uncomment the below lines as appropriate:
# owner /your/wazero/cache/directory/ r,
# owner /your/wazero/cache/directory/** rwk,
# If you've enabled logging to syslog, allow GoToSocial
# to write logs by uncommenting the following line:
# owner /var/log/syslog w,
# These directories are not currently used by any of the recommended
# GoToSocial installation methods, but they may be used in the future and/or
# for custom installations.
# These directories are not currently used by any of
# the recommended GoToSocial installation methods, but
# may be used in the future and/or for custom installs.
# Delete them if you prefer.
owner /etc/gotosocial/{,**} r,
owner /usr/local/etc/gotosocial/{,**} r,
owner /usr/share/gotosocial/{,**} r,
@ -55,9 +76,10 @@ profile gotosocial flags=(attach_disconnected, mediate_deleted) {
network inet dgram,
network inet6 dgram,
# Allow GoToSocial to receive signals from unconfined processes
# Allow GoToSocial to receive signals from unconfined processes.
signal (receive) peer=unconfined,
# Allow GoToSocial to send signals to/receive signals from worker processes
# Allow GoToSocial to send signals to/receive signals from worker processes.
signal (send,receive) peer=gotosocial,
}

View file

@ -348,6 +348,7 @@ func parseUpdateAccountForm(c *gin.Context) (*apimodel.UpdateCredentialsRequest,
form.Theme == nil &&
form.CustomCSS == nil &&
form.EnableRSS == nil &&
form.HideBoosts == nil &&
form.HideCollections == nil &&
form.WebVisibility == nil) {
return nil, errors.New("empty form submitted")

View file

@ -104,6 +104,9 @@ type Account struct {
// Account has enabled RSS feed.
// Key/value omitted if false.
EnableRSS bool `json:"enable_rss,omitempty"`
// Account has opted to hide boosts from their profile.
// Key/value omitted if false.
HideBoosts bool `json:"hide_boosts,omitempty"`
// Account has opted to hide their followers/following collections.
// Key/value omitted if false.
HideCollections bool `json:"hide_collections,omitempty"`
@ -225,6 +228,8 @@ type UpdateCredentialsRequest struct {
CustomCSS *string `form:"custom_css" json:"custom_css"`
// Enable RSS feed of public toots for this account at /@[username]/feed.rss
EnableRSS *bool `form:"enable_rss" json:"enable_rss"`
// Hide boosts from this account's profile page.
HideBoosts *bool `form:"hide_boosts" json:"hide_boosts"`
// Hide this account's following/followers collections.
HideCollections *bool `form:"hide_collections" json:"hide_collections"`
// Visibility of statuses to show via the web view.

View file

@ -118,6 +118,10 @@ type WebStatus struct {
// Override API account with web account.
Account *WebAccount `json:"account"`
// Account that reblogged the status.
// needed to properly render reblogged statuses on profile pages.
ReblogAccount *WebAccount `json:"reblog_account"`
// Web version of media
// attached to this status.
MediaAttachments []*WebAttachment `json:"media_attachments"`

View file

@ -1017,6 +1017,7 @@ func (a *accountDB) GetAccountWebStatuses(
) ([]*gtsmodel.Status, error) {
// Check for an easy case: account exposes no statuses via the web.
webVisibility := account.Settings.WebVisibility
hideBoosts := *account.Settings.HideBoosts
if webVisibility == gtsmodel.VisibilityNone {
return nil, db.ErrNoEntries
}
@ -1035,9 +1036,12 @@ func (a *accountDB) GetAccountWebStatuses(
// Select only IDs from table
Column("status.id").
Where("? = ?", bun.Ident("status.account_id"), account.ID).
// Don't show replies or boosts.
Where("? IS NULL", bun.Ident("status.in_reply_to_uri")).
Where("? IS NULL", bun.Ident("status.boost_of_id"))
// Don't show replies.
Where("? IS NULL", bun.Ident("status.in_reply_to_uri"))
if hideBoosts {
q = q.Where("? IS NULL", bun.Ident("status.boost_of_id"))
}
// Select statuses for this account according
// to their web visibility preference.

View file

@ -0,0 +1,44 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// 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 migrations
import (
"context"
"strings"
"github.com/uptrace/bun"
)
func init() {
up := func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? BOOLEAN DEFAULT FALSE", bun.Ident("account_settings"), bun.Ident("hide_boosts"))
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
return err
}
return nil
}
down := func(ctx context.Context, db *bun.DB) error {
_, err := db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("account_settings"), bun.Ident("hide_boosts"))
return err
}
if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}

View file

@ -33,6 +33,7 @@ type AccountSettings struct {
Theme string `bun:",nullzero"` // Preset CSS theme filename selected by this Account (empty string if nothing set).
CustomCSS string `bun:",nullzero"` // Custom CSS that should be displayed for this Account's profile and statuses.
EnableRSS *bool `bun:",nullzero,notnull,default:false"` // enable RSS feed subscription for this account's public posts at [URL]/feed
HideBoosts *bool `bun:",nullzero,notnull,default:false"` // Hide boosts from this accounts profile page.
HideCollections *bool `bun:",nullzero,notnull,default:false"` // Hide this account's followers/following collections.
WebVisibility Visibility `bun:",nullzero,notnull,default:public"` // Visibility level of statuses that visitors can view via the web profile.
InteractionPolicyDirect *InteractionPolicy `bun:""` // Interaction policy to use for new direct visibility statuses by this account. If null, assume default policy.

View file

@ -42,6 +42,16 @@ func (suite *GetRSSTestSuite) TestGetAccountRSSAdmin() {
<description>Posts from @admin@localhost:8080</description>
<pubDate>Wed, 20 Oct 2021 10:41:37 +0000</pubDate>
<lastBuildDate>Wed, 20 Oct 2021 10:41:37 +0000</lastBuildDate>
<item>
<title>introduction post</title>
<link>http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</link>
<description>@the_mighty_zork@localhost:8080 made a new post: &#34;hello everyone!&#34;</description>
<content:encoded><![CDATA[hello everyone!]]></content:encoded>
<author>@the_mighty_zork@localhost:8080</author>
<guid isPermaLink="true">http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY</guid>
<pubDate>Wed, 20 Oct 2021 10:40:37 +0000</pubDate>
<source>http://localhost:8080/@the_mighty_zork/feed.rss</source>
</item>
<item>
<title>open to see some puppies</title>
<link>http://localhost:8080/@admin/statuses/01F8MHAAY43M6RJ473VQFCVH37</link>

View file

@ -274,6 +274,11 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
settingsColumns = append(settingsColumns, "enable_rss")
}
if form.HideBoosts != nil {
account.Settings.HideBoosts = form.HideBoosts
settingsColumns = append(settingsColumns, "hide_boosts")
}
if form.HideCollections != nil {
account.Settings.HideCollections = form.HideCollections
settingsColumns = append(settingsColumns, "hide_collections")

View file

@ -302,7 +302,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
// Bits that vary between remote + local accounts:
// - Account (acct) string.
// - Role.
// - Settings things (enableRSS, theme, customCSS, hideCollections).
// - Settings things (enableRSS, theme, customCSS, hideBoosts ,hideCollections).
var (
acct string
@ -310,6 +310,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
enableRSS bool
theme string
customCSS string
hideBoosts bool
hideCollections bool
)
@ -338,6 +339,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
enableRSS = *a.Settings.EnableRSS
theme = a.Settings.Theme
customCSS = a.Settings.CustomCSS
hideBoosts = *a.Settings.HideBoosts
hideCollections = *a.Settings.HideCollections
}
@ -380,6 +382,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
Theme: theme,
CustomCSS: customCSS,
EnableRSS: enableRSS,
HideBoosts: hideBoosts,
HideCollections: hideCollections,
Roles: roles,
}
@ -1092,7 +1095,15 @@ func (c *Converter) StatusToWebStatus(
ctx context.Context,
s *gtsmodel.Status,
) (*apimodel.WebStatus, error) {
apiStatus, err := c.statusToFrontend(ctx, s,
isBoost := s.BoostOf != nil
status := s
if isBoost {
status = s.BoostOf
}
apiStatus, err := c.statusToFrontend(ctx, status,
nil, // No authed requester.
statusfilter.FilterContextNone, // No filters.
nil, // No filters.
@ -1103,7 +1114,7 @@ func (c *Converter) StatusToWebStatus(
}
// Convert status author to web model.
acct, err := c.AccountToWebAccount(ctx, s.Account)
acct, err := c.AccountToWebAccount(ctx, status.Account)
if err != nil {
return nil, err
}
@ -1113,6 +1124,14 @@ func (c *Converter) StatusToWebStatus(
Account: acct,
}
if isBoost {
reblogAcct, err := c.AccountToWebAccount(ctx, s.Account)
if err != nil {
return nil, err
}
webStatus.ReblogAccount = reblogAcct
}
// Whack a newline before and after each "pre" to make it easier to outdent it.
webStatus.Content = strings.ReplaceAll(webStatus.Content, "<pre>", "\n<pre>")
webStatus.Content = strings.ReplaceAll(webStatus.Content, "</pre>", "</pre>\n")

View file

@ -1402,6 +1402,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToWebStatus() {
"emojis": [],
"fields": []
},
"reblog_account": null,
"media_attachments": [
{
"id": "01HE7Y3C432WRSNS10EZM86SA5",

View file

@ -39,6 +39,12 @@ const (
func (c *Converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error) {
// see https://cyber.harvard.edu/rss/rss.html
// If status is a boost,
// display the boost instead.
if s.BoostOf != nil {
s = s.BoostOf
}
// Title -- The title of the item.
// example: Venice Film Festival Tries to Quit Sinking
var title string

View file

@ -657,6 +657,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
Sensitive: util.Ptr(false),
Language: "en",
EnableRSS: util.Ptr(false),
HideBoosts: util.Ptr(false),
HideCollections: util.Ptr(false),
WebVisibility: gtsmodel.VisibilityPublic,
},
@ -668,6 +669,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
Sensitive: util.Ptr(false),
Language: "en",
EnableRSS: util.Ptr(true),
HideBoosts: util.Ptr(false),
HideCollections: util.Ptr(false),
WebVisibility: gtsmodel.VisibilityPublic,
},
@ -679,6 +681,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
Sensitive: util.Ptr(false),
Language: "en",
EnableRSS: util.Ptr(true),
HideBoosts: util.Ptr(false),
HideCollections: util.Ptr(false),
WebVisibility: gtsmodel.VisibilityUnlocked,
},
@ -690,6 +693,7 @@ func NewTestAccountSettings() map[string]*gtsmodel.AccountSettings {
Sensitive: util.Ptr(true),
Language: "fr",
EnableRSS: util.Ptr(false),
HideBoosts: util.Ptr(false),
HideCollections: util.Ptr(true),
WebVisibility: gtsmodel.VisibilityPublic,
},

View file

@ -41,6 +41,12 @@ main {
text-decoration: none;
}
.boosted {
padding: 0 0.75rem 0.75rem;
color: var(--fg-reduced);
font-weight: bold;
}
.status-header > address {
/*
Avoid stretching so wide that user
@ -65,11 +71,21 @@ main {
height: 3.5rem;
width: 3.5rem;
object-fit: cover;
position: relative;
border: 0.15rem solid $avatar-border;
border-radius: $br;
overflow: hidden; /* hides corners from img overflowing */
.boosted-avatar {
height: 50%;
width: 50%;
z-index: 10;
position: absolute;
bottom: 0;
inset-inline-end: 0;
}
img {
height: 100%;
width: 100%;

View file

@ -114,6 +114,7 @@ function UserProfileForm({ data: profile }) {
locked: useBoolInput("locked", { source: profile }),
discoverable: useBoolInput("discoverable", { source: profile}),
enableRSS: useBoolInput("enable_rss", { source: profile }),
hideBoosts: useBoolInput("hide_boosts", { source: profile }),
hideCollections: useBoolInput("hide_collections", { source: profile }),
webVisibility: useTextInput("web_visibility", { source: profile, valueSelector: (p) => p.source?.web_visibility }),
fields: useFieldArrayInput("fields_attributes", {
@ -257,6 +258,10 @@ function UserProfileForm({ data: profile }) {
field={form.enableRSS}
label="Enable RSS feed of posts."
/>
<Checkbox
field={form.hideBoosts}
label="Hide boosts from your public page"
/>
<Checkbox
field={form.hideCollections}
label="Hide who you follow / are followed by."

View file

@ -247,6 +247,16 @@
class="status expanded"
{{- includeAttr "status_attributes.tmpl" . | indentAttr 6 }}
>
{{- if .ReblogAccount }}
<div class="boosted text-cutoff">
<i class="fa fa-retweet" aria-hidden="true"></i>&nbsp
{{- if $.account.DisplayName }}
{{- emojify $.account.Emojis (escape $.account.DisplayName) }} boosted
{{- else }}
{{- $.account.Username }} boosted
{{- end }}
</div>
{{- end }}
{{- include "status.tmpl" . | indent 6 }}
</article>
{{- end }}

View file

@ -48,6 +48,16 @@
alt="Avatar for {{ .Username -}}"
title="Avatar for {{ .Username -}}"
>
{{ if $.ReblogAccount }}
<img
class="boosted-avatar"
src="{{ $.ReblogAccount.Avatar }}"
alt="Avatar for {{ $.ReblogAccount.Username -}}"
title="Avatar for {{ $.ReblogAccount.Username -}}"
>
{{ end }}
</picture>
<div class="author-strap">
<span class="displayname text-cutoff">