Add polling for new messages in a chat room while on the page
This commit is contained in:
parent
d80a2bd5b1
commit
bd90b1c528
@ -5,12 +5,16 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence"
|
||||
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
||||
)
|
||||
|
||||
type MessageData persistence.DMChatView
|
||||
type MessageData struct {
|
||||
persistence.DMChatView
|
||||
LatestPollingTimestamp int
|
||||
}
|
||||
|
||||
func (t MessageData) Tweet(id scraper.TweetID) scraper.Tweet {
|
||||
return t.Tweets[id]
|
||||
@ -38,6 +42,16 @@ func (app *Application) Messages(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
parts := strings.Split(strings.Trim(r.URL.Path, "/"), "/")
|
||||
room_id := scraper.DMChatRoomID(parts[0])
|
||||
|
||||
if r.URL.Query().Has("scrape") {
|
||||
// TODO: where is this going to be used?
|
||||
app.background_dm_polling_scrape()
|
||||
}
|
||||
|
||||
chat_view_data := MessageData{DMChatView: app.Profile.GetChatRoomsPreview(app.ActiveUser.ID)} // Get message list previews
|
||||
|
||||
if room_id != "" {
|
||||
// First send a message, if applicable
|
||||
if len(parts) == 2 && parts[1] == "send" {
|
||||
body, err := io.ReadAll(r.Body)
|
||||
panic_if(err)
|
||||
@ -49,14 +63,26 @@ func (app *Application) Messages(w http.ResponseWriter, r *http.Request) {
|
||||
app.Profile.SaveDMTrove(trove, false)
|
||||
go app.Profile.SaveDMTrove(trove, true)
|
||||
}
|
||||
|
||||
chat_view := app.Profile.GetChatRoomsPreview(app.ActiveUser.ID)
|
||||
if strings.Trim(r.URL.Path, "/") != "" {
|
||||
chat_view.ActiveRoomID = room_id
|
||||
chat_contents := app.Profile.GetChatRoomContents(room_id)
|
||||
chat_view.MergeWith(chat_contents.DMTrove)
|
||||
chat_view.MessageIDs = chat_contents.MessageIDs
|
||||
chat_view_data.ActiveRoomID = room_id
|
||||
chat_view_data.LatestPollingTimestamp = -1
|
||||
if latest_timestamp_str := r.URL.Query().Get("latest_timestamp"); latest_timestamp_str != "" {
|
||||
var err error
|
||||
chat_view_data.LatestPollingTimestamp, err = strconv.Atoi(latest_timestamp_str)
|
||||
panic_if(err)
|
||||
}
|
||||
chat_contents := app.Profile.GetChatRoomContents(room_id, chat_view_data.LatestPollingTimestamp)
|
||||
chat_view_data.MergeWith(chat_contents.DMTrove)
|
||||
chat_view_data.MessageIDs = chat_contents.MessageIDs
|
||||
if len(chat_view_data.MessageIDs) > 0 {
|
||||
last_message_id := chat_view_data.MessageIDs[len(chat_view_data.MessageIDs) - 1]
|
||||
chat_view_data.LatestPollingTimestamp = int(chat_view_data.Messages[last_message_id].SentAt.Unix())
|
||||
}
|
||||
|
||||
app.buffered_render_tweet_page(w, "tpl/messages.tpl", MessageData(chat_view))
|
||||
if r.URL.Query().Has("poll") {
|
||||
app.buffered_render_tweet_htmx(w, "messages-with-poller", chat_view_data)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
app.buffered_render_tweet_page(w, "tpl/messages.tpl", chat_view_data)
|
||||
}
|
||||
|
@ -608,4 +608,30 @@ func TestMessagesRoom(t *testing.T) {
|
||||
require.NoError(err)
|
||||
assert.Len(cascadia.QueryAll(root, selector(".chat-list .chat")), 2) // Chat list still renders
|
||||
assert.Len(cascadia.QueryAll(root, selector("#chat-view .dm-message-and-reacts-container")), 5)
|
||||
|
||||
// Should have the poller at the bottom
|
||||
node := cascadia.Query(root, selector("#new-messages-poller"))
|
||||
assert.NotNil(node)
|
||||
assert.Contains(node.Attr, html.Attribute{Key: "hx-get", Val: "/messages/1488963321701171204-1178839081222115328?poll&latest_timestamp=1686025129144"})
|
||||
}
|
||||
|
||||
// Loading the page since
|
||||
func TestMessagesRoomPollForUpdates(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// Boilerplate for setting an active user
|
||||
app := webserver.NewApp(profile)
|
||||
app.IsScrapingDisabled = true
|
||||
app.ActiveUser = scraper.User{ID: 1488963321701171204, Handle: "Offline_Twatter"} // Simulate a login
|
||||
|
||||
// Chat detail
|
||||
recorder := httptest.NewRecorder()
|
||||
req := httptest.NewRequest("GET", "/messages/1488963321701171204-1178839081222115328?poll&latest_timestamp=1686025129141", nil)
|
||||
req.Header.Set("HX-Request", "true")
|
||||
app.ServeHTTP(recorder, req)
|
||||
resp := recorder.Result()
|
||||
root, err := html.Parse(resp.Body)
|
||||
require.NoError(err)
|
||||
assert.Len(cascadia.QueryAll(root, selector(".dm-message-and-reacts-container")), 3)
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
{{define "chat-view"}}
|
||||
<div id="chat-view">
|
||||
<div class="chat-messages">
|
||||
{{define "messages-with-poller"}}
|
||||
{{range .MessageIDs}}
|
||||
{{$message := (index $.DMTrove.Messages .)}}
|
||||
{{$user := (user $message.SenderID)}}
|
||||
@ -53,6 +51,20 @@
|
||||
</p>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div id="new-messages-poller"
|
||||
hx-swap="outerHTML"
|
||||
hx-trigger="load delay:7s"
|
||||
hx-get="/messages/{{$.ActiveRoomID}}?poll&latest_timestamp={{$.LatestPollingTimestamp}}"
|
||||
></div>
|
||||
{{end}}
|
||||
|
||||
{{define "chat-view"}}
|
||||
<div id="chat-view">
|
||||
<div class="chat-messages">
|
||||
{{if $.ActiveRoomID}}
|
||||
{{template "messages-with-poller" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
{{if $.ActiveRoomID}}
|
||||
<div class="dm-composer-container">
|
||||
|
@ -234,7 +234,7 @@ func (p Profile) GetChatRoomsPreview(id UserID) DMChatView {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p Profile) GetChatRoomContents(id DMChatRoomID) DMChatView {
|
||||
func (p Profile) GetChatRoomContents(id DMChatRoomID, latest_timestamp int) DMChatView {
|
||||
ret := NewDMChatView()
|
||||
var room DMChatRoom
|
||||
err := p.DB.Get(&room, `
|
||||
@ -274,9 +274,10 @@ func (p Profile) GetChatRoomContents(id DMChatRoomID) DMChatView {
|
||||
select id, chat_room_id, sender_id, sent_at, request_id, text, in_reply_to_id, embedded_tweet_id
|
||||
from chat_messages
|
||||
where chat_room_id = ?
|
||||
and sent_at > ?
|
||||
order by sent_at desc
|
||||
limit 50
|
||||
`, room.ID)
|
||||
`, room.ID, latest_timestamp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@ -342,6 +343,32 @@ func (p Profile) GetChatRoomContents(id DMChatRoomID) DMChatView {
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch message previews
|
||||
replied_message_ids := []interface{}{}
|
||||
for _, m := range ret.Messages {
|
||||
if m.InReplyToID != 0 {
|
||||
// Don't clobber if it's already been fetched
|
||||
if _, is_ok := ret.Messages[m.InReplyToID]; !is_ok {
|
||||
replied_message_ids = append(replied_message_ids, m.InReplyToID)
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(replied_message_ids) > 0 {
|
||||
var replied_msgs []DMMessage
|
||||
err = p.DB.Select(&replied_msgs, `
|
||||
select id, chat_room_id, sender_id, sent_at, request_id, text, in_reply_to_id, embedded_tweet_id
|
||||
from chat_messages
|
||||
where id in (`+strings.Repeat("?,", len(replied_message_ids)-1)+`?)`,
|
||||
replied_message_ids...)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, msg := range replied_msgs {
|
||||
msg.Reactions = make(map[UserID]DMReaction)
|
||||
ret.Messages[msg.ID] = msg
|
||||
}
|
||||
}
|
||||
|
||||
p.fill_content(&ret.DMTrove.TweetTrove, UserID(0))
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ func TestGetChatRoomContents(t *testing.T) {
|
||||
require.NoError(err)
|
||||
|
||||
room_id := DMChatRoomID("1458284524761075714-1488963321701171204")
|
||||
chat_view := profile.GetChatRoomContents(room_id)
|
||||
chat_view := profile.GetChatRoomContents(room_id, -1)
|
||||
assert.Len(chat_view.Rooms, 1)
|
||||
room, is_ok := chat_view.Rooms[room_id]
|
||||
require.True(is_ok)
|
||||
@ -232,3 +232,28 @@ func TestGetChatRoomContents(t *testing.T) {
|
||||
require.True(is_ok)
|
||||
assert.Equal(u.Location, "on my computer")
|
||||
}
|
||||
|
||||
func TestGetChatRoomContentsAfterTimestamp(t *testing.T) {
|
||||
require := require.New(t)
|
||||
assert := assert.New(t)
|
||||
|
||||
profile, err := persistence.LoadProfile("../../sample_data/profile")
|
||||
require.NoError(err)
|
||||
|
||||
room_id := DMChatRoomID("1488963321701171204-1178839081222115328")
|
||||
chat_view := profile.GetChatRoomContents(room_id, 1686025129141)
|
||||
|
||||
// MessageIDs should just be the ones in the thread
|
||||
require.Equal(chat_view.MessageIDs, []DMMessageID{1665936253483614215, 1665936253483614216, 1665936253483614217})
|
||||
|
||||
// Replied messages should be available, but not in the list of MessageIDs
|
||||
require.Len(chat_view.Messages, 4)
|
||||
msg, is_ok := chat_view.Messages[1665936253483614214]
|
||||
assert.True(is_ok)
|
||||
assert.Equal(msg.ID, DMMessageID(1665936253483614214))
|
||||
for _, msg_id := range chat_view.MessageIDs {
|
||||
msg, is_ok := chat_view.Messages[msg_id]
|
||||
assert.True(is_ok)
|
||||
assert.Equal(msg.ID, msg_id)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user