149 lines
4.6 KiB
Go
149 lines
4.6 KiB
Go
package webserver
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
. "gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence"
|
|
"gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper"
|
|
)
|
|
|
|
type LoginData struct {
|
|
LoginForm
|
|
ExistingSessions []UserHandle
|
|
}
|
|
|
|
type LoginForm struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
FormErrors
|
|
}
|
|
type FormErrors map[string]string
|
|
|
|
func (f *LoginForm) Validate() {
|
|
if f.FormErrors == nil {
|
|
f.FormErrors = make(FormErrors)
|
|
}
|
|
if len(f.Username) == 0 {
|
|
f.FormErrors["username"] = "cannot be blank"
|
|
}
|
|
if len(f.Password) == 0 {
|
|
f.FormErrors["password"] = "cannot be blank"
|
|
}
|
|
}
|
|
|
|
func (app *Application) Login(w http.ResponseWriter, r *http.Request) {
|
|
app.traceLog.Printf("'Login' handler (path: %q)", r.URL.Path)
|
|
var form LoginForm
|
|
if r.Method == "POST" {
|
|
data, err := io.ReadAll(r.Body)
|
|
panic_if(err)
|
|
panic_if(json.Unmarshal(data, &form)) // TODO: HTTP 400 not 500
|
|
form.Validate()
|
|
if len(form.FormErrors) == 0 {
|
|
api, err := scraper.NewGuestSession()
|
|
if err != nil {
|
|
panic(err.Error()) // Return it as a toast
|
|
}
|
|
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.")
|
|
}
|
|
app.after_login(w, r, api)
|
|
} else {
|
|
s := ""
|
|
for i := range form.FormErrors {
|
|
s += i + " " + form.FormErrors[i] + "\n"
|
|
}
|
|
panic(s)
|
|
}
|
|
return
|
|
}
|
|
|
|
// method == "GET"
|
|
data := LoginData{
|
|
LoginForm: form,
|
|
ExistingSessions: app.Profile.ListSessions(),
|
|
}
|
|
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.UserHandle, api.MustMarshalJSON())
|
|
|
|
// Ensure the user is downloaded
|
|
user, err := api.GetUser(api.UserHandle)
|
|
if err != nil { // ErrDoesntExist or otherwise
|
|
app.error_404(w, r)
|
|
return
|
|
}
|
|
panic_if(app.Profile.SaveUser(&user)) // TODO: handle conflicting users
|
|
panic_if(app.Profile.DownloadUserContentFor(&user, app.API.DownloadMedia))
|
|
|
|
// 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 := app.API.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.full_save_tweet_trove(trove)
|
|
|
|
// Scrape the user's followers
|
|
trove, err = app.API.GetFollowees(user.ID, 1000)
|
|
if err != nil {
|
|
app.ErrorLog.Printf("Failed to scrape followers: %s", err.Error())
|
|
http.Redirect(w, r, "/", 303)
|
|
}
|
|
app.full_save_tweet_trove(trove)
|
|
app.Profile.SaveAsFolloweesList(user.ID, trove)
|
|
|
|
// 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 {
|
|
AccountName string `json:"account"`
|
|
}{}
|
|
formdata, err := io.ReadAll(r.Body)
|
|
panic_if(err)
|
|
panic_if(json.Unmarshal(formdata, &form)) // TODO: HTTP 400 not 500
|
|
err = app.SetActiveUser(UserHandle(form.AccountName))
|
|
if err != nil {
|
|
app.error_400_with_message(w, r, fmt.Sprintf("User not in database: %s", form.AccountName))
|
|
return
|
|
}
|
|
app.LastReadNotificationSortIndex = 0 // Clear unread notifications
|
|
|
|
// Update notifications info in background (avoid latency when switching users)
|
|
go func() {
|
|
trove, last_unread_notification_sort_index, err := app.API.GetNotifications(1) // Just 1 page
|
|
if err != nil && !errors.Is(err, scraper.END_OF_FEED) && !errors.Is(err, scraper.ErrRateLimited) {
|
|
app.ErrorLog.Printf("Error occurred on getting notifications after switching users: %s", err.Error())
|
|
return
|
|
}
|
|
// We have to save the notifications first, otherwise it'll just report 0 since the last-read sort index
|
|
app.full_save_tweet_trove(trove)
|
|
// Set the notifications count
|
|
app.LastReadNotificationSortIndex = last_unread_notification_sort_index
|
|
}()
|
|
data := NotificationBubbles{
|
|
NumMessageNotifications: len(app.Profile.GetUnreadConversations(app.ActiveUser.ID)),
|
|
}
|
|
if app.LastReadNotificationSortIndex != 0 {
|
|
data.NumRegularNotifications = app.Profile.GetUnreadNotificationsCount(app.ActiveUser.ID, app.LastReadNotificationSortIndex)
|
|
}
|
|
app.buffered_render_htmx(w, "nav-sidebar", PageGlobalData{}, data)
|
|
}
|