From 8255c4f65ec34257d10b32380e9dd4456d070855 Mon Sep 17 00:00:00 2001 From: Alessio Date: Sun, 3 Mar 2024 15:04:19 -0800 Subject: [PATCH] Improve login flow - on successful login, kick off scraping of logged-in user, their followees, and only-followed home timeline - `--auto-open` flag now opens `/login` instead of home page - add HTMX spinner for login page - provide toast error notification if login is challenged (e.g., 2FA) --- internal/webserver/handler_login.go | 51 ++++++++++++++++++++++++++--- internal/webserver/server.go | 2 +- internal/webserver/tpl/login.tpl | 5 +++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/internal/webserver/handler_login.go b/internal/webserver/handler_login.go index 4b8e2fb..df9641e 100644 --- a/internal/webserver/handler_login.go +++ b/internal/webserver/handler_login.go @@ -42,12 +42,13 @@ func (app *Application) Login(w http.ResponseWriter, r *http.Request) { form.Validate() if len(form.FormErrors) == 0 { api := scraper.NewGuestSession() - api.LogIn(form.Username, form.Password) - app.Profile.SaveSession(api) - if err := app.SetActiveUser(api.UserHandle); err != nil { - app.ErrorLog.Printf(err.Error()) + challenge := api.LogIn(form.Username, form.Password) + if challenge != nil { + panic( // Middleware will trap this panic and return an HTMX error toast + "Twitter challenged your login. Make sure your username is your user handle (not email). " + + "If you're logging in from a new device, try doing it on the official Twitter site first, then try again here.") } - http.Redirect(w, r, "/login", 303) + app.after_login(w, r, api) } return } @@ -60,6 +61,46 @@ func (app *Application) Login(w http.ResponseWriter, r *http.Request) { app.buffered_render_page(w, "tpl/login.tpl", PageGlobalData{}, &data) } +func (app *Application) after_login(w http.ResponseWriter, r *http.Request, api scraper.API) { + app.Profile.SaveSession(api) + + // Ensure the user is downloaded + user, err := scraper.GetUser(api.UserHandle) + if err != nil { + app.error_404(w) + return + } + panic_if(app.Profile.SaveUser(&user)) + panic_if(app.Profile.DownloadUserContentFor(&user)) + + // Now that the user is scraped for sure, set them as the logged-in user + err = app.SetActiveUser(api.UserHandle) + panic_if(err) + + // Scrape the user's feed + trove, err := scraper.GetHomeTimeline("", true) + if err != nil { + app.ErrorLog.Printf("Initial timeline scrape failed: %s", err.Error()) + http.Redirect(w, r, "/", 303) + } + fmt.Println("Saving initial feed results...") + app.Profile.SaveTweetTrove(trove, false) + go app.Profile.SaveTweetTrove(trove, true) + + // Scrape the user's followers + trove, err = scraper.GetFollowees(user.ID, 1000) + if err != nil { + app.ErrorLog.Printf("Failed to scrape followers: %s", err.Error()) + http.Redirect(w, r, "/", 303) + } + app.Profile.SaveTweetTrove(trove, false) + app.Profile.SaveAsFolloweesList(user.ID, trove) + go app.Profile.SaveTweetTrove(trove, true) + + // Redirect to Timeline + http.Redirect(w, r, "/", 303) +} + func (app *Application) ChangeSession(w http.ResponseWriter, r *http.Request) { app.traceLog.Printf("'change-session' handler (path: %q)", r.URL.Path) form := struct { diff --git a/internal/webserver/server.go b/internal/webserver/server.go index 4b2e5e8..e024809 100644 --- a/internal/webserver/server.go +++ b/internal/webserver/server.go @@ -150,7 +150,7 @@ func (app *Application) Run(address string, should_auto_open bool) { app.start_background() if should_auto_open { - go openWebPage("http://" + address) + go openWebPage("http://" + address + "/login") } err := srv.ListenAndServe() app.ErrorLog.Fatal(err) diff --git a/internal/webserver/tpl/login.tpl b/internal/webserver/tpl/login.tpl index 461a644..0f63525 100644 --- a/internal/webserver/tpl/login.tpl +++ b/internal/webserver/tpl/login.tpl @@ -35,6 +35,11 @@
+ +
+
+ +
{{end}}