Add marking notifications as read

This commit is contained in:
Alessio 2024-11-06 21:54:21 -08:00
parent 212c1b4e50
commit faac7e9b16
7 changed files with 79 additions and 5 deletions

View File

@ -83,7 +83,7 @@ func main() {
if len(args) < 2 {
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] == "get_notifications") {
args[0] == "get_notifications" || args[0] == "mark_notifications_as_read") {
// Doesn't need a target, so create a fake second arg
args = append(args, "")
} else {
@ -195,6 +195,8 @@ func main() {
fetch_timeline(true)
case "get_notifications":
get_notifications(*how_many)
case "mark_notifications_as_read":
mark_notification_as_read()
case "download_tweet_content":
download_tweet_content(target)
case "search":
@ -664,3 +666,10 @@ func get_notifications(how_many int) {
len(trove.Notifications), len(trove.Tweets), len(trove.Users),
), nil)
}
func mark_notification_as_read() {
if err := api.MarkNotificationsAsRead(); err != nil {
panic(err)
}
happy_exit("Notifications marked as read", nil)
}

View File

@ -3,9 +3,17 @@ package webserver
import (
"net/http"
"strconv"
"strings"
)
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_param := r.URL.Query().Get("cursor")
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)
}
}
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,
})
}

View File

@ -134,7 +134,7 @@ func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
case "bookmarks":
app.Bookmarks(w, r)
case "notifications":
app.Notifications(w, r)
http.StripPrefix("/notifications", http.HandlerFunc(app.Notifications)).ServeHTTP(w, r)
case "messages":
http.StripPrefix("/messages", http.HandlerFunc(app.Messages)).ServeHTTP(w, r)
case "nav-sidebar-poll-updates":

View File

@ -6,17 +6,28 @@
<div class="dummy"></div> {{/* Extra div to take up a slot in the `row` */}}
<h1>Notifications</h1>
<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">
<img class="svg-icon" src="/static/icons/external-link.svg" width="24" height="24" />
</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" />
</a>
</div>
</div>
</div>
<div class="timeline">
{{template "timeline" .}}
<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">
{{template "timeline" .}}
</div>
</div>
{{end}}

View File

@ -526,6 +526,17 @@ func (t *TweetResponse) GetCursor() string {
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
* there's no new content. This seems to happen when there's a pinned tweet.

View File

@ -63,6 +63,25 @@ func (api *API) GetNotifications(how_many int) (TweetTrove, int64, error) {
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.
func (t TweetResponse) CheckUnreadNotifications() int64 {
for _, instr := range t.Timeline.Instructions {

View File

@ -163,6 +163,9 @@ func TestParseNotificationsPage(t *testing.T) {
bottom_cursor := resp.GetCursor()
assert.Equal("DAACDAABCgABFKncQJGVgAQIAAIAAAABCAADSQ3bEQgABIsN6BEACwACAAAAC0FaRkxRSXFNLTJJAAA", bottom_cursor)
assert.False(resp.IsEndOfFeed())
// Test cursor-top
assert.Equal(resp.GetCursorTop(), "DAABDAABCgABFKncQJGVgAQIAAIAAAABCAADSQ3bEQgABIsN6BEACwACAAAAC0FaR0lLcUhkUVhrAAA")
}
func TestParseNotificationsEndOfFeed(t *testing.T) {