This commit is contained in:
2026-04-20 22:51:41 +02:00
parent 89fc0119f3
commit 81eec53978
27 changed files with 1067 additions and 399 deletions
+66 -4
View File
@@ -3,7 +3,11 @@ package yahoo
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"strings"
"sync"
"time"
)
@@ -11,7 +15,9 @@ const baseURL = "https://query1.finance.yahoo.com/v8/finance/chart"
const summaryURL = "https://query1.finance.yahoo.com/v10/finance/quoteSummary"
type Client struct {
http *http.Client
http *http.Client
mu sync.Mutex
crumb string
}
type Bar struct {
@@ -57,11 +63,51 @@ type chartResponse struct {
}
func New() *Client {
jar, _ := cookiejar.New(nil)
return &Client{
http: &http.Client{Timeout: 10 * time.Second},
http: &http.Client{Timeout: 10 * time.Second, Jar: jar},
}
}
// initCrumb obtient un cookie de session Yahoo Finance puis récupère le crumb.
func (c *Client) initCrumb() error {
// 1. Visite Yahoo Finance pour obtenir les cookies
req, _ := http.NewRequest("GET", "https://finance.yahoo.com", nil)
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
req.Header.Set("Accept", "text/html")
resp, err := c.http.Do(req)
if err != nil {
return err
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
// 2. Récupère le crumb
req2, _ := http.NewRequest("GET", "https://query1.finance.yahoo.com/v1/test/getcrumb", nil)
req2.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
resp2, err := c.http.Do(req2)
if err != nil {
return err
}
defer resp2.Body.Close()
body, _ := io.ReadAll(resp2.Body)
crumb := strings.TrimSpace(string(body))
if crumb == "" || strings.Contains(crumb, "Unauthorized") {
return fmt.Errorf("yahoo: crumb invalide")
}
c.crumb = crumb
return nil
}
func (c *Client) getCrumb() string {
c.mu.Lock()
defer c.mu.Unlock()
if c.crumb == "" {
c.initCrumb()
}
return c.crumb
}
func (c *Client) History(symbol string, days int) ([]Bar, error) {
rangeStr := "3mo"
if days > 90 {
@@ -193,13 +239,14 @@ type quoteSummaryResponse struct {
// GetMarketCap retourne les données fondamentales d'un ticker.
func (c *Client) GetMarketCap(symbol string) (*MarketCapInfo, error) {
url := fmt.Sprintf("%s/%s?modules=summaryDetail,defaultKeyStatistics", summaryURL, symbol)
crumb := c.getCrumb()
url := fmt.Sprintf("%s/%s?modules=summaryDetail,defaultKeyStatistics&crumb=%s", summaryURL, symbol, crumb)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", "Mozilla/5.0")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
resp, err := c.http.Do(req)
if err != nil {
@@ -207,6 +254,21 @@ func (c *Client) GetMarketCap(symbol string) (*MarketCapInfo, error) {
}
defer resp.Body.Close()
if resp.StatusCode == 401 {
// Crumb expiré — on le renouvelle et on réessaie une fois
resp.Body.Close()
c.mu.Lock()
c.crumb = ""
c.mu.Unlock()
newCrumb := c.getCrumb()
url2 := fmt.Sprintf("%s/%s?modules=summaryDetail,defaultKeyStatistics&crumb=%s", summaryURL, symbol, newCrumb)
req2, _ := http.NewRequest("GET", url2, nil)
req2.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
resp, err = c.http.Do(req2)
if err != nil {
return nil, err
}
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("yahoo quoteSummary: HTTP %d for %s", resp.StatusCode, symbol)
}