mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
492 lines
16 KiB
Go
492 lines
16 KiB
Go
// 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 gtsmodel contains types used *internally* by GoToSocial and added/removed/selected from the database.
|
|
// These types should never be serialized and/or sent out via public APIs, as they contain sensitive information.
|
|
// The annotation used on these structs is for handling them via the bun-db ORM.
|
|
// See here for more info on bun model annotations: https://bun.uptrace.dev/guide/models.html
|
|
package gtsmodel
|
|
|
|
import (
|
|
"crypto/rsa"
|
|
"slices"
|
|
"strings"
|
|
"time"
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/config"
|
|
"code.superseriousbusiness.org/gotosocial/internal/log"
|
|
)
|
|
|
|
// Account represents either a local or a remote ActivityPub actor.
|
|
// https://www.w3.org/TR/activitypub/#actor-objects
|
|
type Account struct {
|
|
// Database ID of the account.
|
|
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
|
|
|
|
// Datetime when the account was created.
|
|
// Corresponds to ActivityStreams `published` prop.
|
|
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
|
|
|
// Datetime when was the account was last updated,
|
|
// ie., when the actor last sent out an Update
|
|
// activity, or if never, when it was `published`.
|
|
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
|
|
|
|
// Datetime when the account was last fetched /
|
|
// dereferenced by this GoToSocial instance.
|
|
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
|
|
|
|
// Username of the account.
|
|
//
|
|
// Corresponds to AS `preferredUsername` prop, which gives
|
|
// no uniqueness guarantee. However, we do enforce uniqueness
|
|
// for it as, in practice, it always is and we rely on this.
|
|
Username string `bun:",nullzero,notnull,unique:accounts_username_domain_uniq"`
|
|
|
|
// Domain of the account, discovered via webfinger.
|
|
//
|
|
// Null if this is a local account, otherwise
|
|
// something like `example.org`.
|
|
Domain string `bun:",nullzero,unique:accounts_username_domain_uniq"`
|
|
|
|
// Database ID of the account's avatar MediaAttachment, if set.
|
|
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
|
|
|
|
// MediaAttachment corresponding to AvatarMediaAttachmentID.
|
|
AvatarMediaAttachment *MediaAttachment `bun:"-"`
|
|
|
|
// URL of the avatar media.
|
|
//
|
|
// Null for local accounts.
|
|
AvatarRemoteURL string `bun:",nullzero"`
|
|
|
|
// Database ID of the account's header MediaAttachment, if set.
|
|
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
|
|
|
|
// MediaAttachment corresponding to HeaderMediaAttachmentID.
|
|
HeaderMediaAttachment *MediaAttachment `bun:"-"`
|
|
|
|
// URL of the header media.
|
|
//
|
|
// Null for local accounts.
|
|
HeaderRemoteURL string `bun:",nullzero"`
|
|
|
|
// Display name for this account, if set.
|
|
//
|
|
// Corresponds to the ActivityStreams `name` property.
|
|
//
|
|
// If null, fall back to username for display purposes.
|
|
DisplayName string `bun:",nullzero"`
|
|
|
|
// Database IDs of any emojis used in
|
|
// this account's bio, display name, etc
|
|
EmojiIDs []string `bun:"emojis,array"`
|
|
|
|
// Emojis corresponding to EmojiIDs.
|
|
Emojis []*Emoji `bun:"-"`
|
|
|
|
// A slice of of key/value fields that
|
|
// this account has added to their profile.
|
|
//
|
|
// Corresponds to schema.org PropertyValue types in `attachments`.
|
|
Fields []*Field `bun:",nullzero"`
|
|
|
|
// The raw (unparsed) content of fields that this
|
|
// account has added to their profile, before
|
|
// conversion to HTML.
|
|
//
|
|
// Only set for local accounts.
|
|
FieldsRaw []*Field `bun:",nullzero"`
|
|
|
|
// A note that this account has on their profile
|
|
// (ie., the account's bio/description of themselves).
|
|
//
|
|
// Corresponds to the ActivityStreams `summary` property.
|
|
Note string `bun:",nullzero"`
|
|
|
|
// The raw (unparsed) version of Note, before conversion to HTML.
|
|
//
|
|
// Only set for local accounts.
|
|
NoteRaw string `bun:",nullzero"`
|
|
|
|
// ActivityPub URI/IDs by which this account is also known.
|
|
//
|
|
// Corresponds to the ActivityStreams `alsoKnownAs` property.
|
|
AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"`
|
|
|
|
// Accounts matching AlsoKnownAsURIs.
|
|
AlsoKnownAs []*Account `bun:"-"`
|
|
|
|
// URI/ID to which the account has (or claims to have) moved.
|
|
//
|
|
// Corresponds to the ActivityStreams `movedTo` property.
|
|
//
|
|
// Even if this field is set the move may not yet have been
|
|
// processed. Check `move` for this.
|
|
MovedToURI string `bun:",nullzero"`
|
|
|
|
// Account matching MovedToURI.
|
|
MovedTo *Account `bun:"-"`
|
|
|
|
// ID of a Move in the database for this account.
|
|
// Only set if we received or created a Move activity
|
|
// for which this account URI was the origin.
|
|
MoveID string `bun:"type:CHAR(26),nullzero"`
|
|
|
|
// Move corresponding to MoveID, if set.
|
|
Move *Move `bun:"-"`
|
|
|
|
// True if account requires manual approval of Follows.
|
|
//
|
|
// Corresponds to AS `manuallyApprovesFollowers` prop.
|
|
Locked *bool `bun:",nullzero,notnull,default:true"`
|
|
|
|
// True if account has opted in to being shown in
|
|
// directories and exposed to search engines.
|
|
//
|
|
// Corresponds to the toot `discoverable` property.
|
|
Discoverable *bool `bun:",nullzero,notnull,default:false"`
|
|
|
|
// ActivityPub URI/ID for this account.
|
|
//
|
|
// Must be set, must be unique.
|
|
URI string `bun:",nullzero,notnull,unique"`
|
|
|
|
// URL at which a web representation of this
|
|
// account should be available, if set.
|
|
//
|
|
// Corresponds to ActivityStreams `url` prop.
|
|
URL string `bun:",nullzero"`
|
|
|
|
// URI of the actor's inbox.
|
|
//
|
|
// Corresponds to ActivityPub `inbox` property.
|
|
//
|
|
// According to AP this MUST be set, but some
|
|
// implementations don't set it for service actors.
|
|
InboxURI string `bun:",nullzero"`
|
|
|
|
// URI/ID of this account's sharedInbox, if set.
|
|
//
|
|
// Corresponds to ActivityPub `endpoints.sharedInbox`.
|
|
//
|
|
// Gotcha warning: this is a string pointer because
|
|
// it has three possible states:
|
|
//
|
|
// 1. null: We don't know (yet) if actor has a shared inbox.
|
|
// 2. empty: We know it doesn't have a shared inbox.
|
|
// 3. not empty: We know it does have a shared inbox.
|
|
SharedInboxURI *string `bun:""`
|
|
|
|
// URI/ID of the actor's outbox.
|
|
//
|
|
// Corresponds to ActivityPub `outbox` property.
|
|
//
|
|
// According to AP this MUST be set, but some
|
|
// implementations don't set it for service actors.
|
|
OutboxURI string `bun:",nullzero"`
|
|
|
|
// URI/ID of the actor's following collection.
|
|
//
|
|
// Corresponds to ActivityPub `following` property.
|
|
//
|
|
// According to AP this SHOULD be set.
|
|
FollowingURI string `bun:",nullzero"`
|
|
|
|
// URI/ID of the actor's followers collection.
|
|
//
|
|
// Corresponds to ActivityPub `followers` property.
|
|
//
|
|
// According to AP this SHOULD be set.
|
|
FollowersURI string `bun:",nullzero"`
|
|
|
|
// URI/ID of the actor's featured collection.
|
|
//
|
|
// Corresponds to the Toot `featured` property.
|
|
FeaturedCollectionURI string `bun:",nullzero"`
|
|
|
|
// ActivityStreams type of the actor.
|
|
//
|
|
// Application, Group, Organization, Person, or Service.
|
|
ActorType AccountActorType `bun:",nullzero,notnull"`
|
|
|
|
// Private key for signing http requests.
|
|
//
|
|
// Only defined for local accounts
|
|
PrivateKey *rsa.PrivateKey `bun:""`
|
|
|
|
// Public key for authorizing signed http requests.
|
|
//
|
|
// Defined for both local and remote accounts
|
|
PublicKey *rsa.PublicKey `bun:",notnull"`
|
|
|
|
// Dereferenceable location of this actor's public key.
|
|
//
|
|
// Corresponds to https://w3id.org/security/v1 `publicKey.id`.
|
|
PublicKeyURI string `bun:",nullzero,notnull,unique"`
|
|
|
|
// Datetime at which public key will expire/has expired,
|
|
// and should be fetched again as appropriate.
|
|
//
|
|
// Only ever set for remote accounts.
|
|
PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"`
|
|
|
|
// Datetime at which account was marked as a "memorial",
|
|
// ie., user owning the account has passed away.
|
|
MemorializedAt time.Time `bun:"type:timestamptz,nullzero"`
|
|
|
|
// Datetime at which account was set to
|
|
// have all its media shown as sensitive.
|
|
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"`
|
|
|
|
// Datetime at which account was silenced.
|
|
SilencedAt time.Time `bun:"type:timestamptz,nullzero"`
|
|
|
|
// Datetime at which account was suspended.
|
|
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"`
|
|
|
|
// ID of the database entry that caused this account to
|
|
// be suspended. Can be an account ID or a domain block ID.
|
|
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"`
|
|
|
|
// gtsmodel.AccountSettings for this account.
|
|
//
|
|
// Local, non-instance-actor accounts only.
|
|
Settings *AccountSettings `bun:"-"`
|
|
|
|
// gtsmodel.AccountStats for this account.
|
|
//
|
|
// Local accounts only.
|
|
Stats *AccountStats `bun:"-"`
|
|
}
|
|
|
|
// UsernameDomain returns account @username@domain (missing domain if local).
|
|
func (a *Account) UsernameDomain() string {
|
|
if a.IsLocal() {
|
|
return "@" + a.Username
|
|
}
|
|
return "@" + a.Username + "@" + a.Domain
|
|
}
|
|
|
|
// IsLocal returns whether account is a local user account.
|
|
func (a *Account) IsLocal() bool {
|
|
return a.Domain == "" ||
|
|
a.Domain == config.GetHost() ||
|
|
a.Domain == config.GetAccountDomain()
|
|
}
|
|
|
|
// IsRemote returns whether account is a remote user account.
|
|
func (a *Account) IsRemote() bool {
|
|
return !a.IsLocal()
|
|
}
|
|
|
|
// IsNew returns whether an account is "new" in the sense
|
|
// that it has not been previously stored in the database.
|
|
func (a *Account) IsNew() bool {
|
|
return a.CreatedAt.IsZero()
|
|
}
|
|
|
|
// IsInstance returns whether account is an instance internal actor account.
|
|
func (a *Account) IsInstance() bool {
|
|
if a.IsLocal() {
|
|
// Check if our instance account.
|
|
return a.Username == config.GetHost()
|
|
}
|
|
|
|
// Check if remote instance account.
|
|
return a.Username == a.Domain ||
|
|
a.FollowersURI == "" ||
|
|
a.FollowingURI == "" ||
|
|
(a.Username == "internal.fetch" && strings.Contains(a.Note, "internal service actor")) ||
|
|
a.Username == "instance.actor" // <- misskey
|
|
}
|
|
|
|
// EmojisPopulated returns whether emojis are
|
|
// populated according to current EmojiIDs.
|
|
func (a *Account) EmojisPopulated() bool {
|
|
if len(a.EmojiIDs) != len(a.Emojis) {
|
|
// this is the quickest indicator.
|
|
return false
|
|
}
|
|
|
|
// Emojis must be in same order.
|
|
for i, id := range a.EmojiIDs {
|
|
if a.Emojis[i] == nil {
|
|
log.Warnf(nil, "nil emoji in slice for account %s", a.URI)
|
|
continue
|
|
}
|
|
if a.Emojis[i].ID != id {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// AlsoKnownAsPopulated returns whether alsoKnownAs accounts
|
|
// are populated according to current AlsoKnownAsURIs.
|
|
func (a *Account) AlsoKnownAsPopulated() bool {
|
|
if len(a.AlsoKnownAsURIs) != len(a.AlsoKnownAs) {
|
|
// this is the quickest indicator.
|
|
return false
|
|
}
|
|
|
|
// Accounts must be in same order.
|
|
for i, uri := range a.AlsoKnownAsURIs {
|
|
if a.AlsoKnownAs[i] == nil {
|
|
log.Warnf(nil, "nil account in alsoKnownAs slice for account %s", a.URI)
|
|
continue
|
|
}
|
|
if a.AlsoKnownAs[i].URI != uri {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// PubKeyExpired returns true if the account's public key
|
|
// has been marked as expired, and the expiry time has passed.
|
|
func (a *Account) PubKeyExpired() bool {
|
|
if a == nil {
|
|
return false
|
|
}
|
|
|
|
return !a.PublicKeyExpiresAt.IsZero() &&
|
|
a.PublicKeyExpiresAt.Before(time.Now())
|
|
}
|
|
|
|
// IsAliasedTo returns true if account
|
|
// is aliased to the given account URI.
|
|
func (a *Account) IsAliasedTo(uri string) bool {
|
|
return slices.Contains(a.AlsoKnownAsURIs, uri)
|
|
}
|
|
|
|
// IsSuspended returns true if account
|
|
// has been suspended from this instance.
|
|
func (a *Account) IsSuspended() bool {
|
|
return !a.SuspendedAt.IsZero()
|
|
}
|
|
|
|
// IsMoving returns true if
|
|
// account is Moving or has Moved.
|
|
func (a *Account) IsMoving() bool {
|
|
return a.MovedToURI != "" || a.MoveID != ""
|
|
}
|
|
|
|
// AccountToEmoji is an intermediate struct to facilitate the many2many relationship between an account and one or more emojis.
|
|
type AccountToEmoji struct {
|
|
AccountID string `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
|
Account *Account `bun:"rel:belongs-to"`
|
|
EmojiID string `bun:"type:CHAR(26),unique:accountemoji,nullzero,notnull"`
|
|
Emoji *Emoji `bun:"rel:belongs-to"`
|
|
}
|
|
|
|
// Field represents a key value field on an account, for things like pronouns, website, etc.
|
|
// VerifiedAt is optional, to be used only if Value is a URL to a webpage that contains the
|
|
// username of the user.
|
|
type Field struct {
|
|
Name string // Name of this field.
|
|
Value string // Value of this field.
|
|
VerifiedAt time.Time `bun:",nullzero"` // This field was verified at (optional).
|
|
}
|
|
|
|
// AccountActorType is the ActivityStreams type of an actor.
|
|
type AccountActorType enumType
|
|
|
|
const (
|
|
AccountActorTypeUnknown AccountActorType = 0
|
|
AccountActorTypeApplication AccountActorType = 1 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application
|
|
AccountActorTypeGroup AccountActorType = 2 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group
|
|
AccountActorTypeOrganization AccountActorType = 3 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization
|
|
AccountActorTypePerson AccountActorType = 4 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person
|
|
AccountActorTypeService AccountActorType = 5 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service
|
|
)
|
|
|
|
// String returns a stringified form of AccountActorType.
|
|
func (t AccountActorType) String() string {
|
|
switch t {
|
|
case AccountActorTypeApplication:
|
|
return "Application"
|
|
case AccountActorTypeGroup:
|
|
return "Group"
|
|
case AccountActorTypeOrganization:
|
|
return "Organization"
|
|
case AccountActorTypePerson:
|
|
return "Person"
|
|
case AccountActorTypeService:
|
|
return "Service"
|
|
default:
|
|
panic("invalid actor type")
|
|
}
|
|
}
|
|
|
|
// ParseAccountActorType returns an
|
|
// actor type from the given value.
|
|
func ParseAccountActorType(in string) AccountActorType {
|
|
switch strings.ToLower(in) {
|
|
case "application":
|
|
return AccountActorTypeApplication
|
|
case "group":
|
|
return AccountActorTypeGroup
|
|
case "organization":
|
|
return AccountActorTypeOrganization
|
|
case "person":
|
|
return AccountActorTypePerson
|
|
case "service":
|
|
return AccountActorTypeService
|
|
default:
|
|
return AccountActorTypeUnknown
|
|
}
|
|
}
|
|
|
|
func (t AccountActorType) IsBot() bool {
|
|
return t == AccountActorTypeApplication || t == AccountActorTypeService
|
|
}
|
|
|
|
// Relationship describes a requester's relationship with another account.
|
|
type Relationship struct {
|
|
ID string // The account id.
|
|
Following bool // Are you following this user?
|
|
ShowingReblogs bool // Are you receiving this user's boosts in your home timeline?
|
|
Notifying bool // Have you enabled notifications for this user?
|
|
FollowedBy bool // Are you followed by this user?
|
|
Blocking bool // Are you blocking this user?
|
|
BlockedBy bool // Is this user blocking you?
|
|
Muting bool // Are you muting this user?
|
|
MutingNotifications bool // Are you muting notifications from this user?
|
|
Requested bool // Do you have a pending follow request targeting this user?
|
|
RequestedBy bool // Does the user have a pending follow request targeting you?
|
|
DomainBlocking bool // Are you blocking this user's domain?
|
|
Endorsed bool // Are you featuring this user on your profile?
|
|
Note string // Your note on this account.
|
|
}
|
|
|
|
// Theme represents a user-selected
|
|
// CSS theme for an account.
|
|
type Theme struct {
|
|
// User-facing title of this theme.
|
|
Title string
|
|
|
|
// User-facing description of this theme.
|
|
Description string
|
|
|
|
// FileName of this theme in the themes
|
|
// directory (eg., `light-blurple.css`).
|
|
FileName string
|
|
}
|