diff options
| author | Xe Iaso <me@xeiaso.net> | 2024-05-10 12:03:29 -0400 |
|---|---|---|
| committer | Xe Iaso <me@xeiaso.net> | 2024-05-10 14:36:37 -0400 |
| commit | e8a099f150bf5bd7599a6a44298c101580b10b84 (patch) | |
| tree | 982e344f1f38ed455698373db28778f068e22349 | |
| parent | 5df84ba08d59ef9acf276f84080bf34e566ae648 (diff) | |
| download | x-e8a099f150bf5bd7599a6a44298c101580b10b84.tar.xz x-e8a099f150bf5bd7599a6a44298c101580b10b84.zip | |
cmd/mimi: add IRC announcer bot API call
Signed-off-by: Xe Iaso <me@xeiaso.net>
| -rw-r--r-- | cmd/mimi/main.go | 7 | ||||
| -rw-r--r-- | cmd/mimi/modules/irc/announcer.go | 96 | ||||
| -rw-r--r-- | cmd/mimi/modules/irc/irc.go | 68 | ||||
| -rw-r--r-- | proto/external/jsonfeed.proto | 160 | ||||
| -rw-r--r-- | proto/external/jsonfeed/external/jsonfeed.pb.go | 751 | ||||
| -rw-r--r-- | proto/external/jsonfeed/jsonfeed.pb.go | 751 | ||||
| -rw-r--r-- | proto/mimi/announce.proto | 10 | ||||
| -rw-r--r-- | proto/mimi/announce/announce.pb.go | 79 | ||||
| -rw-r--r-- | proto/mimi/announce/announce.twirp.go | 1105 | ||||
| -rw-r--r-- | proto/mimi/statuspage/mimi/statuspage.pb.go | 1018 | ||||
| -rw-r--r-- | proto/mimi/statuspage/mimi/statuspage.twirp.go | 1154 | ||||
| -rw-r--r-- | proto/yeetfile.js | 3 |
12 files changed, 5202 insertions, 0 deletions
diff --git a/cmd/mimi/main.go b/cmd/mimi/main.go index 8a386ce..398414f 100644 --- a/cmd/mimi/main.go +++ b/cmd/mimi/main.go @@ -11,6 +11,7 @@ import ( "within.website/x/cmd/mimi/internal" "within.website/x/cmd/mimi/modules/discord" "within.website/x/cmd/mimi/modules/discord/flyio" + "within.website/x/cmd/mimi/modules/irc" "within.website/x/cmd/mimi/modules/scheduling" ) @@ -39,6 +40,11 @@ func main() { d.Open() + ircBot, err := irc.New(ctx) + if err != nil { + log.Fatalf("error creating irc module: %v", err) + } + slog.Info("bot started") gs := grpc.NewServer() @@ -48,6 +54,7 @@ func main() { mux := http.NewServeMux() b.RegisterHTTP(mux) + ircBot.RegisterHTTP(mux) go func() { log.Fatal(gs.Serve(lis)) diff --git a/cmd/mimi/modules/irc/announcer.go b/cmd/mimi/modules/irc/announcer.go new file mode 100644 index 0000000..eb58798 --- /dev/null +++ b/cmd/mimi/modules/irc/announcer.go @@ -0,0 +1,96 @@ +package irc + +import ( + "context" + "log/slog" + "net/http" + "os" + "path/filepath" + "sync" + + "github.com/twitchtv/twirp" + "google.golang.org/protobuf/types/known/emptypb" + "tailscale.com/jsondb" + "within.website/x/cmd/mimi/internal" + "within.website/x/proto/external/jsonfeed" + "within.website/x/proto/mimi/announce" +) + +func (m *Module) RegisterHTTP(mux *http.ServeMux) { + mux.Handle(announce.AnnouncePathPrefix, announce.NewAnnounceServer(&AnnounceService{Module: m})) +} + +type AnnounceService struct { + *Module + db *jsondb.DB[State] + lock sync.Mutex +} + +type State struct { + Announced map[string]struct{} +} + +func (a *AnnounceService) initDB() error { + if a.db != nil { + slog.Debug("db already initialized") + return nil + } + + fname := filepath.Join(internal.DataDir(), "announced.json") + slog.Debug("opening jsondb for announced items", "fname", fname) + + if _, err := os.Stat(fname); os.IsNotExist(err) { + os.WriteFile(fname, []byte(`{"Announced":{}}`), 0644) + } + + db, err := jsondb.Open[State](fname) + if err != nil { + return err + } + db.Save() + + if db.Data == nil { + slog.Debug("creating empty state") + db.Data = &State{ + Announced: map[string]struct{}{ + "https://within.website/": struct{}{}, + }, + } + if err := db.Save(); err != nil { + return err + } + } + + a.db = db + + return nil +} + +func (a *AnnounceService) Announce(ctx context.Context, msg *jsonfeed.Item) (*emptypb.Empty, error) { + a.lock.Lock() + defer a.lock.Unlock() + + if err := a.initDB(); err != nil { + return nil, twirp.InternalErrorWith(err) + } + + if msg.Title == "" { + return nil, twirp.NewError(twirp.InvalidArgument, "missing title") + } + if msg.Url == "" { + return nil, twirp.NewError(twirp.InvalidArgument, "missing url") + } + + if _, ok := a.db.Data.Announced[msg.Url]; ok { + return &emptypb.Empty{}, nil + } + + a.db.Data.Announced[msg.Url] = struct{}{} + if err := a.db.Save(); err != nil { + return nil, twirp.InternalErrorWith(err) + } + + a.conn.Privmsgf(*ircChannel, "New article :: %s :: %s", msg.Title, msg.Url) + + return &emptypb.Empty{}, nil +} diff --git a/cmd/mimi/modules/irc/irc.go b/cmd/mimi/modules/irc/irc.go new file mode 100644 index 0000000..03d2bb8 --- /dev/null +++ b/cmd/mimi/modules/irc/irc.go @@ -0,0 +1,68 @@ +package irc + +import ( + "context" + "crypto/tls" + "flag" + "fmt" + "log/slog" + + ircevent "github.com/thoj/go-ircevent" +) + +var ( + ircServer = flag.String("irc-server", "irc.libera.chat:6697", "IRC server to connect to") + ircUsername = flag.String("irc-username", "[Mara]", "IRC username") + ircIdent = flag.String("irc-ident", "mara", "IRC ident") + ircPassword = flag.String("irc-password", "", "IRC password") + ircChannel = flag.String("irc-channel", "#mimi", "IRC channel to join") +) + +type Module struct { + conn *ircevent.Connection +} + +func New(ctx context.Context) (*Module, error) { + conn := ircevent.IRC(*ircUsername, *ircIdent) + conn.UseTLS = true + conn.UseSASL = false + conn.SASLLogin = *ircUsername + conn.SASLPassword = *ircPassword + conn.SASLMech = "PLAIN" + + conn.TLSConfig = &tls.Config{ + ServerName: "irc.libera.chat", + } + + conn.AddCallback("001", func(e *ircevent.Event) { + conn.Privmsgf("NickServ", "IDENTIFY %s %s", *ircUsername, *ircPassword) + }) + + conn.AddCallback("900", func(e *ircevent.Event) { + conn.Join(*ircChannel) + slog.Debug("joined channel", "channel", *ircChannel) + }) + if err := conn.Connect(*ircServer); err != nil { + return nil, fmt.Errorf("irc: error connecting to IRC server: %w", err) + } + + go func() { + <-ctx.Done() + conn.Quit() + }() + + go func() { + for { + select { + case <-ctx.Done(): + return + case err := <-conn.Error: + slog.Error("error from IRC server", "err", err) + } + } + }() + + return &Module{ + conn: conn, + }, nil +} diff --git a/proto/external/jsonfeed.proto b/proto/external/jsonfeed.proto new file mode 100644 index 0000000..b3d0901 --- /dev/null +++ b/proto/external/jsonfeed.proto @@ -0,0 +1,160 @@ +syntax = "proto3"; +package jsonfeed; +option go_package = "within.website/x/proto/external/jsonfeed"; + +import "google/protobuf/timestamp.proto"; + +// Feed is the root of a Proto Feed document. A feed must at least contain a +// title and items. +message Feed { + // (required, string) is the name of the feed, which will often correspond to + // the name of the website (blog, for instance), though not necessarily. + string title = 2; + // (optional but strongly recommended, string) is the URL of the resource that + // the feed describes. This resource may or may not actually be a “home” page, + // but it should be an HTML page. If a feed is published on the public web, + // this should be considered as required. But it may not make sense in the + // case of a file created on a desktop computer, when that file is not shared + // or is shared only privately. + string home_page_url = 3; + // (optional but strongly recommended, string) is the URL of the feed, and + // serves as the unique identifier for the feed. As with home_page_url, this + // should be considered required for feeds on the public web. + string feed_url = 4; + // (optional, string) provides more detail, beyond the title, on what the feed + // is about. A feed reader may display this text. + string description = 5; + // (optional, string) is a description of the purpose of the feed. This is for + // the use of people looking at the raw Protobuf, and should be ignored by + // feed readers. + string user_comment = 6; + // (optional, string) is the URL of an image for the feed suitable to be used + // in a source list. It should be square and relatively large — such as 512 x + // 512 — so that it can be scaled down and so that it can look good on retina + // displays. It should use transparency where appropriate, since it may be + // rendered on a non-white background. + string icon = 7; + // (optional, string) is the URL of an image for the feed suitable to be used + // in a source list. It should be square and relatively small, but not smaller + // than 64 x 64. + string favicon = 8; + // (optional, array of objects) specifies the feed authors. + repeated Author authors = 9; + // (optional, string) is the primary language for the feed. + string language = 10; + // (optional, boolean) says whether or not the feed is finished — that is, + // whether or not it will ever update again. A feed for a temporary event, + // such as an instance of the Olympics, could expire. If the value is true, + // then it’s expired. Any other value, or the absence of expired, means the + // feed may continue to update. + bool expired = 11; + // (required, array of objects) contains the items in the feed. This is the + // most important element of the feed after the version field. Each item is a + // story, blog post, article, photograph, video, or other thing. For example, + // if a feed contains a long article, a podcast episode, and a photo, those + // three items would be included in items. + repeated Item items = 12; +} + +// Author is an object representing the author of the feed or item. +message Author { + // (optional, string) is the author’s name. + string name = 1; + // (optional, string) is the URL of a site owned by the author. It could be a + // blog, micro-blog, Twitter account, and so on. Ideally the linked-to page + // provides a way to contact the author, but that’s not required. The URL + // could be a mailto: link, though we suspect that will be rare. + string url = 2; + // (optional, string) is the URL for an image for the author. As with icon, it + // should be square and relatively large — such as 512 x 512 pixels — and + // should use transparency where appropriate, since it may be rendered on a + // non-white background. + string avatar = 3; +} + +// Item is an object representing a single story, blog post, article, +// photograph, video, or other thing within a feed. +message Item { + // (required, string) is unique for that item for that feed over time. If an + // item is ever updated, the id should be unchanged. New items should never + // use a previously-used id. If an id is presented as a number or other type, + // a JSON Feed reader must coerce it to a string. Ideally, the id is the full + // URL of the resource described by the item, since URLs make great unique + // identifiers. + string id = 1; + // (optional, string) is the URL of the resource described by the item. It’s + // the permalink. This may be the same as the id — but should be present + // regardless. + string url = 2; + // (optional, string) is the URL of a page elsewhere. This is especially + // useful for linkblogs. If url links to where you’re talking about a thing, + // then external_url links to the thing you’re talking about. + string external_url = 3; + // (optional, string) is plain text. Microblog items in particular may omit + // titles. + string title = 4; + // (optional, string) is the body of the item. It can be plain text, HTML, or + // a snippet of Markdown. (It should not be the entire Markdown document; just + // a snippet.) This is complete enough that it can be displayed alone in a + // reader. + string content_text = 5; + // (optional, string) is the body of the item. It can be plain text, HTML, or + // a snippet of Markdown. (It should not be the entire Markdown document; just + // a snippet.) This is complete enough that it can be displayed alone in a + // reader. + string content_html = 6; + // (optional, string) is a plain text sentence or two describing the item. + // This might be presented in a timeline, for instance, where a detail view + // would display all of content_html or content_text. + string summary = 7; + // (optional, string) is the URL of the main image for the item. This image + // may also appear in the content_html — if so, it’s a hint to the feed reader + // that this is the main, featured image. Even if it’s not, it will appear in + // the detail view. Images should be square, with a 4:3 aspect ratio. (We will + // be flexible on this in the future.) + string image = 8; + // (optional, string) is the URL of an image to use as a banner. Some blogging + // systems (such as Medium) display a different banner image in the list view + // from the detail view. In those systems, this image should be used in the + // list view, and image in the detail view. + string banner_image = 9; + // (optional, string) specifies the date in RFC 3339 format. + google.protobuf.Timestamp date_published = 10; + // (optional, string) specifies the modification date in RFC 3339 format. + google.protobuf.Timestamp date_modified = 11; + // (optional, array of objects) has the same structure as the top-level + // authors. If not specified in an item, then the top-level authors, if + // present, are the authors of the item. + repeated Author authors = 12; + // (optional, array of strings) can have any plain text values you want. Tags + // tend to be just one word, but they may be anything. Note: they are not the + // equivalent of Twitter hashtags. Some blogging systems and other feed + // formats call these categories. + repeated string tags = 13; + // (optional, string) is the language for this item, using the same format as + // the top-level language field. The value can be different than the primary + // language for the feed when a specific item is written in a different + // language than other items in the feed. + string language = 14; + // (optional, array of objects) specifies the attachments associated with the + // item. Attachments are files that are associated with an item. The value of + // the attachments field is an array of objects, each of which has a url + // field, and other fields as specified in the attachment object definition. + repeated Attachment attachments = 15; +} + +// Attachment is an object representing a file associated with an item. +message Attachment { + // (required, string) specifies the location of the attachment. + string url = 1; + // (required, string) specifies the type of the attachment, such as + // “audio/mpeg.” + string mime_type = 2; + // (optional, string) specifies the title of the attachment. + string title = 3; + // (optional, number) specifies how large the file is. + int32 size_in_bytes = 4; + // (optional, number) specifies how long it takes to listen to or watch, when + // played at normal speed. + int32 duration_in_seconds = 5; +}
\ No newline at end of file diff --git a/proto/external/jsonfeed/external/jsonfeed.pb.go b/proto/external/jsonfeed/external/jsonfeed.pb.go new file mode 100644 index 0000000..d3e5cd9 --- /dev/null +++ b/proto/external/jsonfeed/external/jsonfeed.pb.go @@ -0,0 +1,751 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v4.25.3 +// source: external/jsonfeed.proto + +package jsonfeed + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// Feed is the root of a Proto Feed document. A feed must at least contain a +// title and items. +type Feed struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // (required, string) is the name of the feed, which will often correspond to + // the name of the website (blog, for instance), though not necessarily. + Title string `protobuf:"bytes,2,opt,name=title,proto3" json:"title,omitempty"` + // (optional but strongly recommended, string) is the URL of the resource that + // the feed describes. This resource may or may not actually be a “home” page, + // but it should be an HTML page. If a feed is published on the public web, + // this should be considered as required. But it may not make sense in the + // case of a file created on a desktop computer, when that file is not shared + // or is shared only privately. + HomePageUrl string `protobuf:"bytes,3,opt,name=home_page_url,json=homePageUrl,proto3" json:"home_page_url,omitempty"` + // (optional but strongly recommended, string) is the URL of the feed, and + // serves as the unique identifier for the feed. As with home_page_url, this + // should be considered required for feeds on the public web. + FeedUrl string `protobuf:"bytes,4,opt,name=feed_url,json=feedUrl,proto3" json:"feed_url,omitempty"` + // (optional, string) provides more detail, beyond the title, on what the feed + // is about. A feed reader may display this text. + Description string `protobuf:"bytes,5,opt,name=description,proto3" json:"description,omitempty"` + // (optional, string) is a description of the purpose of the feed. This is for + // the use of people looking at the raw Protobuf, and should be ignored by + // feed readers. + UserComment string `protobuf:"bytes,6,opt,name=user_comment,json=userComment,proto3" json:"user_comment,omitempty"` + // (optional, string) is the URL of an image for the feed suitable to be used + // in a source list. It should be square and relatively large — such as 512 x + // 512 — so that it can be scaled down and so that it can look good on retina + // displays. It should use transparency where appropriate, since it may be + // rendered on a non-white background. + Icon string `protobuf:"bytes,7,opt,name=icon,proto3" json:"icon,omitempty"` + // (optional, string) is the URL of an image for the feed suitable to be used + // in a source list. It should be square and relatively small, but not smaller + // than 64 x 64. + Favicon string `protobuf:"bytes,8,opt,name=favicon,proto3" json:"favicon,omitempty"` + // (optional, array of objects) specifies the feed authors. + Authors []*Author `protobuf:"bytes,9,rep,name=authors,proto3" json:"authors,omitempty"` + // (optional, string) is the primary language for the feed. + Language string `protobuf:"bytes,10,opt,name=language,proto3" json:"language,omitempty"` + // (optional, boolean) says whether or not the feed is finished — that is, + // whether or not it will ever update again. A feed for a temporary event, + // such as an instance of the Olympics, could expire. If the value is true, + // then it’s expired. Any other value, or the absence of expired, means the + // feed may continue to update. + Expired bool `protobuf:"varint,11,opt,name=expired,proto3" json:"expired,omitempty"` + // (required, array of objects) contains the items in the feed. This is the + // most important element of the feed after the version field. Each item is a + // story, blog post, article, photograph, video, or other thing. For example, + // if a feed contains a long article, a podcast episode, and a photo, those + // three items would be included in items. + Items []*Item `protobuf:"bytes,12,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *Feed) Reset() { + *x = Feed{} + if protoimpl.UnsafeEnabled { + mi := &file_external_jsonfeed_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Feed) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Feed) ProtoMessage() {} + +func (x *Feed) ProtoReflect() protoreflect.Message { + mi := &file_external_jsonfeed_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Feed.ProtoReflect.Descriptor instead. +func (*Feed) Descriptor() ([]byte, []int) { + return file_external_jsonfeed_proto_rawDescGZIP(), []int{0} +} + +func (x *Feed) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Feed) GetHomePageUrl() string { + if x != nil { + return x.HomePageUrl + } + return "" +} + +func (x *Feed) GetFeedUrl() string { + if x != nil { + return x.FeedUrl + } + return "" +} + +func (x *Feed) GetDescription() string { + if x != nil { + return x.Description + } + return "" +} + +func (x *Feed) GetUserComment() string { + if x != nil { + return x.UserComment + } + return "" +} + +func (x *Feed) GetIcon() string { + if x != nil { + return x.Icon + } + return "" +} + +func (x *Feed) GetFavicon() string { + if x != nil { + return x.Favicon + } + return "" +} + +func (x *Feed) GetAuthors() []*Author { + if x != nil { + return x.Authors + } + return nil +} + +func (x *Feed) GetLanguage() string { + if x != nil { + return x.Language + } + return "" +} + +func (x *Feed) GetExpired() bool { + if x != nil { + return x.Expired + } + return false +} + +func (x *Feed) GetItems() []*Item { + if x != nil { + return x.Items + } + return nil +} + +// Author is an object representing the author of the feed or item. +type Author struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // (optional, string) is the author’s name. + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + // (optional, string) is the URL of a site owned by the author. It could be a + // blog, micro-blog, Twitter account, and so on. Ideally the linked-to page + // provides a way to contact the author, but that’s not required. The URL + // could be a mailto: link, though we suspect that will be rare. + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + // (optional, string) is the URL for an image for the author. As with icon, it + // should be square and relatively large — such as 512 x 512 pixels — and + // should use transparency where appropriate, since it may be rendered on a + // non-white background. + Avatar string `protobuf:"bytes,3,opt,name=avatar,proto3" json:"avatar,omitempty"` +} + +func (x *Author) Reset() { + *x = Author{} + if protoimpl.UnsafeEnabled { + mi := &file_external_jsonfeed_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Author) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Author) ProtoMessage() {} + +func (x *Author) ProtoReflect() protoreflect.Message { + mi := &file_external_jsonfeed_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Author.ProtoReflect.Descriptor instead. +func (*Author) Descriptor() ([]byte, []int) { + return file_external_jsonfeed_proto_rawDescGZIP(), []int{1} +} + +func (x *Author) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *Author) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Author) GetAvatar() string { + if x != nil { + return x.Avatar + } + return "" +} + +// Item is an object representing a single story, blog post, article, +// photograph, video, or other thing within a feed. +type Item struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // (required, string) is unique for that item for that feed over time. If an + // item is ever updated, the id should be unchanged. New items should never + // use a previously-used id. If an id is presented as a number or other type, + // a JSON Feed reader must coerce it to a string. Ideally, the id is the full + // URL of the resource described by the item, since URLs make great unique + // identifiers. + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // (optional, string) is the URL of the resource described by the item. It’s + // the permalink. This may be the same as the id — but should be present + // regardless. + Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"` + // (optional, string) is the URL of a page elsewhere. This is especially + // useful for linkblogs. If url links to where you’re talking about a thing, + // then external_url links to the thing you’re talking about. + ExternalUrl string `protobuf:"bytes,3,opt,name=external_url,json=externalUrl,proto3" json:"external_url,omitempty"` + // (optional, string) is plain text. Microblog items in particular may omit + // titles. + Title string `protobuf:"bytes,4,opt,name=title,proto3" json:"title,omitempty"` + // (optional, string) is the body of the item. It can be plain text, HTML, or + // a snippet of Markdown. (It should not be the entire Markdown document; just + // a snippet.) This is complete enough that it can be displayed alone in a + // reader. + ContentText string `protobuf:"bytes,5,opt,name=content_text,json=contentText,proto3" json:"content_text,omitempty"` + // (optional, string) is the body of the item. It can be plain text, HTML, or + // a snippet of Markdown. (It should not be the entire Markdown document; just + // a snippet.) This is complete enough that it can be displayed alone in a + // reader. + ContentHtml string `protobuf:"bytes,6,opt,name=content_html,json=contentHtml,proto3" json:"content_html,omitempty"` + // (optional, string) is a plain text sentence or two describing the item. + // This might be presented in a timeline, for instance, where a detail view + // would display all of content_html or content_text. + Summary string `protobuf:"bytes,7,opt,name=summary,proto3" json:"summary,omitempty"` + // (optional, string) is the URL of the main image for the item. This image + // may also appear in the content_html — if so, it’s a hint to the feed reader + // that this is the main, featured image. Even if it’s not, it will appear in + // the detail view. Images should be square, with a 4:3 aspect ratio. (We will + // be flexible on this in the future.) + Image string `protobuf:"bytes,8,opt,name=image,proto3" json:"image,omitempty"` + // (optional, string) is the URL of an image to use as a banner. Some blogging + // systems (such as Medium) display a different banner image in the list view + // from the detail view. In those systems, this image should be used in the + // list view, and image in the detail view. + BannerImage string `protobuf:"bytes,9,opt,name=banner_image,json=bannerImage,proto3" json:"banner_image,omitempty"` + // (optional, string) specifies the date in RFC 3339 format. + DatePublished *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=date_published,json=datePublished,proto3" json:"date_published,omitempty"` + // (optional, string) specifies the modification date in RFC 3339 format. + DateModified *timestamppb.Timestamp `protobuf:"bytes,11,opt,name=date_modified,json=dateModified,proto3" json:"date_modified,omitempty"` + // (optional, array of objects) has the same structure as the top-level + // authors. If not specified in an item, then the top-level authors, if + // present, are the authors of the item. + Authors []*Author `protobuf:"bytes,12,rep,name=authors,proto3" json:"authors,omitempty"` + // (optional, array of strings) can have any plain text values you want. Tags + // tend to be just one word, but they may be anything. Note: they are not the + // equivalent of Twitter hashtags. Some blogging systems and other feed + // formats call these categories. + Tags []string `protobuf:"bytes,13,rep,name=tags,proto3" json:"tags,omitempty"` + // (optional, string) is the language for this item, using the same format as + // the top-level language field. The value can be different than the primary + // language for the feed when a specific item is written in a different + // language than other items in the feed. + Language string `protobuf:"bytes,14,opt,name=language,proto3" json:"language,omitempty"` + // (optional, array of objects) specifies the attachments associated with the + // item. Attachments are files that are associated with an item. The value of + // the attachments field is an array of objects, each of which has a url + // field, and other fields as specified in the attachment object definition. + Attachments []*Attachment `protobuf:"bytes,15,rep,name=attachments,proto3" json:"attachments,omitempty"` +} + +func (x *Item) Reset() { + *x = Item{} + if protoimpl.UnsafeEnabled { + mi := &file_external_jsonfeed_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Item) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Item) ProtoMessage() {} + +func (x *Item) ProtoReflect() protoreflect.Message { + mi := &file_external_jsonfeed_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Item.ProtoReflect.Descriptor instead. +func (*Item) Descriptor() ([]byte, []int) { + return file_external_jsonfeed_proto_rawDescGZIP(), []int{2} +} + +func (x *Item) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *Item) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Item) GetExternalUrl() string { + if x != nil { + return x.ExternalUrl + } + return "" +} + +func (x *Item) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Item) GetContentText() string { + if x != nil { + return x.ContentText + } + return "" +} + +func (x *Item) GetContentHtml() string { + if x != nil { + return x.ContentHtml + } + return "" +} + +func (x *Item) GetSummary() string { + if x != nil { + return x.Summary + } + return "" +} + +func (x *Item) GetImage() string { + if x != nil { + return x.Image + } + return "" +} + +func (x *Item) GetBannerImage() string { + if x != nil { + return x.BannerImage + } + return "" +} + +func (x *Item) GetDatePublished() *timestamppb.Timestamp { + if x != nil { + return x.DatePublished + } + return nil +} + +func (x *Item) GetDateModified() *timestamppb.Timestamp { + if x != nil { + return x.DateModified + } + return nil +} + +func (x *Item) GetAuthors() []*Author { + if x != nil { + return x.Authors + } + return nil +} + +func (x *Item) GetTags() []string { + if x != nil { + return x.Tags + } + return nil +} + +func (x *Item) GetLanguage() string { + if x != nil { + return x.Language + } + return "" +} + +func (x *Item) GetAttachments() []*Attachment { + if x != nil { + return x.Attachments + } + return nil +} + +// Attachment is an object representing a file associated with an item. +type Attachment struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + // (required, string) specifies the location of the attachment. + Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` + // (required, string) specifies the type of the attachment, such as + // “audio/mpeg.” + MimeType string `protobuf:"bytes,2,opt,name=mime_type,json=mimeType,proto3" json:"mime_type,omitempty"` + // (optional, string) specifies the title of the attachment. + Title string `protobuf:"bytes,3,opt,name=title,proto3" json:"title,omitempty"` + // (optional, number) specifies how large the file is. + SizeInBytes int32 `protobuf:"varint,4,opt,name=size_in_bytes,json=sizeInBytes,proto3" json:"size_in_bytes,omitempty"` + // (optional, number) specifies how long it takes to listen to or watch, when + // played at normal speed. + DurationInSeconds int32 `protobuf:"varint,5,opt,name=duration_in_seconds,json=durationInSeconds,proto3" json:"duration_in_seconds,omitempty"` +} + +func (x *Attachment) Reset() { + *x = Attachment{} + if protoimpl.UnsafeEnabled { + mi := &file_external_jsonfeed_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Attachment) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Attachment) ProtoMessage() {} + +func (x *Attachment) ProtoReflect() protoreflect.Message { + mi := &file_external_jsonfeed_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Attachment.ProtoReflect.Descriptor instead. +func (*Attachment) Descriptor() ([]byte, []int) { + return file_external_jsonfeed_proto_rawDescGZIP(), []int{3} +} + +func (x *Attachment) GetUrl() string { + if x != nil { + return x.Url + } + return "" +} + +func (x *Attachment) GetMimeType() string { + if x != nil { + return x.MimeType + } + return "" +} + +func (x *Attachment) GetTitle() string { + if x != nil { + return x.Title + } + return "" +} + +func (x *Attachment) GetSizeInBytes() int32 { + if x != nil { + return x.SizeInBytes + } + return 0 +} + +func (x *Attachment) GetDurationInSeconds() int32 { + if x != nil { + return x.DurationInSeconds + } + return 0 +} + +var File_external_jsonfeed_proto protoreflect.FileDescriptor + +var file_external_jsonfeed_proto_rawDesc = []byte{ + 0x0a, 0x17, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x6a, 0x73, 0x6f, 0x6e, 0x66, + 0x65, 0x65, 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x08, 0x6a, 0x73, 0x6f, 0x6e, 0x66, + 0x65, 0x65, 0x64, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd6, 0x02, 0x0a, 0x04, 0x46, 0x65, 0x65, 0x64, 0x12, 0x14, 0x0a, + 0x05, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x74, 0x69, + 0x74, 0x6c, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x68, 0x6f, 0x6d, 0x65, 0x5f, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x68, 0x6f, 0x6d, 0x65, + 0x50, 0x61, 0x67, 0x65, 0x55, 0x72, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x66, 0x65, 0x65, 0x64, 0x5f, + 0x75, 0x72, 0x6 |
