// 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
ℹ️ Note from your.instance.com: 2 attachment(s) in this status were not downloaded. Treat the following external link(s) with care:
//ℹ️ Note from `) note.WriteString(config.GetHost()) note.WriteString(`: `) note.WriteString(strconv.Itoa(len(nonLocal))) if len(nonLocal) > 1 { // Use plural word form. note.WriteString(` attachments in this status were not downloaded. ` + `Treat the following external links with care:`) } else { // Use singular word form. note.WriteString(` attachment in this status was not downloaded. ` + `Treat the following external link with care:`) } note.WriteString(`
ℹ️ Note from ` + host + `: `) note.WriteString(`This reply is pending your approval. You can quickly accept it by liking, boosting or replying to it. You can also accept or reject it at the following link: `) note.WriteString(``) note.WriteString(settingsURL) note.WriteString(`.`) note.WriteString(`
`) return text.SanitizeToHTML(note.String()), nil } // ContentToContentLanguage tries to // extract a content string and language // tag string from the given intermediary // content. // // Either/both of the returned strings may // be empty, depending on how things go. func ContentToContentLanguage( ctx context.Context, content gtsmodel.Content, ) ( string, // content string, // language ) { var ( contentStr string langTagStr string ) switch contentMap := content.ContentMap; { // Simplest case: no `contentMap`. // Return `content`, even if empty. case contentMap == nil: return content.Content, "" // `content` and `contentMap` set. // Try to infer "primary" language. case content.Content != "": // Assume `content` is intended // primary content, and look for // corresponding language tag. contentStr = content.Content for t, c := range contentMap { if contentStr == c { langTagStr = t break } } // `content` not set; `contentMap` // is set with only one value. // This must be the "primary" lang. case len(contentMap) == 1: // Use an empty loop to // get the values we want. // nolint:revive for langTagStr, contentStr = range contentMap { } // Only `contentMap` is set, with more // than one value. Map order is not // guaranteed so we can't know the // "primary" language. // // Try to select content using our // instance's configured languages. // // In case of no hits, just take the // first tag and content in the map. default: instanceLangs := config.GetInstanceLanguages() for _, langTagStr = range instanceLangs.TagStrs() { if contentStr = contentMap[langTagStr]; contentStr != "" { // Hit! break } } // If nothing found, just take // the first entry we can get by // breaking after the first iter. if contentStr == "" { for langTagStr, contentStr = range contentMap { break } } } if langTagStr != "" { // Found a lang tag for this content, // make sure it's valid / parseable. lang, err := language.Parse(langTagStr) if err != nil { log.Warnf( ctx, "could not parse %s as BCP47 language tag in status contentMap: %v", langTagStr, err, ) } else { // Inferred the language! // Use normalized version. langTagStr = lang.TagStr } } return contentStr, langTagStr } // filterableFields returns text fields from // a status that we might want to filter on: // // - content warning // - content (converted to plaintext from HTML) // - media descriptions // - poll options // // Each field should be filtered separately. // This avoids scenarios where false-positive // multiple-word matches can be made by matching // the last word of one field + the first word // of the next field together. func filterableFields(s *gtsmodel.Status) []string { // Estimate length of fields. fieldCount := 2 + len(s.Attachments) if s.Poll != nil { fieldCount += len(s.Poll.Options) } fields := make([]string, 0, fieldCount) // Content warning / title. if s.ContentWarning != "" { fields = append(fields, s.ContentWarning) } // Status content. Though we have raw text // available for statuses created on our // instance, use the html2text version to // remove markdown-formatting characters // and ensure more consistent filtering. if s.Content != "" { text := html2text.HTML2TextWithOptions( s.Content, html2text.WithLinksInnerText(), html2text.WithUnixLineBreaks(), ) if text != "" { fields = append(fields, text) } } // Media descriptions. for _, attachment := range s.Attachments { if attachment.Description != "" { fields = append(fields, attachment.Description) } } // Poll options. if s.Poll != nil { for _, opt := range s.Poll.Options { if opt != "" { fields = append(fields, opt) } } } return fields }