Skip to content

Commit 620ef6d

Browse files
authored
Merge pull request #107 from 0xjuanma/refactor/extra-goal-link-logic
refactor[replays]: improve reddit goal replay link search
2 parents 5cd5ad4 + 0fc79f8 commit 620ef6d

File tree

5 files changed

+84
-6
lines changed

5 files changed

+84
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212

1313
### Changed
1414
- **Code Quality** - Resolved all golangci-lint warnings (errcheck, staticcheck, unused)
15+
- **Goal Replay Links** - Added a new Reddit search strategy using short/alternative team names, improving goal link discovery when standard queries miss
1516

1617
### Fixed
18+
- **Stats View Focus** - Fixed focus state persisting when navigating away from stats view, ensuring fresh state on re-entry
1719

1820
## [0.20.0] - 2026-02-05
1921

internal/app/commands.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,8 @@ func fetchGoalLinks(redditClient *reddit.Client, details *api.MatchDetails) tea.
352352
MatchID: details.ID,
353353
HomeTeam: details.HomeTeam.Name,
354354
AwayTeam: details.AwayTeam.Name,
355+
HomeTeamShort: details.HomeTeam.ShortName,
356+
AwayTeamShort: details.AwayTeam.ShortName,
355357
ScorerName: scorer,
356358
Minute: event.Minute,
357359
DisplayMinute: event.DisplayMinute,

internal/app/update.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ func (m model) resetToMainView() (tea.Model, tea.Cmd) {
354354
m.polling = false
355355
m.matches = nil
356356
m.upcomingMatches = nil
357+
m.statsRightPanelFocused = false
358+
m.statsScrollOffset = 0
357359
return m, nil
358360
}
359361

internal/reddit/client.go

Lines changed: 76 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ type DebugLogger func(message string)
1717
// Fetcher defines the interface for fetching data from Reddit.
1818
// Uses Reddit's public JSON API for goal link retrieval.
1919
type Fetcher interface {
20-
Search(query string, limit int, matchTime time.Time) ([]SearchResult, error)
20+
Search(query string, limit int, matchTime time.Time, sort string) ([]SearchResult, error)
2121
}
2222

