Add marking notifications as read
This commit is contained in:
parent
212c1b4e50
commit
faac7e9b16
@ -83,7 +83,7 @@ func main() {
|
|||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
if len(args) == 1 && (args[0] == "webserver" || args[0] == "fetch_timeline" ||
|
if len(args) == 1 && (args[0] == "webserver" || args[0] == "fetch_timeline" ||
|
||||||
args[0] == "fetch_timeline_following_only" || args[0] == "fetch_inbox" || args[0] == "get_bookmarks" ||
|
args[0] == "fetch_timeline_following_only" || args[0] == "fetch_inbox" || args[0] == "get_bookmarks" ||
|
||||||
args[0] == "get_notifications") {
|
args[0] == "get_notifications" || args[0] == "mark_notifications_as_read") {
|
||||||
// Doesn't need a target, so create a fake second arg
|
// Doesn't need a target, so create a fake second arg
|
||||||
args = append(args, "")
|
args = append(args, "")
|
||||||
} else {
|
} else {
|
||||||
@ -195,6 +195,8 @@ func main() {
|
|||||||
fetch_timeline(true)
|
fetch_timeline(true)
|
||||||
case "get_notifications":
|
case "get_notifications":
|
||||||
get_notifications(*how_many)
|
get_notifications(*how_many)
|
||||||
|
case "mark_notifications_as_read":
|
||||||
|
mark_notification_as_read()
|
||||||
case "download_tweet_content":
|
case "download_tweet_content":
|
||||||
download_tweet_content(target)
|
download_tweet_content(target)
|
||||||
case "search":
|
case "search":
|
||||||
@ -664,3 +666,10 @@ func get_notifications(how_many int) {
|
|||||||
len(trove.Notifications), len(trove.Tweets), len(trove.Users),
|
len(trove.Notifications), len(trove.Tweets), len(trove.Users),
|
||||||
), nil)
|
), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mark_notification_as_read() {
|
||||||
|
if err := api.MarkNotificationsAsRead(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
happy_exit("Notifications marked as read", nil)
|
||||||
|
}
|
||||||
|
@ -3,9 +3,17 @@ package webserver
|
|||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (app *Application) Notifications(w http.ResponseWriter, r *http.Request) {
|
func (app *Application) Notifications(w http.ResponseWriter, r *http.Request) {
|
||||||
|
app.traceLog.Printf("'Notifications' handler (path: %q)", r.URL.Path)
|
||||||
|
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
||||||
|
if parts[0] == "mark-all-as-read" {
|
||||||
|
app.NotificationsMarkAsRead(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cursor_val := 0
|
cursor_val := 0
|
||||||
cursor_param := r.URL.Query().Get("cursor")
|
cursor_param := r.URL.Query().Get("cursor")
|
||||||
if cursor_param != "" {
|
if cursor_param != "" {
|
||||||
@ -26,3 +34,16 @@ func (app *Application) Notifications(w http.ResponseWriter, r *http.Request) {
|
|||||||
app.buffered_render_page(w, "tpl/notifications.tpl", PageGlobalData{TweetTrove: feed.TweetTrove}, feed)
|
app.buffered_render_page(w, "tpl/notifications.tpl", PageGlobalData{TweetTrove: feed.TweetTrove}, feed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (app *Application) NotificationsMarkAsRead(w http.ResponseWriter, r *http.Request) {
|
||||||
|
err := app.API.MarkNotificationsAsRead()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
app.toast(w, r, Toast{
|
||||||
|
Title: "Success",
|
||||||
|
Message: `Notifications marked as "read"`,
|
||||||
|
Type: "success",
|
||||||
|
AutoCloseDelay: 2000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -134,7 +134,7 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
case "bookmarks":
|
case "bookmarks":
|
||||||
app.Bookmarks(w, r)
|
app.Bookmarks(w, r)
|
||||||
case "notifications":
|
case "notifications":
|
||||||
app.Notifications(w, r)
|
http.StripPrefix("/notifications", http.HandlerFunc(app.Notifications)).ServeHTTP(w, r)
|
||||||
case "messages":
|
case "messages":
|
||||||
http.StripPrefix("/messages", http.HandlerFunc(app.Messages)).ServeHTTP(w, r)
|
http.StripPrefix("/messages", http.HandlerFunc(app.Messages)).ServeHTTP(w, r)
|
||||||
case "nav-sidebar-poll-updates":
|
case "nav-sidebar-poll-updates":
|
||||||
|
@ -6,17 +6,28 @@
|
|||||||
<div class="dummy"></div> {{/* Extra div to take up a slot in the `row` */}}
|
<div class="dummy"></div> {{/* Extra div to take up a slot in the `row` */}}
|
||||||
<h1>Notifications</h1>
|
<h1>Notifications</h1>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<a class="button" hx-post="/notifications/mark-all-as-read" hx-indicator=".notifications-timeline" title="Mark all as read">
|
||||||
|
<img class="svg-icon" src="/static/icons/eye.svg" width="24" height="24" />
|
||||||
|
</a>
|
||||||
<a class="button" target="_blank" href="https://twitter.com/notifications" title="Open on twitter.com">
|
<a class="button" target="_blank" href="https://twitter.com/notifications" title="Open on twitter.com">
|
||||||
<img class="svg-icon" src="/static/icons/external-link.svg" width="24" height="24" />
|
<img class="svg-icon" src="/static/icons/external-link.svg" width="24" height="24" />
|
||||||
</a>
|
</a>
|
||||||
<a class="button" hx-get="?scrape" hx-target="body" hx-indicator=".search-header" title="Refresh">
|
<a class="button" hx-get="?scrape" hx-target="body" hx-indicator=".notifications-timeline" title="Refresh">
|
||||||
<img class="svg-icon" src="/static/icons/refresh.svg" width="24" height="24" />
|
<img class="svg-icon" src="/static/icons/refresh.svg" width="24" height="24" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="notifications-timeline">
|
||||||
|
<div class="htmx-spinner">
|
||||||
|
<div class="htmx-spinner__fullscreen-forcer">
|
||||||
|
<div class="htmx-spinner__background"></div>
|
||||||
|
<img class="svg-icon htmx-spinner__icon" src="/static/icons/spinner.svg" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
{{template "timeline" .}}
|
{{template "timeline" .}}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
@ -526,6 +526,17 @@ func (t *TweetResponse) GetCursor() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TweetResponse) GetCursorTop() string {
|
||||||
|
for _, instr := range t.Timeline.Instructions {
|
||||||
|
for _, entry := range instr.AddEntries.Entries {
|
||||||
|
if strings.Contains(entry.EntryID, "cursor-top") {
|
||||||
|
return entry.Content.Operation.Cursor.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test for one case of end-of-feed. Cursor increments on each request for some reason, but
|
* Test for one case of end-of-feed. Cursor increments on each request for some reason, but
|
||||||
* there's no new content. This seems to happen when there's a pinned tweet.
|
* there's no new content. This seems to happen when there's a pinned tweet.
|
||||||
|
@ -63,6 +63,25 @@ func (api *API) GetNotifications(how_many int) (TweetTrove, int64, error) {
|
|||||||
return trove, resp.CheckUnreadNotifications(), nil
|
return trove, resp.CheckUnreadNotifications(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) MarkNotificationsAsRead() error {
|
||||||
|
resp, err := api.GetNotificationsPage("")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cursor := resp.GetCursorTop()
|
||||||
|
if cursor == "" {
|
||||||
|
panic(fmt.Sprintf("No top cursor found: \n%#v", resp))
|
||||||
|
}
|
||||||
|
rslt := struct {
|
||||||
|
Cursor string `json:"cursor"`
|
||||||
|
}{}
|
||||||
|
api.do_http_POST("https://twitter.com/i/api/2/notifications/all/last_seen_cursor.json", "cursor=" + cursor, &rslt)
|
||||||
|
if rslt.Cursor == "" {
|
||||||
|
panic("got blank cursor back...?")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Check a Notifications result for unread notifications. Returns `0` if there are none.
|
// Check a Notifications result for unread notifications. Returns `0` if there are none.
|
||||||
func (t TweetResponse) CheckUnreadNotifications() int64 {
|
func (t TweetResponse) CheckUnreadNotifications() int64 {
|
||||||
for _, instr := range t.Timeline.Instructions {
|
for _, instr := range t.Timeline.Instructions {
|
||||||
|
@ -163,6 +163,9 @@ func TestParseNotificationsPage(t *testing.T) {
|
|||||||
bottom_cursor := resp.GetCursor()
|
bottom_cursor := resp.GetCursor()
|
||||||
assert.Equal("DAACDAABCgABFKncQJGVgAQIAAIAAAABCAADSQ3bEQgABIsN6BEACwACAAAAC0FaRkxRSXFNLTJJAAA", bottom_cursor)
|
assert.Equal("DAACDAABCgABFKncQJGVgAQIAAIAAAABCAADSQ3bEQgABIsN6BEACwACAAAAC0FaRkxRSXFNLTJJAAA", bottom_cursor)
|
||||||
assert.False(resp.IsEndOfFeed())
|
assert.False(resp.IsEndOfFeed())
|
||||||
|
|
||||||
|
// Test cursor-top
|
||||||
|
assert.Equal(resp.GetCursorTop(), "DAABDAABCgABFKncQJGVgAQIAAIAAAABCAADSQ3bEQgABIsN6BEACwACAAAAC0FaR0lLcUhkUVhrAAA")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseNotificationsEndOfFeed(t *testing.T) {
|
func TestParseNotificationsEndOfFeed(t *testing.T) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user