-
Notifications
You must be signed in to change notification settings - Fork 5
/
entries.go
311 lines (288 loc) · 9.93 KB
/
entries.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
package wallabago
import (
"encoding/json"
"log"
"net/url"
"strconv"
"strings"
"time"
)
// Entries represents the object being returned from the API request /entries
type Entries struct {
Page int
Limit int
Pages int
Total int
NaviLinks Links `json:"_links"`
Embedded Embedded `json:"_embedded"`
}
// Embedded items in the API request
type Embedded struct {
Items []Item `json:"items"`
}
// Item represents individual items in API responses
type Item struct {
Links Links `json:"_links"`
Annotations []Annotation `json:"annotations"`
ArchivedAt *WallabagTime `json:"archived_at"`
CreatedAt *WallabagTime `json:"created_at"`
Content string `json:"content"`
DomainName string `json:"domain_name"`
GivenURL string `json:"given_url"`
HashedGivenURL string `json:"hashed_given_url"`
HashedURL string `json:"hashed_url"`
ID int `json:"id"`
IsArchived int `json:"is_archived"`
IsPublic bool `json:"is_public"`
IsStarred int `json:"is_starred"`
Language string `json:"language"`
Mimetype string `json:"mimetype"`
OriginURL string `json:"origin_url"`
PreviewPicture string `json:"preview_picture"`
PublishedAt *WallabagTime `json:"published_at"`
PublishedBy []string `json:"published_by"`
ReadingTime int `json:"reading_time"`
StarredAt *WallabagTime `json:"starred_at"`
Tags []Tag `json:"tags"`
Title string `json:"title"`
UID string `json:"uid"`
UpdatedAt *WallabagTime `json:"updated_at"`
URL string `json:"url"`
UserEmail string `json:"user_email"`
UserID int `json:"user_id"`
UserName string `json:"user_name"`
}
// WallabagTimeLayout is a variation of RFC3339 but without colons in
// the timezone delimeter, breaking the RFC
const WallabagTimeLayout = "2006-01-02T15:04:05-0700"
// WallabagTime overrides builtin time to allow for custom time parsing
type WallabagTime struct {
time.Time
}
// UnmarshalJSON parses the custom date format wallabag returns
func (t *WallabagTime) UnmarshalJSON(buf []byte) (err error) {
s := strings.Trim(string(buf), `"`)
if s == "null" {
t.Time = time.Time{}
return err
}
t.Time, err = time.Parse(WallabagTimeLayout, s)
if err != nil {
t.Time = time.Time{}
return err
}
return err
}
// MarshalJSON converts the given time according to custom wallabag time format for saving as JSON
func (t *WallabagTime) MarshalJSON() ([]byte, error) {
s := t.Time.Format(WallabagTimeLayout)
_json, err := json.Marshal(s)
return _json, err
}
// Equal compares the year, month, day, hours, minutes and seconds of the given WallabagTimes
func (t1 *WallabagTime) Equal(t2 *WallabagTime) bool {
if t1.Year() != t2.Year() || t1.Month() != t2.Month() || t1.Day() != t2.Day() || t1.Hour() != t2.Hour() || t1.Minute() != t2.Minute() || t1.Second() != t2.Second() {
return false
}
return true
}
// Links contains four links (self, first, last, next), being part of the Entries object
type Links struct {
Self *Link
First *Link
Last *Link
Next *Link
}
// Link object consists of its URL
type Link struct {
Href string
}
// GetEntries queries the API for articles according to the API request /entries
func GetEntries(bodyByteGetterFunc BodyByteGetter, archive int, starred int, sort string, order string, page int, perPage int, tags string, since int, public int, detail string, domain_name string) (Entries, error) {
var e Entries
entriesURL := LibConfig.WallabagURL + "/api/entries.json?"
if archive == 0 || archive == 1 {
entriesURL += "archive=" + strconv.Itoa(archive) + "&"
}
if starred == 0 || starred == 1 {
entriesURL += "starred=" + strconv.Itoa(starred) + "&"
}
if sort == "created" || sort == "updated" || sort == "archived" {
entriesURL += "sort=" + sort + "&"
}
if order == "asc" || order == "desc" {
entriesURL += "order=" + order + "&"
}
if page > 0 {
entriesURL += "page=" + strconv.Itoa(page) + "&"
}
if perPage > 0 {
entriesURL += "perPage=" + strconv.Itoa(perPage) + "&"
}
if tags != "" {
entriesURL += "tags=" + tags + "&"
}
if since > 0 {
entriesURL += "since=" + strconv.Itoa(since) + "&"
}
if public >= 0 && (public == 0 || public == 1) {
entriesURL += "public=" + strconv.Itoa(public) + "&"
}
if detail == "metadata" || detail == "full" {
entriesURL += "detail=" + detail + "&"
}
if domain_name != "" {
domain_name_encoded := url.QueryEscape(domain_name)
entriesURL += "domain_name=" + domain_name_encoded + "&"
}
//log.Printf("getEntries: entriesURL=%s", entriesURL)
body, err := bodyByteGetterFunc(entriesURL, "GET", nil)
if err != nil {
return e, err
}
//log.Printf("getEntries: body=\n%v\n\n\n\n", string(body))
err = json.Unmarshal(body, &e)
return e, err
}
// GetAllEntries calls GetEntries with no parameters, thus using the default values of the API request /entries and returning all articles as []wallabago.Item
func GetAllEntries() ([]Item, error) {
page := -1
perPage := -1
e, err := GetEntries(APICall, -1, -1, "", "", page, perPage, "", 0, -1, "", "")
if err != nil {
log.Println("GetAllEntries: first GetEntries call failed", err)
return nil, err
}
allEntries := e.Embedded.Items
if e.Total > len(allEntries) {
secondPage := e.Page + 1
perPage = e.Limit
pages := e.Pages
for i := secondPage; i <= pages; i++ {
e, err := GetEntries(APICall, -1, -1, "", "", i, perPage, "", 0, -1, "", "")
if err != nil {
log.Printf("GetAllEntries: GetEntries for page %d failed: %v", i, err)
return nil, err
}
tmpAllEntries := e.Embedded.Items
allEntries = append(allEntries, tmpAllEntries...)
}
}
return allEntries, err
}
// GetAllEntriesWithAnnotationsSince calls GetEntries with the since parameter only
func GetAllEntriesWithAnnotationsSince(since int) ([]Item, error) {
return GetAllEntriesWithAnnotations(since, "", "")
}
// GetAllEntriesWithAnnotations calls GetEntries with the since, sort and order parameter only
func GetAllEntriesWithAnnotations(since int, sort string, order string) ([]Item, error) {
page := 1
perPage := -1
e, err := GetEntries(APICall, -1, -1, sort, order, page, perPage, "", since, -1, "", "")
if err != nil {
log.Println("GetAllEntries: first GetEntries with since call failed", err)
return nil, err
}
allEntries := e.Embedded.Items
if e.Total > len(allEntries) {
secondPage := e.Page + 1
perPage = e.Limit
pages := e.Pages
for i := secondPage; i <= pages; i++ {
e, err := GetEntries(APICall, -1, -1, sort, order, i, perPage, "", since, -1, "", "")
if err != nil {
log.Printf("GetAllEntries: GetEntries with since for page %d failed: %v", i, err)
return nil, err
}
tmpAllEntries := e.Embedded.Items
allEntries = append(allEntries, tmpAllEntries...)
}
}
var entriesWithAnnotations []Item
for i := 0; i < len(allEntries); i++ {
if len(allEntries[i].Annotations) > 0 {
entriesWithAnnotations = append(entriesWithAnnotations, allEntries[i])
}
}
return entriesWithAnnotations, err
}
// GetNumberOfTotalArticles returns the number of all articles saved in wallabag
func GetNumberOfTotalArticles() (int, error) {
e, err := GetEntries(APICall, -1, -1, "", "", -1, -1, "", 0, -1, "", "")
if err != nil {
return -1, err
}
return e.Total, err
}
// GetNumberOfArchivedArticles returns the number of archived articles in wallabag
func GetNumberOfArchivedArticles() (int, error) {
e, err := GetEntries(APICall, 1, -1, "", "", -1, -1, "", 0, -1, "", "")
if err != nil {
return -1, err
}
return e.Total, err
}
// GetNumberOfStarredArticles returns the number of starred articles in wallabag (including unread and archived starred ones)
func GetNumberOfStarredArticles() (int, error) {
e, err := GetEntries(APICall, -1, 1, "", "", -1, -1, "", 0, -1, "", "")
if err != nil {
return -1, err
}
return e.Total, err
}
// PostEntry creates a new article in wallabag
func PostEntry(url, title, tags string, starred, archive int) error {
postData := map[string]string{
"url": url,
"title": title,
"tags": tags,
"starred": strconv.Itoa(starred),
"archive": strconv.Itoa(archive),
}
postDataJSON, err := json.Marshal(postData)
if err != nil {
return err
}
entriesURL := LibConfig.WallabagURL + "/api/entries.json"
_, err = APICall(entriesURL, "POST", postDataJSON)
//log.Println("PostEntry: response:", string(body))
return err
}
// GetEntriesExists queries the API for articles according to the API request /entries/exists
// it checks if the urls in the given array exist
// returns a map with the URL as key and the result as value
func GetEntriesExists(bodyByteGetterFunc BodyByteGetter, urls []string) (map[string]bool, error) {
var m map[string]bool
entriesExistsURL := LibConfig.WallabagURL + "/api/entries/exists.json?"
if len(urls) > 0 {
for i := 0; i < len(urls); i++ {
entriesExistsURL += "urls[]=" + urls[i] + "&"
}
}
body, err := bodyByteGetterFunc(entriesExistsURL, "GET", nil)
if err != nil {
return m, err
}
err = json.Unmarshal(body, &m)
return m, err
}
// GetEntry queries the API for a specific article according to the API request /entries/ID
func GetEntry(bodyByteGetterFunc BodyByteGetter, articleID int) (Item, error) {
var item Item
entryURL := LibConfig.WallabagURL + "/api/entries/" + strconv.Itoa(articleID) + ".json"
body, err := bodyByteGetterFunc(entryURL, "GET", nil)
if err != nil {
return item, err
}
err = json.Unmarshal(body, &item)
return item, err
}
// ExportEntry queries the API to retrieve a single entry in a predefined format according to the API request /entries/ID/export.FORMAT
func ExportEntry(bodyByteGetterFunc BodyByteGetter, articleID int, format string) ([]byte, error) {
entryURL := LibConfig.WallabagURL + "/api/entries/" + strconv.Itoa(articleID) + "/export." + format
body, err := bodyByteGetterFunc(entryURL, "GET", nil)
if err != nil {
return nil, err
}
return body, err
}