IndeedSearchURLBuilder.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. //
  2. // IndeedSearchURLBuilder.swift
  3. // indeed_ai_job_search
  4. //
  5. // Builds regional Indeed /jobs search URLs from SearchCriteria.
  6. //
  7. import Foundation
  8. enum IndeedSearchURLBuilder {
  9. static func searchURL(
  10. criteria: SearchCriteria,
  11. query: String,
  12. pageIndex: Int = 0,
  13. pageSize: Int = IndeedSearchPageRequest.defaultPageSize
  14. ) -> URL {
  15. let what = whatQuery(criteria: criteria, query: query)
  16. let whereValue = whereQuery(criteria: criteria)
  17. let host = regionalHost(for: criteria)
  18. var components = URLComponents()
  19. components.scheme = "https"
  20. components.host = host
  21. components.path = "/jobs"
  22. var items: [URLQueryItem] = [
  23. URLQueryItem(name: "q", value: what),
  24. URLQueryItem(name: "l", value: whereValue)
  25. ]
  26. if criteria.remoteTypes.contains(.remote), criteria.remoteTypes.count == 1 {
  27. items.append(URLQueryItem(name: "sc", value: "0kf:attr(DSQF7);"))
  28. }
  29. let start = max(0, pageIndex) * max(1, pageSize)
  30. if start > 0 {
  31. items.append(URLQueryItem(name: "start", value: String(start)))
  32. }
  33. components.queryItems = items
  34. return components.url ?? URL(string: "https://www.indeed.com/jobs")!
  35. }
  36. static func whatQuery(criteria: SearchCriteria, query: String) -> String {
  37. let title = criteria.jobTitle.trimmingCharacters(in: .whitespacesAndNewlines)
  38. let keywords = criteria.keywords.trimmingCharacters(in: .whitespacesAndNewlines)
  39. let trimmedQuery = query.trimmingCharacters(in: .whitespacesAndNewlines)
  40. var parts: [String] = []
  41. if !title.isEmpty {
  42. parts.append(criteria.exactPhrase ? "\"\(title)\"" : title)
  43. } else if !trimmedQuery.isEmpty {
  44. parts.append(trimmedQuery)
  45. }
  46. if !keywords.isEmpty, keywords.caseInsensitiveCompare(title) != .orderedSame,
  47. keywords.caseInsensitiveCompare(trimmedQuery) != .orderedSame {
  48. parts.append(keywords)
  49. }
  50. let excluded = criteria.excludedKeywords
  51. .split(separator: " ")
  52. .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
  53. .filter { !$0.isEmpty }
  54. for term in excluded {
  55. parts.append("-\(term)")
  56. }
  57. let joined = parts.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines)
  58. return joined.isEmpty ? "jobs" : joined
  59. }
  60. static func whereQuery(criteria: SearchCriteria) -> String {
  61. let city = criteria.city.trimmingCharacters(in: .whitespacesAndNewlines)
  62. let country = criteria.country.trimmingCharacters(in: .whitespacesAndNewlines)
  63. if criteria.remoteTypes.contains(.remote),
  64. criteria.remoteTypes.count == 1,
  65. city.isEmpty {
  66. return "Remote"
  67. }
  68. if !city.isEmpty, !country.isEmpty {
  69. return "\(city), \(country)"
  70. }
  71. if !city.isEmpty { return city }
  72. if !country.isEmpty { return country }
  73. return ""
  74. }
  75. static func regionalHost(for criteria: SearchCriteria) -> String {
  76. let haystack = "\(criteria.country) \(criteria.city)".lowercased()
  77. if haystack.contains("pakistan")
  78. || haystack.contains("lahore")
  79. || haystack.contains("karachi")
  80. || haystack.contains("islamabad") {
  81. return "pk.indeed.com"
  82. }
  83. if haystack.contains("united kingdom")
  84. || haystack.contains("london")
  85. || haystack.contains(" england")
  86. || haystack.hasSuffix(" uk")
  87. || haystack == "uk" {
  88. return "uk.indeed.com"
  89. }
  90. if haystack.contains("canada")
  91. || haystack.contains("toronto")
  92. || haystack.contains("vancouver") {
  93. return "ca.indeed.com"
  94. }
  95. return "www.indeed.com"
  96. }
  97. }