summaryrefslogtreecommitdiff
path: root/internal/typeutils
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2022-10-08 14:00:39 +0200
committerLibravatar GitHub <noreply@github.com>2022-10-08 14:00:39 +0200
commit80663061d8f361ae4bcea1307a10a40c41174ebe (patch)
tree999f9e8decfb3e6e211e8462415103819eddd1c2 /internal/typeutils
parent[chore] Standardize database queries, use `bun.Ident()` properly (#886) (diff)
downloadgotosocial-80663061d8f361ae4bcea1307a10a40c41174ebe.tar.xz
[feature] Add opt-in RSS feed for account's latest Public posts (#897)
* start adding rss functionality * add gorilla/feeds dependency * first bash at building rss feed still needs work, this is an interim commit * tidy up a bit * add publicOnly option to GetAccountLastPosted * implement rss endpoint * fix test * add initial user docs for rss * update rss logo * docs update * add rssFeed to frontend * feed -> feed.rss * enableRSS * increase rss logo size a lil bit * add rss toggle * move emojify to text package * fiddle with rss feed formatting * add Text field to test statuses * move status to rss item to typeconverter * update bun schema for enablerss * simplify 304 checking * assume account not rss * update tests * update swagger docs * allow more characters in title, trim nicer * update last posted to be more consistent
Diffstat (limited to 'internal/typeutils')
-rw-r--r--internal/typeutils/astointernal.go4
-rw-r--r--internal/typeutils/converter.go7
-rw-r--r--internal/typeutils/internaltofrontend.go3
-rw-r--r--internal/typeutils/internaltofrontend_test.go10
-rw-r--r--internal/typeutils/internaltorss.go177
-rw-r--r--internal/typeutils/internaltorss_test.go86
6 files changed, 281 insertions, 6 deletions
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index 27464809b..c44f4ebe8 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -149,6 +149,10 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a
acct.Discoverable = &d
}
+ // assume not rss feed
+ enableRSS := false
+ acct.EnableRSS = &enableRSS
+
// url property
url, err := ap.ExtractURL(accountable)
if err == nil {
diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go
index 3effb9388..b1a771458 100644
--- a/internal/typeutils/converter.go
+++ b/internal/typeutils/converter.go
@@ -23,6 +23,7 @@ import (
"net/url"
"sync"
+ "github.com/gorilla/feeds"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/api/model"
@@ -84,6 +85,12 @@ type TypeConverter interface {
DomainBlockToAPIDomainBlock(ctx context.Context, b *gtsmodel.DomainBlock, export bool) (*model.DomainBlock, error)
/*
+ INTERNAL (gts) MODEL TO FRONTEND (rss) MODEL
+ */
+
+ StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error)
+
+ /*
FRONTEND (api) MODEL TO INTERNAL (gts) MODEL
*/
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index 778b73dc9..09bd5fc7d 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -105,7 +105,7 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
// check when the last status was
var lastStatusAt string
- lastPosted, err := c.db.GetAccountLastPosted(ctx, a.ID)
+ lastPosted, err := c.db.GetAccountLastPosted(ctx, a.ID, false)
if err == nil && !lastPosted.IsZero() {
lastStatusAt = util.FormatISO8601(lastPosted)
}
@@ -219,6 +219,7 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
Fields: fields,
Suspended: suspended,
CustomCSS: a.CustomCSS,
+ EnableRSS: *a.EnableRSS,
}
c.ensureAvatar(accountFrontend)
diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go
index a13e5255c..9dd8ed4e3 100644
--- a/internal/typeutils/internaltofrontend_test.go
+++ b/internal/typeutils/internaltofrontend_test.go
@@ -40,7 +40,7 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontend() {
b, err := json.Marshal(apiAccount)
suite.NoError(err)
- suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[],"fields":[]}`, string(b))
+ suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[],"fields":[],"enable_rss":true}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct() {
@@ -55,7 +55,7 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiStruct()
b, err := json.Marshal(apiAccount)
suite.NoError(err)
- suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"fields":[]}`, string(b))
+ suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"fields":[],"enable_rss":true}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
@@ -70,7 +70,7 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendWithEmojiIDs() {
b, err := json.Marshal(apiAccount)
suite.NoError(err)
- suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"fields":[]}`, string(b))
+ suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"fields":[],"enable_rss":true}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
@@ -81,7 +81,7 @@ func (suite *InternalToFrontendTestSuite) TestAccountToFrontendSensitive() {
b, err := json.Marshal(apiAccount)
suite.NoError(err)
- suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[],"fields":[],"source":{"privacy":"public","language":"en","status_format":"plain","note":"hey yo this is my profile!","fields":[]}}`, string(b))
+ suite.Equal(`{"id":"01F8MH1H7YV1Z7D2C8K2730QBF","username":"the_mighty_zork","acct":"the_mighty_zork","display_name":"original zork (he/they)","locked":false,"bot":false,"created_at":"2022-05-20T11:09:18.000Z","note":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","url":"http://localhost:8080/@the_mighty_zork","avatar":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg","avatar_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/small/01F8MH58A357CV5K7R7TJMSH6S.jpeg","header":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","header_static":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/small/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg","followers_count":2,"following_count":2,"statuses_count":5,"last_status_at":"2022-05-20T11:37:55.000Z","emojis":[],"fields":[],"source":{"privacy":"public","language":"en","status_format":"plain","note":"hey yo this is my profile!","fields":[]},"enable_rss":true}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {
@@ -93,7 +93,7 @@ func (suite *InternalToFrontendTestSuite) TestStatusToFrontend() {
b, err := json.Marshal(apiStatus)
suite.NoError(err)
- suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[]},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":null,"preview_remote_url":null,"meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null}`, string(b))
+ suite.Equal(`{"id":"01F8MH75CBF9JFX4ZAD54N0W0R","created_at":"2021-10-20T11:36:45.000Z","in_reply_to_id":null,"in_reply_to_account_id":null,"sensitive":false,"spoiler_text":"","visibility":"public","language":"en","uri":"http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","url":"http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R","replies_count":0,"reblogs_count":0,"favourites_count":1,"favourited":true,"reblogged":false,"muted":false,"bookmarked":false,"pinned":false,"content":"hello world! #welcome ! first post on the instance :rainbow: !","reblog":null,"application":{"name":"superseriousbusiness","website":"https://superserious.business"},"account":{"id":"01F8MH17FWEB39HZJ76B6VXSKF","username":"admin","acct":"admin","display_name":"","locked":false,"bot":false,"created_at":"2022-05-17T13:10:59.000Z","note":"","url":"http://localhost:8080/@admin","avatar":"","avatar_static":"","header":"http://localhost:8080/assets/default_header.png","header_static":"http://localhost:8080/assets/default_header.png","followers_count":1,"following_count":1,"statuses_count":4,"last_status_at":"2021-10-20T10:41:37.000Z","emojis":[],"fields":[],"enable_rss":true},"media_attachments":[{"id":"01F8MH6NEM8D7527KZAECTCR76","type":"image","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","text_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg","preview_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/small/01F8MH6NEM8D7527KZAECTCR76.jpeg","remote_url":null,"preview_remote_url":null,"meta":{"original":{"width":1200,"height":630,"size":"1200x630","aspect":1.9047619},"small":{"width":256,"height":134,"size":"256x134","aspect":1.9104477},"focus":{"x":0,"y":0}},"description":"Black and white image of some 50's style text saying: Welcome On Board","blurhash":"LNJRdVM{00Rj%Mayt7j[4nWBofRj"}],"mentions":[],"tags":[{"name":"welcome","url":"http://localhost:8080/tags/welcome"}],"emojis":[{"shortcode":"rainbow","url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","static_url":"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/static/01F8MH9H8E4VG3KDYJR9EGPXCQ.png","visible_in_picker":true}],"card":null,"poll":null,"text":"hello world! #welcome ! first post on the instance :rainbow: !"}`, string(b))
}
func (suite *InternalToFrontendTestSuite) TestInstanceToFrontend() {
diff --git a/internal/typeutils/internaltorss.go b/internal/typeutils/internaltorss.go
new file mode 100644
index 000000000..609725d73
--- /dev/null
+++ b/internal/typeutils/internaltorss.go
@@ -0,0 +1,177 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 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 typeutils
+
+import (
+ "context"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/gorilla/feeds"
+ "github.com/superseriousbusiness/gotosocial/internal/api/model"
+ "github.com/superseriousbusiness/gotosocial/internal/config"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/text"
+)
+
+const (
+ rssMaxTitleChars = 128
+ rssDescriptionMaxChars = 256
+)
+
+func (c *converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error) {
+ // see https://cyber.harvard.edu/rss/rss.html
+
+ // Title -- The title of the item.
+ // example: Venice Film Festival Tries to Quit Sinking
+ var title string
+ if s.ContentWarning != "" {
+ title = trimTo(s.ContentWarning, rssMaxTitleChars)
+ } else {
+ title = trimTo(s.Text, rssMaxTitleChars)
+ }
+
+ // Link -- The URL of the item.
+ // example: http://nytimes.com/2004/12/07FEST.html
+ link := &feeds.Link{
+ Href: s.URL,
+ }
+
+ // Author -- Email address of the author of the item.
+ // example: oprah\@oxygen.net
+ if s.Account == nil {
+ a, err := c.db.GetAccountByID(ctx, s.AccountID)
+ if err != nil {
+ return nil, fmt.Errorf("error getting status author: %s", err)
+ }
+ s.Account = a
+ }
+ authorName := "@" + s.Account.Username + "@" + config.GetAccountDomain()
+ author := &feeds.Author{
+ Name: authorName,
+ }
+
+ // Source -- The RSS channel that the item came from.
+ source := &feeds.Link{
+ Href: s.Account.URL + "/feed.rss",
+ }
+
+ // Description -- The item synopsis.
+ // example: Some of the most heated chatter at the Venice Film Festival this week was about the way that the arrival of the stars at the Palazzo del Cinema was being staged.
+ descriptionBuilder := strings.Builder{}
+ descriptionBuilder.WriteString(authorName + " ")
+
+ attachmentCount := len(s.Attachments)
+ if len(s.AttachmentIDs) > attachmentCount {
+ attachmentCount = len(s.AttachmentIDs)
+ }
+ switch {
+ case attachmentCount > 1:
+ descriptionBuilder.WriteString(fmt.Sprintf("posted [%d] attachments", attachmentCount))
+ case attachmentCount == 1:
+ descriptionBuilder.WriteString("posted 1 attachment")
+ default:
+ descriptionBuilder.WriteString("made a new post")
+ }
+
+ if s.Text != "" {
+ descriptionBuilder.WriteString(": \"")
+ descriptionBuilder.WriteString(s.Text)
+ descriptionBuilder.WriteString("\"")
+ }
+
+ description := trimTo(descriptionBuilder.String(), rssDescriptionMaxChars)
+
+ // ID -- A string that uniquely identifies the item.
+ // example: http://inessential.com/2002/09/01.php#a2
+ id := s.URL
+
+ // Enclosure -- Describes a media object that is attached to the item.
+ enclosure := &feeds.Enclosure{}
+ // get first attachment if present
+ var attachment *gtsmodel.MediaAttachment
+ if len(s.Attachments) > 0 {
+ attachment = s.Attachments[0]
+ } else if len(s.AttachmentIDs) > 0 {
+ a, err := c.db.GetAttachmentByID(ctx, s.AttachmentIDs[0])
+ if err == nil {
+ attachment = a
+ }
+ }
+ if attachment != nil {
+ enclosure.Type = attachment.File.ContentType
+ enclosure.Length = strconv.Itoa(attachment.File.FileSize)
+ enclosure.Url = attachment.URL
+ }
+
+ // Content
+ apiEmojis := []model.Emoji{}
+ // the status might already have some gts emojis on it if it's not been pulled directly from the database
+ // if so, we can directly convert the gts emojis into api ones
+ if s.Emojis != nil {
+ for _, gtsEmoji := range s.Emojis {
+ apiEmoji, err := c.EmojiToAPIEmoji(ctx, gtsEmoji)
+ if err != nil {
+ log.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err)
+ continue
+ }
+ apiEmojis = append(apiEmojis, apiEmoji)
+ }
+ // the status doesn't have gts emojis on it, but it does have emoji IDs
+ // in this case, we need to pull the gts emojis from the db to convert them into api ones
+ } else {
+ for _, e := range s.EmojiIDs {
+ gtsEmoji := &gtsmodel.Emoji{}
+ if err := c.db.GetByID(ctx, e, gtsEmoji); err != nil {
+ log.Errorf("error getting emoji with id %s: %s", e, err)
+ continue
+ }
+ apiEmoji, err := c.EmojiToAPIEmoji(ctx, gtsEmoji)
+ if err != nil {
+ log.Errorf("error converting emoji with id %s: %s", gtsEmoji.ID, err)
+ continue
+ }
+ apiEmojis = append(apiEmojis, apiEmoji)
+ }
+ }
+ content := text.Emojify(apiEmojis, s.Content)
+
+ return &feeds.Item{
+ Title: title,
+ Link: link,
+ Author: author,
+ Source: source,
+ Description: description,
+ Id: id,
+ Updated: s.UpdatedAt,
+ Created: s.CreatedAt,
+ Enclosure: enclosure,
+ Content: content,
+ }, nil
+}
+
+func trimTo(in string, to int) string {
+ if len(in) <= to {
+ return in
+ }
+
+ return in[:to-3] + "..."
+}
diff --git a/internal/typeutils/internaltorss_test.go b/internal/typeutils/internaltorss_test.go
new file mode 100644
index 000000000..e30304ee9
--- /dev/null
+++ b/internal/typeutils/internaltorss_test.go
@@ -0,0 +1,86 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 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 typeutils_test
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+)
+
+type InternalToRSSTestSuite struct {
+ TypeUtilsTestSuite
+}
+
+func (suite *InternalToRSSTestSuite) TestStatusToRSSItem1() {
+ s := suite.testStatuses["local_account_1_status_1"]
+ item, err := suite.typeconverter.StatusToRSSItem(context.Background(), s)
+ suite.NoError(err)
+
+ suite.Equal("introduction post", item.Title)
+ suite.Equal("http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY", item.Link.Href)
+ suite.Equal("", item.Link.Length)
+ suite.Equal("", item.Link.Rel)
+ suite.Equal("", item.Link.Type)
+ suite.Equal("http://localhost:8080/@the_mighty_zork/feed.rss", item.Source.Href)
+ suite.Equal("", item.Source.Length)
+ suite.Equal("", item.Source.Rel)
+ suite.Equal("", item.Source.Type)
+ suite.Equal("", item.Author.Email)
+ suite.Equal("@the_mighty_zork@localhost:8080", item.Author.Name)
+ suite.Equal("@the_mighty_zork@localhost:8080 made a new post: \"hello everyone!\"", item.Description)
+ suite.Equal("http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY", item.Id)
+ suite.EqualValues(1634726437, item.Updated.Unix())
+ suite.EqualValues(1634726437, item.Created.Unix())
+ suite.Equal("", item.Enclosure.Length)
+ suite.Equal("", item.Enclosure.Type)
+ suite.Equal("", item.Enclosure.Url)
+ suite.Equal("hello everyone!", item.Content)
+}
+
+func (suite *InternalToRSSTestSuite) TestStatusToRSSItem2() {
+ s := suite.testStatuses["admin_account_status_1"]
+ item, err := suite.typeconverter.StatusToRSSItem(context.Background(), s)
+ suite.NoError(err)
+
+ suite.Equal("hello world! #welcome ! first post on the instance :rainbow: !", item.Title)
+ suite.Equal("http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R", item.Link.Href)
+ suite.Equal("", item.Link.Length)
+ suite.Equal("", item.Link.Rel)
+ suite.Equal("", item.Link.Type)
+ suite.Equal("http://localhost:8080/@admin/feed.rss", item.Source.Href)
+ suite.Equal("", item.Source.Length)
+ suite.Equal("", item.Source.Rel)
+ suite.Equal("", item.Source.Type)
+ suite.Equal("", item.Author.Email)
+ suite.Equal("@admin@localhost:8080", item.Author.Name)
+ suite.Equal("@admin@localhost:8080 posted 1 attachment: \"hello world! #welcome ! first post on the instance :rainbow: !\"", item.Description)
+ suite.Equal("http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R", item.Id)
+ suite.EqualValues(1634729805, item.Updated.Unix())
+ suite.EqualValues(1634729805, item.Created.Unix())
+ suite.Equal("62529", item.Enclosure.Length)
+ suite.Equal("image/jpeg", item.Enclosure.Type)
+ suite.Equal("http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/attachment/original/01F8MH6NEM8D7527KZAECTCR76.jpeg", item.Enclosure.Url)
+ suite.Equal("hello world! #welcome ! first post on the instance <img src=\"http://localhost:8080/fileserver/01F8MH17FWEB39HZJ76B6VXSKF/emoji/original/01F8MH9H8E4VG3KDYJR9EGPXCQ.png\" title=\":rainbow:\" alt=\":rainbow:\" class=\"emoji\"/> !", item.Content)
+}
+
+func TestInternalToRSSTestSuite(t *testing.T) {
+ suite.Run(t, new(InternalToRSSTestSuite))
+}