2323
// PublicJSONFetcher uses Reddit's public JSON endpoints (no auth required).
@@ -67,21 +67,28 @@ func NewPublicJSONFetcher() *PublicJSONFetcher {
6767

6868
// Search performs a search on r/soccer for Media posts matching the query.
6969
// matchTime is used to filter results to posts created around the match date.
70-
func (f *PublicJSONFetcher) Search(query string, limit int, matchTime time.Time) ([]SearchResult, error) {
70+
// sort controls the result ordering (e.g., "relevance", "top", "new", "hot").
71+
func (f *PublicJSONFetcher) Search(query string, limit int, matchTime time.Time, sort string) ([]SearchResult, error) {
7172
f.rateLimiter.wait()
7273

7374
// Build timestamp range for filtering (match day only ±12 hours)
7475
// Goal videos are posted very soon after goals happen - limit to match day
7576
startTime := matchTime.Add(-12 * time.Hour).Unix()
7677
endTime := matchTime.Add(12 * time.Hour).Unix()
7778

79+
// Default to relevance if sort is empty
80+
if sort == "" {
81+
sort = "relevance"
82+
}
83+
7884
// Build search URL for r/soccer with Media flair filter and timestamp
7985
// Reddit CloudSearch supports timestamp:START..END syntax
8086
searchURL := fmt.Sprintf(
81-
"https://www.reddit.com/r/soccer/search.json?q=%s+flair:Media+timestamp:%d..%d&restrict_sr=on&sort=relevance&limit=%d",
87+
"https://www.reddit.com/r/soccer/search.json?q=%s+flair:Media+timestamp:%d..%d&restrict_sr=on&sort=%s&limit=%d",
8288
url.QueryEscape(query),
8389
startTime,
8490
endTime,
91+
url.QueryEscape(sort),
8592
limit,
8693
)
8794

@@ -316,7 +323,7 @@ func (c *Client) searchForGoalOnce(goal GoalInfo) (*GoalLink, error) {
316323
query1 := fmt.Sprintf("%s %s %d'", goal.HomeTeam, goal.AwayTeam, goal.Minute)
317324
c.debugLog(fmt.Sprintf("Reddit search query: '%s' for goal %d:%d (%s vs %s)",
318325
query1, goal.MatchID, goal.Minute, goal.HomeTeam, goal.AwayTeam))
319-
results1, err := c.fetcher.Search(query1, 15, goal.MatchTime)
326+
results1, err := c.fetcher.Search(query1, 15, goal.MatchTime, "relevance")
320327
if err != nil {
321328
c.debugLog(fmt.Sprintf("Reddit search failed for query '%s': %v", query1, err))
322329
} else {
@@ -361,7 +368,7 @@ func (c *Client) searchForGoalOnce(goal GoalInfo) (*GoalLink, error) {
361368
}
362369
query2 := fmt.Sprintf("%s %d'", scoringTeam, goal.Minute)
363370
c.debugLog(fmt.Sprintf("Reddit search query (strategy 2): '%s' for goal %d:%d", query2, goal.MatchID, goal.Minute))
364-
results2, err := c.fetcher.Search(query2, 15, goal.MatchTime)
371+
results2, err := c.fetcher.Search(query2, 15, goal.MatchTime, "relevance")
365372
if err != nil {
366373
c.debugLog(fmt.Sprintf("Reddit search failed for strategy 2 query '%s': %v", query2, err))
367374
} else {
@@ -379,12 +386,75 @@ func (c *Client) searchForGoalOnce(goal GoalInfo) (*GoalLink, error) {
379386
}
380387
}
381388

382-
// Find the best matching result
389+
// Check if strategies 1+2 found a match before trying strategy 3
383390
match := findBestMatch(uniqueResults, goal)
391+
if match != nil {
392+
c.debugLog(fmt.Sprintf("Strategy 1+2 match found for goal %d:%d, skipping strategy 3", goal.MatchID, goal.Minute))
393+
c.debugLog(fmt.Sprintf("Found goal link for %d:%d: %s (post: %s)", goal.MatchID, goal.Minute, match.URL, match.PostURL))
394+
return &GoalLink{
395+
MatchID: goal.MatchID,
396+
Minute: goal.Minute,
397+
URL: match.URL,
398+
Title: match.Title,
399+
PostURL: match.PostURL,
400+
FetchedAt: time.Now(),
401+
}, nil
402+
}
403+
404+
// Strategy 3: Try with short/alternative team names + sort by top (upvotes)
405+
// Only runs if strategies 1+2 did not find a match
406+
homeShort := strings.TrimSpace(goal.HomeTeamShort)
407+
awayShort := strings.TrimSpace(goal.AwayTeamShort)
408+
409+
// Skip if short names are empty or identical to full names (avoids redundant API call)
410+
homeShortDifferent := homeShort != "" && !strings.EqualFold(homeShort, goal.HomeTeam)
411+
awayShortDifferent := awayShort != "" && !strings.EqualFold(awayShort, goal.AwayTeam)
412+
413+
if !homeShortDifferent && !awayShortDifferent {
414+
c.debugLog(fmt.Sprintf("Skipping strategy 3 for goal %d:%d: short names empty or identical to full names", goal.MatchID, goal.Minute))
415+
return nil, nil // No match found across all strategies
416+
}
417+
418+
// Build query using short names where they differ, falling back to full names
419+
homeQuery := goal.HomeTeam
420+
if homeShortDifferent {
421+
homeQuery = homeShort
422+
}
423+
awayQuery := goal.AwayTeam
424+
if awayShortDifferent {
425+
awayQuery = awayShort
426+
}
427+
428+
query3 := fmt.Sprintf("%s %s %d'", homeQuery, awayQuery, goal.Minute)
429+
c.debugLog(fmt.Sprintf("Reddit search query (strategy 3): '%s' for goal %d:%d", query3, goal.MatchID, goal.Minute))
430+
results3, err := c.fetcher.Search(query3, 15, goal.MatchTime, "top")
431+
if err != nil {
432+
c.debugLog(fmt.Sprintf("Reddit search failed for strategy 3 query '%s': %v", query3, err))
433+
} else {
434+
c.debugLog(fmt.Sprintf("Reddit search returned %d results for strategy 3 query '%s'", len(results3), query3))
435+
// Debug: log the first few result titles
436+
for i, result := range results3 {
437+
if i < 3 { // Log first 3 results
438+
c.debugLog(fmt.Sprintf("Strategy 3 result %d: '%s' (score: %d)", i+1, result.Title, result.Score))
439+
}
440+
}
441+
// Combine with all prior results for best match selection
442+
for _, result := range results3 {
443+
if !seen[result.URL] {
444+
seen[result.URL] = true
445+
uniqueResults = append(uniqueResults, result)
446+
}
447+
}
448+
}
449+
450+
// Find the best matching result across all strategies
451+
match = findBestMatch(uniqueResults, goal)
452+
c.debugLog(fmt.Sprintf("findBestMatch result (strategy 3) for goal %d:%d: %v", goal.MatchID, goal.Minute, match != nil))
384453
if match == nil {
385454
return nil, nil // No match found, but not an error
386455
}
387456

457+
c.debugLog(fmt.Sprintf("Found goal link (strategy 3) for %d:%d: %s (post: %s)", goal.MatchID, goal.Minute, match.URL, match.PostURL))
388458
return &GoalLink{
389459
MatchID: goal.MatchID,
390460
Minute: goal.Minute,

internal/reddit/types.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ type GoalInfo struct {
9090
MatchID int
9191
HomeTeam string
9292
AwayTeam string
93+
HomeTeamShort string // Short/alternative name (e.g., "Wolves", "Man Utd")
94+
AwayTeamShort string // Short/alternative name (e.g., "Wolves", "Man Utd")
9395
ScorerName string
9496
Minute int
9597
DisplayMinute string // e.g., "45+2'" for stoppage time display

0 commit comments

Comments
 (0)