1 fd79aa76 2024-03-28 o // Package hn provides a filesystem interface to items on Hacker News.
19 fd79aa76 2024-03-28 o const APIRoot = "https://hacker-news.firebaseio.com/v0"
21 fd79aa76 2024-03-28 o type Item struct {
32 fd79aa76 2024-03-28 o func (it *Item) Name() string { return strconv.Itoa(it.ID) }
33 fd79aa76 2024-03-28 o func (it *Item) Size() int64 { r := toMessage(it); return r.Size() }
34 fd79aa76 2024-03-28 o func (it *Item) Mode() fs.FileMode { return 0o444 }
35 fd79aa76 2024-03-28 o func (it *Item) ModTime() time.Time { return time.Unix(int64(it.Time), 0) }
36 fd79aa76 2024-03-28 o func (it *Item) IsDir() bool { return false }
37 fd79aa76 2024-03-28 o func (it *Item) Sys() any { return nil }
39 fd79aa76 2024-03-28 o type FS struct {
43 fd79aa76 2024-03-28 o func CacheDirFS(name string) *FS {
44 fd79aa76 2024-03-28 o return &FS{cache: os.DirFS(name)}
47 fd79aa76 2024-03-28 o func (fsys *FS) Open(name string) (fs.File, error) {
48 fd79aa76 2024-03-28 o if !fs.ValidPath(name) {
49 fd79aa76 2024-03-28 o return nil, &fs.PathError{"open", name, fs.ErrInvalid}
51 fd79aa76 2024-03-28 o name = path.Clean(name)
54 fd79aa76 2024-03-28 o return nil, fmt.Errorf("TODO")
56 fd79aa76 2024-03-28 o if _, err := strconv.Atoi(name); err != nil {
57 fd79aa76 2024-03-28 o return nil, &fs.PathError{"open", name, fs.ErrNotExist}
60 fd79aa76 2024-03-28 o if fsys.cache != nil {
61 fd79aa76 2024-03-28 o if f, err := fsys.cache.Open(name); err == nil {
66 fd79aa76 2024-03-28 o u := fmt.Sprintf("%s/item/%s.json", APIRoot, name)
67 fd79aa76 2024-03-28 o resp, err := http.Get(u)
68 fd79aa76 2024-03-28 o if err != nil {
69 fd79aa76 2024-03-28 o return nil, err
71 fd79aa76 2024-03-28 o if resp.StatusCode != http.StatusOK {
72 fd79aa76 2024-03-28 o return nil, err
74 fd79aa76 2024-03-28 o return &file{rc: resp.Body}, nil
77 fd79aa76 2024-03-28 o type file struct {
78 fd79aa76 2024-03-28 o rc io.ReadCloser
80 fd79aa76 2024-03-28 o msg *bytes.Reader
83 fd79aa76 2024-03-28 o func (f *file) Read(p []byte) (int, error) {
85 fd79aa76 2024-03-28 o if f.item == nil {
86 fd79aa76 2024-03-28 o if err := json.NewDecoder(f.rc).Decode(&f.item); err != nil {
87 fd79aa76 2024-03-28 o return n, fmt.Errorf("decode item: %v", err)
90 fd79aa76 2024-03-28 o if f.msg == nil {
91 fd79aa76 2024-03-28 o f.msg = toMessage(f.item)
93 fd79aa76 2024-03-28 o return f.msg.Read(p)
96 fd79aa76 2024-03-28 o func (f *file) Stat() (fs.FileInfo, error) { return f.item, nil }
98 fd79aa76 2024-03-28 o func (f *file) Close() error {
100 fd79aa76 2024-03-28 o return f.rc.Close()
103 fd79aa76 2024-03-28 o func toMessage(item *Item) *bytes.Reader {
104 fd79aa76 2024-03-28 o buf := &bytes.Buffer{}
105 fd79aa76 2024-03-28 o fmt.Fprintf(buf, "From: %s\n", item.By)
106 fd79aa76 2024-03-28 o fmt.Fprintf(buf, "Message-ID: <%d@news.ycombinator.com>\n", item.ID)
107 fd79aa76 2024-03-28 o fmt.Fprintf(buf, "Date: %s\n", time.Unix(int64(item.Time), 0).Format(time.RFC1123Z))
108 fd79aa76 2024-03-28 o if item.Parent != 0 {
109 fd79aa76 2024-03-28 o fmt.Fprintf(buf, "References: <%d@news.ycombinator.com>\n", item.Parent)
111 fd79aa76 2024-03-28 o if item.Title != "" {
112 fd79aa76 2024-03-28 o fmt.Fprintf(buf, "Subject: %s\n", item.Title)
114 fd79aa76 2024-03-28 o fmt.Fprintln(buf)
115 fd79aa76 2024-03-28 o if item.URL != "" {
116 fd79aa76 2024-03-28 o fmt.Fprintln(buf, item.URL)
118 fd79aa76 2024-03-28 o if item.Text != "" {
119 fd79aa76 2024-03-28 o fmt.Fprintln(buf, strings.ReplaceAll(html.UnescapeString(item.Text), "<p>", "\n\n"))
121 fd79aa76 2024-03-28 o return bytes.NewReader(buf.Bytes())