package webserver import ( "errors" "fmt" "net/http" "net/url" "strconv" "strings" "gitlab.com/offline-twitter/twitter_offline_engine/pkg/persistence" "gitlab.com/offline-twitter/twitter_offline_engine/pkg/scraper" ) type SearchPageData struct { persistence.Feed SearchText string SortOrder persistence.SortOrder SortOrderOptions []string IsUsersSearch bool UserIDs []scraper.UserID // TODO: fill out the search text in the search bar as well (needs modifying the base template) } func NewSearchPageData() SearchPageData { ret := SearchPageData{SortOrderOptions: []string{}, Feed: persistence.NewFeed()} for i := 0; i < 4; i++ { // Don't include "Liked At" option which is #4 ret.SortOrderOptions = append(ret.SortOrderOptions, persistence.SortOrder(i).String()) } return ret } func (app *Application) SearchUsers(w http.ResponseWriter, r *http.Request) { ret := NewSearchPageData() ret.IsUsersSearch = true ret.SearchText = strings.Trim(r.URL.Path, "/") ret.UserIDs = []scraper.UserID{} for _, u := range app.Profile.SearchUsers(ret.SearchText) { ret.TweetTrove.Users[u.ID] = u ret.UserIDs = append(ret.UserIDs, u.ID) } app.buffered_render_page(w, "tpl/search.tpl", PageGlobalData{TweetTrove: ret.Feed.TweetTrove, SearchText: ret.SearchText}, ret) } func (app *Application) Search(w http.ResponseWriter, r *http.Request) { app.traceLog.Printf("'Search' handler (path: %q)", r.URL.Path) search_text := strings.Trim(r.URL.Path, "/") if search_text == "" { // Redirect GET param "q" to use a URL param instead search_text = r.URL.Query().Get("q") if search_text == "" { app.error_400_with_message(w, "Empty search query") return // TODO: return an actual page } http.Redirect(w, r, fmt.Sprintf("/search/%s", url.PathEscape(search_text)), 302) return } // Handle users search if r.URL.Query().Get("type") == "users" { app.SearchUsers(w, r) return } // Handle "@username" if search_text[0] == '@' { http.Redirect(w, r, fmt.Sprintf("/%s", search_text[1:]), 302) return } // Handle pasted URLs maybe_url, err := url.Parse(search_text) if err == nil && (maybe_url.Host == "twitter.com" || maybe_url.Host == "mobile.twitter.com" || maybe_url.Host == "x.com") { // TODO: use scraper.TryParseTweetUrl for this somehow // Problem: it currently only supports tweet URLs parts := strings.Split(strings.Trim(maybe_url.Path, "/"), "/") // Handle tweet links if len(parts) == 3 && parts[1] == "status" { id, err := strconv.Atoi(parts[2]) if err == nil { http.Redirect(w, r, fmt.Sprintf("/tweet/%d", id), 302) return } } // Handle user profile links if len(parts) == 1 || (len(parts) == 2 && parts[1] == "with_replies") { http.Redirect(w, r, fmt.Sprintf("/%s", parts[0]), 302) return } } // Actual search // Scrape if needed if r.URL.Query().Has("scrape") { if app.IsScrapingDisabled { app.InfoLog.Printf("Would have scraped: %s", r.URL.Path) http.Error(w, "Scraping is disabled (are you logged in?)", 401) return } // Run scraper trove, err := scraper.Search(search_text, 1) // TODO: parameterizable if err != nil { app.ErrorLog.Print(err) // TOOD: show error in UI } app.Profile.SaveTweetTrove(trove, false) go app.Profile.SaveTweetTrove(trove, true) } c, err := persistence.NewCursorFromSearchQuery(search_text) if err != nil { app.error_400_with_message(w, err.Error()) return // TODO: return actual page } err = parse_cursor_value(&c, r) if err != nil { app.error_400_with_message(w, "invalid cursor (must be a number)") return } var is_ok bool c.SortOrder, is_ok = persistence.SortOrderFromString(r.URL.Query().Get("sort-order")) if !is_ok && r.URL.Query().Get("sort-order") != "" { app.error_400_with_message(w, "Invalid sort order") } feed, err := app.Profile.NextPage(c, app.ActiveUser.ID) if err != nil && !errors.Is(err, persistence.ErrEndOfFeed) { panic(err) } data := NewSearchPageData() data.Feed = feed data.SearchText = search_text data.SortOrder = c.SortOrder if r.Header.Get("HX-Request") == "true" && c.CursorPosition == persistence.CURSOR_MIDDLE { // It's a Show More request app.buffered_render_htmx(w, "timeline", PageGlobalData{TweetTrove: data.Feed.TweetTrove, SearchText: search_text}, data) } else { app.buffered_render_page(w, "tpl/search.tpl", PageGlobalData{TweetTrove: data.Feed.TweetTrove, SearchText: search_text}, data) } }