]> git.r.bdr.sh - rbdr/mobius/blob - hotline/news.go
Ignore files with incompatible names
[rbdr/mobius] / hotline / news.go
1 package hotline
2
3 import (
4 "bytes"
5 "crypto/rand"
6 "encoding/binary"
7 "sort"
8 )
9
10 const defaultNewsDateFormat = "Jan02 15:04" // Jun23 20:49
11
12 const defaultNewsTemplate = `From %s (%s):
13
14 %s
15
16 __________________________________________________________`
17
18 type ThreadedNews struct {
19 Categories map[string]NewsCategoryListData15 `yaml:"Categories"`
20 }
21
22 type NewsCategoryListData15 struct {
23 Type []byte `yaml:"Type"` // Size 2 ; Bundle (2) or category (3)
24 Count []byte // Article or SubCategory count Size 2
25 NameSize byte
26 Name string `yaml:"Name"` //
27 Articles map[uint32]*NewsArtData `yaml:"Articles"` // Optional, if Type is Category
28 SubCats map[string]NewsCategoryListData15 `yaml:"SubCats"`
29 GUID []byte // Size 16
30 AddSN []byte // Size 4
31 DeleteSN []byte // Size 4
32 }
33
34 func (newscat *NewsCategoryListData15) GetNewsArtListData() NewsArtListData {
35 var newsArts []NewsArtList
36 var newsArtsPayload []byte
37
38 for i, art := range newscat.Articles {
39 ID := make([]byte, 4)
40 binary.BigEndian.PutUint32(ID, i)
41
42 newArt := NewsArtList{
43 ID: ID,
44 TimeStamp: art.Date,
45 ParentID: art.ParentArt,
46 Flags: []byte{0, 0, 0, 0},
47 FlavorCount: []byte{0, 0},
48 Title: []byte(art.Title),
49 Poster: []byte(art.Poster),
50 ArticleSize: art.DataSize(),
51 }
52 newsArts = append(newsArts, newArt)
53 }
54
55 sort.Sort(byID(newsArts))
56
57 for _, v := range newsArts {
58 newsArtsPayload = append(newsArtsPayload, v.Payload()...)
59 }
60
61 nald := NewsArtListData{
62 ID: []byte{0, 0, 0, 0},
63 Count: len(newsArts),
64 Name: []byte{},
65 Description: []byte{},
66 NewsArtList: newsArtsPayload,
67 }
68
69 return nald
70 }
71
72 // NewsArtData represents single news article
73 type NewsArtData struct {
74 Title string `yaml:"Title"`
75 Poster string `yaml:"Poster"`
76 Date []byte `yaml:"Date"` // size 8
77 PrevArt []byte `yaml:"PrevArt"` // size 4
78 NextArt []byte `yaml:"NextArt"` // size 4
79 ParentArt []byte `yaml:"ParentArt"` // size 4
80 FirstChildArt []byte `yaml:"FirstChildArtArt"` // size 4
81 DataFlav []byte `yaml:"DataFlav"` // "text/plain"
82 Data string `yaml:"Data"`
83 }
84
85 func (art *NewsArtData) DataSize() []byte {
86 dataLen := make([]byte, 2)
87 binary.BigEndian.PutUint16(dataLen, uint16(len(art.Data)))
88
89 return dataLen
90 }
91
92 type NewsArtListData struct {
93 ID []byte `yaml:"ID"` // Size 4
94 Name []byte `yaml:"Name"`
95 Description []byte `yaml:"Description"` // not used?
96 NewsArtList []byte // List of articles Optional (if article count > 0)
97 Count int
98 }
99
100 func (nald *NewsArtListData) Payload() []byte {
101 count := make([]byte, 4)
102 binary.BigEndian.PutUint32(count, uint32(nald.Count))
103
104 out := append(nald.ID, count...)
105 out = append(out, []byte{uint8(len(nald.Name))}...)
106 out = append(out, nald.Name...)
107 out = append(out, []byte{uint8(len(nald.Description))}...)
108 out = append(out, nald.Description...)
109 out = append(out, nald.NewsArtList...)
110
111 return out
112 }
113
114 // NewsArtList is a summarized version of a NewArtData record for display in list view
115 type NewsArtList struct {
116 ID []byte // Size 4
117 TimeStamp []byte // Year (2 bytes), milliseconds (2 bytes) and seconds (4 bytes)
118 ParentID []byte // Size 4
119 Flags []byte // Size 4
120 FlavorCount []byte // Size 2
121 // Title size 1
122 Title []byte // string
123 // Poster size 1
124 // Poster Poster string
125 Poster []byte
126 FlavorList []NewsFlavorList
127 // Flavor list… Optional (if flavor count > 0)
128 ArticleSize []byte // Size 2
129 }
130
131 type byID []NewsArtList
132
133 func (s byID) Len() int {
134 return len(s)
135 }
136 func (s byID) Swap(i, j int) {
137 s[i], s[j] = s[j], s[i]
138 }
139 func (s byID) Less(i, j int) bool {
140 return binary.BigEndian.Uint32(s[i].ID) < binary.BigEndian.Uint32(s[j].ID)
141 }
142
143 func (nal *NewsArtList) Payload() []byte {
144 out := append(nal.ID, nal.TimeStamp...)
145 out = append(out, nal.ParentID...)
146 out = append(out, nal.Flags...)
147
148 out = append(out, []byte{0, 1}...)
149
150 out = append(out, []byte{uint8(len(nal.Title))}...)
151 out = append(out, nal.Title...)
152 out = append(out, []byte{uint8(len(nal.Poster))}...)
153 out = append(out, nal.Poster...)
154 out = append(out, []byte{0x0a, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x69, 0x6e}...) // TODO: wat?
155 out = append(out, nal.ArticleSize...)
156
157 return out
158 }
159
160 type NewsFlavorList struct {
161 // Flavor size 1
162 // Flavor text size MIME type string
163 // Article size 2
164 }
165
166 func (newscat *NewsCategoryListData15) MarshalBinary() (data []byte, err error) {
167 count := make([]byte, 2)
168 binary.BigEndian.PutUint16(count, uint16(len(newscat.Articles)+len(newscat.SubCats)))
169
170 out := append(newscat.Type, count...)
171
172 if bytes.Equal(newscat.Type, []byte{0, 3}) {
173 // Generate a random GUID // TODO: does this need to be random?
174 b := make([]byte, 16)
175 _, err := rand.Read(b)
176 if err != nil {
177 return data, err
178 }
179
180 out = append(out, b...) // GUID
181 out = append(out, []byte{0, 0, 0, 1}...) // Add SN (TODO: not sure what this is)
182 out = append(out, []byte{0, 0, 0, 2}...) // Delete SN (TODO: not sure what this is)
183 }
184
185 out = append(out, newscat.nameLen()...)
186 out = append(out, []byte(newscat.Name)...)
187
188 return out, err
189 }
190
191 // ReadNewsCategoryListData parses a byte slice into a NewsCategoryListData15 struct
192 // For use on the client side
193 func ReadNewsCategoryListData(payload []byte) NewsCategoryListData15 {
194 ncld := NewsCategoryListData15{
195 Type: payload[0:2],
196 Count: payload[2:4],
197 }
198
199 if bytes.Equal(ncld.Type, []byte{0, 3}) {
200 ncld.GUID = payload[4:20]
201 ncld.AddSN = payload[20:24]
202 ncld.AddSN = payload[24:28]
203 ncld.Name = string(payload[29:])
204 } else {
205 ncld.Name = string(payload[5:])
206 }
207
208 return ncld
209 }
210
211 func (newscat *NewsCategoryListData15) nameLen() []byte {
212 return []byte{uint8(len(newscat.Name))}
213 }
214
215 // TODO: re-implement as bufio.Scanner interface
216 func ReadNewsPath(newsPath []byte) []string {
217 if len(newsPath) == 0 {
218 return []string{}
219 }
220 pathCount := binary.BigEndian.Uint16(newsPath[0:2])
221
222 pathData := newsPath[2:]
223 var paths []string
224
225 for i := uint16(0); i < pathCount; i++ {
226 pathLen := pathData[2]
227 paths = append(paths, string(pathData[3:3+pathLen]))
228
229 pathData = pathData[pathLen+3:]
230 }
231
232 return paths
233 }
234
235 func (s *Server) GetNewsCatByPath(paths []string) map[string]NewsCategoryListData15 {
236 cats := s.ThreadedNews.Categories
237 for _, path := range paths {
238 cats = cats[path].SubCats
239 }
240 return cats
241 }