+1
-4
cmd_main.go
+1
-4
cmd_main.go
+3
go.mod
+3
go.mod
···
7
7
github.com/adrg/xdg v0.5.3
8
8
github.com/charmbracelet/bubbletea v1.3.10
9
9
github.com/pelletier/go-toml/v2 v2.2.4
10
+
github.com/tidwall/gjson v1.18.0
10
11
github.com/urfave/cli/v3 v3.6.1
11
12
modernc.org/sqlite v1.40.1
12
13
olexsmir.xyz/x v0.1.1
···
40
41
github.com/ncruces/go-strftime v0.1.9 // indirect
41
42
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
42
43
github.com/rivo/uniseg v0.4.7 // indirect
44
+
github.com/tidwall/match v1.1.1 // indirect
45
+
github.com/tidwall/pretty v1.2.0 // indirect
43
46
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
44
47
github.com/zclconf/go-cty v1.14.4 // indirect
45
48
github.com/zclconf/go-cty-yaml v1.1.0 // indirect
+6
go.sum
+6
go.sum
···
81
81
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
82
82
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
83
83
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
84
+
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
85
+
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
86
+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
87
+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
88
+
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
89
+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
84
90
github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
85
91
github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
86
92
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
+89
-61
internal/provider/freshrss.go
+89
-61
internal/provider/freshrss.go
···
12
12
"strconv"
13
13
"strings"
14
14
"time"
15
+
16
+
"github.com/tidwall/gjson"
15
17
)
16
18
17
19
var (
···
67
69
type subscriptionList struct {
68
70
Subscriptions []Subscriptions `json:"subscriptions"`
69
71
}
72
+
70
73
type Subscriptions struct {
71
-
Categories struct {
72
-
ID string `json:"id"`
73
-
Label string `json:"label"`
74
-
} `json:"categories"`
75
-
ID string `json:"id"`
76
-
HTMLURL string `json:"htmlUrl"`
77
-
IconURL string `json:"iconUrl"`
78
-
Title string `json:"title"`
79
-
URL string `json:"url"`
74
+
Categories []SubscriptionCategory `json:"categories"`
75
+
ID string `json:"id"`
76
+
HTMLURL string `json:"htmlUrl"`
77
+
Title string `json:"title"`
78
+
URL string `json:"url"`
79
+
80
+
// IconURL string `json:"iconUrl"`
81
+
}
82
+
83
+
type SubscriptionCategory struct {
84
+
ID string `json:"id"`
85
+
Label string `json:"label"`
80
86
}
81
87
82
88
func (g FreshRSS) SubscriptionList(ctx context.Context) ([]Subscriptions, error) {
83
-
var resp subscriptionList
84
-
err := g.request(ctx, "/reader/api/0/subscription/list?output=json", nil, &resp)
85
-
return resp.Subscriptions, err
89
+
params := url.Values{}
90
+
params.Set("output", "json")
91
+
92
+
var jsonResp subscriptionList
93
+
err := g.request(ctx, "/reader/api/0/subscription/list", params, &jsonResp)
94
+
return jsonResp.Subscriptions, err
86
95
}
87
96
88
97
type tagList struct {
···
95
104
}
96
105
97
106
func (g FreshRSS) TagList(ctx context.Context) ([]Tag, error) {
107
+
params := url.Values{}
108
+
params.Set("output", "json")
109
+
98
110
var resp tagList
99
-
err := g.request(ctx, "/reader/api/0/tag/list?output=json", nil, &resp)
111
+
err := g.request(ctx, "/reader/api/0/tag/list", params, &resp)
100
112
return resp.Tags, err
101
113
}
102
114
103
-
type StreamContents struct {
104
-
Continuation string `json:"continuation"`
105
-
ID string `json:"id"`
106
-
Items []struct {
107
-
Alternate []struct {
108
-
Href string `json:"href"`
109
-
} `json:"alternate"`
110
-
Author string `json:"author"`
111
-
Canonical []struct {
112
-
Href string `json:"href"`
113
-
} `json:"canonical"`
114
-
Categories []string `json:"categories"`
115
-
CrawlTimeMsec string `json:"crawlTimeMsec"`
116
-
ID string `json:"id"`
117
-
Origin struct {
118
-
HTMLURL string `json:"htmlUrl"`
119
-
StreamID string `json:"streamId"`
120
-
Title string `json:"title"`
121
-
} `json:"origin"`
122
-
Published int `json:"published"`
123
-
Summary struct {
124
-
Content string `json:"content"`
125
-
} `json:"summary"`
126
-
TimestampUsec string `json:"timestampUsec"`
127
-
Title string `json:"title"`
128
-
} `json:"items"`
129
-
Updated int `json:"updated"`
115
+
type ContentItem struct {
116
+
ID string
117
+
Published int64
118
+
Title string
119
+
Author string
120
+
Canonical []string
121
+
Content string
122
+
Categories []string
123
+
Origin struct {
124
+
HTMLURL string
125
+
StreamID string
126
+
Title string
127
+
}
128
+
129
+
// CrawlTimeMsec string `json:"crawlTimeMsec"`
130
+
// TimestampUsec string `json:"timestampUsec"`
130
131
}
131
132
132
-
func (g FreshRSS) GetItems(ctx context.Context, excludeTarget string, lastModified, n int) (StreamContents, error) {
133
+
func (g FreshRSS) StreamContents(ctx context.Context, steamID, excludeTarget string, lastModified, n int) ([]ContentItem, error) {
133
134
params := url.Values{}
134
135
setOption(¶ms, "xt", excludeTarget)
135
136
setOptionInt(¶ms, "ot", lastModified)
136
137
setOptionInt(¶ms, "n", n)
138
+
params.Set("r", "n")
137
139
138
-
var resp StreamContents
139
-
err := g.request(ctx, "/reader/api/0/stream/contents/user/-/state/com.google/reading-list", params, &resp)
140
-
return resp, err
141
-
}
140
+
var jsonResp string
141
+
if err := g.request(ctx, "/reader/api/0/stream/contents/"+steamID, params, &jsonResp); err != nil {
142
+
return nil, err
143
+
}
142
144
143
-
func (g FreshRSS) GetStaredItems(ctx context.Context, n int) (StreamContents, error) {
144
-
params := url.Values{}
145
-
setOptionInt(¶ms, "n", n)
145
+
items := gjson.GetBytes([]byte(jsonResp), "items").Array()
146
+
if len(items) == 0 {
147
+
return []ContentItem{}, nil
148
+
}
149
+
150
+
res := make([]ContentItem, len(items))
151
+
for i, item := range items {
152
+
var ci ContentItem
153
+
ci.ID = item.Get("id").String()
154
+
ci.Title = item.Get("title").String()
155
+
ci.Published = item.Get("published").Int()
156
+
ci.Author = item.Get("author").String()
157
+
ci.Content = item.Get("summary.content").String()
158
+
ci.Origin.StreamID = item.Get("origin.streamId").String()
159
+
ci.Origin.HTMLURL = item.Get("origin.htmlUrl").String()
160
+
ci.Origin.Title = item.Get("origin.title").String()
161
+
162
+
for _, href := range item.Get("canonical.#.href").Array() {
163
+
if h := href.String(); h != "" {
164
+
ci.Canonical = append(ci.Canonical, h)
165
+
}
166
+
}
167
+
for _, cat := range item.Get("categories").Array() {
168
+
ci.Categories = append(ci.Categories, cat.String())
169
+
}
146
170
147
-
var resp StreamContents
148
-
err := g.request(ctx, "/reader/api/0/stream/contents/user/-/state/com.google/starred", params, &resp)
149
-
return resp, err
150
-
}
171
+
res[i] = ci
172
+
}
151
173
152
-
type StreamItemsIDs struct {
153
-
Continuation string `json:"continuation"`
154
-
ItemRefs []struct {
155
-
ID string `json:"id"`
156
-
} `json:"itemRefs"`
174
+
return res, nil
157
175
}
158
176
159
-
func (g FreshRSS) GetItemsIDs(ctx context.Context, excludeTarget, includeTarget string, n int) (StreamItemsIDs, error) {
177
+
func (g FreshRSS) StreamIDs(ctx context.Context, excludeTarget, includeTarget string, n int) ([]string, error) {
160
178
params := url.Values{}
161
179
setOption(¶ms, "xt", excludeTarget)
162
180
setOption(¶ms, "s", includeTarget)
163
181
setOptionInt(¶ms, "n", n)
182
+
params.Set("r", "n")
164
183
165
-
var resp StreamItemsIDs
166
-
err := g.request(ctx, "/reader/api/0/stream/items/ids", params, &resp)
167
-
return resp, err
184
+
var jsonResp string
185
+
if err := g.request(ctx, "/reader/api/0/stream/items/ids", params, &jsonResp); err != nil {
186
+
return nil, err
187
+
}
188
+
189
+
ids := gjson.Get(jsonResp, "itemRefs.#.id").Array()
190
+
resp := make([]string, len(ids))
191
+
for i, v := range ids {
192
+
resp[i] = v.String()
193
+
}
194
+
195
+
return resp, nil
168
196
}
169
197
170
198
func (g FreshRSS) SetItemsState(ctx context.Context, token, itemID string, addAction, removeAction string) error {
+14
-7
internal/sync/freshrss.go
+14
-7
internal/sync/freshrss.go
···
19
19
}
20
20
}
21
21
22
-
func (g *FreshRSS) Sync(ctx context.Context, initial bool) error {
23
-
writeToken, err := g.api.GetWriteToken(ctx)
24
-
if err != nil {
25
-
return err
26
-
}
27
-
28
-
_ = writeToken
22
+
func (g *FreshRSS) Sync(ctx context.Context) error {
23
+
// tags, err := g.api.TagList(ctx)
24
+
// subscriptions, err := g.api.SubscriptionList(ctx)
25
+
// unreadItems, err := g.api.StreamContents(
26
+
// ctx,
27
+
// "user/-/state/com.google/reading-list",
28
+
// "user/-/state/com.google/read",
29
+
// 0,
30
+
// 1000)
31
+
// ids, err := g.api.GetItemsIDs(ctx,
32
+
// "user/-/state/com.google/read",
33
+
// "user/-/state/com.google/reading-list",
34
+
// 1000,
35
+
// )
29
36
30
37
return nil
31
38
}