cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists ๐Ÿƒ
charm leaflet readability golang

docs: media feature roadmap/tasks feat: media service layer

+2927 -132
+100 -86
ROADMAP.md
··· 1 1 # ROADMAP 2 2 3 - ## Core Task Management (TaskWarrior-inspired) 3 + ## Task Management Commands (TaskWarrior-inspired) 4 4 5 - ### Basic Operations 5 + ### Implemented Commands 6 6 7 - - [x] `create|new` - Add new task with description and optional metadata 8 - - [x] `list` - Display tasks with filtering and sorting options 9 - - [x] `view` - View task by ID 10 - - [x] `update` - Edit task properties (description, priority, project, tags) 11 - - [x] `done` - Mark task as completed 12 - - [x] `delete` - Remove task permanently 7 + - [x] `todo add [description]` - Create new task with metadata (priority, project, context, due, tags) 8 + - [x] `todo list` - Display tasks with filtering (status, priority, project, context) and interactive/static modes 9 + - [x] `todo view [task-id]` - View task details with format options (detailed, brief, json) 10 + - [x] `todo update [task-id]` - Edit task properties via flags 11 + - [x] `todo edit [task-id]` - Interactive task editor with status picker and priority toggle 12 + - [x] `todo done [task-id]` - Mark task as completed 13 + - [x] `todo delete [task-id]` - Remove task permanently 13 14 14 - ### Organization & Metadata 15 + --- 15 16 16 - - [x] Project & context organization 17 - - [x] `projects` - List all project names 18 - - [x] Tag management system 19 - - [x] `tags` - List all tag names 20 - - [x] Status tracking - todo, in-progress, blocked, done, abandoned 21 - - [x] Status indicators in task list view (colored unicode/non-emoji symbols) 22 - - [x] Priority system - High/medium/low or numeric scales 23 - - [x] Priority indicators in task list view (โ˜…โ˜…โ˜… visual + color coding) 24 - - [x] Edit View 25 - - [x] Interactive status picker in task edit view 26 - - [x] Priority system toggle in task edit view (Text/Numeric/Legacy modes) 27 - - [ ] Due dates & scheduling - Including recurring tasks 28 - - [ ] Task dependencies - Task A blocks task B relationships 17 + - [x] `todo projects` - List all project names (interactive/static modes) 18 + - [x] `todo tags` - List all tag names (interactive/static modes) 19 + - [x] `todo contexts` - List all contexts/locations (interactive/static modes) 29 20 30 - ### Time Management 21 + --- 31 22 32 - - [ ] Time tracking functionality 33 - - [ ] `start/stop` - Track active time on tasks 34 - - [ ] `timesheet` - Show time tracking summaries 35 - - [ ] `calendar` - Display tasks in calendar view 23 + - [x] `todo start [task-id]` - Start time tracking for a task 24 + - [x] `todo stop [task-id]` - Stop time tracking for a task 25 + - [x] `todo timesheet` - Show time tracking summaries (with date range and task filters) 36 26 37 - ### Advanced Task Features 27 + ### Commands To Be Implemented 38 28 29 + - [ ] Due dates & scheduling - Including recurring tasks 30 + - [ ] Task dependencies - Task A blocks task B relationships 39 31 - [ ] `annotate` - Add notes/comments to existing tasks 40 32 - [ ] Recurring tasks 41 33 - [ ] Smart due date suggestions 42 34 - [ ] Completion notifications 35 + - [ ] `calendar` - Display tasks in calendar view 43 36 44 - ## Content Queue Management 37 + ## Media Queue Management Commands 45 38 46 - ### Reading Management 39 + ### Implemented Commands 47 40 48 - - [x] `book add` - Add book to reading list 49 - - [x] `book list` - Show reading queue with progress 50 - - [x] `book reading` - Mark book as currently reading 51 - - [x] `book finished|read` - Mark book as completed 52 - - [x] `book remove|rm` - Remove from reading list 53 - - [x] `book progress` - Update reading progress percentage 41 + Book Management 42 + 43 + - [x] `media book add [search query...]` - Search and add book to reading list (with interactive mode) 44 + - [x] `media book list` - Show reading queue with progress and status filtering 45 + - [x] `media book reading <id>` - Mark book as currently reading 46 + - [x] `media book finished <id>` - Mark book as completed 47 + - [x] `media book remove <id>` - Remove from reading list 48 + - [x] `media book progress <id> <percentage>` - Update reading progress (0-100%) 49 + - [x] `media book update <id> <status>` - Update book status (queued|reading|finished|removed) 54 50 55 - #### Enhanced Reading Features 51 + ### Commands To Be Implemented 52 + 53 + Movie Management 54 + 55 + - [ ] `media movie add [title]` - Add movie to watch queue 56 + - [ ] `media movie list` - Show movie queue with ratings/metadata 57 + - [ ] `media movie watched <id>` - Mark movie as watched 58 + - [ ] `media movie remove <id>` - Remove from queue 59 + 60 + TV Show Management 61 + 62 + - [ ] `media tv add [title]` - Add TV show/season to queue 63 + - [ ] `media tv list` - Show TV queue with episode tracking 64 + - [ ] `media tv watched <id>` - Mark episodes/seasons as watched 65 + - [ ] `media tv remove <id>` - Remove from TV queue 66 + 67 + --- 56 68 57 69 - [ ] Articles, papers, blogs support (implement article parser) 58 - - [ ] Reading status: want-to-read, currently-reading, completed, abandoned 59 70 - [ ] Source tracking (recommendation sources) 60 71 - [ ] Ratings and personal notes 61 72 - [ ] Genre/topic tagging 62 - - [ ] Progress tracking (pages/chapters read, completion %) 63 - 64 - ### Watching Management 73 + - [ ] Episode/season progress tracking for TV 74 + - [ ] Platform tracking (Netflix, Amazon, etc.) 75 + - [ ] Watch status: queued, watching, completed, dropped 65 76 66 - - [ ] `movie add` - Add movie to watch queue 67 - - [ ] `movie list` - Show movie queue with ratings/metadata 68 - - [ ] `movie watched|seen` - Mark movie as watched 69 - - [ ] `movie remove|rm` - Remove from queue 77 + ## Management Commands 70 78 71 - - [ ] `tv add` - Add TV show/season to queue 72 - - [ ] `tv list` - Show TV queue with episode tracking 73 - - [ ] `tv watched|seen` - Mark episodes/seasons as watched 74 - - [ ] `tv remove|rm` - Remove from TV queue 79 + ### Implemented Commands 75 80 76 - #### Enhanced Watching Features 81 + Application Management 77 82 78 - - [ ] Episode/season progress tracking 79 - - [ ] Watch status: queued, watching, completed, dropped 80 - - [ ] Platform tracking (Netflix, Amazon, etc.) 81 - - [ ] Ratings and reviews 82 - - [ ] Genre/mood tagging 83 + - [x] `status` - Show application status and configuration 84 + - [x] `setup` - Initialize and manage application setup 85 + - [x] `setup seed` - Populate database with test data (with --force flag) 86 + - [x] `reset` - Reset the application (removes all data) 87 + - [x] `config [key] [value]` - Manage configuration settings (stubbed) 83 88 84 - ## Organization & Discovery Features 89 + ### Commands To Be Implemented 85 90 86 - ### Smart Views & Filtering 91 + Organization Features 87 92 88 93 - [ ] Custom queries and saved searches 89 94 - [ ] Context-aware suggestions ··· 92 97 - [ ] Seasonal/mood-based filtering 93 98 - [ ] Full-text search across titles, notes, tags 94 99 95 - ### Analytics & Insights 100 + Analytics 96 101 97 102 - [ ] Reading/watching velocity tracking 98 103 - [ ] Completion rates by content type ··· 100 105 - [ ] Personal productivity metrics 101 106 - [ ] Content source analysis 102 107 103 - ## Advanced Workflow Features 104 - 105 - ### Integration & Import 108 + Integrations 106 109 107 110 - [ ] `import` - Import from various formats (CSV, JSON, todo.txt) 108 111 - [ ] `export` - Export to various formats ··· 112 115 - [ ] TaskWarrior import/export 113 116 - [ ] URL parsing for automatic metadata 114 117 115 - ### Todo.txt Compatibility 118 + `todo.txt` Compatibility 116 119 117 120 - [ ] `archive` - Move completed tasks to done.txt 118 121 - [ ] `[con]texts` - List all contexts (@context) ··· 122 125 - [ ] `[re]place` - Replace task text entirely 123 126 - [ ] `prepend/append` - Add text to beginning/end of task 124 127 125 - ### Automation 128 + Automation 126 129 127 130 - [ ] Auto-categorization of new items 128 131 - [ ] Smart due date suggestions 129 132 - [ ] Recurring content (weekly podcast check-ins) 130 133 - [ ] Completion notifications 131 134 132 - ## Data Management 133 - 134 - ### Storage & Sync 135 + Storage 135 136 136 137 - [ ] `sync` - Synchronize with remote storage 137 138 - [ ] `sync setup` - Setup remote storage ··· 140 141 - [ ] `backup` - Create local backup 141 142 - [ ] Backup/restore functionality 142 143 143 - ### Configuration 144 + Configuration 144 145 145 - - [ ] `config` - Manage configuration settings 146 + - [ ] Enhanced `config` command implementation 146 147 - [ ] `undo` - Reverse last operation 147 148 - [ ] Themes and personalization 148 149 - [ ] Customizable output formats 149 150 150 - ## Notes Management 151 + ## Notes Management Commands 152 + 153 + ### Implemented Commands 151 154 152 - ### Basic Operations 155 + Core Notes Operations 153 156 154 - - [x] `create|new` - Creates a new markdown note and optionally opens in configured editor 155 - - [x] `list` - Opens interactive TUI browser for navigating and viewing notes 156 - - [x] `read|view` - Displays formatted note content with syntax highlighting 157 - - [x] `edit|update` - Opens configured editor OR replaces note content with new markdown file 158 - - [x] `remove|rm|delete|del` - Permanently removes the note file and metadata 157 + - [x] `note create [title] [content...]` - Create new markdown note with optional interactive editor 158 + - [x] `note list` - Interactive TUI browser for navigating and viewing notes (with archive and tag filtering) 159 + - [x] `note read <note-id>` - Display formatted note content with syntax highlighting 160 + - [x] `note edit <note-id>` - Edit note in configured editor 161 + - [x] `note remove <note-id>` - Permanently remove note file and metadata 159 162 160 - ### Advanced Notes Features 163 + Additional Options 161 164 162 - - [ ] `search` - Search notes by content, title, or tags 163 - - [ ] `tag` - Add/remove tags from notes 164 - - [ ] `recent` - Show recently created/modified notes 165 - - [ ] `templates` - Create notes from predefined templates 166 - - [ ] `archive` - Archive old notes 167 - - [ ] `export` - Export notes to various formats 165 + - [x] `--interactive|-i` flag for create command (opens editor) 166 + - [x] `--file|-f` flag for create command (create from markdown file) 167 + - [x] `--archived|-a` flag for list command 168 + - [x] `--tags` filtering for list command 169 + 170 + ### Commands To Be Implemented 171 + 172 + - [ ] `note search [query]` - Search notes by content, title, or tags 173 + - [ ] `note tag <note-id> [tags...]` - Add/remove tags from notes 174 + - [ ] `note recent` - Show recently created/modified notes 175 + - [ ] `note templates` - Create notes from predefined templates 176 + - [ ] `note archive <note-id>` - Archive old notes 177 + - [ ] `note export` - Export notes to various formats 168 178 - [ ] Full-text search integration 169 179 - [ ] Linking between notes and tasks/content 170 180 171 181 ## User Experience 172 182 173 - ### Interface 183 + - [x] Interactive TUI modes for task lists, projects, tags, contexts, and notes 184 + - [x] Static output modes as alternatives to interactive TUI 185 + - [x] Color-coded priority and status indicators 186 + - [x] Comprehensive help system via cobra CLI framework 187 + 188 + --- 174 189 175 - - [ ] Interactive TUI mode for browsing (likely using Bubbletea) 176 190 - [ ] Quick-add commands for rapid entry 177 - - [ ] Progress tracking UI 178 - - [ ] Comprehensive help system 191 + - [ ] Enhanced progress tracking UI 192 + - [ ] Calendar view for tasks 179 193 180 194 ### Technical Infrastructure 181 195
+257 -45
internal/services/media.go
··· 1 1 package services 2 2 3 3 import ( 4 + "context" 4 5 "encoding/json" 5 6 "fmt" 6 7 "io" 8 + "net/http" 7 9 "net/url" 8 10 "strconv" 9 11 "strings" 12 + "time" 10 13 11 14 "github.com/PuerkitoBio/goquery" 12 15 "github.com/gocolly/colly/v2" 16 + "github.com/stormlightlabs/noteleaf/internal/models" 17 + "golang.org/x/time/rate" 13 18 ) 14 19 15 20 type Media struct { ··· 29 34 } 30 35 31 36 var results []Media 32 - doc.Find("a.score-list-item").Each(func(i int, e *goquery.Selection) { 33 - title := e.Find("span").Text() 34 - link, _ := e.Attr("href") 35 - if link == "" { 36 - return 37 - } 38 - fullLink := "https://www.rottentomatoes.com" + link 37 + doc.Find("search-page-result").Each(func(i int, resultBlock *goquery.Selection) { 38 + mediaType, _ := resultBlock.Attr("type") 39 39 40 - mediaType := "unknown" 41 - if strings.HasPrefix(link, "/m/") { 42 - mediaType = "movie" 43 - } else if strings.HasPrefix(link, "/tv/") { 44 - mediaType = "tv" 45 - } 40 + resultBlock.Find("search-page-media-row").Each(func(j int, s *goquery.Selection) { 41 + link, _ := s.Find("a[slot='thumbnail']").Attr("href") 42 + if link == "" { 43 + link, _ = s.Find("a[slot='title']").Attr("href") 44 + if link == "" { 45 + return 46 + } 47 + } 46 48 47 - score := e.Find("rt-text.critics-score").Text() 48 - if score == "" { 49 - score = "--" 50 - } 49 + title := s.Find("a[slot='title']").Text() 51 50 52 - certified := false 53 - if v, ok := e.Find("score-icon-critics").Attr("certified"); ok && v == "true" { 54 - certified = true 55 - } 51 + var itemType string 52 + switch mediaType { 53 + case "movie": 54 + itemType = "movie" 55 + case "tvSeries": 56 + itemType = "tv" 57 + default: 58 + if strings.HasPrefix(link, "/m/") { 59 + itemType = "movie" 60 + } else if strings.HasPrefix(link, "/tv/") { 61 + itemType = "tv" 62 + } 63 + } 56 64 57 - results = append(results, Media{ 58 - Title: title, 59 - Link: fullLink, 60 - Type: mediaType, 61 - CriticScore: score, 62 - CertifiedFresh: certified, 65 + score, _ := s.Attr("tomatometerscore") 66 + if score == "" { 67 + score = "--" 68 + } 69 + 70 + certified := false 71 + if v, ok := s.Attr("tomatometeriscertified"); ok && v == "true" { 72 + certified = true 73 + } 74 + 75 + results = append(results, Media{ 76 + Title: strings.TrimSpace(title), 77 + Link: link, 78 + Type: itemType, 79 + CriticScore: score, 80 + CertifiedFresh: certified, 81 + }) 63 82 }) 64 83 }) 84 + 65 85 return results, nil 66 86 } 67 87 68 88 // SearchRottenTomatoes fetches live search results for a query. 69 - func SearchRottenTomatoes(q string) ([]Media, error) { 89 + var SearchRottenTomatoes = func(q string) ([]Media, error) { 70 90 searchURL := "https://www.rottentomatoes.com/search?search=" + url.QueryEscape(q) 71 91 html, err := FetchHTML(searchURL) 72 92 if err != nil { ··· 92 112 URL string `json:"url"` 93 113 } 94 114 115 + type PartOfSeries struct { 116 + Name string `json:"name"` 117 + URL string `json:"url"` 118 + } 119 + 95 120 type TVSeries struct { 96 121 Context string `json:"@context"` 97 122 Type string `json:"@type"` ··· 126 151 } 127 152 128 153 type TVSeason struct { 129 - Context string `json:"@context"` 130 - Type string `json:"@type"` 131 - Name string `json:"name"` 132 - URL string `json:"url"` 133 - Description string `json:"description"` 134 - Image string `json:"image"` 135 - SeasonNumber int `json:"seasonNumber"` 136 - DatePublished string `json:"datePublished"` 137 - PartOfSeries struct { 138 - Name string `json:"name"` 139 - URL string `json:"url"` 140 - } `json:"partOfSeries"` 154 + Context string `json:"@context"` 155 + Type string `json:"@type"` 156 + Name string `json:"name"` 157 + URL string `json:"url"` 158 + Description string `json:"description"` 159 + Image string `json:"image"` 160 + SeasonNumber int `json:"seasonNumber"` 161 + DatePublished string `json:"datePublished"` 162 + PartOfSeries PartOfSeries `json:"partOfSeries"` 141 163 AggregateRating AggregateRating `json:"aggregateRating"` 142 164 } 143 165 ··· 172 194 var movie Movie 173 195 found := false 174 196 doc.Find("script[type='application/ld+json']").Each(func(i int, s *goquery.Selection) { 175 - var tmp map[string]interface{} 197 + var tmp map[string]any 176 198 if err := json.Unmarshal([]byte(s.Text()), &tmp); err == nil { 177 199 if t, ok := tmp["@type"].(string); ok && t == "Movie" { 178 200 if err := json.Unmarshal([]byte(s.Text()), &movie); err == nil { ··· 237 259 return &season, nil 238 260 } 239 261 240 - func FetchHTML(url string) (string, error) { 262 + var FetchHTML = func(url string) (string, error) { 241 263 var html string 242 264 c := colly.NewCollector( 243 265 colly.AllowedDomains("www.rottentomatoes.com", "rottentomatoes.com"), ··· 249 271 return html, nil 250 272 } 251 273 252 - func FetchTVSeries(url string) (*TVSeries, error) { 274 + var FetchTVSeries = func(url string) (*TVSeries, error) { 253 275 html, err := FetchHTML(url) 254 276 if err != nil { 255 277 return nil, err ··· 257 279 return ExtractTVSeriesMetadata(strings.NewReader(html)) 258 280 } 259 281 260 - func FetchMovie(url string) (*Movie, error) { 282 + var FetchMovie = func(url string) (*Movie, error) { 261 283 html, err := FetchHTML(url) 262 284 if err != nil { 263 285 return nil, err ··· 265 287 return ExtractMovieMetadata(strings.NewReader(html)) 266 288 } 267 289 268 - func FetchTVSeason(url string) (*TVSeason, error) { 290 + var FetchTVSeason = func(url string) (*TVSeason, error) { 269 291 html, err := FetchHTML(url) 270 292 if err != nil { 271 293 return nil, err 272 294 } 273 295 return ExtractTVSeasonMetadata(strings.NewReader(html)) 274 296 } 297 + 298 + type MovieService struct { 299 + client *http.Client 300 + limiter *rate.Limiter 301 + } 302 + 303 + // NewMovieService creates a new movie service with rate limiting 304 + func NewMovieService() *MovieService { 305 + return &MovieService{ 306 + client: &http.Client{ 307 + Timeout: 30 * time.Second, 308 + }, 309 + limiter: rate.NewLimiter(rate.Limit(requestsPerSecond), burstLimit), 310 + } 311 + } 312 + 313 + // Search searches for movies on Rotten Tomatoes 314 + func (s *MovieService) Search(ctx context.Context, query string, page, limit int) ([]*models.Model, error) { 315 + if err := s.limiter.Wait(ctx); err != nil { 316 + return nil, fmt.Errorf("rate limit wait failed: %w", err) 317 + } 318 + 319 + results, err := SearchRottenTomatoes(query) 320 + if err != nil { 321 + return nil, fmt.Errorf("failed to search rotten tomatoes: %w", err) 322 + } 323 + 324 + var movies []*models.Model 325 + for _, media := range results { 326 + if media.Type == "movie" { 327 + movie := &models.Movie{ 328 + Title: media.Title, 329 + Status: "queued", 330 + Added: time.Now(), 331 + Notes: fmt.Sprintf("Critic Score: %s, Certified: %v, URL: %s", media.CriticScore, media.CertifiedFresh, media.Link), 332 + } 333 + var m models.Model = movie 334 + movies = append(movies, &m) 335 + } 336 + } 337 + 338 + // Basic pagination approximation 339 + start := (page - 1) * limit 340 + end := start + limit 341 + if start > len(movies) { 342 + return []*models.Model{}, nil 343 + } 344 + if end > len(movies) { 345 + end = len(movies) 346 + } 347 + 348 + return movies[start:end], nil 349 + } 350 + 351 + // Get retrieves a specific movie by its Rotten Tomatoes URL 352 + func (s *MovieService) Get(ctx context.Context, id string) (*models.Model, error) { 353 + if err := s.limiter.Wait(ctx); err != nil { 354 + return nil, fmt.Errorf("rate limit wait failed: %w", err) 355 + } 356 + 357 + movieData, err := FetchMovie(id) 358 + if err != nil { 359 + return nil, fmt.Errorf("failed to fetch movie: %w", err) 360 + } 361 + 362 + movie := &models.Movie{ 363 + Title: movieData.Name, 364 + Status: "queued", 365 + Added: time.Now(), 366 + Notes: movieData.Description, 367 + } 368 + 369 + if movieData.DateCreated != "" { 370 + if year, err := strconv.Atoi(strings.Split(movieData.DateCreated, "-")[0]); err == nil { 371 + movie.Year = year 372 + } 373 + } 374 + 375 + var model models.Model = movie 376 + return &model, nil 377 + } 378 + 379 + // Check verifies the API connection to Rotten Tomatoes 380 + func (s *MovieService) Check(ctx context.Context) error { 381 + if err := s.limiter.Wait(ctx); err != nil { 382 + return fmt.Errorf("rate limit wait failed: %w", err) 383 + } 384 + 385 + _, err := FetchHTML("https://www.rottentomatoes.com") 386 + return err 387 + } 388 + 389 + // Close cleans up the service resources 390 + func (s *MovieService) Close() error { 391 + return nil 392 + } 393 + 394 + // TVService implements APIService for Rotten Tomatoes TV shows 395 + type TVService struct { 396 + client *http.Client 397 + limiter *rate.Limiter 398 + } 399 + 400 + // NewTVService creates a new TV service with rate limiting 401 + func NewTVService() *TVService { 402 + return &TVService{ 403 + client: &http.Client{ 404 + Timeout: 30 * time.Second, 405 + }, 406 + limiter: rate.NewLimiter(rate.Limit(requestsPerSecond), burstLimit), 407 + } 408 + } 409 + 410 + // Search searches for TV shows on Rotten Tomatoes 411 + func (s *TVService) Search(ctx context.Context, query string, page, limit int) ([]*models.Model, error) { 412 + if err := s.limiter.Wait(ctx); err != nil { 413 + return nil, fmt.Errorf("rate limit wait failed: %w", err) 414 + } 415 + 416 + results, err := SearchRottenTomatoes(query) 417 + if err != nil { 418 + return nil, fmt.Errorf("failed to search rotten tomatoes: %w", err) 419 + } 420 + 421 + var shows []*models.Model 422 + for _, media := range results { 423 + if media.Type == "tv" { 424 + show := &models.TVShow{ 425 + Title: media.Title, 426 + Status: "queued", 427 + Added: time.Now(), 428 + Notes: fmt.Sprintf("Critic Score: %s, Certified: %v, URL: %s", media.CriticScore, media.CertifiedFresh, media.Link), 429 + } 430 + var m models.Model = show 431 + shows = append(shows, &m) 432 + } 433 + } 434 + 435 + start := (page - 1) * limit 436 + end := start + limit 437 + if start > len(shows) { 438 + return []*models.Model{}, nil 439 + } 440 + if end > len(shows) { 441 + end = len(shows) 442 + } 443 + 444 + return shows[start:end], nil 445 + } 446 + 447 + // Get retrieves a specific TV show by its Rotten Tomatoes URL 448 + func (s *TVService) Get(ctx context.Context, id string) (*models.Model, error) { 449 + if err := s.limiter.Wait(ctx); err != nil { 450 + return nil, fmt.Errorf("rate limit wait failed: %w", err) 451 + } 452 + 453 + seriesData, err := FetchTVSeries(id) 454 + if err != nil { 455 + return nil, fmt.Errorf("failed to fetch tv series: %w", err) 456 + } 457 + 458 + show := &models.TVShow{ 459 + Title: seriesData.Name, 460 + Status: "queued", 461 + Added: time.Now(), 462 + Notes: seriesData.Description, 463 + } 464 + 465 + if seriesData.NumberOfSeasons > 0 { 466 + show.Notes = fmt.Sprintf("%s\nSeasons: %d", show.Notes, seriesData.NumberOfSeasons) 467 + } 468 + 469 + var model models.Model = show 470 + return &model, nil 471 + } 472 + 473 + // Check verifies the API connection to Rotten Tomatoes 474 + func (s *TVService) Check(ctx context.Context) error { 475 + if err := s.limiter.Wait(ctx); err != nil { 476 + return fmt.Errorf("rate limit wait failed: %w", err) 477 + } 478 + 479 + _, err := FetchHTML("https://www.rottentomatoes.com") 480 + return err 481 + } 482 + 483 + // Close cleans up the service resources 484 + func (s *TVService) Close() error { 485 + return nil 486 + }
+240 -1
internal/services/media_test.go
··· 2 2 3 3 import ( 4 4 "bytes" 5 + "context" 5 6 _ "embed" 7 + "errors" 6 8 "strings" 7 9 "testing" 10 + 11 + "github.com/stormlightlabs/noteleaf/internal/models" 8 12 ) 9 13 10 14 // From: https://www.rottentomatoes.com/m/the_fantastic_four_first_steps ··· 27 31 //go:embed samples/series_season.html 28 32 var SeasonSample []byte 29 33 30 - func TestMediaService(t *testing.T) { 34 + // From: https://www.rottentomatoes.com/search?search=Fantastic%20Four 35 + // 36 + //go:embed samples/movie_search.html 37 + var MovieSearchSample []byte 38 + 39 + func TestMovieService(t *testing.T) { 40 + t.Run("Search", func(t *testing.T) { 41 + originalSearch := SearchRottenTomatoes 42 + defer func() { SearchRottenTomatoes = originalSearch }() 43 + 44 + SearchRottenTomatoes = func(q string) ([]Media, error) { 45 + if q == "error" { 46 + return nil, errors.New("search error") 47 + } 48 + if q == "Fantastic Four" { 49 + return ParseSearch(bytes.NewReader(MovieSearchSample)) 50 + } 51 + return nil, errors.New("unexpected query") 52 + } 53 + 54 + service := NewMovieService() 55 + 56 + t.Run("successful search", func(t *testing.T) { 57 + results, err := service.Search(context.Background(), "Fantastic Four", 1, 10) 58 + if err != nil { 59 + t.Fatalf("Search failed: %v", err) 60 + } 61 + if len(results) == 0 { 62 + t.Fatal("expected search results, got none") 63 + } 64 + 65 + var movieFound bool 66 + for _, r := range results { 67 + m, ok := (*r).(*models.Movie) 68 + if !ok { 69 + continue 70 + } 71 + if strings.Contains(m.Title, "Fantastic Four") { 72 + movieFound = true 73 + break 74 + } 75 + } 76 + if !movieFound { 77 + t.Error("expected to find a movie in search results") 78 + } 79 + }) 80 + 81 + t.Run("search returns error", func(t *testing.T) { 82 + _, err := service.Search(context.Background(), "error", 1, 10) 83 + if err == nil { 84 + t.Fatal("expected error from search, got nil") 85 + } 86 + if !strings.Contains(err.Error(), "search error") { 87 + t.Errorf("expected error to contain 'search error', got %v", err) 88 + } 89 + }) 90 + }) 91 + 92 + t.Run("Get", func(t *testing.T) { 93 + originalFetch := FetchMovie 94 + defer func() { FetchMovie = originalFetch }() 95 + 96 + FetchMovie = func(url string) (*Movie, error) { 97 + if url == "error" { 98 + return nil, errors.New("fetch error") 99 + } 100 + return ExtractMovieMetadata(bytes.NewReader(MovieSample)) 101 + } 102 + 103 + service := NewMovieService() 104 + 105 + t.Run("successful get", func(t *testing.T) { 106 + result, err := service.Get(context.Background(), "some-url") 107 + if err != nil { 108 + t.Fatalf("Get failed: %v", err) 109 + } 110 + movie, ok := (*result).(*models.Movie) 111 + if !ok { 112 + t.Fatalf("expected a movie model, got %T", *result) 113 + } 114 + if movie.Title != "The Fantastic Four: First Steps" { 115 + t.Errorf("expected title 'The Fantastic Four: First Steps', got '%s'", movie.Title) 116 + } 117 + }) 118 + 119 + t.Run("get returns error", func(t *testing.T) { 120 + _, err := service.Get(context.Background(), "error") 121 + if err == nil { 122 + t.Fatal("expected error from get, got nil") 123 + } 124 + if !strings.Contains(err.Error(), "fetch error") { 125 + t.Errorf("expected error to contain 'fetch error', got %v", err) 126 + } 127 + }) 128 + }) 129 + 130 + t.Run("Check", func(t *testing.T) { 131 + originalFetchHTML := FetchHTML 132 + defer func() { FetchHTML = originalFetchHTML }() 133 + 134 + service := NewMovieService() 135 + 136 + t.Run("successful check", func(t *testing.T) { 137 + FetchHTML = func(url string) (string, error) { 138 + return "ok", nil 139 + } 140 + err := service.Check(context.Background()) 141 + if err != nil { 142 + t.Fatalf("Check failed: %v", err) 143 + } 144 + }) 145 + 146 + t.Run("check returns error", func(t *testing.T) { 147 + FetchHTML = func(url string) (string, error) { 148 + return "", errors.New("html fetch error") 149 + } 150 + err := service.Check(context.Background()) 151 + if err == nil { 152 + t.Fatal("expected error from check, got nil") 153 + } 154 + if !strings.Contains(err.Error(), "html fetch error") { 155 + t.Errorf("expected error to contain 'html fetch error', got %v", err) 156 + } 157 + }) 158 + }) 159 + 31 160 t.Run("Parse Search results", func(t *testing.T) { 32 161 results, err := ParseSearch(bytes.NewReader(SearchSample)) 33 162 if err != nil { ··· 144 273 }) 145 274 146 275 } 276 + 277 + func TestTVService(t *testing.T) { 278 + t.Run("Search", func(t *testing.T) { 279 + originalSearch := SearchRottenTomatoes 280 + defer func() { SearchRottenTomatoes = originalSearch }() 281 + 282 + SearchRottenTomatoes = func(q string) ([]Media, error) { 283 + if q == "error" { 284 + return nil, errors.New("search error") 285 + } 286 + return ParseSearch(bytes.NewReader(SearchSample)) 287 + } 288 + 289 + service := NewTVService() 290 + 291 + t.Run("successful search", func(t *testing.T) { 292 + results, err := service.Search(context.Background(), "peacemaker", 1, 10) 293 + if err != nil { 294 + t.Fatalf("Search failed: %v", err) 295 + } 296 + if len(results) == 0 { 297 + t.Fatal("expected search results, got none") 298 + } 299 + 300 + var tvFound bool 301 + for _, r := range results { 302 + s, ok := (*r).(*models.TVShow) 303 + if !ok { 304 + continue 305 + } 306 + if strings.Contains(s.Title, "Peacemaker") { 307 + tvFound = true 308 + break 309 + } 310 + } 311 + if !tvFound { 312 + t.Error("expected to find a tv show in search results") 313 + } 314 + }) 315 + 316 + t.Run("search returns error", func(t *testing.T) { 317 + _, err := service.Search(context.Background(), "error", 1, 10) 318 + if err == nil { 319 + t.Fatal("expected error from search, got nil") 320 + } 321 + }) 322 + }) 323 + 324 + t.Run("Get", func(t *testing.T) { 325 + originalFetch := FetchTVSeries 326 + defer func() { FetchTVSeries = originalFetch }() 327 + 328 + FetchTVSeries = func(url string) (*TVSeries, error) { 329 + if url == "error" { 330 + return nil, errors.New("fetch error") 331 + } 332 + return ExtractTVSeriesMetadata(bytes.NewReader(SeriesSample)) 333 + } 334 + 335 + service := NewTVService() 336 + 337 + t.Run("successful get", func(t *testing.T) { 338 + result, err := service.Get(context.Background(), "some-url") 339 + if err != nil { 340 + t.Fatalf("Get failed: %v", err) 341 + } 342 + show, ok := (*result).(*models.TVShow) 343 + if !ok { 344 + t.Fatalf("expected a tv show model, got %T", *result) 345 + } 346 + if !strings.Contains(show.Title, "Peacemaker") { 347 + t.Errorf("expected title to contain 'Peacemaker', got '%s'", show.Title) 348 + } 349 + }) 350 + 351 + t.Run("get returns error", func(t *testing.T) { 352 + _, err := service.Get(context.Background(), "error") 353 + if err == nil { 354 + t.Fatal("expected error from get, got nil") 355 + } 356 + }) 357 + }) 358 + 359 + t.Run("Check", func(t *testing.T) { 360 + originalFetchHTML := FetchHTML 361 + defer func() { FetchHTML = originalFetchHTML }() 362 + 363 + service := NewTVService() 364 + 365 + t.Run("successful check", func(t *testing.T) { 366 + FetchHTML = func(url string) (string, error) { 367 + return "ok", nil 368 + } 369 + err := service.Check(context.Background()) 370 + if err != nil { 371 + t.Fatalf("Check failed: %v", err) 372 + } 373 + }) 374 + 375 + t.Run("check returns error", func(t *testing.T) { 376 + FetchHTML = func(url string) (string, error) { 377 + return "", errors.New("html fetch error") 378 + } 379 + err := service.Check(context.Background()) 380 + if err == nil { 381 + t.Fatal("expected error from check, got nil") 382 + } 383 + }) 384 + }) 385 + }
+2194
internal/services/samples/movie_search.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en" dir="ltr" xmlns="http://www.w3.org/1999/xhtml" 3 + prefix="fb: http://www.facebook.com/2008/fbml og: http://opengraphprotocol.org/schema/"> 4 + 5 + <head prefix="og: http://ogp.me/ns# flixstertomatoes: http://ogp.me/ns/apps/flixstertomatoes#"> 6 + 7 + 8 + 9 + 10 + <script charset="UTF-8" crossorigin="anonymous" data-domain-script="7e979733-6841-4fce-9182-515fac69187f" 11 + integrity="sha384-TKdmlzVmoD70HzftTw4WtOzIBL5mNx8mXSRzEvwrWjpIJ7FZ/EuX758yMDWXtRUN" 12 + src="https://cdn.cookielaw.org/consent/7e979733-6841-4fce-9182-515fac69187f/otSDKStub.js" 13 + type="text/javascript"> 14 + </script> 15 + <script type="text/javascript"> 16 + function OptanonWrapper() { 17 + if (OnetrustActiveGroups.includes('7')) { 18 + document.querySelector('search-results-nav-manager')?.setAlgoliaInsightUserToken?.(); 19 + } 20 + } 21 + </script> 22 + 23 + 24 + 25 + <script ccpa-opt-out-ids="USP" ccpa-opt-out-geo="US" ccpa-opt-out-lspa="false" charset="UTF-8" 26 + src="https://cdn.cookielaw.org/opt-out/otCCPAiab.js" type="text/javascript"> 27 + </script> 28 + 29 + 30 + 31 + 32 + 33 + 34 + <script src="/assets/pizza-pie/javascripts/bundles/roma/rt-common.js?single"></script> 35 + 36 + 37 + 38 + <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 39 + <meta http-equiv="x-ua-compatible" content="ie=edge"> 40 + <meta name="viewport" content="width=device-width, initial-scale=1"> 41 + 42 + <link rel="shortcut icon" sizes="76x76" type="image/x-icon" 43 + href="https://www.rottentomatoes.com/assets/pizza-pie/images/favicon.ico" /> 44 + 45 + 46 + <title>Search Results | Rotten Tomatoes</title> 47 + <meta name="description" 48 + content="Rotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and Tickets"> 49 + 50 + 51 + 52 + 53 + 54 + 55 + 56 + 57 + 58 + 59 + <meta property="fb:app_id" content=""> 60 + <meta property="og:site_name" content="Rotten Tomatoes"> 61 + <meta property="og:title" content="Search Results"> 62 + 63 + <meta property="og:description" 64 + content="Rotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and Tickets"> 65 + <meta property="og:type" content=""> 66 + <meta property="og:url" content=""> 67 + <meta property="og:image" 68 + content="https://www.rottentomatoes.com/assets/pizza-pie/head-assets/images/RT_TwitterCard_2018.jpg"> 69 + <meta property="og:locale" content="en_US"> 70 + 71 + 72 + <meta name="twitter:card" content="summary_large_image"> 73 + <meta name="twitter:image" 74 + content="https://www.rottentomatoes.com/assets/pizza-pie/head-assets/images/RT_TwitterCard_2018.jpg"> 75 + <meta name="twitter:title" content="Search Results"> 76 + <meta name="twitter:text:title" content="Search Results"> 77 + <meta name="twitter:description" 78 + content="Rotten Tomatoes, home of the Tomatometer, is the most trusted measurement of quality for Movies & TV. The definitive site for Reviews, Trailers, Showtimes, and Tickets"> 79 + <meta name="twitter:site" content="@rottentomatoes"> 80 + 81 + <!-- JSON+LD --> 82 + 83 + 84 + 85 + <script> 86 + var dataLayer = dataLayer || []; 87 + dataLayer.push({ "webVersion": "node", "rtVersion": 3.1, "loggedInStatus": "", "customerId": "" }); 88 + </script> 89 + 90 + 91 + 92 + <script id="mps-page-integration"> 93 + window.mpscall = { "cag[score]": null, "cag[certified_fresh]": null, "cag[fresh_rotten]": null, "cag[rating]": null, "cag[release]": null, "cag[movieshow]": null, "cag[genre]": null, "cag[urlid]": null, "cat": "search|results", "field[env]": "production", "field[rtid]": null, "path": "/search", "site": "rottentomatoes-web", "title": "Search Results", "type": "results" }; 94 + var mpsopts = { 'host': 'mps.nbcuni.com', 'updatecorrelator': 1 }; 95 + var mps = mps || {}; mps._ext = mps._ext || {}; mps._adsheld = []; mps._queue = mps._queue || {}; mps._queue.mpsloaded = mps._queue.mpsloaded || []; mps._queue.mpsinit = mps._queue.mpsinit || []; mps._queue.gptloaded = mps._queue.gptloaded || []; mps._queue.adload = mps._queue.adload || []; mps._queue.adclone = mps._queue.adclone || []; mps._queue.adview = mps._queue.adview || []; mps._queue.refreshads = mps._queue.refreshads || []; mps.__timer = Date.now || function () { return +new Date }; mps.__intcode = "v2"; if (typeof mps.getAd != "function") mps.getAd = function (adunit) { if (typeof adunit != "string") return false; var slotid = "mps-getad-" + adunit.replace(/\W/g, ""); if (!mps._ext || !mps._ext.loaded) { mps._queue.gptloaded.push(function () { typeof mps._gptfirst == "function" && mps._gptfirst(adunit, slotid); mps.insertAd("#" + slotid, adunit) }); mps._adsheld.push(adunit) } return '<div id="' + slotid + '" class="mps-wrapper" data-mps-fill-slot="' + adunit + '"></div>' }; 96 + </script> 97 + <script src="//mps.nbcuni.com/fetch/ext/load-rottentomatoes-web.js?nowrite=2" id="mps-load"></script> 98 + 99 + 100 + 101 + 102 + 103 + 104 + 105 + <link rel="manifest" href="https://www.rottentomatoes.com/assets/pizza-pie/manifest/manifest.json" /> 106 + 107 + <link rel="apple-touch-icon" 108 + href="https://www.rottentomatoes.com/assets/pizza-pie/head-assets/images/apple-touch-icon-60.jpg" /> 109 + <link rel="apple-touch-icon" sizes="152x152" 110 + href="https://www.rottentomatoes.com/assets/pizza-pie/head-assets/images/apple-touch-icon-152.jpg" /> 111 + <link rel="apple-touch-icon" sizes="167x167" 112 + href="https://www.rottentomatoes.com/assets/pizza-pie/head-assets/images/apple-touch-icon-167.jpg" /> 113 + <link rel="apple-touch-icon" sizes="180x180" 114 + href="https://www.rottentomatoes.com/assets/pizza-pie/head-assets/images/apple-touch-icon-180.jpg" /> 115 + 116 + 117 + <!-- iOS Smart Banner --> 118 + <meta name="apple-itunes-app" content="app-id=6673916573, app-argument=https://www.rottentomatoes.com/"> 119 + 120 + 121 + 122 + 123 + 124 + 125 + <meta name="google-site-verification" content="VPPXtECgUUeuATBacnqnCm4ydGO99reF-xgNklSbNbc" /> 126 + 127 + 128 + <meta name="msvalidate.01" content="034F16304017CA7DCF45D43850915323" /> 129 + <meta name="theme-color" content="#FA320A"> 130 + 131 + <!-- DNS prefetch --> 132 + <meta http-equiv="x-dns-prefetch-control" content="on"> 133 + 134 + <link rel="dns-prefetch" href="//www.rottentomatoes.com" /> 135 + 136 + 137 + <link rel="preconnect" href="//www.rottentomatoes.com" /> 138 + 139 + 140 + 141 + 142 + <link rel="stylesheet" href="/assets/pizza-pie/stylesheets/bundles/layouts/default.b6077682d41.css" /> 143 + 144 + 145 + 146 + 147 + <link rel="preload" href="/assets/pizza-pie/stylesheets/bundles/pages/search.2697d48faa3.css" as="style" 148 + onload="this.onload=null;this.rel='stylesheet'" /> 149 + 150 + 151 + <script> 152 + window.RottenTomatoes = {}; 153 + window.RTLocals = {}; 154 + window.nunjucksPrecompiled = {}; 155 + window.__RT__ = {}; 156 + </script> 157 + 158 + 159 + 160 + <script src="https://cdn.jwplayer.com/libraries/U8MHzHHR.js"></script> 161 + <script src="https://sb.scorecardresearch.com/c2/plugins/streamingtag_plugin_jwplayer.js"></script> 162 + 163 + 164 + 165 + 166 + 167 + <script>!function (e) { var n = "https://s.go-mpulse.net/boomerang/"; if ("False" == "True") e.BOOMR_config = e.BOOMR_config || {}, e.BOOMR_config.PageParams = e.BOOMR_config.PageParams || {}, e.BOOMR_config.PageParams.pci = !0, n = "https://s2.go-mpulse.net/boomerang/"; if (window.BOOMR_API_key = "4RDDZ-2Z6GP-RRNMC-PYEUL-SK6K9", function () { function e() { if (!o) { var e = document.createElement("script"); e.id = "boomr-scr-as", e.src = window.BOOMR.url, e.async = !0, i.parentNode.appendChild(e), o = !0 } } function t(e) { o = !0; var n, t, a, r, d = document, O = window; if (window.BOOMR.snippetMethod = e ? "if" : "i", t = function (e, n) { var t = d.createElement("script"); t.id = n || "boomr-if-as", t.src = window.BOOMR.url, BOOMR_lstart = (new Date).getTime(), e = e || d.body, e.appendChild(t) }, !window.addEventListener && window.attachEvent && navigator.userAgent.match(/MSIE [67]\./)) return window.BOOMR.snippetMethod = "s", void t(i.parentNode, "boomr-async"); a = document.createElement("IFRAME"), a.src = "about:blank", a.title = "", a.role = "presentation", a.loading = "eager", r = (a.frameElement || a).style, r.width = 0, r.height = 0, r.border = 0, r.display = "none", i.parentNode.appendChild(a); try { O = a.contentWindow, d = O.document.open() } catch (_) { n = document.domain, a.src = "javascript:var d=document.open();d.domain='" + n + "';void(0);", O = a.contentWindow, d = O.document.open() } if (n) d._boomrl = function () { this.domain = n, t() }, d.write("<bo" + "dy onload='document._boomrl();'>"); else if (O._boomrl = function () { t() }, O.addEventListener) O.addEventListener("load", O._boomrl, !1); else if (O.attachEvent) O.attachEvent("onload", O._boomrl); d.close() } function a(e) { window.BOOMR_onload = e && e.timeStamp || (new Date).getTime() } if (!window.BOOMR || !window.BOOMR.version && !window.BOOMR.snippetExecuted) { window.BOOMR = window.BOOMR || {}, window.BOOMR.snippetStart = (new Date).getTime(), window.BOOMR.snippetExecuted = !0, window.BOOMR.snippetVersion = 12, window.BOOMR.url = n + "4RDDZ-2Z6GP-RRNMC-PYEUL-SK6K9"; var i = document.currentScript || document.getElementsByTagName("script")[0], o = !1, r = document.createElement("link"); if (r.relList && "function" == typeof r.relList.supports && r.relList.supports("preload") && "as" in r) window.BOOMR.snippetMethod = "p", r.href = window.BOOMR.url, r.rel = "preload", r.as = "script", r.addEventListener("load", e), r.addEventListener("error", function () { t(!0) }), setTimeout(function () { if (!o) t(!0) }, 3e3), BOOMR_lstart = (new Date).getTime(), i.parentNode.appendChild(r); else t(!1); if (window.addEventListener) window.addEventListener("load", a, !1); else if (window.attachEvent) window.attachEvent("onload", a) } }(), "".length > 0) if (e && "performance" in e && e.performance && "function" == typeof e.performance.setResourceTimingBufferSize) e.performance.setResourceTimingBufferSize(); !function () { if (BOOMR = e.BOOMR || {}, BOOMR.plugins = BOOMR.plugins || {}, !BOOMR.plugins.AK) { var n = "" == "true" ? 1 : 0, t = "", a = "eyd6zaauaeceajqacqcoyaaafful3urj-f-2fe59f729-clienttons-s.akamaihd.net", i = "false" == "true" ? 2 : 1, o = { "ak.v": "39", "ak.cp": "1839344", "ak.ai": parseInt("1226367", 10), "ak.ol": "0", "ak.cr": 22, "ak.ipv": 6, "ak.proto": "h2", "ak.rid": "145cc987", "ak.r": 43883, "ak.a2": n, "ak.m": "dsca", "ak.n": "essl", "ak.bpcip": "2607:ec80:1401:440::", "ak.cport": 52718, "ak.gh": "23.205.103.137", "ak.quicv": "", "ak.tlsv": "tls1.3", "ak.0rtt": "", "ak.0rtt.ed": "", "ak.csrc": "-", "ak.acc": "", "ak.t": "1757270569", "ak.ak": "hOBiQwZUYzCg5VSAfCLimQ==HoWupekmMV4uHab0PLlXY+CBQto5aTSzQcOnOHE4Fsgy79LQEzsKLX5E0tq026IpDioT3NUJGq5SyTHq8tYRa7eyXfPwcMYN1z4ggVVKuq6fT3seX33qlYTTI+DTzI2rTCjS+34g3JYwkfNX7+N1/hYIvjpvoR6Oibxnf+sOFeLJuZKhPIN8CDeCutyyRmsYodltgYR663WDRisUxoX0rAXAl+KN5/PnDB8yBn53oAusQjXUJRU+IZpKDkdXgVOvKsbAYi6TeOk2mqLbuUz0gBA3+De4ado/dIvLivpRKGbYWUqNjsbIsKV70T4/WXm8j2nFLi3EjDnHJpqD94h0kRsG9y0G6gQi9LcSYQZtwAYzU307DkzOTVa2PZP3+DhT+Rz1NuBWp3pM6oHqRDYwSe7fBOSM3WtnjLGV5lfsijc=", "ak.pv": "3", "ak.dpoabenc": "", "ak.tf": i }; if ("" !== t) o["ak.ruds"] = t; var r = { i: !1, av: function (n) { var t = "http.initiator"; if (n && (!n[t] || "spa_hard" === n[t])) o["ak.feo"] = void 0 !== e.aFeoApplied ? 1 : 0, BOOMR.addVar(o) }, rv: function () { var e = ["ak.bpcip", "ak.cport", "ak.cr", "ak.csrc", "ak.gh", "ak.ipv", "ak.m", "ak.n", "ak.ol", "ak.proto", "ak.quicv", "ak.tlsv", "ak.0rtt", "ak.0rtt.ed", "ak.r", "ak.acc", "ak.t", "ak.tf"]; BOOMR.removeVar(e) } }; BOOMR.plugins.AK = { akVars: o, akDNSPreFetchDomain: a, init: function () { if (!r.i) { var e = BOOMR.subscribe; e("before_beacon", r.av, null, null), e("onbeacon", r.rv, null, null), r.i = !0 } return this }, is_complete: function () { return !0 } } } }() }(window);</script> 168 + </head> 169 + 170 + <body class="body no-touch js-mptd-layout" data-AdsGlobalSkinTakeoverManager="body" data-SearchResultsNavManager="body"> 171 + <cookie-manager></cookie-manager> 172 + <device-inspection-manager 173 + endpoint="https://www.rottentomatoes.com/napi/device/inspection"></device-inspection-manager> 174 + 175 + <user-activity-manager profiles-features-enabled="false"></user-activity-manager> 176 + <user-identity-manager profiles-features-enabled="false"></user-identity-manager> 177 + <ad-unit-manager></ad-unit-manager> 178 + 179 + <auth-initiate-manager profiles-username-enabled="false" data-ArtiManager="authInitiateManager" 180 + data-WatchlistButtonManager="authInitiateManager:createAccount"> 181 + </auth-initiate-manager> 182 + <auth-profile-manager data-AuthInitiateManager="authProfileManager"></auth-profile-manager> 183 + <auth-validation-manager data-AuthInitiateManager="authValidation"></auth-validation-manager> 184 + <overlay-base data-AuthInitiateManager="overlayBase:close" data-PagePollsIndexManager="authOverlay:close" hidden> 185 + <overlay-flows data-AuthInitiateManager="overlayFlows" slot="content"> 186 + <action-icon slot="close" class="auth-overlay__icon-button auth-overlay__icon-button--close" 187 + aria-label="Close" data-qa="close-overlay-btn" icon="close"></action-icon> 188 + 189 + </overlay-flows> 190 + </overlay-base> 191 + 192 + <notification-alert data-AuthInitiateManager="authSuccess" animate hidden> 193 + <rt-icon icon="check-circled"></rt-icon> 194 + <span>Signed in</span> 195 + </notification-alert> 196 + 197 + <div id="auth-templates" data-AuthInitiateManager="authTemplates"> 198 + <template slot="screens" id="account-create-username-screen"> 199 + <account-create-username-screen data-qa="account-create-username-screen"> 200 + <input-label slot="input-username" state="default" data-qa="username-input-label"> 201 + <label slot="label" for="create-username-input">Username</label> 202 + <input slot="input" id="create-username-input" type="text" placeholder="Username" 203 + data-qa="username-input" /> 204 + </input-label> 205 + <rt-button disabled slot="btn-continue" shape="pill" data-qa="continue-btn">Continue</rt-button> 206 + <rt-text class="terms-and-policies" slot="terms-and-policies" size="0.75"> 207 + By joining, you agree to the <rt-link href="/policies/terms-and-policies" target="_blank" 208 + data-qa="terms-policies-link">Terms and Policies</rt-link> and 209 + the <rt-link href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" target="_blank" 210 + data-qa="privacy-policy-link">Privacy Policy</rt-link> and to receive email from 211 + <rt-link href="https://www.fandango.com/about-us" target="_blank" 212 + data-qa="about-fandango-link">Fandango Media Brands</rt-link>. 213 + </rt-text> 214 + </account-create-username-screen> 215 + </template> 216 + 217 + <template slot="screens" id="account-email-change-screen"> 218 + <account-email-change-screen data-qa="account-email-change-screen" email="user@email.com"> 219 + <input-label class="new-email-input" state="default" slot="new-email-input" data-qa="email-input-label"> 220 + <label slot="label" for="newEmail">Enter new email</label> 221 + <input slot="input" name="newEmail" type="text" placeholder="Enter new email" autocomplete="off" 222 + data-qa="email-input"></input> 223 + </input-label> 224 + <rt-button slot="submit-button" disabled shape="pill" data-qa="submit-btn">Submit</rt-button> 225 + <rt-text class="terms-and-policies" slot="terms-and-policies" size="0.75"> 226 + By joining, you agree to the 227 + <rt-link href="/policies/terms-and-policies" target="_blank" data-qa="terms-policies-link">Terms and 228 + Policies</rt-link> 229 + and <rt-link href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" target="_blank" 230 + data-qa="privacy-policy-link">Privacy Policy</rt-link> 231 + and to receive email from the 232 + <rt-link href="https://www.fandango.com/about-us" target="_blank" 233 + data-qa="about-fandango-link">Fandango Media Brands</rt-link>. 234 + </rt-text> 235 + </account-email-change-screen> 236 + </template> 237 + 238 + <template slot="screens" id="account-email-change-success-screen"> 239 + <account-email-change-success-screen data-qa="login-create-success-screen"> 240 + <rt-text slot="message" size="1.5" style="--fontWeight: var(--franklinGothicDemi);">Email change 241 + successful</rt-text> 242 + <rt-text slot="submessage">You are signed out for your security. </br> Please sign in again.</rt-text> 243 + <img slot="icon" src="/assets/pizza-pie/images/icons/cognito-success.3a3d5f32fab.svg" width="111" 244 + height="105" /> 245 + </account-email-change-success-screen> 246 + </template> 247 + 248 + <template slot="screens" id="account-password-change-screen"> 249 + <account-password-change-screen data-qa="account-password-change-screen"> 250 + <input-label state="default" slot="input-password-existing"> 251 + <label slot="label" for="password-existing">Existing password</label> 252 + <input slot="input" name="password-existing" type="password" placeholder="Enter existing password" 253 + autocomplete="off"></input> 254 + </input-label> 255 + <input-label state="default" slot="input-password-new"> 256 + <label slot="label" for="password-new">New password</label> 257 + <input slot="input" name="password-new" type="password" placeholder="Enter new password" 258 + autocomplete="off"></input> 259 + </input-label> 260 + <rt-button disabled shape="pill" slot="submit-button">Submit</rt-button> 261 + </account-password-change-screen> 262 + </template> 263 + 264 + <template slot="screens" id="account-password-change-updating-screen"> 265 + <login-success-screen data-qa="account-password-change-updating-screen" hidebanner> 266 + <rt-text slot="status" size="1.5" style="--fontWeight: var(--franklinGothicDemi);"> 267 + Updating your password... 268 + </rt-text> 269 + <img slot="icon" src="/assets/pizza-pie/images/icons/cognito-success.3a3d5f32fab.svg" width="111" 270 + height="105" /> 271 + </login-success-screen> 272 + </template> 273 + <template slot="screens" id="account-password-change-success-screen"> 274 + <login-success-screen data-qa="account-password-change-success-screen" hidebanner> 275 + <rt-text slot="status" size="1.5" style="--fontWeight: var(--franklinGothicDemi);"> 276 + Success! 277 + </rt-text> 278 + <img slot="icon" src="/assets/pizza-pie/images/icons/cognito-success.3a3d5f32fab.svg" width="111" 279 + height="105" /> 280 + </login-success-screen> 281 + </template> 282 + <template slot="screens" id="account-verifying-email-screen"> 283 + <account-verifying-email-screen data-qa="account-verifying-email-screen"> 284 + <img slot="icon" src="/assets/pizza-pie/images/icons/cognito-auth-verify.e74a69e9a77.svg" alt="email" /> 285 + <rt-text slot="status"> Verifying your email... </rt-text> 286 + <rt-button type="cta-large" href="/?authFlowScreen=loginStartScreen" slot="retryLink" size="0.875" 287 + style="--fontWeight: var(--franklinGothicMedium);" data-qa="retry-link"> 288 + Retry 289 + </rt-button> 290 + </account-verifying-email-screen> 291 + </template> 292 + <template slot="screens" id="cognito-loading"> 293 + <div> 294 + <loading-spinner id="cognito-auth-loading-spinner"></loading-spinner> 295 + <style> 296 + #cognito-auth-loading-spinner { 297 + font-size: 2rem; 298 + transform: translate(calc(100% - 1em), 250px); 299 + width: 50%; 300 + } 301 + </style> 302 + </div> 303 + </template> 304 + 305 + <template slot="screens" id="login-check-email-screen"> 306 + <login-check-email-screen data-qa="login-check-email-screen" email="user@email.com"> 307 + <rt-text class="note-text" size="1" slot="noteText"> 308 + Please open the email link from the same browser you initiated the change email process from. 309 + </rt-text> 310 + <rt-text slot="gotEmailMessage" size="0.875"> 311 + Didn't you get the email? 312 + </rt-text> 313 + <rt-button slot="resendEmailLink" size="0.875" type="cta-large" data-qa="resend-email-link"> 314 + Resend email 315 + </rt-button> 316 + <rt-link context="label" slot="troubleLoginLink" size="0.875" href="/reset-client" 317 + data-qa="reset-link">Having trouble logging in?</rt-link> 318 + 319 + <rt-text class="terms-and-policies" slot="termsAndPolicies" size="0.75"> 320 + By continuing, you agree to the 321 + <rt-link href="/policies/terms-and-policies" target="_blank" data-qa="terms-policies-link" 322 + style="--textColor: var(--blueLink);">Terms and Policies</rt-link> 323 + and the 324 + <rt-link href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" target="_blank" 325 + data-qa="privacy-policy-link" style="--textColor: var(--blueLink);">Privacy Policy</rt-link> 326 + and to receive email from 327 + <rt-link href="https://www.fandango.com/about-us" target="_blank" data-qa="about-fandango-link" 328 + style="--textColor: var(--blueLink);">Fandango Media Brands</rt-link>. 329 + </rt-text> 330 + </login-check-email-screen> 331 + </template> 332 + 333 + <template slot="screens" id="login-error-screen"> 334 + <login-error-screen data-qa="login-error"> 335 + <rt-text slot="header" size="1.5" context="heading" data-qa="header"> 336 + Something went wrong... 337 + </rt-text> 338 + <rt-text slot="description1" size="1" context="label" data-qa="description1"> 339 + Please try again. 340 + </rt-text> 341 + <img slot="image" src="/assets/pizza-pie/images/icons/cognito-error.c55e509a7fd.svg" /> 342 + <rt-text hidden slot="description2" size="1" context="label" data-qa="description2"></rt-text> 343 + <rt-link slot="ctaLink" hidden context="label" size="0.875" data-qa="retry-link">Retry</rt-link> 344 + </login-error-screen> 345 + </template> 346 + 347 + <template slot="screens" id="login-enter-password-screen"> 348 + <login-enter-password-screen data-qa="login-enter-password-screen"> 349 + <rt-text slot="title" size="1.5" style="--fontWeight: var(--franklinGothicMedium);"> 350 + Welcome back! 351 + </rt-text> 352 + <rt-text slot="username" data-qa="user-email"> 353 + username@email.com 354 + </rt-text> 355 + <input-label slot="inputPassword" state="default" data-qa="password-input-label"> 356 + <label slot="label" for="pass">Password</label> 357 + <input slot="input" id="pass" type="password" placeholder="Password" autocomplete="off" 358 + data-qa="password-input"></input> 359 + </input-label> 360 + <rt-button disabled slot="continueButton" type="cta-large" data-qa="continue-btn"> 361 + Continue 362 + </rt-button> 363 + <rt-button slot="emailLoginButton" theme="light" shape="pill" data-qa="send-email-btn"> 364 + Send email to verify 365 + </rt-button> 366 + <rt-link slot="forgotPasswordLink" theme="light" data-qa="forgot-password-link">Forgot 367 + password</rt-link> 368 + <rt-text class="terms-and-policies" slot="termsAndPolicies" size="0.75"> 369 + By continuing, you agree to the 370 + <rt-link href="/policies/terms-and-policies" target="_blank" data-qa="terms-policies-link" 371 + style="--textColor: var(--blueLink);">Terms and Policies</rt-link> 372 + and the 373 + <rt-link href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" target="_blank" 374 + data-qa="privacy-policy-link" style="--textColor: var(--blueLink);">Privacy Policy</rt-link> 375 + and to receive email from 376 + <rt-link href="https://www.fandango.com/about-us" target="_blank" data-qa="about-fandango-link" 377 + style="--textColor: var(--blueLink);">Fandango Media Brands</rt-link>. 378 + </rt-text> 379 + </login-enter-password-screen> 380 + </template> 381 + 382 + <template slot="screens" id="login-start-screen"> 383 + <login-start-screen data-qa="login-start-screen"> 384 + <input-label slot="inputEmail" state="default" data-qa="email-input-label"> 385 + <label slot="label" for="login-email-input">Email address</label> 386 + <input slot="input" autocomplete="username" id="login-email-input" placeholder="Email address" 387 + type="text" data-qa="email-input" /> 388 + </input-label> 389 + <rt-button disabled slot="emailLoginButton" type="cta-large" data-qa="continue-btn"> 390 + Continue 391 + </rt-button> 392 + <rt-button slot="googleLoginButton" shape="pill" theme="light" 393 + style="--buttonHeight: 52px; --borderRadius: 32px;" data-qa="google-login-btn" data-type="google"> 394 + <div class="social-login-btn-content"> 395 + <img height="16px" width="16px" 396 + src="/assets/pizza-pie/images/vendor/google/google_logo.28d9eb28faa.svg" /> 397 + Continue with Google 398 + </div> 399 + </rt-button> 400 + 401 + <rt-button slot="appleLoginButton" shape="pill" theme="light" 402 + style="--buttonHeight: 52px; --borderRadius: 32px;" data-qa="apple-login-btn" data-type="apple"> 403 + <div class="social-login-btn-content"> 404 + <rt-icon size="1" icon="apple"></rt-icon> 405 + Continue with apple 406 + </div> 407 + </rt-button> 408 + 409 + <rt-link slot="resetLink" class="reset-link" context="label" size="0.875" href="/reset-client" 410 + data-qa="reset-link"> 411 + Having trouble logging in? 412 + </rt-link> 413 + <rt-text class="terms-and-policies" slot="termsAndPolicies" size="0.75"> 414 + By continuing, you agree to the 415 + <rt-link href="/policies/terms-and-policies" target="_blank" data-qa="terms-policies-link" 416 + style="--textColor: var(--blueLink);">Terms and Policies</rt-link> 417 + and the 418 + <rt-link href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" target="_blank" 419 + data-qa="privacy-policy-link" style="--textColor: var(--blueLink);">Privacy Policy</rt-link> 420 + and to receive email from 421 + <rt-link href="https://www.fandango.com/about-us" target="_blank" data-qa="about-fandango-link" 422 + style="--textColor: var(--blueLink);">Fandango Media Brands</rt-link>. 423 + </rt-text> 424 + </login-start-screen> 425 + </template> 426 + 427 + <template slot="screens" id="login-success-screen"> 428 + <login-success-screen data-qa="login-success-screen"> 429 + <rt-text slot="status" size="1.5"> 430 + Login successful! 431 + </rt-text> 432 + <img slot="icon" src="/assets/pizza-pie/images/icons/cognito-success.3a3d5f32fab.svg" width="111" 433 + height="105" /> 434 + </login-success-screen> 435 + </template> 436 + <template slot="screens" id="cognito-opt-in-us"> 437 + <auth-optin-screen data-qa="auth-opt-in-screen"> 438 + <div slot="newsletter-text"> 439 + <h2 class="cognito-optin-form__header unset">Let's keep in touch!</h2> 440 + </div> 441 + <img slot="image" class="image" 442 + src="https://images.fandango.com/cms/assets/97c33f00-313f-11ee-9aaf-6762c75465cf--newsletter.png" 443 + alt="Rotten Tomatoes Newsletter">> 444 + <h2 slot="sub-title" class="subTitle unset">Sign up for the Rotten Tomatoes newsletter to get weekly 445 + updates on:</h2> 446 + <ul slot="options"> 447 + <li class="icon-item">Upcoming Movies and TV shows</li> 448 + <li class="icon-item">Rotten Tomatoes Podcast</li> 449 + <li class="icon-item">Media News + More</li> 450 + </ul> 451 + <rt-button slot="opt-in-button" data-qa="auth-opt-in-screen-opt-in-btn"> 452 + Sign me up 453 + </rt-button> 454 + <rt-button slot="opt-out-button" class="button--outline" data-qa="auth-opt-in-screen-opt-out-btn"> 455 + No thanks 456 + </rt-button> 457 + <p slot="foot-note"> 458 + By clicking "Sign Me Up," you are agreeing to receive occasional emails and communications from 459 + Fandango Media (Fandango, Vudu, and Rotten Tomatoes) and consenting to Fandango's 460 + <a href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" class="optin-link" 461 + target="_blank" rel="noopener" data-qa="auth-name-screen-privacy-policy-link">Privacy Policy</a> 462 + and 463 + <a href="/policies/terms-and-policies" class="optin-link" target="_blank" rel="noopener" 464 + data-qa="auth-name-screen-privacy-policy-link">Terms and Policies</a>. 465 + Please allow 10 business days for your account to reflect your preferences. 466 + </p> 467 + </auth-optin-screen> 468 + </template> 469 + <template slot="screens" id="cognito-opt-in-foreign"> 470 + <auth-optin-screen data-qa="auth-opt-in-screen"> 471 + <div slot="newsletter-text"> 472 + <h2 class="cognito-optin-form__header unset">Let's keep in touch!</h2> 473 + </div> 474 + <img slot="image" class="image" 475 + src="https://images.fandango.com/cms/assets/97c33f00-313f-11ee-9aaf-6762c75465cf--newsletter.png" 476 + alt="Rotten Tomatoes Newsletter">> 477 + <h2 slot="sub-title" class="subTitle unset">Sign up for the Rotten Tomatoes newsletter to get weekly 478 + updates on:</h2> 479 + <ul slot="options"> 480 + <li class="icon-item">Upcoming Movies and TV shows</li> 481 + <li class="icon-item">Rotten Tomatoes Podcast</li> 482 + <li class="icon-item">Media News + More</li> 483 + </ul> 484 + <rt-button slot="opt-in-button" data-qa="auth-opt-in-screen-opt-in-btn"> 485 + Sign me up 486 + </rt-button> 487 + <rt-button slot="opt-out-button" class="button--outline" data-qa="auth-opt-in-screen-opt-out-btn"> 488 + No thanks 489 + </rt-button> 490 + </auth-optin-screen> 491 + </template> 492 + <template slot="screens" id="cognito-opt-in-success"> 493 + <auth-verify-screen> 494 + <rt-icon icon="check-circled" slot="icon"></rt-icon> 495 + <p class="h3" slot="status">OK, got it!</p> 496 + </auth-verify-screen> 497 + </template> 498 + 499 + </div> 500 + 501 + 502 + <div id="emptyPlaceholder"></div> 503 + 504 + 505 + 506 + <script ASYNC src="//assets.adobedtm.com/launch-EN549327edc13e414a9beb5d61bfd9aac6.min.js"></script> 507 + 508 + 509 + 510 + <div id="main" class="container rt-layout__body"> 511 + <a href="#main-page-content" class="skip-link">Skip to Main Content</a> 512 + 513 + 514 + 515 + <div id="header_and_leaderboard"> 516 + <div id="top_leaderboard_wrapper" class="leaderboard_wrapper "> 517 + <ad-unit hidden unit-display="desktop" unit-type="topbanner" adjust-height> 518 + <div slot="ad-inject"></div> 519 + </ad-unit> 520 + 521 + <ad-unit hidden unit-display="mobile" unit-type="mbanner"> 522 + <div slot="ad-inject"></div> 523 + </ad-unit> 524 + </div> 525 + </div> 526 + 527 + 528 + <rt-header-manager></rt-header-manager> 529 + 530 + <rt-header aria-label="navigation bar" class="navbar" data-qa="header-nav-bar" 531 + data-AdsGlobalNavTakeoverManager="header" id="header-main" skeleton="panel"> 532 + 533 + 534 + <button aria-label="Open aRTi" class="arti-mobile" data-ArtiManager="btnArti:click" slot="arti-mobile"> 535 + <img alt="arti" src="/assets/pizza-pie/images/arti.041d204c4a4.svg" /> 536 + </button> 537 + 538 + 539 + <div slot="mobile-header-nav"> 540 + <rt-button id="mobile-header-nav-btn" data-RtHeaderManager="mobileHeaderNavBtn:click" size="1.6" 541 + style="--backgroundColor: transparent; --backgroundColorHover: transparent; --buttonPadding: 0 10px 4px;"> 542 + &#9776; 543 + </rt-button> 544 + <mobile-header-nav id="mobile-header-nav" data-RtHeaderManager="mobileHeaderNav"> 545 + <rt-img slot="logoImage" alt="Rotten Tomatoes" fetchpriority="high" 546 + src="/assets/pizza-pie/images/rt-tomato-logo.20c3bdbc97b.svg"></rt-img> 547 + <div slot="menusCss"></div> 548 + <div slot="menus"></div> 549 + </mobile-header-nav> 550 + </div> 551 + 552 + <a class="logo-wrap" data-AdsGlobalNavTakeoverManager="logoLink" data-SearchResultsNavManager="rtNavLogo" 553 + href="/" id="navbar" slot="logo"> 554 + <img alt="Rotten Tomatoes" data-qa="header-logo" data-AdsGlobalNavTakeoverManager="logo" 555 + src="/assets/pizza-pie/images/rtlogo.9b892cff3fd.png" fetchpriority="high" /> 556 + 557 + <div class="hide"> 558 + <ad-unit hidden unit-display="desktop,mobile" unit-type="logorepeat" unit-targeting="ploc=rtlogo;"> 559 + <div slot="ad-inject"></div> 560 + </ad-unit> 561 + </div> 562 + </a> 563 + 564 + <search-results-nav-manager></search-results-nav-manager> 565 + 566 + <search-results-nav data-adobe-id="global-nav-search" data-SearchResultsNavManager="search" slot="search" 567 + skeleton="chip"> 568 + <search-results-controls data-SearchResultsNavManager="searchControls" slot="controls"> 569 + <input aria-label="Search" data-AdsGlobalNavTakeoverManager="searchInput" 570 + data-SearchResultsNavManager="inputText:click,input,keydown" data-qa="search-input" 571 + placeholder="Search" slot="search-input" type="text" /> 572 + <rt-button class="search-clear" data-qa="search-clear" 573 + data-AdsGlobalNavTakeoverManager="searchClearBtn" data-SearchResultsNavManager="clearBtn:click" 574 + size="0.875" slot="search-clear" theme="transparent"> 575 + <rt-icon icon="close"></rt-icon> 576 + </rt-button> 577 + <rt-link class="search-submit" aria-label="Submit search" data-qa="search-submit" 578 + data-AdsGlobalNavTakeoverManager="searchSubmitBtn" 579 + data-SearchResultsNavManager="submitBtn:click" href="/search" size="0.875" slot="search-submit"> 580 + <rt-icon icon="search"></rt-icon> 581 + </rt-link> 582 + <rt-button class="search-cancel" data-qa="search-cancel" 583 + data-AdsGlobalNavTakeoverManager="searchCancelBtn" 584 + data-SearchResultsNavManager="cancelBtn:click" size="0.875" slot="search-cancel" 585 + theme="transparent"> 586 + Cancel 587 + </rt-button> 588 + </search-results-controls> 589 + 590 + <search-results aria-expanded="false" class="hide" data-SearchResultsNavManager="searchResults" 591 + slot="results"> 592 + </search-results> 593 + </search-results-nav> 594 + 595 + <ul slot="nav-links"> 596 + <li> 597 + <a href="/about" data-qa="header:link-whats-tmeter" data-AdsGlobalNavTakeoverManager="text"> 598 + About Rotten Tomatoes&reg; 599 + </a> 600 + </li> 601 + <li> 602 + <a href="/critics" data-qa="header:link-critics-home" data-AdsGlobalNavTakeoverManager="text"> 603 + Critics 604 + </a> 605 + </li> 606 + <li data-RtHeaderManager="loginLink"> 607 + <ul> 608 + <li> 609 + <button id="masthead-show-login-btn" class="js-cognito-signin button--link" 610 + data-AuthInitiateManager="btnSignIn:click" data-qa="header:login-btn" 611 + data-AdsGlobalNavTakeoverManager="text"> 612 + Login/signup 613 + </button> 614 + </li> 615 + </ul> 616 + </li> 617 + <li class="hide" data-RtHeaderManager="userItem:keydown,keyup,mouseenter" data-qa="header:user"> 618 + <a class="masthead-user-link" data-RtHeaderManager="navUserlink:focus" rel="nofollow" 619 + data-qa="user-profile-link"> 620 + <img data-RtHeaderManager="navUserImg" data-qa="user-profile-thumb"> 621 + <p data-AdsGlobalNavTakeoverManager="text" data-RtHeaderManager="navUserFirstName" 622 + data-qa="user-profile-name"></p> 623 + <rt-icon data-AdsGlobalNavTakeoverManager="text" icon="down-dir" image> 624 + </rt-icon> 625 + </a> 626 + <rt-header-user-info class="hide" data-RtHeaderManager="userInfo:focusout,mouseleave"> 627 + <a data-qa="user-stats-profile-pic" href="" rel="nofollow" slot="imageExpanded" tabindex="-1"> 628 + <img src="" width="40" alt=""> 629 + </a> 630 + <a slot="fullName" rel="nofollow" href="" class="username" data-qa="user-stats-name"></a> 631 + <a slot="wts" rel="nofollow" href="" class="wts-count-block" data-qa="user-stats-wts"> 632 + <rt-icon icon="plus" data-qa="user-stats-ratings-count"></rt-icon> 633 + <span class="count" data-qa="user-stats-wts-count"></span> 634 + &nbsp;Wants to See 635 + </a> 636 + <a slot="rating" rel="nofollow" href="" class="rating-count-block" data-qa="user-stats-ratings"> 637 + <rt-icon icon="star" data-qa="user-stats-ratings-count"></rt-icon> 638 + <span class="count"></span> 639 + &nbsp;Ratings 640 + </a> 641 + 642 + <a slot="profileLink" rel="nofollow" class="dropdown-link" href="" 643 + data-qa="user-stats-profile-link">Profile</a> 644 + <a slot="accountLink" rel="nofollow" class="dropdown-link" href="/user/account" 645 + data-qa="user-stats-account-link">Account</a> 646 + <a slot="logoutLink" class="dropdown-link" data-RtHeaderManager="logoutLink:click" 647 + href="#logout" data-qa="user-stats-logout-link">Log Out</a> 648 + </rt-header-user-info> 649 + </li> 650 + </ul> 651 + 652 + <rt-header-nav slot="nav-dropdowns"> 653 + 654 + <button aria-label="Open aRTi" class="arti-desktop" data-ArtiManager="btnArti:click" 655 + slot="arti-desktop"> 656 + <img alt="arti" src="/assets/pizza-pie/images/arti.041d204c4a4.svg" /> 657 + </button> 658 + 659 + <rt-header-nav-item slot="movies" data-qa="masthead:movies-dvds"> 660 + <a class="unset" slot="link" href="/browse/movies_in_theaters/sort:popular" 661 + data-qa="masthead:movies-dvds-link" data-AdsGlobalNavTakeoverManager="text"> 662 + Movies 663 + </a> 664 + <rt-header-nav-item-dropdown aria-expanded="false" slot="dropdown" data-qa="movies-menu"> 665 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-movies-in-theaters"> 666 + <p slot="title" class="h4" data-qa="movies-in-theaters-main-link"><a class="unset" 667 + href="/browse/movies_in_theaters/sort:popular">Movies in theaters</a></p> 668 + <ul slot="links"> 669 + <li data-qa="in-theaters-item"> 670 + <a href="/browse/movies_in_theaters/sort:newest" 671 + data-qa="opening-this-week-link">Opening This Week</a> 672 + </li> 673 + <li data-qa="in-theaters-item"> 674 + <a href="/browse/movies_in_theaters/sort:top_box_office" 675 + data-qa="top-box-office-link">Top Box Office</a> 676 + </li> 677 + <li data-qa="in-theaters-item"> 678 + <a href="/browse/movies_coming_soon/" data-qa="coming-soon-link">Coming Soon to 679 + Theaters</a> 680 + </li> 681 + <li data-qa="in-theaters-item"> 682 + <a href="/browse/movies_in_theaters/critics:certified_fresh~sort:popular" 683 + data-qa="certified-fresh-link">Certified Fresh Movies</a> 684 + </li> 685 + </ul> 686 + </rt-header-nav-item-dropdown-list> 687 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-on-dvd-streaming"> 688 + <p slot="title" class="h4" data-qa="dvd-streaming-main-link"><a class="unset" 689 + href="/browse/movies_at_home">Movies at Home</a></p> 690 + <ul slot="links"> 691 + 692 + <li data-qa="movies-at-home-item"> 693 + <a href="/browse/movies_at_home/affiliates:fandango-at-home" 694 + data-qa="fandango-at-home-link">Fandango at Home</a> 695 + </li> 696 + 697 + <li data-qa="movies-at-home-item"> 698 + <a href="/browse/movies_at_home/affiliates:peacock" 699 + data-qa="peacock-link">Peacock</a> 700 + </li> 701 + 702 + <li data-qa="movies-at-home-item"> 703 + <a href="/browse/movies_at_home/affiliates:netflix" 704 + data-qa="netflix-link">Netflix</a> 705 + </li> 706 + 707 + <li data-qa="movies-at-home-item"> 708 + <a href="/browse/movies_at_home/affiliates:apple-tv-plus" 709 + data-qa="apple-tv-link">Apple TV+</a> 710 + </li> 711 + 712 + <li data-qa="movies-at-home-item"> 713 + <a href="/browse/movies_at_home/affiliates:prime-video" 714 + data-qa="prime-video-link">Prime Video</a> 715 + </li> 716 + 717 + <li data-qa="movies-at-home-item"> 718 + <a href="/browse/movies_at_home/sort:popular" 719 + data-qa="most-popular-streaming-movies-link">Most Popular Streaming movies</a> 720 + </li> 721 + 722 + <li data-qa="movies-at-home-item"> 723 + <a href="/browse/movies_at_home/critics:certified_fresh" 724 + data-qa="certified-fresh-movies-link">Certified Fresh movies</a> 725 + </li> 726 + 727 + <li data-qa="movies-at-home-item"> 728 + <a href="/browse/movies_at_home" data-qa="browse-all-link">Browse all</a> 729 + </li> 730 + 731 + </ul> 732 + </rt-header-nav-item-dropdown-list> 733 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-movies-more"> 734 + <p slot="title" class="h4">More</p> 735 + <ul slot="links"> 736 + <li data-qa="what-to-watch-item"> 737 + <a href="https://editorial.rottentomatoes.com/rt-hub/what-to-watch" 738 + class="what-to-watch" data-qa="what-to-watch-link">What to 739 + Watch<rt-badge>New</rt-badge></a> 740 + </li> 741 + </ul> 742 + </rt-header-nav-item-dropdown-list> 743 + 744 + <rt-header-nav-item-dropdown-list slot="column" cfp> 745 + <p slot="title" class="h4">Certified fresh picks</p> 746 + <ul slot="links" class="cfp-wrap" data-qa="header-certified-fresh-picks" 747 + data-curation="rt-nav-list-cf-picks"> 748 + 749 + <li data-qa="cert-fresh-item"> 750 + 751 + <a class="cfp-tile" href="/m/twinless" data-qa="cert-fresh-link"> 752 + <tile-dynamic data-qa="tile"> 753 + <rt-img alt="Twinless poster image" slot="image" 754 + src="https://resizing.flixster.com/j7lw2KeY9_XyfZQdqRZGku7_9C8=/206x305/v2/https://resizing.flixster.com/uxoeWz7uWmeYIV94_SzEV_osqe4=/fit-in/180x240/v2/https://resizing.flixster.com/VlylB3xT2RIYmRivMx37O3yD76Q=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2ZlNDQ1MGQ5LTFjN2QtNDIwNC04NWE1LTM5NGM4N2U5ZTgzYy5qcGc=" 755 + loading="lazy"></rt-img> 756 + <div slot="caption" data-track="scores"> 757 + <div class="score-wrap"> 758 + <score-icon-critics certified sentiment="positive" 759 + size="1"></score-icon-critics> 760 + <rt-text class="critics-score" size="1" 761 + context="label">98%</rt-text> 762 + </div> 763 + <span class="p--small">Twinless</span> 764 + <span class="sr-only">Link to Twinless</span> 765 + </div> 766 + </tile-dynamic> 767 + </a> 768 + </li> 769 + 770 + 771 + <li data-qa="cert-fresh-item"> 772 + 773 + <a class="cfp-tile" href="/m/hamilton_2020" data-qa="cert-fresh-link"> 774 + <tile-dynamic data-qa="tile"> 775 + <rt-img alt="Hamilton poster image" slot="image" 776 + src="https://resizing.flixster.com/1woquJmQfEhWCZtm7GcH0NMHsYA=/206x305/v2/https://resizing.flixster.com/PeAJ5ZpF5qB98ZiX6ixNDCgW2P0=/fit-in/180x240/v2/https://resizing.flixster.com/VmBvlTk8-z7pQvDZXTgSdj93WDE=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzLzkzY2IxZjFkLTE1NjEtNDQ4Yi05NDY3LTcxNzFmMDVhMDczNi5qcGc=" 777 + loading="lazy"></rt-img> 778 + <div slot="caption" data-track="scores"> 779 + <div class="score-wrap"> 780 + <score-icon-critics certified sentiment="positive" 781 + size="1"></score-icon-critics> 782 + <rt-text class="critics-score" size="1" 783 + context="label">98%</rt-text> 784 + </div> 785 + <span class="p--small">Hamilton</span> 786 + <span class="sr-only">Link to Hamilton</span> 787 + </div> 788 + </tile-dynamic> 789 + </a> 790 + </li> 791 + 792 + 793 + <li data-qa="cert-fresh-item"> 794 + 795 + <a class="cfp-tile" href="/m/the_thursday_murder_club" data-qa="cert-fresh-link"> 796 + <tile-dynamic data-qa="tile"> 797 + <rt-img alt="The Thursday Murder Club poster image" slot="image" 798 + src="https://resizing.flixster.com/jeeldFGcfSMgG09ey5VB7TCFiek=/206x305/v2/https://resizing.flixster.com/9LXDkCzIBBNEiPURkB9t6VefF5Q=/fit-in/180x240/v2/https://resizing.flixster.com/rwdeR5xIiN0k7SWr6yXdnmb6zP8=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2EzYWFkZWJiLWE5N2MtNDc3MS1iMDRlLTk0YWVlYzI5M2UxZS5qcGc=" 799 + loading="lazy"></rt-img> 800 + <div slot="caption" data-track="scores"> 801 + <div class="score-wrap"> 802 + <score-icon-critics certified sentiment="positive" 803 + size="1"></score-icon-critics> 804 + <rt-text class="critics-score" size="1" 805 + context="label">76%</rt-text> 806 + </div> 807 + <span class="p--small">The Thursday Murder Club</span> 808 + <span class="sr-only">Link to The Thursday Murder Club</span> 809 + </div> 810 + </tile-dynamic> 811 + </a> 812 + </li> 813 + 814 + </ul> 815 + </rt-header-nav-item-dropdown-list> 816 + 817 + </rt-header-nav-item-dropdown> 818 + </rt-header-nav-item> 819 + 820 + <rt-header-nav-item slot="tv" data-qa="masthead:tv"> 821 + <a class="unset" slot="link" href="/browse/tv_series_browse/sort:popular" data-qa="masthead:tv-link" 822 + data-AdsGlobalNavTakeoverManager="text"> 823 + Tv shows 824 + </a> 825 + <rt-header-nav-item-dropdown aria-expanded="false" slot="dropdown" data-qa="tv-menu"> 826 + 827 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-tv-list1"> 828 + <p slot="title" class="h4" data-curation="rt-hp-text-list-3"> 829 + New TV Tonight 830 + </p> 831 + <ul slot="links" class="score-list-wrap"> 832 + 833 + <li data-qa="list-item"> 834 + <a class="score-list-item" href="/tv/task/s01" data-qa="list-item-link"> 835 + <div class="score-wrap"> 836 + <score-icon-critics certified="true" sentiment="positive" 837 + size="1"></score-icon-critics> 838 + 839 + <rt-text class="critics-score" context="label" size="1" 840 + style="--lineHeight: 1; --letterSpacing: 0.016em;">89%</rt-text> 841 + 842 + </div> 843 + <span> 844 + 845 + Task: Season 1 846 + 847 + </span> 848 + </a> 849 + </li> 850 + 851 + <li data-qa="list-item"> 852 + <a class="score-list-item" href="/tv/the_walking_dead_daryl_dixon/s03" 853 + data-qa="list-item-link"> 854 + <div class="score-wrap"> 855 + <score-icon-critics certified="false" sentiment="positive" 856 + size="1"></score-icon-critics> 857 + 858 + <rt-text class="critics-score" context="label" size="1" 859 + style="--lineHeight: 1; --letterSpacing: 0.016em;">80%</rt-text> 860 + 861 + </div> 862 + <span> 863 + 864 + The Walking Dead: Daryl Dixon: Season 3 865 + 866 + </span> 867 + </a> 868 + </li> 869 + 870 + <li data-qa="list-item"> 871 + <a class="score-list-item" href="/tv/the_crow_girl/s01" data-qa="list-item-link"> 872 + <div class="score-wrap"> 873 + <score-icon-critics certified="false" sentiment="positive" 874 + size="1"></score-icon-critics> 875 + 876 + <rt-text class="critics-score" context="label" size="1" 877 + style="--lineHeight: 1; --letterSpacing: 0.016em;">80%</rt-text> 878 + 879 + </div> 880 + <span> 881 + 882 + The Crow Girl: Season 1 883 + 884 + </span> 885 + </a> 886 + </li> 887 + 888 + <li data-qa="list-item"> 889 + <a class="score-list-item" href="/tv/only_murders_in_the_building/s05" 890 + data-qa="list-item-link"> 891 + <div class="score-wrap"> 892 + <score-icon-critics certified="false" sentiment="empty" 893 + size="1"></score-icon-critics> 894 + 895 + <rt-text class="critics-score-empty" context="label" size="1" 896 + style="--textColor: var(--grayLight4); --lineHeight: 1; --letterSpacing: 0.2em;">--</rt-text> 897 + 898 + </div> 899 + <span> 900 + 901 + Only Murders in the Building: Season 5 902 + 903 + </span> 904 + </a> 905 + </li> 906 + 907 + <li data-qa="list-item"> 908 + <a class="score-list-item" href="/tv/the_girlfriend/s01" data-qa="list-item-link"> 909 + <div class="score-wrap"> 910 + <score-icon-critics certified="false" sentiment="empty" 911 + size="1"></score-icon-critics> 912 + 913 + <rt-text class="critics-score-empty" context="label" size="1" 914 + style="--textColor: var(--grayLight4); --lineHeight: 1; --letterSpacing: 0.2em;">--</rt-text> 915 + 916 + </div> 917 + <span> 918 + 919 + The Girlfriend: Season 1 920 + 921 + </span> 922 + </a> 923 + </li> 924 + 925 + <li data-qa="list-item"> 926 + <a class="score-list-item" href="/tv/aka_charlie_sheen/s01" 927 + data-qa="list-item-link"> 928 + <div class="score-wrap"> 929 + <score-icon-critics certified="false" sentiment="empty" 930 + size="1"></score-icon-critics> 931 + 932 + <rt-text class="critics-score-empty" context="label" size="1" 933 + style="--textColor: var(--grayLight4); --lineHeight: 1; --letterSpacing: 0.2em;">--</rt-text> 934 + 935 + </div> 936 + <span> 937 + 938 + aka Charlie Sheen: Season 1 939 + 940 + </span> 941 + </a> 942 + </li> 943 + 944 + <li data-qa="list-item"> 945 + <a class="score-list-item" href="/tv/wizards_beyond_waverly_place/s02" 946 + data-qa="list-item-link"> 947 + <div class="score-wrap"> 948 + <score-icon-critics certified="false" sentiment="empty" 949 + size="1"></score-icon-critics> 950 + 951 + <rt-text class="critics-score-empty" context="label" size="1" 952 + style="--textColor: var(--grayLight4); --lineHeight: 1; --letterSpacing: 0.2em;">--</rt-text> 953 + 954 + </div> 955 + <span> 956 + 957 + Wizards Beyond Waverly Place: Season 2 958 + 959 + </span> 960 + </a> 961 + </li> 962 + 963 + <li data-qa="list-item"> 964 + <a class="score-list-item" 965 + href="/tv/seen_and_heard_the_history_of_black_television/s01" 966 + data-qa="list-item-link"> 967 + <div class="score-wrap"> 968 + <score-icon-critics certified="false" sentiment="empty" 969 + size="1"></score-icon-critics> 970 + 971 + <rt-text class="critics-score-empty" context="label" size="1" 972 + style="--textColor: var(--grayLight4); --lineHeight: 1; --letterSpacing: 0.2em;">--</rt-text> 973 + 974 + </div> 975 + <span> 976 + 977 + Seen &amp; Heard: the History of Black Television: Season 1 978 + 979 + </span> 980 + </a> 981 + </li> 982 + 983 + <li data-qa="list-item"> 984 + <a class="score-list-item" href="/tv/the_fragrant_flower_blooms_with_dignity/s01" 985 + data-qa="list-item-link"> 986 + <div class="score-wrap"> 987 + <score-icon-critics certified="false" sentiment="empty" 988 + size="1"></score-icon-critics> 989 + 990 + <rt-text class="critics-score-empty" context="label" size="1" 991 + style="--textColor: var(--grayLight4); --lineHeight: 1; --letterSpacing: 0.2em;">--</rt-text> 992 + 993 + </div> 994 + <span> 995 + 996 + The Fragrant Flower Blooms With Dignity: Season 1 997 + 998 + </span> 999 + </a> 1000 + </li> 1001 + 1002 + <li data-qa="list-item"> 1003 + <a class="score-list-item" href="/tv/guts_and_glory/s01" data-qa="list-item-link"> 1004 + <div class="score-wrap"> 1005 + <score-icon-critics certified="false" sentiment="empty" 1006 + size="1"></score-icon-critics> 1007 + 1008 + <rt-text class="critics-score-empty" context="label" size="1" 1009 + style="--textColor: var(--grayLight4); --lineHeight: 1; --letterSpacing: 0.2em;">--</rt-text> 1010 + 1011 + </div> 1012 + <span> 1013 + 1014 + Guts &amp; Glory: Season 1 1015 + 1016 + </span> 1017 + </a> 1018 + </li> 1019 + 1020 + </ul> 1021 + <a class="a--short" data-qa="tv-list1-view-all-link" 1022 + href="/browse/tv_series_browse/sort:newest" slot="view-all-link"> 1023 + View All 1024 + </a> 1025 + </rt-header-nav-item-dropdown-list> 1026 + 1027 + 1028 + 1029 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-tv-list2"> 1030 + <p slot="title" class="h4" data-curation="rt-hp-text-list-2"> 1031 + Most Popular TV on RT 1032 + </p> 1033 + <ul slot="links" class="score-list-wrap"> 1034 + 1035 + <li data-qa="list-item"> 1036 + <a class="score-list-item" href="/tv/the_paper_2025/s01" data-qa="list-item-link"> 1037 + <div class="score-wrap"> 1038 + <score-icon-critics certified="true" sentiment="positive" 1039 + size="1"></score-icon-critics> 1040 + 1041 + <rt-text class="critics-score" context="label" size="1" 1042 + style="--lineHeight: 1; --letterSpacing: 0.016em;">83%</rt-text> 1043 + 1044 + </div> 1045 + <span> 1046 + 1047 + The Paper: Season 1 1048 + 1049 + </span> 1050 + </a> 1051 + </li> 1052 + 1053 + <li data-qa="list-item"> 1054 + <a class="score-list-item" href="/tv/dexter_resurrection/s01" 1055 + data-qa="list-item-link"> 1056 + <div class="score-wrap"> 1057 + <score-icon-critics certified="true" sentiment="positive" 1058 + size="1"></score-icon-critics> 1059 + 1060 + <rt-text class="critics-score" context="label" size="1" 1061 + style="--lineHeight: 1; --letterSpacing: 0.016em;">95%</rt-text> 1062 + 1063 + </div> 1064 + <span> 1065 + 1066 + Dexter: Resurrection: Season 1 1067 + 1068 + </span> 1069 + </a> 1070 + </li> 1071 + 1072 + <li data-qa="list-item"> 1073 + <a class="score-list-item" href="/tv/alien_earth/s01" data-qa="list-item-link"> 1074 + <div class="score-wrap"> 1075 + <score-icon-critics certified="true" sentiment="positive" 1076 + size="1"></score-icon-critics> 1077 + 1078 + <rt-text class="critics-score" context="label" size="1" 1079 + style="--lineHeight: 1; --letterSpacing: 0.016em;">95%</rt-text> 1080 + 1081 + </div> 1082 + <span> 1083 + 1084 + Alien: Earth: Season 1 1085 + 1086 + </span> 1087 + </a> 1088 + </li> 1089 + 1090 + <li data-qa="list-item"> 1091 + <a class="score-list-item" href="/tv/task/s01" data-qa="list-item-link"> 1092 + <div class="score-wrap"> 1093 + <score-icon-critics certified="true" sentiment="positive" 1094 + size="1"></score-icon-critics> 1095 + 1096 + <rt-text class="critics-score" context="label" size="1" 1097 + style="--lineHeight: 1; --letterSpacing: 0.016em;">89%</rt-text> 1098 + 1099 + </div> 1100 + <span> 1101 + 1102 + Task: Season 1 1103 + 1104 + </span> 1105 + </a> 1106 + </li> 1107 + 1108 + <li data-qa="list-item"> 1109 + <a class="score-list-item" href="/tv/wednesday/s02" data-qa="list-item-link"> 1110 + <div class="score-wrap"> 1111 + <score-icon-critics certified="true" sentiment="positive" 1112 + size="1"></score-icon-critics> 1113 + 1114 + <rt-text class="critics-score" context="label" size="1" 1115 + style="--lineHeight: 1; --letterSpacing: 0.016em;">87%</rt-text> 1116 + 1117 + </div> 1118 + <span> 1119 + 1120 + Wednesday: Season 2 1121 + 1122 + </span> 1123 + </a> 1124 + </li> 1125 + 1126 + <li data-qa="list-item"> 1127 + <a class="score-list-item" href="/tv/peacemaker_2022/s02" data-qa="list-item-link"> 1128 + <div class="score-wrap"> 1129 + <score-icon-critics certified="true" sentiment="positive" 1130 + size="1"></score-icon-critics> 1131 + 1132 + <rt-text class="critics-score" context="label" size="1" 1133 + style="--lineHeight: 1; --letterSpacing: 0.016em;">99%</rt-text> 1134 + 1135 + </div> 1136 + <span> 1137 + 1138 + Peacemaker: Season 2 1139 + 1140 + </span> 1141 + </a> 1142 + </li> 1143 + 1144 + <li data-qa="list-item"> 1145 + <a class="score-list-item" href="/tv/the_terminal_list_dark_wolf/s01" 1146 + data-qa="list-item-link"> 1147 + <div class="score-wrap"> 1148 + <score-icon-critics certified="false" sentiment="positive" 1149 + size="1"></score-icon-critics> 1150 + 1151 + <rt-text class="critics-score" context="label" size="1" 1152 + style="--lineHeight: 1; --letterSpacing: 0.016em;">73%</rt-text> 1153 + 1154 + </div> 1155 + <span> 1156 + 1157 + The Terminal List: Dark Wolf: Season 1 1158 + 1159 + </span> 1160 + </a> 1161 + </li> 1162 + 1163 + <li data-qa="list-item"> 1164 + <a class="score-list-item" href="/tv/hostage_2025/s01" data-qa="list-item-link"> 1165 + <div class="score-wrap"> 1166 + <score-icon-critics certified="true" sentiment="positive" 1167 + size="1"></score-icon-critics> 1168 + 1169 + <rt-text class="critics-score" context="label" size="1" 1170 + style="--lineHeight: 1; --letterSpacing: 0.016em;">82%</rt-text> 1171 + 1172 + </div> 1173 + <span> 1174 + 1175 + Hostage: Season 1 1176 + 1177 + </span> 1178 + </a> 1179 + </li> 1180 + 1181 + <li data-qa="list-item"> 1182 + <a class="score-list-item" href="/tv/chief_of_war/s01" data-qa="list-item-link"> 1183 + <div class="score-wrap"> 1184 + <score-icon-critics certified="true" sentiment="positive" 1185 + size="1"></score-icon-critics> 1186 + 1187 + <rt-text class="critics-score" context="label" size="1" 1188 + style="--lineHeight: 1; --letterSpacing: 0.016em;">93%</rt-text> 1189 + 1190 + </div> 1191 + <span> 1192 + 1193 + Chief of War: Season 1 1194 + 1195 + </span> 1196 + </a> 1197 + </li> 1198 + 1199 + <li data-qa="list-item"> 1200 + <a class="score-list-item" href="/tv/irish_blood/s01" data-qa="list-item-link"> 1201 + <div class="score-wrap"> 1202 + <score-icon-critics certified="false" sentiment="positive" 1203 + size="1"></score-icon-critics> 1204 + 1205 + <rt-text class="critics-score" context="label" size="1" 1206 + style="--lineHeight: 1; --letterSpacing: 0.016em;">100%</rt-text> 1207 + 1208 + </div> 1209 + <span> 1210 + 1211 + Irish Blood: Season 1 1212 + 1213 + </span> 1214 + </a> 1215 + </li> 1216 + 1217 + </ul> 1218 + <a class="a--short" data-qa="tv-list2-view-all-link" 1219 + href="/browse/tv_series_browse/sort:popular?" slot="view-all-link"> 1220 + View All 1221 + </a> 1222 + </rt-header-nav-item-dropdown-list> 1223 + 1224 + 1225 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-tv-more"> 1226 + <p slot="title" class="h4">More</p> 1227 + <ul slot="links"> 1228 + <li> 1229 + <a href="https://editorial.rottentomatoes.com/rt-hub/what-to-watch/" 1230 + class="what-to-watch" data-qa="what-to-watch-link-tv"> 1231 + What to Watch<rt-badge>New</rt-badge> 1232 + </a> 1233 + </li> 1234 + <li> 1235 + <a href="/browse/tv_series_browse/sort:popular" data-qa="tv-best-link"> 1236 + <span>Best TV Shows</span> 1237 + </a> 1238 + </li> 1239 + <li> 1240 + <a href="/browse/tv_series_browse/sort:popular" data-qa="tv-popular-link"> 1241 + <span>Most Popular TV</span> 1242 + </a> 1243 + </li> 1244 + <li> 1245 + <a href="/browse/tv_series_browse/affiliates:fandango-at-home" 1246 + data-qa="tv-fandango-at-home-link"> 1247 + <span>Fandango at Home</span> 1248 + </a> 1249 + </li> 1250 + <li> 1251 + <a href="/browse/tv_series_browse/affiliates:peacock" data-qa="tv-peacock-link"> 1252 + <span>Peacock</span> 1253 + </a> 1254 + </li> 1255 + <li> 1256 + <a href="/browse/tv_series_browse/affiliates:paramount-plus" 1257 + data-qa="tv-paramount-link"> 1258 + <span>Paramount+</span> 1259 + </a> 1260 + </li> 1261 + <li> 1262 + <a href="/browse/tv_series_browse/affiliates:netflix" data-qa="tv-netflix-link"> 1263 + <span>Netflix</span> 1264 + </a> 1265 + </li> 1266 + <li> 1267 + <a href="/browse/tv_series_browse/affiliates:prime-video" 1268 + data-qa="tv-prime-video-link"> 1269 + <span>Prime Video</span> 1270 + </a> 1271 + </li> 1272 + <li> 1273 + <a href="/browse/tv_series_browse/affiliates:apple-tv-plus" 1274 + data-qa="tv-apple-tv-plus-link"> 1275 + <span>Apple TV+</span> 1276 + </a> 1277 + </li> 1278 + </ul> 1279 + </rt-header-nav-item-dropdown-list> 1280 + 1281 + 1282 + <rt-header-nav-item-dropdown-list slot="column" cfp data-qa="header-certified-fresh-pick"> 1283 + <p slot="title" class="h4"> 1284 + Certified fresh pick 1285 + </p> 1286 + <ul slot="links" class="cfp-wrap" data-curation="rt-nav-list-cf-picks"> 1287 + <li> 1288 + 1289 + <a class="cfp-tile" href="/tv/the_paper_2025/s01" data-qa="cert-fresh-link"> 1290 + <tile-dynamic data-qa="tile"> 1291 + <rt-img alt="The Paper: Season 1 poster image" slot="image" 1292 + src="https://resizing.flixster.com/yFijQcjPYUWUelgmiZLHgkXU7hw=/206x305/v2/https://resizing.flixster.com/DFkkHf5pEVX_apKtIQZcoEvI6RU=/fit-in/180x240/v2/https://resizing.flixster.com/texEZJLAG-KcVpfCdkT2R1t4cmE=/ems.cHJkLWVtcy1hc3NldHMvdHZzZWFzb24vYTM3OWM2MTctN2M3Ny00MjdhLTk4NDUtODE5ZWUwMWExNGRhLnBuZw==" 1293 + loading="lazy"></rt-img> 1294 + <div slot="caption" data-track="scores"> 1295 + <div class="score-wrap"> 1296 + <score-icon-critics certified sentiment="positive" 1297 + size="1"></score-icon-critics> 1298 + <rt-text class="critics-score" size="1" 1299 + context="label">83%</rt-text> 1300 + </div> 1301 + <span class="p--small">The Paper: Season 1</span> 1302 + <span class="sr-only">Link to The Paper: Season 1</span> 1303 + </div> 1304 + </tile-dynamic> 1305 + </a> 1306 + </li> 1307 + </ul> 1308 + </rt-header-nav-item-dropdown-list> 1309 + 1310 + </rt-header-nav-item-dropdown> 1311 + </rt-header-nav-item> 1312 + 1313 + <rt-header-nav-item slot="shop"> 1314 + <a class="unset" id="appLink" slot="link" href="https://editorial.rottentomatoes.com/article/app/" 1315 + target="_blank" data-qa="masthead:app-link" data-AdsGlobalNavTakeoverManager="text"> 1316 + RT App 1317 + <temporary-display slot="temporary-display" key="app" element="#appLink" event="click"> 1318 + <rt-badge hidden>New</rt-badge> 1319 + </temporary-display> 1320 + </a> 1321 + </rt-header-nav-item> 1322 + 1323 + <rt-header-nav-item slot="news" data-qa="masthead:news"> 1324 + <a class="unset" slot="link" href="https://editorial.rottentomatoes.com/" 1325 + data-qa="masthead:news-link" data-AdsGlobalNavTakeoverManager="text"> 1326 + News 1327 + </a> 1328 + <rt-header-nav-item-dropdown aria-expanded="false" slot="dropdown" data-qa="news-menu"> 1329 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-news-columns"> 1330 + <p slot="title" class="h4">Columns</p> 1331 + <ul slot="links"> 1332 + <li data-qa="column-item"> 1333 + <a href="https://editorial.rottentomatoes.com/all-time-lists/" 1334 + data-pageheader="All-Time Lists" data-qa="column-link"> 1335 + All-Time Lists 1336 + </a> 1337 + </li> 1338 + <li data-qa="column-item"> 1339 + <a href="https://editorial.rottentomatoes.com/binge-guide/" 1340 + data-pageheader="Binge Guide" data-qa="column-link"> 1341 + Binge Guide 1342 + </a> 1343 + </li> 1344 + <li data-qa="column-item"> 1345 + <a href="https://editorial.rottentomatoes.com/comics-on-tv/" 1346 + data-pageheader="Comics on TV" data-qa="column-link"> 1347 + Comics on TV 1348 + </a> 1349 + </li> 1350 + <li data-qa="column-item"> 1351 + <a href="https://editorial.rottentomatoes.com/countdown/" 1352 + data-pageheader="Countdown" data-qa="column-link"> 1353 + Countdown 1354 + </a> 1355 + </li> 1356 + <li data-qa="column-item"> 1357 + <a href="https://editorial.rottentomatoes.com/five-favorite-films/" 1358 + data-pageheader="Five Favorite Films" data-qa="column-link"> 1359 + Five Favorite Films 1360 + </a> 1361 + </li> 1362 + <li data-qa="column-item"> 1363 + <a href="https://editorial.rottentomatoes.com/video-interviews/" 1364 + data-pageheader="Video Interviews" data-qa="column-link"> 1365 + Video Interviews 1366 + </a> 1367 + </li> 1368 + <li data-qa="column-item"> 1369 + <a href="https://editorial.rottentomatoes.com/weekend-box-office/" 1370 + data-pageheader="Weekend Box Office" data-qa="column-link">Weekend Box Office 1371 + </a> 1372 + </li> 1373 + <li data-qa="column-item"> 1374 + <a href="https://editorial.rottentomatoes.com/weekly-ketchup/" 1375 + data-pageheader="Weekly Ketchup" data-qa="column-link"> 1376 + Weekly Ketchup 1377 + </a> 1378 + </li> 1379 + <li data-qa="column-item"> 1380 + <a href="https://editorial.rottentomatoes.com/what-to-watch/" 1381 + data-pageheader="What to Watch" data-qa="column-link"> 1382 + What to Watch 1383 + </a> 1384 + </li> 1385 + </ul> 1386 + </rt-header-nav-item-dropdown-list> 1387 + 1388 + 1389 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-news-guides"> 1390 + <p slot="title" class="h4">Guides</p> 1391 + <ul slot="links" class="news-wrap"> 1392 + 1393 + <li data-qa="guides-item"> 1394 + <a class="news-tile" 1395 + href="https://editorial.rottentomatoes.com/guide/best-football-movies/" 1396 + data-qa="news-link"> 1397 + <tile-dynamic data-qa="tile" orientation="landscape"> 1398 + <rt-img alt="59 Best Football Movies, Ranked by Tomatometer poster image" 1399 + slot="image" 1400 + src="https://editorial.rottentomatoes.com/wp-content/uploads/2023/09/600EssentialFootballMovies.png" 1401 + loading="lazy"></rt-img> 1402 + <div slot="caption"> 1403 + <p>59 Best Football Movies, Ranked by Tomatometer</p> 1404 + <span class="sr-only">Link to 59 Best Football Movies, Ranked by 1405 + Tomatometer</span> 1406 + </div> 1407 + </tile-dynamic> 1408 + </a> 1409 + </li> 1410 + 1411 + <li data-qa="guides-item"> 1412 + <a class="news-tile" 1413 + href="https://editorial.rottentomatoes.com/guide/best-new-rom-coms-romance-movies/" 1414 + data-qa="news-link"> 1415 + <tile-dynamic data-qa="tile" orientation="landscape"> 1416 + <rt-img alt="50 Best New Rom-Coms and Romance Movies poster image" 1417 + slot="image" 1418 + src="https://editorial.rottentomatoes.com/wp-content/uploads/2025/08/Best_New_Romcoms600.jpg" 1419 + loading="lazy"></rt-img> 1420 + <div slot="caption"> 1421 + <p>50 Best New Rom-Coms and Romance Movies</p> 1422 + <span class="sr-only">Link to 50 Best New Rom-Coms and Romance 1423 + Movies</span> 1424 + </div> 1425 + </tile-dynamic> 1426 + </a> 1427 + </li> 1428 + 1429 + </ul> 1430 + <a class="a--short" data-qa="guides-view-all-link" 1431 + href="https://editorial.rottentomatoes.com/countdown/" slot="view-all-link"> 1432 + View All 1433 + </a> 1434 + </rt-header-nav-item-dropdown-list> 1435 + 1436 + 1437 + 1438 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-news-hubs"> 1439 + <p slot="title" class="h4">Hubs</p> 1440 + <ul slot="links" class="news-wrap"> 1441 + 1442 + <li data-qa="hubs-item"> 1443 + <a class="news-tile" 1444 + href="https://editorial.rottentomatoes.com/rt-hub/what-to-watch/" 1445 + data-qa="news-link"> 1446 + <tile-dynamic data-qa="tile" orientation="landscape"> 1447 + <rt-img alt="What to Watch: In Theaters and On Streaming poster image" 1448 + slot="image" 1449 + src="https://editorial.rottentomatoes.com/wp-content/uploads/2023/05/RT_WTW_Generic_2023_Thumbnail_600x314_021623.jpg" 1450 + loading="lazy"></rt-img> 1451 + <div slot="caption"> 1452 + <p>What to Watch: In Theaters and On Streaming</p> 1453 + <span class="sr-only">Link to What to Watch: In Theaters and On 1454 + Streaming</span> 1455 + </div> 1456 + </tile-dynamic> 1457 + </a> 1458 + </li> 1459 + 1460 + <li data-qa="hubs-item"> 1461 + <a class="news-tile" href="https://editorial.rottentomatoes.com/rt-hub/awards-tour/" 1462 + data-qa="news-link"> 1463 + <tile-dynamic data-qa="tile" orientation="landscape"> 1464 + <rt-img alt="Awards Tour poster image" slot="image" 1465 + src="https://editorial.rottentomatoes.com/wp-content/uploads/2023/02/RT_AwardsTour_Thumbnail_600x314.jpg" 1466 + loading="lazy"></rt-img> 1467 + <div slot="caption"> 1468 + <p>Awards Tour</p> 1469 + <span class="sr-only">Link to Awards Tour</span> 1470 + </div> 1471 + </tile-dynamic> 1472 + </a> 1473 + </li> 1474 + 1475 + </ul> 1476 + <a class="a--short" data-qa="hubs-view-all-link" 1477 + href="https://editorial.rottentomatoes.com/rt-hubs/" slot="view-all-link"> 1478 + View All 1479 + </a> 1480 + </rt-header-nav-item-dropdown-list> 1481 + 1482 + 1483 + 1484 + <rt-header-nav-item-dropdown-list slot="column" data-qa="header-news-rt-news"> 1485 + <p slot="title" class="h4">RT News</p> 1486 + <ul slot="links" class="news-wrap"> 1487 + 1488 + <li data-qa="rt-news-item"> 1489 + <a class="news-tile" 1490 + href="https://editorial.rottentomatoes.com/article/new-movies-and-shows-streaming-in-september-2025-what-to-watch-on-netflix-prime-video-hbo-max-disney-and-more/" 1491 + data-qa="news-link"> 1492 + <tile-dynamic data-qa="tile" orientation="landscape"> 1493 + <rt-img 1494 + alt="New Movies and Shows Streaming in September: What to watch on Netflix, Prime Video, HBO Max, Disney+ and More poster image" 1495 + slot="image" 1496 + src="https://editorial.rottentomatoes.com/wp-content/uploads/2025/08/New_Streaming_September_2025-Rep.jpg" 1497 + loading="lazy"></rt-img> 1498 + <div slot="caption"> 1499 + <p>New Movies and Shows Streaming in September: What to watch on 1500 + Netflix, Prime Video, HBO Max, Disney+ and More</p> 1501 + <span class="sr-only">Link to New Movies and Shows Streaming in 1502 + September: What to watch on Netflix, Prime Video, HBO Max, Disney+ 1503 + and More</span> 1504 + </div> 1505 + </tile-dynamic> 1506 + </a> 1507 + </li> 1508 + 1509 + <li data-qa="rt-news-item"> 1510 + <a class="news-tile" 1511 + href="https://editorial.rottentomatoes.com/article/the-conjuring-last-rites-first-reviews/" 1512 + data-qa="news-link"> 1513 + <tile-dynamic data-qa="tile" orientation="landscape"> 1514 + <rt-img 1515 + alt="<em>The Conjuring: Last Rites</em> First Reviews: A Frightful, Fitting Send-off poster image" 1516 + slot="image" 1517 + src="https://editorial.rottentomatoes.com/wp-content/uploads/2025/09/Conjuring_Last_Rites_Reviews-Rep.jpg" 1518 + loading="lazy"></rt-img> 1519 + <div slot="caption"> 1520 + <p><em>The Conjuring: Last Rites</em> First Reviews: A Frightful, 1521 + Fitting Send-off</p> 1522 + <span class="sr-only">Link to <em>The Conjuring: Last Rites</em> First 1523 + Reviews: A Frightful, Fitting Send-off</span> 1524 + </div> 1525 + </tile-dynamic> 1526 + </a> 1527 + </li> 1528 + 1529 + </ul> 1530 + <a class="a--short" data-qa="rt-news-view-all-link" 1531 + href="https://editorial.rottentomatoes.com/news/" slot="view-all-link"> 1532 + View All 1533 + </a> 1534 + </rt-header-nav-item-dropdown-list> 1535 + 1536 + </rt-header-nav-item-dropdown> 1537 + </rt-header-nav-item> 1538 + 1539 + <rt-header-nav-item slot="showtimes"> 1540 + <a class="unset" slot="link" href="https://www.fandango.com/movies-in-theaters?a=13036" 1541 + target="_blank" rel="noopener" data-qa="masthead:tickets-showtimes-link" 1542 + data-AdsGlobalNavTakeoverManager="text"> 1543 + Showtimes 1544 + </a> 1545 + </rt-header-nav-item> 1546 + </rt-header-nav> 1547 + 1548 + </rt-header> 1549 + 1550 + <ads-global-nav-takeover-manager></ads-global-nav-takeover-manager> 1551 + <section class="trending-bar"> 1552 + 1553 + 1554 + <ad-unit hidden id="trending_bar_ad" unit-display="desktop" unit-type="trendinggraphic"> 1555 + <div slot="ad-inject"></div> 1556 + </ad-unit> 1557 + <div id="trending-bar-start" class="trending-list-wrap" data-qa="trending-bar"> 1558 + <ul class="list-inline trending-bar__list" data-curation="rt-nav-trending" data-qa="trending-bar-list"> 1559 + <li class="trending-bar__header">Trending on RT</li> 1560 + 1561 + <li><a class="trending-bar__link" 1562 + href="https://editorial.rottentomatoes.com/article/2025-emmys-ballot-complete-with-tomatometer-and-popcornmeter-scores/" 1563 + data-qa="trending-bar-item"> Emmy Noms </a></li> 1564 + 1565 + <li><a class="trending-bar__link" 1566 + href="https://editorial.rottentomatoes.com/article/movie-re-releases-calendar/" 1567 + data-qa="trending-bar-item"> Re-Release Calendar </a></li> 1568 + 1569 + <li><a class="trending-bar__link" 1570 + href="https://editorial.rottentomatoes.com/article/renewed-and-cancelled-tv-shows-2025/" 1571 + data-qa="trending-bar-item"> Renewed and Cancelled TV </a></li> 1572 + 1573 + <li><a class="trending-bar__link" href="https://editorial.rottentomatoes.com/article/app/" 1574 + data-qa="trending-bar-item"> The Rotten Tomatoes App </a></li> 1575 + 1576 + </ul> 1577 + <div class="trending-bar__social" data-qa="trending-bar-social-list"> 1578 + <social-media-icons theme="light" size="14"></social-media-icons> 1579 + </div> 1580 + </div> 1581 + </section> 1582 + 1583 + 1584 + 1585 + 1586 + <main id="main_container" class="container rt-layout__content"> 1587 + <div id="main-page-content"> 1588 + 1589 + 1590 + 1591 + 1592 + 1593 + <div class="search__container layout"> 1594 + <section class="search__main layout__column layout__column--main"> 1595 + 1596 + <h1 class="unset"> 1597 + <rt-text context="heading" size="1.625">Search Results for : "Fantastic Four"</rt-text> 1598 + </h1> 1599 + 1600 + <search-page-manager searchQuery="Fantastic Four"></search-page-manager> 1601 + <div id="search-results" data-qa="search-results"> 1602 + <nav class="search__nav" slot="searchNav"> 1603 + <ul class="searchNav__filters"> 1604 + <li class="js-search-filter searchNav__filter searchNav__filter-all searchNav__filter--active" 1605 + data-filter="all" tabindex="0"><span data-qa="search-filter-text">All</span> 1606 + </li> 1607 + 1608 + <li class="js-search-filter searchNav__filter" data-filter="movie" tabindex="0"> 1609 + <span data-qa="search-filter-text">Movies (35)</span></li> 1610 + 1611 + 1612 + <li class="js-search-filter searchNav__filter" data-filter="tvSeries" tabindex="0"> 1613 + <span data-qa="search-filter-text">TV Shows (6)</span></li> 1614 + 1615 + 1616 + <li class="js-search-filter searchNav__filter" data-filter="celebrity" tabindex="0"> 1617 + <span data-qa="search-filter-text">Celebrities (2)</span></li> 1618 + 1619 + </ul> 1620 + </nav> 1621 + 1622 + 1623 + <search-page-result skeleton="panel" type="movie" data-qa="search-result"> 1624 + <h2 class="unset" slot="title" data-qa="search-result-title"> 1625 + <rt-text context="heading" size="1.25"> Movies </rt-text> 1626 + </h2> 1627 + <button slot="prev-btn" data-qa="paging-btn-prev">Prev</button> 1628 + <button slot="next-btn" data-qa="paging-btn-next">Next</button> 1629 + <ul slot="list"> 1630 + 1631 + <search-page-media-row skeleton="panel" 1632 + cast="Pedro Pascal,Vanessa Kirby,Ebon Moss-Bachrach" data-qa="data-row" 1633 + endyear="" releaseyear="2025" startyear="" tomatometeriscertified="true" 1634 + tomatometerscore="87" tomatometersentiment="POSITIVE"> 1635 + <a href="https://www.rottentomatoes.com/m/the_fantastic_four_first_steps" 1636 + class="unset" data-qa="thumbnail-link" slot="thumbnail"> 1637 + <img alt="The Fantastic Four: First Steps" loading="lazy" 1638 + src="https://resizing.flixster.com/kh9TONIlIUrcSELHRnWw0K1u3Cg=/fit-in/80x126/v2/https://resizing.flixster.com/WEfe-vTCqjHioD77J7f-qeoZZdY=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2FjN2M2NzA0LWNiMTctNDhjMy05N2VlLTk3YWI3N2JhZDZmMS5qcGc="> 1639 + </a> 1640 + <a href="https://www.rottentomatoes.com/m/the_fantastic_four_first_steps" 1641 + class="unset" data-qa="info-name" slot="title"> 1642 + The Fantastic Four: First Steps 1643 + </a> 1644 + </search-page-media-row> 1645 + 1646 + <search-page-media-row skeleton="panel" 1647 + cast="Miles Teller,Michael B. Jordan,Kate Mara" data-qa="data-row" endyear="" 1648 + releaseyear="2015" startyear="" tomatometeriscertified="false" 1649 + tomatometerscore="9" tomatometersentiment="NEGATIVE"> 1650 + <a href="https://www.rottentomatoes.com/m/fantastic_four_2015" class="unset" 1651 + data-qa="thumbnail-link" slot="thumbnail"> 1652 + <img alt="Fantastic Four" loading="lazy" 1653 + src="https://resizing.flixster.com/Dh-AxpIB7cnywbmjUyGBmgB1hFU=/fit-in/80x126/v2/https://resizing.flixster.com/EoFPwXUATnha798E0UJc9MpHV5I=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2U1ZjRiZDE1LWFmNjUtNGM3ZS1hMDIwLWQ3YjYzYTU0N2Y5NC5qcGc="> 1654 + </a> 1655 + <a href="https://www.rottentomatoes.com/m/fantastic_four_2015" class="unset" 1656 + data-qa="info-name" slot="title"> 1657 + Fantastic Four 1658 + </a> 1659 + </search-page-media-row> 1660 + 1661 + <search-page-media-row skeleton="panel" 1662 + cast="Ioan Gruffudd,Jessica Alba,Chris Evans" data-qa="data-row" endyear="" 1663 + releaseyear="2005" startyear="" tomatometeriscertified="false" 1664 + tomatometerscore="28" tomatometersentiment="NEGATIVE"> 1665 + <a href="https://www.rottentomatoes.com/m/fantastic_four" class="unset" 1666 + data-qa="thumbnail-link" slot="thumbnail"> 1667 + <img alt="Fantastic Four" loading="lazy" 1668 + src="https://resizing.flixster.com/3__TW0ho8WMbL2BtJlUY4__V7cM=/fit-in/80x126/v2/https://resizing.flixster.com/bWXub3N0V15ZoiGrLkZ5qSPmhVc=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2FlNTAzYmE0LTI2NjctNGQ2OS05MDNhLTAxYzVhM2I3NWU1OC5qcGc="> 1669 + </a> 1670 + <a href="https://www.rottentomatoes.com/m/fantastic_four" class="unset" 1671 + data-qa="info-name" slot="title"> 1672 + Fantastic Four 1673 + </a> 1674 + </search-page-media-row> 1675 + 1676 + <search-page-media-row skeleton="panel" 1677 + cast="Ioan Gruffudd,Jessica Alba,Chris Evans" data-qa="data-row" endyear="" 1678 + releaseyear="2007" startyear="" tomatometeriscertified="false" 1679 + tomatometerscore="37" tomatometersentiment="NEGATIVE"> 1680 + <a href="https://www.rottentomatoes.com/m/fantastic_four_rise_of_the_silver_surfer" 1681 + class="unset" data-qa="thumbnail-link" slot="thumbnail"> 1682 + <img alt="Fantastic Four: Rise of the Silver Surfer" loading="lazy" 1683 + src="https://resizing.flixster.com/MHurLD6EaKJSCaBEVJAQEBl51j4=/fit-in/80x126/v2/https://resizing.flixster.com/GX2x8tQN7yQMlIUbQ1k4-EnpTAw=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzLzc0ZTIzZWQ2LTdmNmYtNGZjNS04NmMyLTA2ZTJjMzFjMjM5Yi5qcGc="> 1684 + </a> 1685 + <a href="https://www.rottentomatoes.com/m/fantastic_four_rise_of_the_silver_surfer" 1686 + class="unset" data-qa="info-name" slot="title"> 1687 + Fantastic Four: Rise of the Silver Surfer 1688 + </a> 1689 + </search-page-media-row> 1690 + 1691 + <search-page-media-row skeleton="panel" 1692 + cast="Alex Hyde-White,Jay Underwood,Rebecca Staab" data-qa="data-row" endyear="" 1693 + releaseyear="1994" startyear="" tomatometeriscertified="false" 1694 + tomatometerscore="33" tomatometersentiment="NEGATIVE"> 1695 + <a href="https://www.rottentomatoes.com/m/10005582-fantastic_four" class="unset" 1696 + data-qa="thumbnail-link" slot="thumbnail"> 1697 + <img alt="The Fantastic Four" loading="lazy" 1698 + src="https://resizing.flixster.com/k3h3tXzUOacsIjzSU46kNsG9jao=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p11974406_p_v10_aa.jpg"> 1699 + </a> 1700 + <a href="https://www.rottentomatoes.com/m/10005582-fantastic_four" class="unset" 1701 + data-qa="info-name" slot="title"> 1702 + The Fantastic Four 1703 + </a> 1704 + </search-page-media-row> 1705 + 1706 + <search-page-media-row skeleton="panel" 1707 + cast="Carl Ciarfalio,Roger Corman,Joseph Culp" data-qa="data-row" endyear="" 1708 + releaseyear="2015" startyear="" tomatometeriscertified="false" 1709 + tomatometerscore="89" tomatometersentiment="POSITIVE"> 1710 + <a href="https://www.rottentomatoes.com/m/doomed_the_untold_story_of_roger_cormans_the_fantastic_four" 1711 + class="unset" data-qa="thumbnail-link" slot="thumbnail"> 1712 + <img alt="Doomed: The Untold Story of Roger Corman&#39;s the Fantastic Four" 1713 + loading="lazy" 1714 + src="https://resizing.flixster.com/oWBX3Tbq6dCxeofYxpduj-3eu1A=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p12021288_p_v8_ab.jpg"> 1715 + </a> 1716 + <a href="https://www.rottentomatoes.com/m/doomed_the_untold_story_of_roger_cormans_the_fantastic_four" 1717 + class="unset" data-qa="info-name" slot="title"> 1718 + Doomed: The Untold Story of Roger Corman&#39;s the Fantastic Four 1719 + </a> 1720 + </search-page-media-row> 1721 + 1722 + <search-page-media-row skeleton="panel" 1723 + cast="Eddie Redmayne,Katherine Waterston,Dan Fogler" data-qa="data-row" 1724 + endyear="" releaseyear="2018" startyear="" tomatometeriscertified="false" 1725 + tomatometerscore="36" tomatometersentiment="NEGATIVE"> 1726 + <a href="https://www.rottentomatoes.com/m/fantastic_beasts_the_crimes_of_grindelwald" 1727 + class="unset" data-qa="thumbnail-link" slot="thumbnail"> 1728 + <img alt="Fantastic Beasts: The Crimes of Grindelwald" loading="lazy" 1729 + src="https://resizing.flixster.com/jupUs-IsF4TwzhGk9zIXV5swiRQ=/fit-in/80x126/v2/https://resizing.flixster.com/47erqYRky74FKjCcwEjBZJsQO2s=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzLzc4M2IyOTU3LTYwMjEtNGY1Ni1hMTE4LTU3NGI5ZjVkYTM2My53ZWJw"> 1730 + </a> 1731 + <a href="https://www.rottentomatoes.com/m/fantastic_beasts_the_crimes_of_grindelwald" 1732 + class="unset" data-qa="info-name" slot="title"> 1733 + Fantastic Beasts: The Crimes of Grindelwald 1734 + </a> 1735 + </search-page-media-row> 1736 + 1737 + <search-page-media-row skeleton="panel" cast="Simon Pegg,Amara Karan,Clare Higgins" 1738 + data-qa="data-row" endyear="" releaseyear="2012" startyear="" 1739 + tomatometeriscertified="false" tomatometerscore="34" 1740 + tomatometersentiment="NEGATIVE"> 1741 + <a href="https://www.rottentomatoes.com/m/a_fantastic_fear_of_everything" 1742 + class="unset" data-qa="thumbnail-link" slot="thumbnail"> 1743 + <img alt="A Fantastic Fear of Everything" loading="lazy" 1744 + src="https://resizing.flixster.com/GfeAPSdwANE5BnTTvY-9d_euTr8=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p9288765_p_v13_ae.jpg"> 1745 + </a> 1746 + <a href="https://www.rottentomatoes.com/m/a_fantastic_fear_of_everything" 1747 + class="unset" data-qa="info-name" slot="title"> 1748 + A Fantastic Fear of Everything 1749 + </a> 1750 + </search-page-media-row> 1751 + 1752 + <search-page-media-row skeleton="panel" cast="Mel Blanc,June Foray,Les Tremayne" 1753 + data-qa="data-row" endyear="" releaseyear="1983" startyear="" 1754 + tomatometeriscertified="false" tomatometerscore="" tomatometersentiment=""> 1755 + <a href="https://www.rottentomatoes.com/m/daffy_ducks_movie_fantastic_island" 1756 + class="unset" data-qa="thumbnail-link" slot="thumbnail"> 1757 + <img alt="Daffy Duck&#39;s Movie: Fantastic Island" loading="lazy" 1758 + src="https://resizing.flixster.com/fWIrhyzpAPSTAaSaVmyPBfQ5I5Y=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p6855_p_v13_ab.jpg"> 1759 + </a> 1760 + <a href="https://www.rottentomatoes.com/m/daffy_ducks_movie_fantastic_island" 1761 + class="unset" data-qa="info-name" slot="title"> 1762 + Daffy Duck&#39;s Movie: Fantastic Island 1763 + </a> 1764 + </search-page-media-row> 1765 + 1766 + <search-page-media-row skeleton="panel" 1767 + cast="Christopher Lloyd,Sarah Michelle Gellar,Christopher Collet" 1768 + data-qa="data-row" endyear="" releaseyear="2013" startyear="" 1769 + tomatometeriscertified="false" tomatometerscore="" tomatometersentiment=""> 1770 + <a href="https://www.rottentomatoes.com/m/freedom_force_2012" class="unset" 1771 + data-qa="thumbnail-link" slot="thumbnail"> 1772 + <img alt="Freedom Force" loading="lazy" 1773 + src="https://resizing.flixster.com/YsSgG6rXnCT6eTYRepAogpgoQWI=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p10403283_p_v8_ae.jpg"> 1774 + </a> 1775 + <a href="https://www.rottentomatoes.com/m/freedom_force_2012" class="unset" 1776 + data-qa="info-name" slot="title"> 1777 + Freedom Force 1778 + </a> 1779 + </search-page-media-row> 1780 + 1781 + </ul> 1782 + <button slot="more-btn" data-qa="search-more-btn">More Movies...</button> 1783 + </search-page-result> 1784 + 1785 + 1786 + 1787 + <search-page-result skeleton="panel" type="tvSeries" data-qa="search-result"> 1788 + <h2 class="unset" slot="title" data-qa="search-result-title"> 1789 + <rt-text context="heading" size="1.25"> TV shows </rt-text> 1790 + </h2> 1791 + <button slot="prev-btn" data-qa="paging-btn-prev">Prev</button> 1792 + <button slot="next-btn" data-qa="paging-btn-next">Next</button> 1793 + <ul slot="list"> 1794 + 1795 + <search-page-media-row skeleton="panel" cast="" data-qa="data-row" endyear="2010" 1796 + releaseyear="" startyear="2006" tomatometeriscertified="false" 1797 + tomatometerscore="" tomatometersentiment=""> 1798 + <a href="https://www.rottentomatoes.com/tv/fantastic_four_worlds_greatest_heroes" 1799 + class="unset" data-qa="thumbnail-link" slot="thumbnail"> 1800 + <img alt="Fantastic Four: World&#39;s Greatest Heroes" loading="lazy" 1801 + src="https://resizing.flixster.com/G3rsmvYdoAMpKp7W0XhvnYtTnFA=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p384193_b_v8_af.jpg"> 1802 + </a> 1803 + <a href="https://www.rottentomatoes.com/tv/fantastic_four_worlds_greatest_heroes" 1804 + class="unset" data-qa="info-name" slot="title"> 1805 + Fantastic Four: World&#39;s Greatest Heroes 1806 + </a> 1807 + </search-page-media-row> 1808 + 1809 + <search-page-media-row skeleton="panel" cast="" data-qa="data-row" endyear="" 1810 + releaseyear="" startyear="" tomatometeriscertified="false" tomatometerscore="" 1811 + tomatometersentiment=""> 1812 + <a href="https://www.rottentomatoes.com/tv/fantastic_four_1994" class="unset" 1813 + data-qa="thumbnail-link" slot="thumbnail"> 1814 + <img alt="Fantastic Four" loading="lazy" 1815 + src="https://resizing.flixster.com/-W5dxKVVhkgAIwQU6JHNp_TYNLU=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p384190_b_v8_aq.jpg"> 1816 + </a> 1817 + <a href="https://www.rottentomatoes.com/tv/fantastic_four_1994" class="unset" 1818 + data-qa="info-name" slot="title"> 1819 + Fantastic Four 1820 + </a> 1821 + </search-page-media-row> 1822 + 1823 + <search-page-media-row skeleton="panel" cast="" data-qa="data-row" endyear="1968" 1824 + releaseyear="" startyear="1967" tomatometeriscertified="false" 1825 + tomatometerscore="" tomatometersentiment=""> 1826 + <a href="https://www.rottentomatoes.com/tv/fantastic_four" class="unset" 1827 + data-qa="thumbnail-link" slot="thumbnail"> 1828 + <img alt="Fantastic Four" loading="lazy" 1829 + src="https://resizing.flixster.com/0CqX0L9stEQ5Fwg1wrSCvV1L40Y=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p384191_b_v10_aa.jpg"> 1830 + </a> 1831 + <a href="https://www.rottentomatoes.com/tv/fantastic_four" class="unset" 1832 + data-qa="info-name" slot="title"> 1833 + Fantastic Four 1834 + </a> 1835 + </search-page-media-row> 1836 + 1837 + <search-page-media-row skeleton="panel" cast="" data-qa="data-row" endyear="1978" 1838 + releaseyear="" startyear="1978" tomatometeriscertified="false" 1839 + tomatometerscore="" tomatometersentiment=""> 1840 + <a href="https://www.rottentomatoes.com/tv/the_new_fantastic_four" class="unset" 1841 + data-qa="thumbnail-link" slot="thumbnail"> 1842 + <img alt="Fantastic Four" loading="lazy" 1843 + src="https://images.fandango.com/cms/assets/5d84d010-59b1-11ea-b175-791e911be53d--rt-poster-defaultgif.gif"> 1844 + </a> 1845 + <a href="https://www.rottentomatoes.com/tv/the_new_fantastic_four" class="unset" 1846 + data-qa="info-name" slot="title"> 1847 + Fantastic Four 1848 + </a> 1849 + </search-page-media-row> 1850 + 1851 + <search-page-media-row skeleton="panel" cast="" data-qa="data-row" endyear="1977" 1852 + releaseyear="" startyear="1977" tomatometeriscertified="false" 1853 + tomatometerscore="" tomatometersentiment=""> 1854 + <a href="https://www.rottentomatoes.com/tv/the_fantastic_journey" class="unset" 1855 + data-qa="thumbnail-link" slot="thumbnail"> 1856 + <img alt="The Fantastic Journey" loading="lazy" 1857 + src="https://images.fandango.com/cms/assets/5d84d010-59b1-11ea-b175-791e911be53d--rt-poster-defaultgif.gif"> 1858 + </a> 1859 + <a href="https://www.rottentomatoes.com/tv/the_fantastic_journey" class="unset" 1860 + data-qa="info-name" slot="title"> 1861 + The Fantastic Journey 1862 + </a> 1863 + </search-page-media-row> 1864 + 1865 + <search-page-media-row skeleton="panel" cast="" data-qa="data-row" endyear="2010" 1866 + releaseyear="" startyear="2010" tomatometeriscertified="false" 1867 + tomatometerscore="" tomatometersentiment=""> 1868 + <a href="https://www.rottentomatoes.com/tv/fantasia_for_real" class="unset" 1869 + data-qa="thumbnail-link" slot="thumbnail"> 1870 + <img alt="Fantasia for Real" loading="lazy" 1871 + src="https://resizing.flixster.com/xFR7z8b8SwD09sIUUHP9Vb6twQA=/fit-in/80x126/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p7967043_b_v8_aa.jpg"> 1872 + </a> 1873 + <a href="https://www.rottentomatoes.com/tv/fantasia_for_real" class="unset" 1874 + data-qa="info-name" slot="title"> 1875 + Fantasia for Real 1876 + </a> 1877 + </search-page-media-row> 1878 + 1879 + </ul> 1880 + <button slot="more-btn" data-qa="search-more-btn">More TV Shows...</button> 1881 + </search-page-result> 1882 + 1883 + 1884 + <ad-unit hidden unit-display="mobile" unit-type="interscroller" no-retry> 1885 + <aside slot="ad-inject" class="center mobile-interscroller"></aside> 1886 + </ad-unit> 1887 + 1888 + 1889 + <search-page-result skeleton="panel" type="celebrity" data-qa="search-result"> 1890 + <h2 class="unset" slot="title" data-qa="search-result-title"> 1891 + <rt-text context="heading" size="1.25"> Celebrities </rt-text> 1892 + </h2> 1893 + <button slot="prev-btn" data-qa="paging-btn-prev">Prev</button> 1894 + <button slot="next-btn" data-qa="paging-btn-next">Next</button> 1895 + <ul slot="list"> 1896 + 1897 + <search-page-item-row skeleton="panel" data-qa="data-row"> 1898 + <a href="/celebrity/the_fantastic_four" class="unset" data-qa="thumbnail-link" 1899 + slot="thumbnail"> 1900 + <img alt="The Fantastic Four" loading="lazy" 1901 + src="https://images.fandango.com/cms/assets/5d84d010-59b1-11ea-b175-791e911be53d--rt-poster-defaultgif.gif"> 1902 + </a> 1903 + <a href="/celebrity/the_fantastic_four" class="unset" data-qa="info-name" 1904 + slot="title"> 1905 + <span>The Fantastic Four</span> 1906 + </a> 1907 + </search-page-item-row> 1908 + 1909 + <search-page-item-row skeleton="panel" data-qa="data-row"> 1910 + <a href="/celebrity/fantastic_our" class="unset" data-qa="thumbnail-link" 1911 + slot="thumbnail"> 1912 + <img alt="Fantastic รดur" loading="lazy" 1913 + src="https://images.fandango.com/cms/assets/5d84d010-59b1-11ea-b175-791e911be53d--rt-poster-defaultgif.gif"> 1914 + </a> 1915 + <a href="/celebrity/fantastic_our" class="unset" data-qa="info-name" 1916 + slot="title"> 1917 + <span>Fantastic รดur</span> 1918 + </a> 1919 + </search-page-item-row> 1920 + 1921 + </ul> 1922 + <button slot="more-btn" data-qa="search-more-btn">More Celebrities...</button> 1923 + </search-page-result> 1924 + 1925 + </div> 1926 + 1927 + </section> 1928 + 1929 + <section class="search__sidebar layout__column layout__column--sidebar"> 1930 + <div class="adColumn__content"> 1931 + <ad-unit hidden unit-display="desktop" unit-type="topmulti" show-ad-link> 1932 + <div slot="ad-inject"></div> 1933 + </ad-unit> 1934 + </div> 1935 + </section> 1936 + 1937 + </div> 1938 + 1939 + 1940 + </div> 1941 + 1942 + <back-to-top hidden></back-to-top> 1943 + </main> 1944 + 1945 + <ad-unit hidden unit-display="desktop" unit-type="bottombanner"> 1946 + <div slot="ad-inject" class="sleaderboard_wrapper"></div> 1947 + </ad-unit> 1948 + 1949 + <ads-global-skin-takeover-manager></ads-global-skin-takeover-manager> 1950 + 1951 + <footer-manager></footer-manager> 1952 + <footer class="footer container" data-PagePicturesManager="footer"> 1953 + 1954 + <mobile-app-desktop-footer env="production" hidden></mobile-app-desktop-footer> 1955 + 1956 + 1957 + <div class="footer__content-desktop-block" data-qa="footer:section"> 1958 + <div class="footer__content-group"> 1959 + <ul class="footer__links-list"> 1960 + <li class="footer__links-list-item"> 1961 + <a href="/help_desk" data-qa="footer:link-helpdesk">Help</a> 1962 + </li> 1963 + <li class="footer__links-list-item"> 1964 + <a href="/about" data-qa="footer:link-about">About Rotten Tomatoes</a> 1965 + </li> 1966 + <li id="footer-feedback" class="footer__links-list-item" data-qa="footer-feedback-desktop"> 1967 + 1968 + </li> 1969 + </ul> 1970 + </div> 1971 + <div class="footer__content-group"> 1972 + <ul class="footer__links-list"> 1973 + <li class="footer__links-list-item"> 1974 + <a href="/critics/criteria" data-qa="footer:link-critic-submission">Critic Submission</a> 1975 + </li> 1976 + <li class="footer__links-list-item"> 1977 + <a href="/help_desk/licensing" data-qa="footer:link-licensing">Licensing</a> 1978 + </li> 1979 + <li class="footer__links-list-item"> 1980 + <a href="https://together.nbcuni.com/advertise/?utm_source=rotten_tomatoes&amp;utm_medium=referral&amp;utm_campaign=property_ad_pages&amp;utm_content=footer" 1981 + target="_blank" rel="noopener" data-qa="footer:link-ads">Advertise With Us</a> 1982 + </li> 1983 + <li class="footer__links-list-item"> 1984 + <a href="//www.fandango.com/careers" target="_blank" rel="noopener" 1985 + data-qa="footer:link-careers">Careers</a> 1986 + </li> 1987 + </ul> 1988 + </div> 1989 + <div class="footer__content-group footer__newsletter-block"> 1990 + <p class="h3 footer__content-group-title"> 1991 + <rt-icon icon="mail" size="1.25" style="fill:#fff"></rt-icon>&ensp;Join the Newsletter 1992 + </p> 1993 + <p class="footer__newsletter-copy">Get the freshest reviews, news, and more delivered right to your 1994 + inbox!</p> 1995 + <rt-button shape="pill" data-FooterManager="btnNewsLetter:click" 1996 + data-qa="footer-newsletter-desktop"> 1997 + Join The Newsletter 1998 + </rt-button> 1999 + <a data-FooterManager="linkNewsLetter" class="button footer__newsletter-btn hide" target="_blank" 2000 + rel="noopener"> 2001 + Join The Newsletter 2002 + </a> 2003 + </div> 2004 + <div class="footer__content-group footer__social-block" data-qa="footer:social"> 2005 + <p class="h3 footer__content-group-title">Follow Us</p> 2006 + <social-media-icons theme="light" size="20"></social-media-icons> 2007 + </div> 2008 + </div> 2009 + 2010 + <div class="footer__content-mobile-block" data-qa="mfooter:section"> 2011 + <div class="footer__content-group"> 2012 + <div class="mobile-app-cta-wrap"> 2013 + 2014 + <mobile-app-cta env="production" showandroid="false" showios="true" hidden></mobile-app-cta> 2015 + </div> 2016 + 2017 + <p class="footer__copyright-legal" data-qa="mfooter:copyright"> 2018 + <rt-text size="0.75">Copyright &copy; Fandango. All rights reserved.</rt-text> 2019 + </p> 2020 + <p> 2021 + <rt-button shape="pill" data-FooterManager="btnNewsLetter:click" 2022 + data-qa="footer-newsletter-mobile">Join The Newsletter</rt-button> 2023 + </p> 2024 + <a data-FooterManager="linkNewsLetter" class="button footer__newsletter-btn hide" target="_blank" 2025 + rel="noopener">Join The Newsletter</a> 2026 + 2027 + <ul class="footer__links-list list-inline"> 2028 + <li class="footer__links-list-item"> 2029 + <a href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" target="_blank" 2030 + rel="noopener" data-qa="mfooter:link-privacy-policy"> 2031 + Privacy Policy 2032 + </a> 2033 + </li> 2034 + <li class="footer__links-list-item"> 2035 + <a href="/policies/terms-and-policies" data-qa="mfooter:link-terms-policies">Terms and 2036 + Policies</a> 2037 + </li> 2038 + <li class="footer__links-list-item"> 2039 + <img data-FooterManager="iconCCPA" 2040 + src="https://images.fandango.com/cms/assets/266533e0-7afb-11ed-83f2-4f600722b564--privacyoptions.svg" 2041 + class="footer__ccpa-icon" loading="lazy" alt="CCPA icon" /> 2042 + <!-- OneTrust Cookies Settings button start --> 2043 + <a href="javascript:void(0)" id="ot-sdk-btn" class="ot-sdk-show-settings mobile" 2044 + data-qa="footer-cookie-settings-mobile">Cookie Settings</a> 2045 + <!-- OneTrust Cookies Settings button end --> 2046 + </li> 2047 + <li class="footer__links-list-item"> 2048 + <a href="https://www.nbcuniversalprivacy.com/privacy/california-consumer-privacy-act" 2049 + target="_blank" rel="noopener" data-qa="mfooter:link-california-notice">California 2050 + Notice</a> 2051 + </li> 2052 + <li class="footer__links-list-item"> 2053 + <a href="https://www.nbcuniversalprivacy.com/privacy/cookies#accordionheader2" 2054 + target="_blank" rel="noopener" data-qa="mfooter:link-adChoices">Ad Choices</a> 2055 + </li> 2056 + <li id="footer-feedback-mobile" class="footer__links-list-item" 2057 + data-qa="footer-feedback-mobile"> 2058 + 2059 + </li> 2060 + <li class="footer__links-list-item"> 2061 + <a href="/faq#accessibility" data-qa="mfooter:link-accessibility">Accessibility</a> 2062 + </li> 2063 + </ul> 2064 + </div> 2065 + </div> 2066 + <div class="footer__copyright"> 2067 + <ul class="footer__links-list list-inline list-inline--separator" data-qa="footer:links-list-privacy"> 2068 + <li class="footer__links-list-item version" data-qa="footer:version"> 2069 + <span>V3.1</span> 2070 + </li> 2071 + <li class="footer__links-list-item"> 2072 + <a href="https://www.nbcuniversalprivacy.com/fandango-privacy-policy" target="_blank" 2073 + rel="noopener" data-qa="footer:link-privacy-policy"> 2074 + Privacy Policy 2075 + </a> 2076 + </li> 2077 + <li class="footer__links-list-item"> 2078 + <a href="/policies/terms-and-policies" data-qa="footer:link-terms-policies">Terms and 2079 + Policies</a> 2080 + </li> 2081 + <li class="footer__links-list-item"> 2082 + <img data-FooterManager="iconCCPA" 2083 + src="https://images.fandango.com/cms/assets/266533e0-7afb-11ed-83f2-4f600722b564--privacyoptions.svg" 2084 + class="footer__ccpa-icon" loading="lazy" alt="CCPA icon" /> 2085 + <!-- OneTrust Cookies Settings button start --> 2086 + <a href="javascript:void(0)" id="ot-sdk-btn" class="ot-sdk-show-settings" 2087 + data-qa="footer-cookie-settings-desktop">Cookie Settings</a> 2088 + <!-- OneTrust Cookies Settings button end --> 2089 + </li> 2090 + <li class="footer__links-list-item"> 2091 + <a href="https://www.nbcuniversalprivacy.com/privacy/california-consumer-privacy-act" 2092 + target="_blank" rel="noopener" data-qa="footer:link-california-notice">California Notice</a> 2093 + </li> 2094 + <li class="footer__links-list-item"> 2095 + <a href="https://www.nbcuniversalprivacy.com/privacy/cookies#accordionheader2" target="_blank" 2096 + rel="noopener" data-qa="footer:link-adChoices">Ad Choices</a> 2097 + </li> 2098 + <li class="footer__links-list-item"> 2099 + <a href="/faq#accessibility" data-qa="footer:link-accessibility">Accessibility</a> 2100 + </li> 2101 + </ul> 2102 + <span class="footer__copyright-legal" data-qa="footer:copyright"> 2103 + Copyright &copy; Fandango. A Division of 2104 + <a href="https://www.nbcuniversal.com" target="_blank" rel="noopener" 2105 + data-qa="footer:link-nbcuniversal">NBCUniversal</a>. 2106 + All rights reserved. 2107 + </span> 2108 + </div> 2109 + </footer> 2110 + 2111 + </div> 2112 + 2113 + 2114 + <iframe-container hidden data-ArtiManager="iframeContainer:close,resize" 2115 + data-iframe-src="https://arti.rottentomatoes.com?theme=iframe" theme="widget"> 2116 + <span slot="logo"><img src="/assets/pizza-pie/images/arti.041d204c4a4.svg" 2117 + alt="Logo"></img><span>beta</span></span> 2118 + <rt-button aria-label="New chat" data-ArtiManager="btnNewChat:click" id="artiNewChatButton" slot="optBtns" 2119 + theme="transparent" title="New chat"> 2120 + <rt-icon icon="new-chat" size="1.25" image></rt-icon> 2121 + </rt-button> 2122 + </iframe-container> 2123 + <arti-manager></arti-manager> 2124 + 2125 + 2126 + 2127 + 2128 + <script type="text/javascript"> 2129 + (function (root) { 2130 + /* -- Data -- */ 2131 + root.RottenTomatoes || (root.RottenTomatoes = {}); 2132 + root.RottenTomatoes.context || (root.RottenTomatoes.context = {}); 2133 + root.RottenTomatoes.context.resetCookies = ["AMCVS_8CF467C25245AE3F0A490D4C%40AdobeOrg", "AMCV_8CF467C25245AE3F0A490D4C%40AdobeOrg", "WRIgnore", "WRUIDAWS", "__CT_Data", "__gads", "_admrla", "_awl", "_cs_c", "_cs_id", "_cs_mk", "_cs_s", "_fbp", "_ga", "_gat_gtmTracker", "_gid", "aam_uuid", "akamai_generated_location", "auth_token", "auth_user", "auth_client", "check", "cognito", "fblo_326803741017", "fbm_326803741017", "fbsr_326803741017", "gpv_Page", "id_token", "is_auth", "loginPlatform", "mbox", "notice_behavior", "optimizelyBuckets", "optimizelyEndUserId", "optimizelyPendingLogEvents", "optimizelySegments", "s_cc", "s_dayslastvisit", "s_dayslastvisit_s", "s_invisit", "s_prevPage", "s_sq", "s_vnum", "cognito", "fbm_326803741017", "fbsr_326803741017", "id_token", "JSESSIONID", "QSI_HistorySession", "QSI_SI_8up4dWDOtjAg0hn_intercept", "_ALGOLIA", "__Host-color-scheme", "__Host-theme-options", "__host_color_scheme", "__host_theme_options", "_cb", "_cb_ls", "_cb_svref", "_chartbeat2", "_chartbeat4", "_chartbeat5", "_sp_id.47f3", "_sp_ses.47f3", "_v__chartbeat3", "adops_master_kvs", "akacd_RTReplatform", "algoliaUT", "cognito", "cl_duid", "fbsr_326803741017", "id_token", "mps_uuid", "session_id", "_admrla", "_awl", "_ga", "_gid", "aam_uuid", "cognito", "fbm_326803741017", "id_token", "_cb", "_cb_ls", "_cb_svref", "_chartbeat2", "adops_master_kvs", "cognito", "id_token", "krg_crb", "krg_uid", "mps_uuid"]; 2134 + root.Fandango || (root.Fandango = {}); 2135 + root.Fandango.dtmData = { "webVersion": "node", "rtVersion": 3.1, "loggedInStatus": "", "customerId": "", "pageName": "trailers" }; 2136 + root.RottenTomatoes.context.video = { "file": "https:\u002F\u002Flink.theplatform.com\u002Fs\u002FNGweTC\u002Fmedia\u002F7IZc_13FDObm?formats=MPEG-DASH+widevine,M3U+appleHlsEncryption,M3U+none,MPEG-DASH+none,MPEG4,MP3", "type": "hls", "description": "Noah (Ryan Guzman) seduces high school teacher Claire (Jennifer Lopez), and the two spend a passionate night together under the covers.", "image": "https:\u002F\u002Fstatcdn.fandango.com\u002FMPX\u002Fimage\u002FNBCU_Fandango\u002F943\u002F727\u002F58042.png", "isRedBand": false, "mediaid": "625906243972", "mpxId": "625906243972", "publicId": "7IZc_13FDObm", "title": "The Boy Next Door: Official Clip - Let Me Love You", "default": false, "label": "0", "duration": "2:56", "durationInSeconds": "176.844", "emsMediaType": "Movie", "emsId": "c46b422b-8ee4-3182-b46d-09da9b195917", "overviewPageUrl": "\u002Fm\u002Fthe_boy_next_door_2015", "videoPageUrl": "\u002Fm\u002Fthe_boy_next_door_2015\u002Fvideos\u002F7IZc_13FDObm", "videoType": "CLIP", "adobeDataLayer": { "content": { "id": "fandango_625906243972", "length": "176.844", "type": "vod", "player_name": "jw", "sdk_version": "web: 6.51.0", "channel": "movie", "originator": "universal pictures", "name": "the boy next door: official clip - let me love you", "rating": "not adult", "stream_type": "video" }, "media_params": { "genre": "mystery & thriller", "show_type": 2 } }, "comscore": { "labelmapping": "c3=\"rottentomatoes.com\", ns_st_st=\"Rotten Tomatoes\", ns_st_pu=\"Universal Pictures\", ns_st_pr=\"The Boy Next Door\", ns_st_sn=\"*null\", ns_st_en=\"*null\", ns_st_ge=\"Mystery & Thriller\", ns_st_ia=\"0\", ns_st_ce=\"0\", ns_st_ddt=\"2015\", ns_st_tdt=\"2015\"" }, "thumbnail": "https:\u002F\u002Fresizing.flixster.com\u002Fk4o6Idbg7006vQ2FmLxnQrCxeTA=\u002F270x160\u002Fv2\u002Fhttps:\u002F\u002Fstatcdn.fandango.com\u002FMPX\u002Fimage\u002FNBCU_Fandango\u002F943\u002F727\u002F58042.png" }; 2137 + root.RottenTomatoes.context.videoClipsJson = { "count": 11 }; 2138 + root.RottenTomatoes.criticPage = { "vanity": "dave-walker", "type": "movies", "typeDisplayName": "Movie", "totalReviews": "", "criticID": "15606" }; 2139 + root.RottenTomatoes.context.req = { "params": { "vanity": "many_adventures_of_winnie_the_pooh", "reviewType": undefined }, "query": {}, "route": {}, "url": "\u002Fm\u002Fmany_adventures_of_winnie_the_pooh\u002Freviews", "secure": false, "buildVersion": undefined }; 2140 + root.RottenTomatoes.context.config = {}; 2141 + root.RottenTomatoes.context.review = { "mediaType": "movie", "title": "The Many Adventures of Winnie the Pooh", "emsId": "3ca166f3-fb40-3899-8faa-b11f954924d7", "type": "all", "sort": undefined, "reviewsCount": 0, "pageInfo": undefined, "reviewerDefaultImg": "https:\u002F\u002Fimages.fandango.com\u002Fcms\u002Fassets\u002F5b6ff500-1663-11ec-ae31-05a670d2d590--rtactordefault.png", "reviewerDefaultImgWidth": "100" }; 2142 + root.RottenTomatoes.context.useCursorPagination = true; 2143 + root.RottenTomatoes.context.verifiedTooltip = undefined; 2144 + root.RottenTomatoes.context.layout = { "header": { "movies": { "moviesAtHome": { "tarsSlug": "rt-nav-movies-at-home", "linkList": [{ "header": "Fandango at Home", "slug": "fandango-at-home-link", "url": "\u002Fbrowse\u002Fmovies_at_home\u002Faffiliates:fandango-at-home" }, { "header": "Peacock", "slug": "peacock-link", "url": "\u002Fbrowse\u002Fmovies_at_home\u002Faffiliates:peacock" }, { "header": "Netflix", "slug": "netflix-link", "url": "\u002Fbrowse\u002Fmovies_at_home\u002Faffiliates:netflix" }, { "header": "Apple TV+", "slug": "apple-tv-link", "url": "\u002Fbrowse\u002Fmovies_at_home\u002Faffiliates:apple-tv-plus" }, { "header": "Prime Video", "slug": "prime-video-link", "url": "\u002Fbrowse\u002Fmovies_at_home\u002Faffiliates:prime-video" }, { "header": "Most Popular Streaming movies", "slug": "most-popular-streaming-movies-link", "url": "\u002Fbrowse\u002Fmovies_at_home\u002Fsort:popular" }, { "header": "Certified Fresh movies", "slug": "certified-fresh-movies-link", "url": "\u002Fbrowse\u002Fmovies_at_home\u002Fcritics:certified_fresh" }, { "header": "Browse all", "slug": "browse-all-link", "url": "\u002Fbrowse\u002Fmovies_at_home" }] } }, "editorial": { "guides": { "posts": [{ "ID": 161109, "author": 12, "featured_image": { "source": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fwp-content\u002Fuploads\u002F2023\u002F09\u002F600EssentialFootballMovies.png" }, "link": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fguide\u002Fbest-football-movies\u002F", "status": "publish", "title": "59 Best Football Movies, Ranked by Tomatometer", "type": "guide" }, { "ID": 253470, "author": 12, "featured_image": { "source": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fwp-content\u002Fuploads\u002F2025\u002F08\u002FBest_New_Romcoms600.jpg" }, "link": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fguide\u002Fbest-new-rom-coms-romance-movies\u002F", "status": "publish", "title": "50 Best New Rom-Coms and Romance Movies", "type": "guide" }], "title": "Guides", "url": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fcountdown\u002F" }, "hubs": { "posts": [{ "ID": 237626, "author": 12, "featured_image": { "source": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fwp-content\u002Fuploads\u002F2023\u002F05\u002FRT_WTW_Generic_2023_Thumbnail_600x314_021623.jpg" }, "link": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Frt-hub\u002Fwhat-to-watch\u002F", "status": "publish", "title": "What to Watch: In Theaters and On Streaming", "type": "rt-hub" }, { "ID": 140214, "author": 12, "featured_image": { "source": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fwp-content\u002Fuploads\u002F2023\u002F02\u002FRT_AwardsTour_Thumbnail_600x314.jpg" }, "link": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Frt-hub\u002Fawards-tour\u002F", "status": "publish", "title": "Awards Tour", "type": "rt-hub" }], "title": "Hubs", "url": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Frt-hubs\u002F" }, "news": { "posts": [{ "ID": 273082, "author": 79, "featured_image": { "source": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fwp-content\u002Fuploads\u002F2025\u002F08\u002FNew_Streaming_September_2025-Rep.jpg" }, "link": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Farticle\u002Fnew-movies-and-shows-streaming-in-september-2025-what-to-watch-on-netflix-prime-video-hbo-max-disney-and-more\u002F", "status": "publish", "title": "New Movies and Shows Streaming in September: What to watch on Netflix, Prime Video, HBO Max, Disney+ and More", "type": "article" }, { "ID": 273326, "author": 669, "featured_image": { "source": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fwp-content\u002Fuploads\u002F2025\u002F09\u002FConjuring_Last_Rites_Reviews-Rep.jpg" }, "link": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Farticle\u002Fthe-conjuring-last-rites-first-reviews\u002F", "status": "publish", "title": "\u003Cem\u003EThe Conjuring: Last Rites\u003C\u002Fem\u003E First Reviews: A Frightful, Fitting Send-off", "type": "article" }], "title": "RT News", "url": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fnews\u002F" } }, "trendingTarsSlug": "rt-nav-trending", "trending": [{ "header": "Emmy Noms", "url": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Farticle\u002F2025-emmys-ballot-complete-with-tomatometer-and-popcornmeter-scores\u002F" }, { "header": "Re-Release Calendar", "url": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Farticle\u002Fmovie-re-releases-calendar\u002F" }, { "header": "Renewed and Cancelled TV", "url": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Farticle\u002Frenewed-and-cancelled-tv-shows-2025\u002F" }, { "header": "The Rotten Tomatoes App ", "url": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Farticle\u002Fapp\u002F" }], "certifiedMedia": { "certifiedFreshTvSeason": { "header": null, "media": { "url": "\u002Ftv\u002Fthe_paper_2025\u002Fs01", "name": "The Paper: Season 1", "score": 83, "posterImg": "https:\u002F\u002Fresizing.flixster.com\u002FyFijQcjPYUWUelgmiZLHgkXU7hw=\u002F206x305\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002FDFkkHf5pEVX_apKtIQZcoEvI6RU=\u002Ffit-in\u002F180x240\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002FtexEZJLAG-KcVpfCdkT2R1t4cmE=\u002Fems.cHJkLWVtcy1hc3NldHMvdHZzZWFzb24vYTM3OWM2MTctN2M3Ny00MjdhLTk4NDUtODE5ZWUwMWExNGRhLnBuZw==" }, "tarsSlug": "rt-nav-list-cf-picks" }, "certifiedFreshMovieInTheater": { "header": null, "media": { "url": "\u002Fm\u002Ftwinless", "name": "Twinless", "score": 98, "posterImg": "https:\u002F\u002Fresizing.flixster.com\u002Fj7lw2KeY9_XyfZQdqRZGku7_9C8=\u002F206x305\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002FuxoeWz7uWmeYIV94_SzEV_osqe4=\u002Ffit-in\u002F180x240\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002FVlylB3xT2RIYmRivMx37O3yD76Q=\u002Fems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2ZlNDQ1MGQ5LTFjN2QtNDIwNC04NWE1LTM5NGM4N2U5ZTgzYy5qcGc=" } }, "certifiedFreshMovieInTheater4": { "header": null, "media": { "url": "\u002Fm\u002Fhamilton_2020", "name": "Hamilton", "score": 98, "posterImg": "https:\u002F\u002Fresizing.flixster.com\u002F1woquJmQfEhWCZtm7GcH0NMHsYA=\u002F206x305\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002FPeAJ5ZpF5qB98ZiX6ixNDCgW2P0=\u002Ffit-in\u002F180x240\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002FVmBvlTk8-z7pQvDZXTgSdj93WDE=\u002Fems.cHJkLWVtcy1hc3NldHMvbW92aWVzLzkzY2IxZjFkLTE1NjEtNDQ4Yi05NDY3LTcxNzFmMDVhMDczNi5qcGc=" } }, "certifiedFreshMovieAtHome": { "header": null, "media": { "url": "\u002Fm\u002Fthe_thursday_murder_club", "name": "The Thursday Murder Club", "score": 76, "posterImg": "https:\u002F\u002Fresizing.flixster.com\u002FjeeldFGcfSMgG09ey5VB7TCFiek=\u002F206x305\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002F9LXDkCzIBBNEiPURkB9t6VefF5Q=\u002Ffit-in\u002F180x240\u002Fv2\u002Fhttps:\u002F\u002Fresizing.flixster.com\u002FrwdeR5xIiN0k7SWr6yXdnmb6zP8=\u002Fems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2EzYWFkZWJiLWE5N2MtNDc3MS1iMDRlLTk0YWVlYzI5M2UxZS5qcGc=" } }, "tarsSlug": "rt-nav-list-cf-picks" }, "tvLists": { "newTvTonight": { "tarsSlug": "rt-hp-text-list-3", "title": "New TV Tonight", "shows": [{ "title": "Task: Season 1", "tomatometer": { "tomatometer": 89, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Ftask\u002Fs01" }, { "title": "The Walking Dead: Daryl Dixon: Season 3", "tomatometer": { "tomatometer": 80, "sentiment": "positive", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fthe_walking_dead_daryl_dixon\u002Fs03" }, { "title": "The Crow Girl: Season 1", "tomatometer": { "tomatometer": 80, "sentiment": "positive", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fthe_crow_girl\u002Fs01" }, { "title": "Only Murders in the Building: Season 5", "tomatometer": { "tomatometer": null, "sentiment": "empty", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fonly_murders_in_the_building\u002Fs05" }, { "title": "The Girlfriend: Season 1", "tomatometer": { "tomatometer": null, "sentiment": "empty", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fthe_girlfriend\u002Fs01" }, { "title": "aka Charlie Sheen: Season 1", "tomatometer": { "tomatometer": null, "sentiment": "empty", "certified": false }, "tvPageUrl": "\u002Ftv\u002Faka_charlie_sheen\u002Fs01" }, { "title": "Wizards Beyond Waverly Place: Season 2", "tomatometer": { "tomatometer": null, "sentiment": "empty", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fwizards_beyond_waverly_place\u002Fs02" }, { "title": "Seen & Heard: the History of Black Television: Season 1", "tomatometer": { "tomatometer": null, "sentiment": "empty", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fseen_and_heard_the_history_of_black_television\u002Fs01" }, { "title": "The Fragrant Flower Blooms With Dignity: Season 1", "tomatometer": { "tomatometer": null, "sentiment": "empty", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fthe_fragrant_flower_blooms_with_dignity\u002Fs01" }, { "title": "Guts & Glory: Season 1", "tomatometer": { "tomatometer": null, "sentiment": "empty", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fguts_and_glory\u002Fs01" }] }, "mostPopularTvOnRt": { "tarsSlug": "rt-hp-text-list-2", "title": "Most Popular TV on RT", "shows": [{ "title": "The Paper: Season 1", "tomatometer": { "tomatometer": 83, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Fthe_paper_2025\u002Fs01" }, { "title": "Dexter: Resurrection: Season 1", "tomatometer": { "tomatometer": 95, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Fdexter_resurrection\u002Fs01" }, { "title": "Alien: Earth: Season 1", "tomatometer": { "tomatometer": 95, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Falien_earth\u002Fs01" }, { "title": "Task: Season 1", "tomatometer": { "tomatometer": 89, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Ftask\u002Fs01" }, { "title": "Wednesday: Season 2", "tomatometer": { "tomatometer": 87, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Fwednesday\u002Fs02" }, { "title": "Peacemaker: Season 2", "tomatometer": { "tomatometer": 99, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Fpeacemaker_2022\u002Fs02" }, { "title": "The Terminal List: Dark Wolf: Season 1", "tomatometer": { "tomatometer": 73, "sentiment": "positive", "certified": false }, "tvPageUrl": "\u002Ftv\u002Fthe_terminal_list_dark_wolf\u002Fs01" }, { "title": "Hostage: Season 1", "tomatometer": { "tomatometer": 82, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Fhostage_2025\u002Fs01" }, { "title": "Chief of War: Season 1", "tomatometer": { "tomatometer": 93, "sentiment": "positive", "certified": true }, "tvPageUrl": "\u002Ftv\u002Fchief_of_war\u002Fs01" }, { "title": "Irish Blood: Season 1", "tomatometer": { "tomatometer": 100, "sentiment": "positive", "certified": false }, "tvPageUrl": "\u002Ftv\u002Firish_blood\u002Fs01" }] } } }, "links": { "moviesInTheaters": { "certifiedFresh": "\u002Fbrowse\u002Fmovies_in_theaters\u002Fcritics:certified_fresh~sort:popular", "comingSoon": "\u002Fbrowse\u002Fmovies_coming_soon\u002F", "openingThisWeek": "\u002Fbrowse\u002Fmovies_in_theaters\u002Fsort:newest", "title": "\u002Fbrowse\u002Fmovies_in_theaters", "topBoxOffice": "\u002Fbrowse\u002Fmovies_in_theaters" }, "onDvdAndStreaming": { "all": "\u002Fbrowse\u002Fmovies_at_home\u002F", "certifiedFresh": "\u002Fbrowse\u002Fmovies_at_home\u002Fcritics:certified_fresh", "title": "\u002Fbrowse\u002Fmovies_at_home\u002F", "top": "\u002Fbrowse\u002Fmovies_at_home\u002Fsort:popular" }, "moreMovies": { "topMovies": "\u002Fbrowse\u002Fmovies_at_home\u002Fsort:popular", "trailers": "\u002Ftrailers" }, "tvTonight": "\u002Fbrowse\u002Ftv_series_browse\u002Fsort:newest", "tvPopular": "\u002Fbrowse\u002Ftv_series_browse\u002Fsort:popular", "moreTv": { "topTv": "\u002Fbrowse\u002Ftv_series_browse\u002Fsort:popular", "certifiedFresh": "\u002Fbrowse\u002Ftv_series_browse\u002Fcritics:fresh" }, "editorial": { "allTimeLists": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fall-time-lists\u002F", "bingeGuide": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fbinge-guide\u002F", "comicsOnTv": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fcomics-on-tv\u002F", "countdown": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fcountdown\u002F", "fiveFavoriteFilms": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Ffive-favorite-films\u002F", "videoInterviews": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fvideo-interviews\u002F", "weekendBoxOffice": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fweekend-box-office\u002F", "weeklyKetchup": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fweekly-ketchup\u002F", "whatToWatch": "https:\u002F\u002Feditorial.rottentomatoes.com\u002Fwhat-to-watch\u002F" }, "advertisingFooter": "https:\u002F\u002Ftogether.nbcuni.com\u002Fadvertise\u002F?utm_source=rotten_tomatoes&utm_medium=referral&utm_campaign=property_ad_pages&utm_content=footer", "californiaNotice": "https:\u002F\u002Fwww.nbcuniversalprivacy.com\u002Fprivacy\u002Fcalifornia-consumer-privacy-act", "careers": "\u002F\u002Fwww.fandango.com\u002Fcareers", "cookieManagement": "https:\u002F\u002Fwww.nbcuniversalprivacy.com\u002Fprivacy\u002Fcookies#accordionheader2", "fandangoAbout": "https:\u002F\u002Fwww.fandango.com\u002Fabout-us", "privacyPolicy": "https:\u002F\u002Fwww.nbcuniversalprivacy.com\u002Ffandango-privacy-policy", "termsPolicies": "\u002Fpolicies\u002Fterms-and-policies" } }; 2145 + root.BK = { "PageName": "http:\u002F\u002Fwww.rottentomatoes.com\u002Fm\u002Ffriendly-fire2006", "SiteID": 37528, "SiteSection": "" }; 2146 + root.RottenTomatoes.thirdParty = { "chartBeat": { "auth": "64558", "domain": "rottentomatoes.com" }, "mpx": { "accountPid": "NGweTC", "playerPid": "y__7B0iQTi4P", "playerPidPDK6": "pdk6_y__7B0iQTi4P", "accountId": "2474312077" }, "algoliaSearch": { "aId": "79FRDP12PN", "sId": "175588f6e5f8319b27702e4cc4013561" }, "cognito": { "upId": "us-west-2_4L0ZX4b1U", "clientId": "7pu48v8i2n25t4vhes0edck31c" } }; 2147 + root.RottenTomatoes.serviceWorker = { "isServiceWokerOn": true }; 2148 + root.__RT__ || (root.__RT__ = {}); 2149 + root.__RT__.featureFlags = { "adsCarouselHP": false, "adsCarouselHPSlug": "rt-sponsored-carousel-list-mcdonalds-hp", "adsCarouselOP": false, "adsCarouselOPSlug": "rt-sponsored-carousel-list-mcdonalds-op", "adsMockDLP": false, "adsPages": "none", "adsSponsoredOverrideOP": true, "adsSponsoredOverrideOPSlugs": "rt-sponsored-override-op-starz", "adsVideoSpotlightHP": false, "appleSigninEnabled": true, "artiEnabled": true, "authPasswordEnabled": true, "authVerboseLogs": false, "bypassCriticValidationEnabled": false, "castAndCrewEnabled": true, "cookieConsentServiceEnabled": false, "crssoEnabled": false, "editorialApiDisabled": false, "faqUpdatesEnabled": true, "legacyBridge": true, "logVerboseEnabled": false, "mobileAppAndroid": "https:\u002F\u002Fplay.google.com\u002Fstore\u002Fapps\u002Fdetails?id=com.rottentomatoes.android", "mobileAppIos": "https:\u002F\u002Fapps.apple.com\u002Fus\u002Fapp\u002Frotten-tomatoes-movies-tv\u002Fid6673916573", "mobileAppIosMeta": "app-id=6673916573, app-argument=https:\u002F\u002Fwww.rottentomatoes.com\u002F", "mobileNavEnabled": true, "oneTrustJwtApiUrl": "https:\u002F\u002Fonetrustjwt.services.fandango.com", "oneTrustJwtServiceEnabled": false, "pageJsonEnabled": false, "profilesFeaturesEnabled": false, "profilesUsernameEnabled": false, "redesignMediaHeroEnabled": true, "redesignMoreLikeThis": true, "redesignSortTable": true, "trafficAndroidEnabled": false, "trafficSafariEnabled": true, "userMigrationEnabled": true, "versantFreewheelEnabled": false, "versantMpsDomain": "app.mps.vsnt.net", "versantMpsEnabled": false, "versantOneTrustScriptBlock": "\u003C!-- OneTrust Cookies Consent Notice start for rottentomatoes.com --\u003E \u003Cscript src=\"https:\u002F\u002Fcdn.cookielaw.org\u002Fconsent\u002F01978557-1604-76a7-ad7c-18216757cf52-test\u002FotSDKStub.js\" type=\"text\u002Fjavascript\" charset=\"UTF-8\" data-domain-script=\"01978557-1604-76a7-ad7c-18216757cf52-test\" integrity=\"sha384-Exfxdyaw5OnsUlHEKlNlz7OwgVCyLlitAtJsDmSNh3LeLlCjWXos3X\u002FCMNUbQ\u002FgA\" crossorigin=\"anonymous\" \u003E\u003C\u002Fscript\u003E \u003Cscript type=\"text\u002Fjavascript\"\u003E function OptanonWrapper() { if (OnetrustActiveGroups.includes('7')) { document.querySelector('search-results-nav-manager')?.setAlgoliaInsightUserToken?.(); } } \u003C\u002Fscript\u003E \u003C!-- OneTrust Cookies Consent Notice end for rottentomatoes.com --\u003E \u003C!-- OneTrust IAB US Privacy (USP) --\u003E \u003Cscript src=\"https:\u002F\u002Fcdn.cookielaw.org\u002Fopt-out\u002FotCCPAiab.js\" id=\"privacyCookie\" type=\"text\u002Fjavascript\" charset=\"UTF-8\" ccpa-opt-out-ids=\"USP\" ccpa-opt-out-geo=\"US\" ccpa-opt-out-lspa=\"false\"\u003E\u003C\u002Fscript\u003E \u003C!-- OneTrust IAB US Privacy (USP) end --\u003E", "videoGeoFencingEnabled": true }; 2150 + root.RottenTomatoes.dtmData = { "webVersion": "node", "rtVersion": 3.1, "loggedInStatus": "", "customerId": "", "pageName": "rt | movies | overview", "titleType": "Movie" }; 2151 + root.RottenTomatoes.context.adsMockDLP = false; 2152 + root.RottenTomatoes.context.gptSite = "search"; 2153 + 2154 + }(this)); 2155 + </script> 2156 + 2157 + <script fetchpriority="high" src="/assets/pizza-pie/javascripts/bundles/roma/preload.18bcfff8e54.js"></script> 2158 + 2159 + <script src="/assets/pizza-pie/javascripts/bundles/roma/vendors.a4cc402b78a.js"></script> 2160 + 2161 + <script src="/assets/pizza-pie/javascripts/bundles/roma/default.24dc1977289.js"></script> 2162 + 2163 + 2164 + <script async data-SearchResultsNavManager="script:load" 2165 + src="https://cdn.jsdelivr.net/npm/algoliasearch@4/dist/algoliasearch-lite.umd.js"> 2166 + </script> 2167 + <script src="/assets/pizza-pie/javascripts/templates/roma/searchNav.a3288ea5efe.js"></script> 2168 + <script src="/assets/pizza-pie/javascripts/bundles/roma/searchNav.6a836b4ca81.js"></script> 2169 + 2170 + 2171 + 2172 + <script src="/assets/pizza-pie/javascripts/bundles/search.3cbb7fc9cd1.js"></script> 2173 + 2174 + 2175 + 2176 + 2177 + <script> 2178 + if (window.mps && typeof window.mps.writeFooter === 'function') { 2179 + window.mps.writeFooter(); 2180 + } 2181 + </script> 2182 + 2183 + 2184 + 2185 + 2186 + 2187 + <script> 2188 + window._satellite && _satellite.pageBottom(); 2189 + </script> 2190 + 2191 + 2192 + </body> 2193 + 2194 + </html>
+136
media.md
··· 1 + # MEDIA management feature 2 + 3 + ## Current State Analysis 4 + 5 + - Existing Media Service (`/internal/services/media.go`): 6 + - Rotten Tomatoes scraping with colly for movies/TV search 7 + - Rich metadata extraction (Movie, TVSeries, TVSeason structs) 8 + - Search functionality via SearchRottenTomatoes() 9 + - Detailed metadata fetching via FetchMovie(), FetchTVSeries(), FetchTVSeason() 10 + 11 + - Book Search Pattern (`/internal/handlers/books.go`): 12 + - Uses APIService interface with `Search()`, `Get()`, `Check()`, `Close()` methods 13 + - Interactive and static search modes 14 + - Number-based selection UX 15 + - Converts API results to models.Book via interface 16 + 17 + - Models (`/internal/models/models.go`): 18 + - Movie and TVShow structs already implement Model interface 19 + - Both have proper status tracking (queued, watched, etc.) 20 + 21 + ## Media Service Refactor 22 + 23 + ### Create MovieService that implement APIService 24 + 25 + ```go 26 + // MovieService implements APIService for Rotten Tomatoes movies 27 + type MovieService struct { 28 + client *http.Client 29 + limiter *rate.Limiter 30 + } 31 + ``` 32 + 33 + ### Create TVService that implement APIService 34 + 35 + ```go 36 + // TVService implements APIService for Rotten Tomatoes TV shows 37 + type TVService struct { 38 + client *http.Client 39 + limiter *rate.Limiter 40 + } 41 + ``` 42 + 43 + ### Implement APIService 44 + 45 + - `Search(ctx, query, page, limit)` - Use existing SearchRottenTomatoes() and convert results to []*models.Model 46 + - `Get(ctx, id)` - Use existing FetchMovie() / FetchTVSeries() with Rotten Tomatoes URLs 47 + - `Check(ctx)` - Simple connectivity test to Rotten Tomatoes 48 + - `Close()` - Cleanup resources 49 + 50 + ### Result Conversion 51 + 52 + - Convert services.Media search results to models.Movie / models.TVShow 53 + - Convert detailed metadata structs to models with proper status defaults 54 + - Extract key information (title, year, rating, description) into notes field 55 + 56 + ## Handler Implementation 57 + 58 + ### Create MovieHandler similar to BookHandler 59 + 60 + ```go 61 + type MovieHandler struct { 62 + db *store.Database 63 + config*store.Config 64 + repos *repo.Repositories 65 + service*services.MovieService 66 + } 67 + ``` 68 + 69 + ### Implement search 70 + 71 + - `SearchAndAddMovie(ctx, args, interactive)` - Mirror book search UX 72 + - `SearchAndAddTV(ctx, args, interactive)` - Same pattern for TV shows 73 + - Number-based selection interface identical to books 74 + 75 + ### Database Integration 76 + 77 + - Add movie/TV repositories if not already present 78 + - Ensure proper CRUD operations for queue management 79 + 80 + ## Commands 81 + 82 + ### Update definitions 83 + 84 + - Replace stubbed movie commands with real implementations 85 + - Replace stubbed TV commands with real implementations 86 + - Connect to new handlers with proper error handling 87 + 88 + ### Structure 89 + 90 + ```sh 91 + # Movies 92 + 93 + media movie add [search query...] [-i for interactive] 94 + media movie list [--all|--watched|--queued] 95 + media movie watched <id> 96 + media movie remove <id> 97 + 98 + # TV Shows 99 + 100 + media tv add [search query...] [-i for interactive] 101 + media tv list [--all|--watched|--queued] 102 + media tv watched <id> 103 + media tv remove <id> 104 + ``` 105 + 106 + ## UX Consistency 107 + 108 + ### Search 109 + 110 + 1. Parse search query from args 111 + 2. Show "Loading..." progress indicator 112 + 3. Display numbered results with title, year, rating 113 + 4. Prompt for selection (1-N or 0 to cancel) 114 + 5. Add selected item to queue with "queued" status 115 + 6. Confirm addition to user 116 + 117 + ### Interactivity 118 + 119 + - Use existing TUI patterns from book/task lists 120 + - Browse search results with keyboard navigation 121 + - Preview detailed metadata before adding 122 + 123 + ## Key Implementation Details 124 + 125 + _Rate Limiting_: Add rate limiter to media services (Rotten Tomatoes likely has limits) 126 + 127 + _Error Handling_: Robust handling of scraping failures, network issues, parsing errors 128 + 129 + _Data Mapping_: 130 + - Map Rotten Tomatoes critic scores to model rating fields 131 + - Extract genres, cast, descriptions into notes field 132 + - Handle missing or incomplete metadata gracefully 133 + 134 + _Caching_: Consider caching search results to reduce API calls during selection 135 + 136 + _Status_: Default new items to "queued" status, provide commands to update