initial commit
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
package deepseek
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
)
|
||||
|
||||
const baseURL = "https://chat.deepseek.com/api/v0"
|
||||
|
||||
// NoParams is an empty JSON object used by endpoints without request fields.
|
||||
var NoParams struct{}
|
||||
|
||||
// Response is the outer response envelope returned by the DeepSeek API.
|
||||
type Response[T any] struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"msg"`
|
||||
Data T `json:"data"`
|
||||
}
|
||||
|
||||
// BizResponse is the business-level response envelope nested inside Response.
|
||||
type BizResponse[T any] struct {
|
||||
Code int `json:"biz_code"`
|
||||
Message string `json:"biz_msg"`
|
||||
Data T `json:"biz_data"`
|
||||
}
|
||||
|
||||
// Client is a configurable client for the DeepSeek chat API.
|
||||
//
|
||||
// A client may be reused for multiple requests. Callers must not mutate its
|
||||
// configuration concurrently with active requests.
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
token string
|
||||
baseURL string
|
||||
|
||||
deviceIDProvider DeviceIDProvider
|
||||
}
|
||||
|
||||
// NewClient creates a client with the default endpoint and a cookie jar.
|
||||
func NewClient() *Client {
|
||||
jar, err := cookiejar.New(nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
return &Client{httpClient: &http.Client{Jar: jar}, baseURL: baseURL}
|
||||
}
|
||||
|
||||
// SetToken sets the bearer token used by subsequent requests.
|
||||
func (api *Client) SetToken(token string) *Client {
|
||||
api.token = token
|
||||
return api
|
||||
}
|
||||
|
||||
// SetDeviceIDProvider sets the source used to obtain login device IDs.
|
||||
// Passing nil restores the random provider behavior used by Login.
|
||||
func (api *Client) SetDeviceIDProvider(p DeviceIDProvider) *Client {
|
||||
api.deviceIDProvider = p
|
||||
return api
|
||||
}
|
||||
|
||||
// SetHTTPClient replaces the underlying HTTP client when client is non-nil.
|
||||
func (api *Client) SetHTTPClient(client *http.Client) *Client {
|
||||
if client != nil {
|
||||
api.httpClient = client
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
// SetBaseURL replaces the API base URL when url is non-empty.
|
||||
func (api *Client) SetBaseURL(url string) *Client {
|
||||
if url != "" {
|
||||
api.baseURL = url
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
// Request describes an HTTP request with parameter type P and response data
|
||||
// type R.
|
||||
type Request[P, R any] struct {
|
||||
params P
|
||||
method string
|
||||
path string
|
||||
powAnswer string
|
||||
}
|
||||
|
||||
// NewRequest creates a typed API request.
|
||||
func NewRequest[R, P any](method, path string, params P) *Request[P, R] {
|
||||
return &Request[P, R]{params: params, method: method, path: path}
|
||||
}
|
||||
func (r *Request[P, R]) marshallRequest() ([]byte, error) {
|
||||
data, err := json.Marshal(r.params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, err
|
||||
}
|
||||
func (r *Request[P, R]) unmarshallResponse(resp *http.Response) (Response[R], error) {
|
||||
var zero Response[R]
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &zero)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
if zero.Code != 0 {
|
||||
return zero, fmt.Errorf("unknown error: %s (%d)", zero.Message, zero.Code)
|
||||
}
|
||||
return zero, err
|
||||
}
|
||||
func (r *Request[P, R]) unmarshallBizResponse(resp *http.Response) (Response[BizResponse[R]], error) {
|
||||
var zero Response[BizResponse[R]]
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &zero)
|
||||
if err != nil {
|
||||
return zero, err
|
||||
}
|
||||
if zero.Code != 0 {
|
||||
return zero, fmt.Errorf("unknown error: %s (%d)", zero.Message, zero.Code)
|
||||
}
|
||||
if zero.Data.Code != 0 {
|
||||
return zero, fmt.Errorf("unknown error: %s (%d)", zero.Data.Message, zero.Data.Code)
|
||||
}
|
||||
return zero, err
|
||||
}
|
||||
|
||||
// SolvePow requests and solves a proof-of-work challenge for r.
|
||||
func (r *Request[P, R]) SolvePow(ctx context.Context, api *Client) error {
|
||||
res, err := api.CreatePowChallengeWithContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := res.Challenge.ToBase64()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.powAnswer = string(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DoWithContext sends r using api and returns the raw HTTP response.
|
||||
// The caller owns and must close the response body.
|
||||
func (r *Request[P, R]) DoWithContext(ctx context.Context, api *Client) (*http.Response, error) {
|
||||
data, err := r.marshallRequest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, r.method, api.baseURL+"/"+r.path, bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf(
|
||||
"request: method=%s scheme=%s host=%s path=%s rawQuery=%s",
|
||||
req.Method,
|
||||
req.URL.Scheme,
|
||||
req.URL.Host,
|
||||
req.URL.Path,
|
||||
req.URL.RawQuery,
|
||||
)
|
||||
|
||||
headers := getHeaders()
|
||||
if r.powAnswer != "" {
|
||||
headers["x-ds-pow-response"] = r.powAnswer
|
||||
}
|
||||
if api.token != "" {
|
||||
headers["authorization"] = "Bearer " + api.token
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Add(k, v)
|
||||
}
|
||||
|
||||
resp, err := api.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Printf("response: method=%s status=%s code=%d",
|
||||
req.Method,
|
||||
resp.Status,
|
||||
resp.StatusCode,
|
||||
)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Do sends r using a background context.
|
||||
// The caller owns and must close the response body.
|
||||
func (r *Request[P, R]) Do(api *Client) (*http.Response, error) {
|
||||
return r.DoWithContext(context.Background(), api)
|
||||
}
|
||||
|
||||
func getHeaders() map[string]string {
|
||||
return map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "DeepSeek/2 CFNetwork/1568.100.1 Darwin/24.0.0",
|
||||
"x-client-platform": "ios",
|
||||
"x-client-version": "2.0.4",
|
||||
"x-client-bundle-id": "com.deepseek.chat",
|
||||
"x-client-locale": "en_US",
|
||||
"x-client-timezone-offset": "3600",
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user