up
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user