package main import ( "encoding/json" "fmt" "log/slog" "net/http" "net/url" "time" ) type DoctoResp struct { Availabilities []struct { Date string `json:"date"` Slots []string `json:"slots"` } `json:"availabilities"` Total int `json:"total"` Reason string `json:"reason"` Message string `json:"message"` NextSlot string `json:"next_slot,omitempty"` } type DoctoError struct { Error []string `json:"error"` } type DoctoClient struct { httpClient *http.Client limit int } func NewDoctoClient() *DoctoClient { return &DoctoClient{ httpClient: &http.Client{Timeout: 10 * time.Second}, limit: defaultDoctolibLimit, } } func (docto *DoctoClient) check(doctor DoctorConfig, startDate string) (bool, error) { params := url.Values{} params.Set("visit_motive_id", doctor.MotiveId) params.Set("agenda_ids", doctor.AgendaId) params.Set("practice_ids", doctor.PracticeId) params.Set("start_date_time", startDate) params.Set("limit", fmt.Sprintf("%d", docto.limit)) u := url.URL{ Scheme: "https", Host: "www.doctolib.fr", Path: "/search/availabilities.json", RawQuery: params.Encode(), } queryUrl := u.String() slog.Debug("check", "Doctolib URL", queryUrl) req, err := http.NewRequest(http.MethodGet, queryUrl, nil) if err != nil { return false, err } req.Header.Add("User-Agent", "Mozilla/5.0 (compatible; DoctoCheck/1.0)") req.Header.Add("Referer", "https://www.doctolib.fr/search") resp, err := docto.httpClient.Do(req) if err != nil { return false, err } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { var doctoError DoctoError _ = json.NewDecoder(resp.Body).Decode(&doctoError) return false, fmt.Errorf("unexpected status %d, message:%v", resp.StatusCode, doctoError.Error) } var doctoResp DoctoResp err = json.NewDecoder(resp.Body).Decode(&doctoResp) if err != nil { return false, err } slog.Debug("Doctolib response", "Availabilities", doctoResp.Availabilities) slog.Debug( "Doctolib response", "Total", doctoResp.Total, "Reason", doctoResp.Reason, "Message", doctoResp.Message, "NextSlot", doctoResp.NextSlot) hasAvailability := doctoResp.Total > 0 || doctoResp.NextSlot != "" return hasAvailability, nil }