From f9275070895dea0582ffebe041d25f48f86c6eb3 Mon Sep 17 00:00:00 2001 From: Alessio Date: Fri, 10 May 2024 22:09:48 -0700 Subject: [PATCH] Enable marking DMs as read --- internal/webserver/handler_messages.go | 29 +++++++++++++++++++ internal/webserver/static/styles.css | 5 ++++ .../tpl/tweet_page_includes/chat_view.tpl | 3 ++ pkg/scraper/api_request_utils.go | 11 ++++++- pkg/scraper/api_types_dms.go | 13 +++++++++ pkg/scraper/dm_trove.go | 6 ++++ 6 files changed, 66 insertions(+), 1 deletion(-) diff --git a/internal/webserver/handler_messages.go b/internal/webserver/handler_messages.go index 9103055..aff75e4 100644 --- a/internal/webserver/handler_messages.go +++ b/internal/webserver/handler_messages.go @@ -25,6 +25,30 @@ func (app *Application) messages_index(w http.ResponseWriter, r *http.Request) { app.buffered_render_page(w, "tpl/messages.tpl", global_data, chat_view_data) } +func (app *Application) message_mark_as_read(w http.ResponseWriter, r *http.Request) { + room_id := get_room_id_from_context(r.Context()) + + c := persistence.NewConversationCursor(room_id) + c.PageSize = 1 + chat_contents := app.Profile.GetChatRoomMessagesByCursor(c) + last_message_id := chat_contents.MessageIDs[len(chat_contents.MessageIDs)-1] + scraper.MarkDMChatRead(room_id, last_message_id) + room := chat_contents.Rooms[room_id] + participant, is_ok := room.Participants[app.ActiveUser.ID] + if !is_ok { + panic(room) + } + participant.LastReadEventID = last_message_id + room.Participants[app.ActiveUser.ID] = participant + panic_if(app.Profile.SaveChatRoom(room)) + app.toast(w, r, Toast{ + Title: "Success", + Message: `Conversation marked as "read"`, + Type: "success", + AutoCloseDelay: 2000, + }) +} + func (app *Application) message_send(w http.ResponseWriter, r *http.Request) { room_id := get_room_id_from_context(r.Context()) @@ -48,6 +72,11 @@ func (app *Application) message_detail(w http.ResponseWriter, r *http.Request) { chat_view_data, global_data := app.get_message_global_data() + if len(parts) == 1 && parts[0] == "mark-as-read" { + app.message_mark_as_read(w, r) + return + } + // First send a message, if applicable if is_sending { app.message_send(w, r) diff --git a/internal/webserver/static/styles.css b/internal/webserver/static/styles.css index 4ae79d5..c135039 100644 --- a/internal/webserver/static/styles.css +++ b/internal/webserver/static/styles.css @@ -184,6 +184,11 @@ h3 { height: auto; } +.disappearing { + transition: opacity 2s ease; + opacity: 0; +} + /*************************************************************************************************** * diff --git a/internal/webserver/tpl/tweet_page_includes/chat_view.tpl b/internal/webserver/tpl/tweet_page_includes/chat_view.tpl index 7798502..72b8f8e 100644 --- a/internal/webserver/tpl/tweet_page_includes/chat_view.tpl +++ b/internal/webserver/tpl/tweet_page_includes/chat_view.tpl @@ -131,6 +131,9 @@ + + + {{end}} diff --git a/pkg/scraper/api_request_utils.go b/pkg/scraper/api_request_utils.go index b277a22..eff25a7 100644 --- a/pkg/scraper/api_request_utils.go +++ b/pkg/scraper/api_request_utils.go @@ -164,7 +164,11 @@ func (api *API) do_http_POST(remote_url string, body string, result interface{}) return fmt.Errorf("Error initializing HTTP POST request:\n %w", err) } - req.Header.Set("content-type", "application/json") + if body[0] == '{' { + req.Header.Set("content-type", "application/json") + } else { + req.Header.Set("content-type", "application/x-www-form-urlencoded") + } api.add_authentication_headers(req) @@ -191,6 +195,11 @@ func (api *API) do_http_POST(remote_url string, body string, result interface{}) panic(err) } + if resp.StatusCode == 204 { + // No Content + return nil + } + if resp.StatusCode != 200 { responseHeaders := "" for header := range resp.Header { diff --git a/pkg/scraper/api_types_dms.go b/pkg/scraper/api_types_dms.go index 9988048..faa2243 100644 --- a/pkg/scraper/api_types_dms.go +++ b/pkg/scraper/api_types_dms.go @@ -521,3 +521,16 @@ func (api *API) SendDMMessage(room_id DMChatRoomID, text string, in_reply_to_id err = api.do_http_POST(url.String(), post_data, &result) return result, err } + +// Mark a chat as read. +func (api *API) MarkDMChatRead(room_id DMChatRoomID, read_message_id DMMessageID) { + url := fmt.Sprintf("https://twitter.com/i/api/1.1/dm/conversation/%s/mark_read.json", room_id) + + // `do_http_POST` will set the "content-type" header based on whether the body starts with '{' or not. + data := fmt.Sprintf("conversationId=%s&last_read_event_id=%d", room_id, read_message_id) + + err := api.do_http_POST(url, data, nil) // Expected: HTTP 204 + if err != nil { + panic(err) + } +} diff --git a/pkg/scraper/dm_trove.go b/pkg/scraper/dm_trove.go index 1f8d892..88bf1a0 100644 --- a/pkg/scraper/dm_trove.go +++ b/pkg/scraper/dm_trove.go @@ -111,3 +111,9 @@ func SendDMMessage(room_id DMChatRoomID, text string, in_reply_to_id DMMessageID } return dm_response.ToDMTrove() } +func MarkDMChatRead(room_id DMChatRoomID, read_message_id DMMessageID) { + if !the_api.IsAuthenticated { + log.Fatalf("Writing DMs can only be done when authenticated. Please provide `--session [user]`") + } + the_api.MarkDMChatRead(room_id, read_message_id) +}