A social RSS reader built on the AT Protocol. glean.at
glean atproto atmosphere rss feed social app
14
fork

Configure Feed

Select the types of activity you want to include in your feed.

Improve skyreader lexicon support

+117 -7
+14 -5
internal/atproto/lexicon_external.go
··· 11 11 Via json.RawMessage `json:"via,omitempty"` 12 12 } 13 13 14 - // Note: app.skyreader.feed.subscription is not published, so thre is no tests that verifies this is correct. (ref: https://tangled.org/julien.rbrt.fr/glean/issues/3#comment-5760) 15 14 type SkyreaderSubscriptionRecord struct { 16 - CreatedAt string `json:"createdAt"` 17 - FeedURL string `json:"feedUrl"` 18 - Title string `json:"title,omitempty"` 19 - SiteURL string `json:"siteUrl,omitempty"` 15 + CreatedAt string `json:"createdAt"` 16 + FeedURL string `json:"feedUrl"` 17 + Title string `json:"title"` 18 + SiteURL string `json:"siteUrl"` 19 + Category string `json:"category"` 20 + Tags []string `json:"tags,omitempty"` 21 + UpdatedAt string `json:"updatedAt,omitempty"` 22 + Source string `json:"source,omitempty"` 23 + ExternalRef string `json:"externalRef,omitempty"` 24 + SourceType string `json:"sourceType,omitempty"` 25 + SubjectDid string `json:"subjectDid,omitempty"` 26 + CollectionNsid string `json:"collectionNsid,omitempty"` 27 + CustomTitle string `json:"customTitle,omitempty"` 28 + CustomIconURL string `json:"customIconUrl,omitempty"` 20 29 } 21 30 22 31 type MarginNoteRecord struct {
+4
internal/atproto/lexicon_test.go
··· 89 89 func TestMarginNoteRecordMatchesLexicon(t *testing.T) { 90 90 assertStructMatchesLexiconPath[MarginNoteRecord](t, lexiconPathFromRoot("at/margin/note.json")) 91 91 } 92 + 93 + func TestSkyreaderSubscriptionRecordMatchesLexicon(t *testing.T) { 94 + assertStructMatchesLexiconPath[SkyreaderSubscriptionRecord](t, lexiconPathFromRoot("app/skyreader/feed/subscription.json")) 95 + }
+6 -2
internal/atproto/stream_handler.go
··· 222 222 return nil 223 223 } 224 224 225 - f := &db.Feed{FeedURL: rec.FeedURL, Title: db.NullStr(rec.Title), SiteURL: db.NullStr(rec.SiteURL)} 225 + f := &db.Feed{ 226 + FeedURL: rec.FeedURL, 227 + Title: db.NullStr(rec.Title), 228 + SiteURL: db.NullStr(rec.SiteURL), 229 + } 226 230 _ = h.db.UpsertFeed(ctx, f) 227 - err = h.db.CreateSubscription(ctx, event.DID, rec.FeedURL, rec.Title, "", event.URI, event.CID) 231 + err = h.db.CreateSubscription(ctx, event.DID, rec.FeedURL, rec.Title, rec.Category, event.URI, event.CID) 228 232 if errors.Is(err, db.ErrDuplicateSubscription) { 229 233 return nil 230 234 }
+93
lexicons/app/skyreader/feed/subscription.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.skyreader.feed.subscription", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A subscription to an RSS/Atom feed or AT Protocol content stream", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": ["createdAt"], 12 + "properties": { 13 + "feedUrl": { 14 + "type": "string", 15 + "format": "uri", 16 + "maxLength": 2048, 17 + "description": "The URL of the RSS/Atom feed. Required for RSS subscriptions." 18 + }, 19 + "title": { 20 + "type": "string", 21 + "maxLength": 512, 22 + "description": "User-provided or auto-detected feed title" 23 + }, 24 + "siteUrl": { 25 + "type": "string", 26 + "format": "uri", 27 + "maxLength": 2048, 28 + "description": "The main website URL associated with the feed" 29 + }, 30 + "category": { 31 + "type": "string", 32 + "maxLength": 128, 33 + "description": "User-defined category/folder for organization" 34 + }, 35 + "tags": { 36 + "type": "array", 37 + "maxLength": 10, 38 + "items": { 39 + "type": "string", 40 + "maxLength": 64 41 + }, 42 + "description": "User-defined tags for the subscription" 43 + }, 44 + "createdAt": { 45 + "type": "string", 46 + "format": "datetime" 47 + }, 48 + "updatedAt": { 49 + "type": "string", 50 + "format": "datetime" 51 + }, 52 + "source": { 53 + "type": "string", 54 + "maxLength": 64, 55 + "description": "Origin of this subscription (e.g., 'leaflet', 'opml', 'manual')" 56 + }, 57 + "externalRef": { 58 + "type": "string", 59 + "format": "at-uri", 60 + "maxLength": 2048, 61 + "description": "Reference to the external source record (e.g., Leaflet subscription at-uri)" 62 + }, 63 + "sourceType": { 64 + "type": "string", 65 + "maxLength": 64, 66 + "description": "Content source type: 'rss', 'atproto.shares', 'atproto.documents', 'atproto.collection'. Omitted means RSS." 67 + }, 68 + "subjectDid": { 69 + "type": "string", 70 + "maxLength": 2048, 71 + "description": "The AT Protocol account DID. Required for atproto.* source types." 72 + }, 73 + "collectionNsid": { 74 + "type": "string", 75 + "maxLength": 256, 76 + "description": "Collection NSID for atproto.collection source type (future extensibility)." 77 + }, 78 + "customTitle": { 79 + "type": "string", 80 + "maxLength": 512, 81 + "description": "User-set custom display title override" 82 + }, 83 + "customIconUrl": { 84 + "type": "string", 85 + "format": "uri", 86 + "maxLength": 2048, 87 + "description": "User-set custom icon URL override" 88 + } 89 + } 90 + } 91 + } 92 + } 93 + }