]> git.r.bdr.sh - rbdr/mobius/blame - hotline/news.go
Limit chat message size to 8192 bytes
[rbdr/mobius] / hotline / news.go
CommitLineData
6988a057
JH
1package hotline
2
3import (
6988a057 4 "encoding/binary"
9cf66aea
JH
5 "io"
6 "slices"
6988a057 7 "sort"
6988a057
JH
8)
9
2d92d26e
JH
10const defaultNewsDateFormat = "Jan02 15:04" // Jun23 20:49
11
12const defaultNewsTemplate = `From %s (%s):
13
14%s
15
16__________________________________________________________`
17
0ed51327 18// ThreadedNews is the top level struct containing all threaded news categories, bundles, and articles
6988a057
JH
19type ThreadedNews struct {
20 Categories map[string]NewsCategoryListData15 `yaml:"Categories"`
21}
22
23type NewsCategoryListData15 struct {
0ed51327
JH
24 Type [2]byte `yaml:"Type,flow"` // Bundle (2) or category (3)
25 Name string `yaml:"Name"`
6988a057
JH
26 Articles map[uint32]*NewsArtData `yaml:"Articles"` // Optional, if Type is Category
27 SubCats map[string]NewsCategoryListData15 `yaml:"SubCats"`
0ed51327
JH
28 GUID [16]byte `yaml:"-"` // What does this do? Undocumented and seeming unused.
29 AddSN [4]byte `yaml:"-"` // What does this do? Undocumented and seeming unused.
30 DeleteSN [4]byte `yaml:"-"` // What does this do? Undocumented and seeming unused.
a2ef262a
JH
31
32 readOffset int // Internal offset to track read progress
6988a057
JH
33}
34
35func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData {
36 var newsArts []NewsArtList
37 var newsArtsPayload []byte
38
39 for i, art := range newscat.Articles {
9cf66aea
JH
40 id := make([]byte, 4)
41 binary.BigEndian.PutUint32(id, i)
6988a057
JH
42
43 newArt := NewsArtList{
0ed51327
JH
44 ID: [4]byte(id),
45 TimeStamp: art.Date,
46 ParentID: art.ParentArt,
6988a057
JH
47 Title: []byte(art.Title),
48 Poster: []byte(art.Poster),
49 ArticleSize: art.DataSize(),
50 }
0ed51327 51
6988a057
JH
52 newsArts = append(newsArts, newArt)
53 }
54
55 sort.Sort(byID(newsArts))
56
57 for _, v := range newsArts {
95159e55
JH
58 b, err := io.ReadAll(&v)
59 if err != nil {
60 // TODO
0ed51327 61 panic(err)
95159e55
JH
62 }
63 newsArtsPayload = append(newsArtsPayload, b...)
6988a057
JH
64 }
65
0ed51327 66 return NewsArtListData{
33265393 67 Count: len(newsArts),
6988a057
JH
68 Name: []byte{},
69 Description: []byte{},
70 NewsArtList: newsArtsPayload,
71 }
6988a057
JH
72}
73
72dd37f1 74// NewsArtData represents single news article
6988a057 75type NewsArtData struct {
95159e55
JH
76 Title string `yaml:"Title"`
77 Poster string `yaml:"Poster"`
0ed51327
JH
78 Date [8]byte `yaml:"Date,flow"`
79 PrevArt [4]byte `yaml:"PrevArt,flow"`
80 NextArt [4]byte `yaml:"NextArt,flow"`
81 ParentArt [4]byte `yaml:"ParentArt,flow"`
82 FirstChildArt [4]byte `yaml:"FirstChildArtArt,flow"`
83 DataFlav []byte `yaml:"-"` // "text/plain"
95159e55 84 Data string `yaml:"Data"`
6988a057
JH
85}
86
87func (art *NewsArtData) DataSize() []byte {
88 dataLen := make([]byte, 2)
89 binary.BigEndian.PutUint16(dataLen, uint16(len(art.Data)))
90
91 return dataLen
92}
93
94type NewsArtListData struct {
0ed51327 95 ID [4]byte `yaml:"ID"`
9cf66aea
JH
96 Name []byte `yaml:"Name"`
97 Description []byte `yaml:"Description"` // not used?
98 NewsArtList []byte // List of articles Optional (if article count > 0)
33265393 99 Count int
0ed51327
JH
100
101 readOffset int // Internal offset to track read progress
6988a057
JH
102}
103
9cf66aea 104func (nald *NewsArtListData) Read(p []byte) (int, error) {
6988a057 105 count := make([]byte, 4)
33265393 106 binary.BigEndian.PutUint32(count, uint32(nald.Count))
6988a057 107
0ed51327
JH
108 buf := slices.Concat(
109 nald.ID[:],
110 count,
111 []byte{uint8(len(nald.Name))},
112 nald.Name,
113 []byte{uint8(len(nald.Description))},
114 nald.Description,
115 nald.NewsArtList,
116 )
117
118 if nald.readOffset >= len(buf) {
119 return 0, io.EOF // All bytes have been read
120 }
121 n := copy(p, buf[nald.readOffset:])
122 nald.readOffset += n
123
124 return n, nil
6988a057
JH
125}
126
72dd37f1 127// NewsArtList is a summarized version of a NewArtData record for display in list view
6988a057 128type NewsArtList struct {
0ed51327
JH
129 ID [4]byte
130 TimeStamp [8]byte // Year (2 bytes), milliseconds (2 bytes) and seconds (4 bytes)
131 ParentID [4]byte
132 Flags [4]byte
133 FlavorCount [2]byte
6988a057
JH
134 // Title size 1
135 Title []byte // string
136 // Poster size 1
137 // Poster Poster string
138 Poster []byte
139 FlavorList []NewsFlavorList
140 // Flavor list… Optional (if flavor count > 0)
141 ArticleSize []byte // Size 2
95159e55
JH
142
143 readOffset int // Internal offset to track read progress
6988a057
JH
144}
145
146type byID []NewsArtList
147
148func (s byID) Len() int {
149 return len(s)
150}
151func (s byID) Swap(i, j int) {
152 s[i], s[j] = s[j], s[i]
153}
154func (s byID) Less(i, j int) bool {
0ed51327 155 return binary.BigEndian.Uint32(s[i].ID[:]) < binary.BigEndian.Uint32(s[j].ID[:])
6988a057
JH
156}
157
0ed51327
JH
158var (
159 NewsFlavorLen = []byte{0x0a}
160 NewsFlavor = []byte("text/plain")
161)
162
95159e55
JH
163func (nal *NewsArtList) Read(p []byte) (int, error) {
164 out := slices.Concat(
0ed51327
JH
165 nal.ID[:],
166 nal.TimeStamp[:],
167 nal.ParentID[:],
168 nal.Flags[:],
a2ef262a 169 []byte{0, 1}, // Flavor Count TODO: make this not hardcoded
95159e55
JH
170 []byte{uint8(len(nal.Title))},
171 nal.Title,
172 []byte{uint8(len(nal.Poster))},
173 nal.Poster,
0ed51327
JH
174 NewsFlavorLen,
175 NewsFlavor,
95159e55
JH
176 nal.ArticleSize,
177 )
178
179 if nal.readOffset >= len(out) {
180 return 0, io.EOF // All bytes have been read
181 }
6988a057 182
95159e55
JH
183 n := copy(p, out[nal.readOffset:])
184 nal.readOffset += n
6988a057 185
95159e55 186 return n, io.EOF
6988a057
JH
187}
188
189type NewsFlavorList struct {
190 // Flavor size 1
191 // Flavor text size MIME type string
192 // Article size 2
193}
194
a2ef262a 195func (newscat *NewsCategoryListData15) Read(p []byte) (int, error) {
6988a057
JH
196 count := make([]byte, 2)
197 binary.BigEndian.PutUint16(count, uint16(len(newscat.Articles)+len(newscat.SubCats)))
198
a2ef262a
JH
199 out := slices.Concat(
200 newscat.Type[:],
201 count,
202 )
6988a057 203
0ed51327 204 // If type is category
a2ef262a
JH
205 if newscat.Type == [2]byte{0, 3} {
206 out = append(out, newscat.GUID[:]...)
207 out = append(out, newscat.AddSN[:]...)
208 out = append(out, newscat.DeleteSN[:]...)
6988a057
JH
209 }
210
211 out = append(out, newscat.nameLen()...)
212 out = append(out, []byte(newscat.Name)...)
213
a2ef262a
JH
214 if newscat.readOffset >= len(out) {
215 return 0, io.EOF // All bytes have been read
216 }
217
218 n := copy(p, out)
219 newscat.readOffset = n
220
221 return n, nil
6988a057
JH
222}
223
6988a057
JH
224func (newscat *NewsCategoryListData15) nameLen() []byte {
225 return []byte{uint8(len(newscat.Name))}
226}
227
8eb43f95 228// TODO: re-implement as bufio.Scanner interface
6988a057
JH
229func ReadNewsPath(newsPath []byte) []string {
230 if len(newsPath) == 0 {
231 return []string{}
232 }
233 pathCount := binary.BigEndian.Uint16(newsPath[0:2])
234
235 pathData := newsPath[2:]
236 var paths []string
237
238 for i := uint16(0); i < pathCount; i++ {
239 pathLen := pathData[2]
240 paths = append(paths, string(pathData[3:3+pathLen]))
241
242 pathData = pathData[pathLen+3:]
243 }
244
245 return paths
246}
247
248func (s *Server) GetNewsCatByPath(paths []string) map[string]NewsCategoryListData15 {
249 cats := s.ThreadedNews.Categories
250 for _, path := range paths {
251 cats = cats[path].SubCats
252 }
253 return cats
254}