-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.go
More file actions
129 lines (109 loc) · 2.72 KB
/
Copy pathclient.go
File metadata and controls
129 lines (109 loc) · 2.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package yfinance_api
import (
"crypto/rand"
"fmt"
"io"
"log/slog"
"math/big"
"net/http"
"net/url"
"sync"
)
type YFinanceAPI struct {
Client *Client
}
type Client struct {
client *http.Client
cookies []*http.Cookie
crumb string
}
var instance *Client
var once sync.Once
func getClient() *Client {
once.Do(func() {
instance = &Client{client: &http.Client{}, cookies: []*http.Cookie{}, crumb: ""}
})
return instance
}
func (c *Client) Get(url string, params url.Values) (*http.Response, error) {
c.getCrumb()
return c.get(url, params)
}
func (c *Client) get(url string, params url.Values) (*http.Response, error) {
if c.crumb != "" {
params.Add("crumb", c.crumb)
}
url = fmt.Sprintf("%s?%s", url, params.Encode())
req, err := http.NewRequest("GET", url, nil)
if err != nil {
slog.Error("Failed to create request", "err", err)
return nil, err
}
for _, cookie := range c.cookies {
req.AddCookie(cookie)
}
// Use crypto/rand for secure random number generation
randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(UserAgents))))
if err != nil {
slog.Error("Failed to generate secure random number", "err", err)
// Fallback to first user agent if random generation fails
req.Header.Set("User-Agent", UserAgents[0])
} else {
req.Header.Set("User-Agent", UserAgents[randomIndex.Int64()])
}
resp, err := c.client.Do(req)
if err != nil {
slog.Error("Failed to get data from Yahoo Finance API", "err", err)
return nil, err
}
return resp, nil
}
func (c *Client) getCookie() {
if len(c.cookies) > 0 {
return
}
endpoint := "https://fc.yahoo.com"
resp, err := c.get(endpoint, url.Values{})
if err != nil {
slog.Error("Failed to get cookie", "err", err)
return
}
c.cookies = resp.Cookies()
}
func (c *Client) getCrumb() {
if c.crumb != "" {
return
}
c.getCookie()
endpoint := fmt.Sprintf("%s/v1/test/getcrumb", BaseUrl)
resp, err := c.get(endpoint, url.Values{})
if err != nil {
slog.Error("Failed to get crumb", "err", err)
return
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
slog.Error("Error closing response body:", "err", err)
}
}(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
slog.Error("Error reading response body:", "err", err)
return
}
c.crumb = string(body)
}
// NewClient creates and returns a new YFinance API client instance
// This is the main entry point for users of the package
func NewClient() *YFinanceAPI {
return &YFinanceAPI{
Client: getClient(),
}
}
// NewTicker creates a new ticker instance for the given symbol
// This is a convenience function that creates a client and ticker in one call
func NewTicker(symbol string) *Ticker {
client := NewClient()
return client.InstantiateTicker(symbol)
}