gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

opengraph.go (5072B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package web
     19 
     20 import (
     21 	"html"
     22 	"strconv"
     23 	"strings"
     24 
     25 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
     26 	"github.com/superseriousbusiness/gotosocial/internal/text"
     27 )
     28 
     29 const maxOGDescriptionLength = 300
     30 
     31 // ogMeta represents supported OpenGraph Meta tags
     32 //
     33 // see eg https://ogp.me/
     34 type ogMeta struct {
     35 	// vanilla og tags
     36 	Title       string // og:title
     37 	Type        string // og:type
     38 	Locale      string // og:locale
     39 	URL         string // og:url
     40 	SiteName    string // og:site_name
     41 	Description string // og:description
     42 
     43 	// image tags
     44 	Image       string // og:image
     45 	ImageWidth  string // og:image:width
     46 	ImageHeight string // og:image:height
     47 	ImageAlt    string // og:image:alt
     48 
     49 	// article tags
     50 	ArticlePublisher     string // article:publisher
     51 	ArticleAuthor        string // article:author
     52 	ArticleModifiedTime  string // article:modified_time
     53 	ArticlePublishedTime string // article:published_time
     54 
     55 	// profile tags
     56 	ProfileUsername string // profile:username
     57 }
     58 
     59 // ogBase returns an *ogMeta suitable for serving at
     60 // the base root of an instance. It also serves as a
     61 // foundation for building account / status ogMeta on
     62 // top of.
     63 func ogBase(instance *apimodel.InstanceV1) *ogMeta {
     64 	var locale string
     65 	if len(instance.Languages) > 0 {
     66 		locale = instance.Languages[0]
     67 	}
     68 
     69 	og := &ogMeta{
     70 		Title:       text.SanitizePlaintext(instance.Title) + " - GoToSocial",
     71 		Type:        "website",
     72 		Locale:      locale,
     73 		URL:         instance.URI,
     74 		SiteName:    instance.AccountDomain,
     75 		Description: parseDescription(instance.ShortDescription),
     76 
     77 		Image:    instance.Thumbnail,
     78 		ImageAlt: instance.ThumbnailDescription,
     79 	}
     80 
     81 	return og
     82 }
     83 
     84 // withAccount uses the given account to build an ogMeta
     85 // struct specific to that account. It's suitable for serving
     86 // at account profile pages.
     87 func (og *ogMeta) withAccount(account *apimodel.Account) *ogMeta {
     88 	og.Title = parseTitle(account, og.SiteName)
     89 	og.Type = "profile"
     90 	og.URL = account.URL
     91 	if account.Note != "" {
     92 		og.Description = parseDescription(account.Note)
     93 	} else {
     94 		og.Description = `content="This GoToSocial user hasn't written a bio yet!"`
     95 	}
     96 
     97 	og.Image = account.Avatar
     98 	og.ImageAlt = "Avatar for " + account.Username
     99 
    100 	og.ProfileUsername = account.Username
    101 
    102 	return og
    103 }
    104 
    105 // withStatus uses the given status to build an ogMeta
    106 // struct specific to that status. It's suitable for serving
    107 // at status pages.
    108 func (og *ogMeta) withStatus(status *apimodel.Status) *ogMeta {
    109 	og.Title = "Post by " + parseTitle(status.Account, og.SiteName)
    110 	og.Type = "article"
    111 	if status.Language != nil {
    112 		og.Locale = *status.Language
    113 	}
    114 	og.URL = status.URL
    115 	switch {
    116 	case status.SpoilerText != "":
    117 		og.Description = parseDescription("CW: " + status.SpoilerText)
    118 	case status.Text != "":
    119 		og.Description = parseDescription(status.Text)
    120 	default:
    121 		og.Description = og.Title
    122 	}
    123 
    124 	if !status.Sensitive && len(status.MediaAttachments) > 0 {
    125 		a := status.MediaAttachments[0]
    126 		og.Image = a.PreviewURL
    127 		og.ImageWidth = strconv.Itoa(a.Meta.Small.Width)
    128 		og.ImageHeight = strconv.Itoa(a.Meta.Small.Height)
    129 		if a.Description != nil {
    130 			og.ImageAlt = *a.Description
    131 		}
    132 	} else {
    133 		og.Image = status.Account.Avatar
    134 		og.ImageAlt = "Avatar for " + status.Account.Username
    135 	}
    136 
    137 	og.ArticlePublisher = status.Account.URL
    138 	og.ArticleAuthor = status.Account.URL
    139 	og.ArticlePublishedTime = status.CreatedAt
    140 	og.ArticleModifiedTime = status.CreatedAt
    141 
    142 	return og
    143 }
    144 
    145 // parseTitle parses a page title from account and accountDomain
    146 func parseTitle(account *apimodel.Account, accountDomain string) string {
    147 	user := "@" + account.Acct + "@" + accountDomain
    148 
    149 	if len(account.DisplayName) == 0 {
    150 		return user
    151 	}
    152 
    153 	return account.DisplayName + " (" + user + ")"
    154 }
    155 
    156 // parseDescription returns a string description which is
    157 // safe to use as a template.HTMLAttr inside templates.
    158 func parseDescription(in string) string {
    159 	i := text.SanitizePlaintext(in)
    160 	i = strings.ReplaceAll(i, "\n", " ")
    161 	i = strings.Join(strings.Fields(i), " ")
    162 	i = html.EscapeString(i)
    163 	i = strings.ReplaceAll(i, `\`, "&bsol;")
    164 	i = trim(i, maxOGDescriptionLength)
    165 	return `content="` + i + `"`
    166 }
    167 
    168 // trim strings trim s to specified length
    169 func trim(s string, length int) string {
    170 	if len(s) < length {
    171 		return s
    172 	}
    173 
    174 	return s[:length]
    175 }