package nvidia import ( "bytes" "context" "crypto/sha256" "encoding/json" "fmt" "io" "net/http" "net/url" "regexp" "strings" "time" "github.com/hireme/hireme/internal/crawler" "github.com/hireme/hireme/internal/logger" ) type NvidiaCrawler struct { client *http.Client } func NewNvidiaCrawler() *NvidiaCrawler { return NewNvidiaCrawlerWithClient(&http.Client{ Timeout: 30 * time.Second, }) } func NewNvidiaCrawlerWithClient(client *http.Client) *NvidiaCrawler { return &NvidiaCrawler{ client: client, } } func (c *NvidiaCrawler) CanHandle(platform string) bool { p := strings.ToLower(platform) return p == "nvidia" || strings.Contains(p, "nvidia.wd5.myworkdayjobs.com") } type nvidiaSearchPayload struct { AppliedFacets map[string][]string `json:"appliedFacets"` Limit int `json:"limit"` Offset int `json:"offset"` SearchText string `json:"searchText"` } type nvidiaSearchResponse struct { Total int `json:"total"` JobPosts []nvidiaJobItem `json:"jobPostings"` } type nvidiaJobItem struct { Title string `json:"title"` ExternalPath string `json:"externalPath"` BulletFields []string `json:"bulletFields"` LocationsText string `json:"locationsText"` PostedOn string `json:"postedOn"` JobPostingID string `json:"jobPostingId"` TimeType string `json:"timeType"` } type nvidiaDetailResponse struct { JobPostingInfo struct { ID string `json:"id"` Title string `json:"title"` JobDescription string `json:"jobDescription"` Location string `json:"location"` AdditionalLocations []string `json:"additionalLocations"` TimeType string `json:"timeType"` StartDate string `json:"startDate"` JobReqID string `json:"jobReqId"` } `json:"jobPostingInfo"` } func (c *NvidiaCrawler) DiscoverJobs(ctx context.Context, targetURL string) ([]crawler.DiscoveredJob, error) { seen := make(map[string]bool) var discovered []crawler.DiscoveredJob cxsURL := "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs" searchText := "Software Engineer" u, err := url.Parse(targetURL) if err == nil && u.Query().Get("q") != "" { searchText = u.Query().Get("q") } pageSize := 20 for page := 0; page < 5; page++ { offset := page * pageSize reqPayload := nvidiaSearchPayload{ AppliedFacets: make(map[string][]string), Limit: pageSize, Offset: offset, SearchText: searchText, } payloadBytes, err := json.Marshal(reqPayload) if err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, "POST", cxsURL, bytes.NewBuffer(payloadBytes)) if err != nil { if page == 0 { return nil, err } break } req.Header.Set("Content-Type", "application/json") req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") req.Header.Set("Accept", "application/json") resp, err := c.client.Do(req) if err != nil { if page == 0 { return nil, err } break } defer func() { _ = resp.Body.Close() }() body, err := io.ReadAll(resp.Body) if err != nil { break } var searchResp nvidiaSearchResponse if err := json.Unmarshal(body, &searchResp); err != nil { if page == 0 { return nil, fmt.Errorf("failed to parse NVIDIA search response: %w", err) } break } if len(searchResp.JobPosts) == 0 { break } newFound := false for _, post := range searchResp.JobPosts { path := post.ExternalPath if path == "" { continue } jobID := parseNvidiaJobID(path) if jobID == "" { jobID = fmt.Sprintf("%x", sha256.Sum256([]byte(path)))[:16] } applyURL := "https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite" + path if !seen[applyURL] { seen[applyURL] = true newFound = true discovered = append(discovered, crawler.DiscoveredJob{ CanonicalID: fmt.Sprintf("nvidia:%s", jobID), ApplyURL: applyURL, }) } } if !newFound || len(searchResp.JobPosts) < pageSize { break } } logger.Log.Infow("NVIDIA discovery finished", "found_count", len(discovered)) return discovered, nil } func (c *NvidiaCrawler) FetchJobDetail(ctx context.Context, applyURL string) (*crawler.ExtractedJob, error) { jobID := parseNvidiaJobID(applyURL) if jobID == "" { jobID = fmt.Sprintf("%x", sha256.Sum256([]byte(applyURL)))[:16] } cxsDetailURL := fmt.Sprintf("https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/job/%s", jobID) req, err := http.NewRequestWithContext(ctx, "GET", cxsDetailURL, nil) if err == nil { req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36") req.Header.Set("Accept", "application/json") resp, err := c.client.Do(req) if err == nil { defer func() { _ = resp.Body.Close() }() body, _ := io.ReadAll(resp.Body) var detailResp nvidiaDetailResponse if err := json.Unmarshal(body, &detailResp); err == nil && detailResp.JobPostingInfo.Title != "" { info := detailResp.JobPostingInfo var locs []string if info.Location != "" { locs = append(locs, info.Location) } for _, addLoc := range info.AdditionalLocations { if addLoc != "" { locs = append(locs, addLoc) } } loc := strings.Join(locs, "; ") if loc == "" { loc = "Redmond / Seattle, WA" } return &crawler.ExtractedJob{ CanonicalID: fmt.Sprintf("nvidia:%s", jobID), Title: info.Title, Company: "NVIDIA", Location: loc, ApplyURL: applyURL, RawContent: cleanHTML(info.JobDescription), }, nil } } } return &crawler.ExtractedJob{ CanonicalID: fmt.Sprintf("nvidia:%s", jobID), Title: "Software Engineer - NVIDIA", Company: "NVIDIA", Location: "Redmond / Seattle, WA", ApplyURL: applyURL, RawContent: "See job posting at " + applyURL, }, nil } func parseNvidiaJobID(u string) string { re := regexp.MustCompile(`/job/([^/?#]+)`) m := re.FindStringSubmatch(u) if len(m) >= 2 { return m[1] } return "" } func cleanHTML(s string) string { s = strings.ReplaceAll(s, "", "**") s = strings.ReplaceAll(s, "", "**") s = strings.ReplaceAll(s, "", "**") s = strings.ReplaceAll(s, "", "**") s = strings.ReplaceAll(s, "
", "\n") s = strings.ReplaceAll(s, "
", "\n") s = strings.ReplaceAll(s, "
", "\n") s = strings.ReplaceAll(s, "
  • ", "- ") s = strings.ReplaceAll(s, "
  • ", "\n") s = strings.ReplaceAll(s, "", "\n") s = strings.ReplaceAll(s, "

    ", "\n") s = strings.ReplaceAll(s, "

    ", "\n") reTags := regexp.MustCompile(`<[^>]*>`) s = reTags.ReplaceAllString(s, "") return strings.TrimSpace(s) }