SnovioModels.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  1. using System.Text.Json;
  2. using System.Text.Json.Serialization;
  3. namespace OASystem.API.OAMethodLib.SnovioAPI;
  4. /// <summary>
  5. /// Snov.io OAuth access_token 请求体。
  6. /// </summary>
  7. public sealed class SnovioAccessTokenRequest
  8. {
  9. /// <summary>
  10. /// OAuth 授权类型,固定为 client_credentials。
  11. /// </summary>
  12. [JsonPropertyName("grant_type")]
  13. public string GrantType { get; set; } = "client_credentials";
  14. /// <summary>
  15. /// Snov.io Client ID。
  16. /// </summary>
  17. [JsonPropertyName("client_id")]
  18. public string ClientId { get; set; } = string.Empty;
  19. /// <summary>
  20. /// Snov.io Client Secret。
  21. /// </summary>
  22. [JsonPropertyName("client_secret")]
  23. public string ClientSecret { get; set; } = string.Empty;
  24. }
  25. /// <summary>
  26. /// Snov.io OAuth access_token 响应体。
  27. /// </summary>
  28. public sealed class SnovioAccessTokenResponse
  29. {
  30. /// <summary>
  31. /// 访问令牌。
  32. /// </summary>
  33. [JsonPropertyName("access_token")]
  34. public string AccessToken { get; set; } = string.Empty;
  35. /// <summary>
  36. /// 令牌类型,通常为 Bearer。
  37. /// </summary>
  38. [JsonPropertyName("token_type")]
  39. public string TokenType { get; set; } = string.Empty;
  40. /// <summary>
  41. /// 令牌有效期,单位为秒。
  42. /// </summary>
  43. [JsonPropertyName("expires_in")]
  44. public int ExpiresIn { get; set; }
  45. }
  46. /// <summary>
  47. /// Snov.io 公司数据库搜索请求体。
  48. /// </summary>
  49. public sealed class SnovioCompanySearchRequest
  50. {
  51. /// <summary>
  52. /// 结果页码,默认从第 1 页开始。
  53. /// </summary>
  54. [JsonPropertyName("page")]
  55. public int Page { get; set; } = 1;
  56. /// <summary>
  57. /// 可选的异步结果回调地址。
  58. /// </summary>
  59. [JsonPropertyName("webhook_url")]
  60. public string? WebhookUrl { get; set; }
  61. /// <summary>
  62. /// 公司搜索筛选条件。
  63. /// </summary>
  64. [JsonPropertyName("filters")]
  65. public SnovioCompanySearchFilters Filters { get; set; } = new();
  66. }
  67. /// <summary>
  68. /// Snov.io 公司数据库搜索筛选条件。
  69. /// </summary>
  70. public sealed class SnovioCompanySearchFilters
  71. {
  72. /// <summary>
  73. /// 公司名称、行业、规模、营收和成立年份等筛选条件。
  74. /// </summary>
  75. [JsonPropertyName("company")]
  76. public SnovioCompanyFilter Company { get; set; } = new();
  77. /// <summary>
  78. /// 公司所在地区的包含和排除条件。
  79. /// </summary>
  80. [JsonPropertyName("locations")]
  81. public SnovioLocationFilter Locations { get; set; } = new();
  82. }
  83. /// <summary>
  84. /// 公司筛选条件。
  85. /// </summary>
  86. public sealed class SnovioCompanyFilter
  87. {
  88. /// <summary>
  89. /// 公司名称包含或排除列表。
  90. /// </summary>
  91. [JsonPropertyName("name")]
  92. public SnovioStringListFilter? Name { get; set; }
  93. /// <summary>
  94. /// 公司行业包含或排除列表。
  95. /// </summary>
  96. [JsonPropertyName("industries")]
  97. public SnovioStringListFilter? Industries { get; set; }
  98. /// <summary>
  99. /// 公司规模数组,例如 ["1-10", "51-200", "10001+", "Self"]。
  100. /// Snovio 要求数组格式,单值也请传单元素数组。
  101. /// </summary>
  102. [JsonPropertyName("size")]
  103. public List<string>? Size { get; set; }
  104. /// <summary>
  105. /// 公司营收最小值和最大值,单位由 Snov.io 数据源定义。
  106. /// </summary>
  107. [JsonPropertyName("revenue")]
  108. public SnovioRevenueFilter? Revenue { get; set; }
  109. /// <summary>
  110. /// 公司专长或业务领域列表。
  111. /// </summary>
  112. [JsonPropertyName("specialities")]
  113. public List<string> Specialities { get; set; } = new();
  114. /// <summary>
  115. /// 公司成立年份范围。
  116. /// </summary>
  117. [JsonPropertyName("founded")]
  118. public SnovioFoundedFilter? Founded { get; set; }
  119. }
  120. /// <summary>
  121. /// 字符串列表包含/排除条件。
  122. /// </summary>
  123. public sealed class SnovioStringListFilter
  124. {
  125. /// <summary>
  126. /// 需要匹配的字符串列表。
  127. /// </summary>
  128. [JsonPropertyName("include")]
  129. public List<string> Include { get; set; } = new();
  130. /// <summary>
  131. /// 需要排除的字符串列表。
  132. /// </summary>
  133. [JsonPropertyName("exclude")]
  134. public List<string> Exclude { get; set; } = new();
  135. }
  136. /// <summary>
  137. /// 地区包含/排除条件。
  138. /// </summary>
  139. public sealed class SnovioLocationFilter
  140. {
  141. /// <summary>
  142. /// 需要包含的地区条件。
  143. /// </summary>
  144. [JsonPropertyName("include")]
  145. public SnovioLocationFilterItem? Include { get; set; }
  146. /// <summary>
  147. /// 需要排除的地区条件。
  148. /// </summary>
  149. [JsonPropertyName("exclude")]
  150. public SnovioLocationFilterItem? Exclude { get; set; }
  151. }
  152. /// <summary>
  153. /// 地区条件明细。
  154. /// </summary>
  155. public sealed class SnovioLocationFilterItem
  156. {
  157. /// <summary>
  158. /// 地区名称,例如 London 或 Atlanta。
  159. /// </summary>
  160. [JsonPropertyName("locality")]
  161. public string? Locality { get; set; }
  162. /// <summary>
  163. /// 地区类型,例如 city、state、country、region、subregion。
  164. /// </summary>
  165. [JsonPropertyName("location_type")]
  166. public string? LocationType { get; set; }
  167. }
  168. /// <summary>
  169. /// 公司营收范围。
  170. /// </summary>
  171. public sealed class SnovioRevenueFilter
  172. {
  173. /// <summary>
  174. /// 营收下限。
  175. /// </summary>
  176. [JsonPropertyName("min")]
  177. public long? Min { get; set; }
  178. /// <summary>
  179. /// 营收上限。
  180. /// </summary>
  181. [JsonPropertyName("max")]
  182. public long? Max { get; set; }
  183. }
  184. /// <summary>
  185. /// 公司成立年份范围。
  186. /// </summary>
  187. public sealed class SnovioFoundedFilter
  188. {
  189. /// <summary>
  190. /// 成立年份下限。
  191. /// </summary>
  192. [JsonPropertyName("from")]
  193. public int? From { get; set; }
  194. /// <summary>
  195. /// 成立年份上限。
  196. /// </summary>
  197. [JsonPropertyName("till")]
  198. public int? Till { get; set; }
  199. }
  200. /// <summary>
  201. /// Snov.io 公司搜索创建任务响应体。
  202. /// </summary>
  203. public sealed class SnovioCompanySearchStartResponse
  204. {
  205. /// <summary>
  206. /// 创建任务时通常为空数组。
  207. /// </summary>
  208. [JsonPropertyName("data")]
  209. public List<JsonElement> Data { get; set; } = new();
  210. /// <summary>
  211. /// 创建任务的元数据,包含 task_hash 和页码。
  212. /// </summary>
  213. [JsonPropertyName("meta")]
  214. public SnovioCompanySearchStartMeta Meta { get; set; } = new();
  215. /// <summary>
  216. /// Snov.io 返回的结果查询地址。
  217. /// </summary>
  218. [JsonPropertyName("links")]
  219. public SnovioApiLinks Links { get; set; } = new();
  220. /// <summary>
  221. /// 创建任务响应状态。
  222. /// </summary>
  223. [JsonPropertyName("status")]
  224. public string? Status { get; set; }
  225. }
  226. /// <summary>
  227. /// Snov.io 公司搜索创建任务元数据。
  228. /// </summary>
  229. public sealed class SnovioCompanySearchStartMeta
  230. {
  231. /// <summary>
  232. /// 异步公司搜索任务唯一标识,用于查询结果。
  233. /// </summary>
  234. [JsonPropertyName("task_hash")]
  235. public string? TaskHash { get; set; }
  236. /// <summary>
  237. /// 本次搜索请求的结果页码。
  238. /// </summary>
  239. [JsonPropertyName("page")]
  240. public int Page { get; set; }
  241. }
  242. /// <summary>
  243. /// Snov.io 公司搜索结果响应体。
  244. /// </summary>
  245. public sealed class SnovioCompanySearchResultResponse
  246. {
  247. /// <summary>
  248. /// 公司搜索结果数据。
  249. /// </summary>
  250. [JsonPropertyName("data")]
  251. public SnovioCompanySearchResultData Data { get; set; } = new();
  252. /// <summary>
  253. /// 公司搜索任务元数据。
  254. /// </summary>
  255. [JsonPropertyName("meta")]
  256. public SnovioCompanySearchResultMeta Meta { get; set; } = new();
  257. /// <summary>
  258. /// 文档示例中该字段为空数组。
  259. /// </summary>
  260. [JsonPropertyName("links")]
  261. public List<JsonElement> Links { get; set; } = new();
  262. /// <summary>
  263. /// 任务处理状态,例如 completed 或 in_progress。
  264. /// </summary>
  265. [JsonPropertyName("status")]
  266. public string? Status { get; set; }
  267. }
  268. /// <summary>
  269. /// 公司搜索结果数据。
  270. /// 兼容 Snov.io 任务处理中时 data 为空数组的响应。
  271. /// </summary>
  272. [System.Text.Json.Serialization.JsonConverter(typeof(SnovioCompanySearchResultDataConverter))]
  273. public sealed class SnovioCompanySearchResultData
  274. {
  275. /// <summary>
  276. /// 符合筛选条件的公司总数。
  277. /// </summary>
  278. [JsonPropertyName("total")]
  279. public long Total { get; set; }
  280. /// <summary>
  281. /// 当前结果页码。
  282. /// </summary>
  283. [JsonPropertyName("page")]
  284. public int Page { get; set; }
  285. /// <summary>
  286. /// 结果总页数。
  287. /// </summary>
  288. [JsonPropertyName("total_pages")]
  289. public int TotalPages { get; set; }
  290. /// <summary>
  291. /// 当前页的公司列表。
  292. /// </summary>
  293. [JsonPropertyName("companies")]
  294. public List<SnovioCompanySearchCompany> Companies { get; set; } = new();
  295. }
  296. /// <summary>
  297. /// 兼容 Snov.io 公司搜索结果中 data 为对象、空数组或 null 的响应转换器。
  298. /// 任务处理中(in_progress)时 Snovio 返回 "data": [],此时按空结果处理,由轮询继续等待。
  299. /// </summary>
  300. internal sealed class SnovioCompanySearchResultDataConverter
  301. : System.Text.Json.Serialization.JsonConverter<SnovioCompanySearchResultData>
  302. {
  303. public override SnovioCompanySearchResultData Read(
  304. ref Utf8JsonReader reader,
  305. Type typeToConvert,
  306. JsonSerializerOptions options)
  307. {
  308. if (reader.TokenType == JsonTokenType.Null)
  309. return new SnovioCompanySearchResultData();
  310. if (reader.TokenType == JsonTokenType.StartArray)
  311. {
  312. // 处理中返回空数组,直接跳过。
  313. using var _ = JsonDocument.ParseValue(ref reader);
  314. return new SnovioCompanySearchResultData();
  315. }
  316. if (reader.TokenType == JsonTokenType.StartObject)
  317. {
  318. using var document = JsonDocument.ParseValue(ref reader);
  319. var root = document.RootElement;
  320. var result = new SnovioCompanySearchResultData();
  321. if (root.TryGetProperty("total", out var totalElement) &&
  322. totalElement.ValueKind == JsonValueKind.Number &&
  323. totalElement.TryGetInt64(out var total))
  324. {
  325. result.Total = total;
  326. }
  327. if (root.TryGetProperty("page", out var pageElement) &&
  328. pageElement.ValueKind == JsonValueKind.Number &&
  329. pageElement.TryGetInt32(out var page))
  330. {
  331. result.Page = page;
  332. }
  333. if (root.TryGetProperty("total_pages", out var totalPagesElement) &&
  334. totalPagesElement.ValueKind == JsonValueKind.Number &&
  335. totalPagesElement.TryGetInt32(out var totalPages))
  336. {
  337. result.TotalPages = totalPages;
  338. }
  339. if (root.TryGetProperty("companies", out var companiesElement) &&
  340. companiesElement.ValueKind == JsonValueKind.Array)
  341. {
  342. result.Companies = companiesElement.Deserialize<List<SnovioCompanySearchCompany>>(options)
  343. ?? new List<SnovioCompanySearchCompany>();
  344. }
  345. return result;
  346. }
  347. throw new System.Text.Json.JsonException(
  348. "Snov.io 公司搜索结果响应中的 data 必须是对象、数组或 null。");
  349. }
  350. public override void Write(
  351. Utf8JsonWriter writer,
  352. SnovioCompanySearchResultData value,
  353. JsonSerializerOptions options)
  354. {
  355. writer.WriteStartObject();
  356. writer.WriteNumber("total", value.Total);
  357. writer.WriteNumber("page", value.Page);
  358. writer.WriteNumber("total_pages", value.TotalPages);
  359. writer.WritePropertyName("companies");
  360. System.Text.Json.JsonSerializer.Serialize(writer, value.Companies, options);
  361. writer.WriteEndObject();
  362. }
  363. }
  364. /// <summary>
  365. /// Snov.io 公司搜索结果中的公司信息。
  366. /// </summary>
  367. public sealed class SnovioCompanySearchCompany
  368. {
  369. /// <summary>
  370. /// 公司名称。
  371. /// </summary>
  372. [JsonPropertyName("name")]
  373. public string? Name { get; set; }
  374. /// <summary>
  375. /// 公司域名。
  376. /// </summary>
  377. [JsonPropertyName("domain")]
  378. public string? Domain { get; set; }
  379. /// <summary>
  380. /// 公司所在地区。
  381. /// </summary>
  382. [JsonPropertyName("location")]
  383. public string? Location { get; set; }
  384. /// <summary>
  385. /// 公司所属行业。
  386. /// </summary>
  387. [JsonPropertyName("industry")]
  388. public string? Industry { get; set; }
  389. /// <summary>
  390. /// 公司规模。
  391. /// </summary>
  392. [JsonPropertyName("size")]
  393. public string? Size { get; set; }
  394. /// <summary>
  395. /// 公司营收信息。
  396. /// </summary>
  397. [JsonPropertyName("revenue")]
  398. public SnovioCompanyRevenue? Revenue { get; set; }
  399. /// <summary>
  400. /// 公司 Logo 地址。
  401. /// </summary>
  402. [JsonPropertyName("logo_url")]
  403. public string? LogoUrl { get; set; }
  404. }
  405. /// <summary>
  406. /// 公司搜索结果中的公司营收信息。
  407. /// </summary>
  408. public sealed class SnovioCompanyRevenue
  409. {
  410. /// <summary>
  411. /// 营收区间下限。
  412. /// </summary>
  413. [JsonPropertyName("min")]
  414. public long? Min { get; set; }
  415. /// <summary>
  416. /// 营收区间上限。
  417. /// </summary>
  418. [JsonPropertyName("max")]
  419. public long? Max { get; set; }
  420. /// <summary>
  421. /// Snov.io 返回的营收值。
  422. /// </summary>
  423. [JsonPropertyName("reported")]
  424. public long? Reported { get; set; }
  425. }
  426. /// <summary>
  427. /// 公司搜索结果元数据。
  428. /// </summary>
  429. public sealed class SnovioCompanySearchResultMeta
  430. {
  431. /// <summary>
  432. /// 异步公司搜索任务唯一标识。
  433. /// </summary>
  434. [JsonPropertyName("task_hash")]
  435. public string? TaskHash { get; set; }
  436. /// <summary>
  437. /// 当前结果页码。
  438. /// </summary>
  439. [JsonPropertyName("page")]
  440. public int Page { get; set; }
  441. }
  442. /// <summary>
  443. /// Snov.io 域名潜在客户搜索请求体。
  444. /// </summary>
  445. public sealed class SnovioDomainProspectsSearchRequest
  446. {
  447. /// <summary>
  448. /// 要搜索的公司域名,例如 snov.io。
  449. /// </summary>
  450. [JsonPropertyName("domain")]
  451. public string Domain { get; set; } = string.Empty;
  452. /// <summary>
  453. /// 结果页码,默认从第 1 页开始。
  454. /// </summary>
  455. [JsonPropertyName("page")]
  456. public int Page { get; set; } = 1;
  457. /// <summary>
  458. /// 潜在客户职位列表,最多 10 个。
  459. /// </summary>
  460. [JsonPropertyName("positions")]
  461. public List<string> Positions { get; set; } = new();
  462. /// <summary>
  463. /// 可选的异步结果回调地址。
  464. /// </summary>
  465. [JsonPropertyName("webhook_url")]
  466. public string? WebhookUrl { get; set; }
  467. }
  468. /// <summary>
  469. /// Snov.io 域名潜在客户搜索创建任务响应体。
  470. /// </summary>
  471. public sealed class SnovioDomainProspectsSearchStartResponse
  472. {
  473. /// <summary>
  474. /// 创建任务时通常为空数组。
  475. /// </summary>
  476. [JsonPropertyName("data")]
  477. public List<JsonElement> Data { get; set; } = new();
  478. /// <summary>
  479. /// 创建任务的元数据,包含域名、页码、职位和 task_hash。
  480. /// </summary>
  481. [JsonPropertyName("meta")]
  482. public SnovioDomainProspectsSearchStartMeta Meta { get; set; } = new();
  483. /// <summary>
  484. /// Snov.io 返回的结果查询地址。
  485. /// </summary>
  486. [JsonPropertyName("links")]
  487. public SnovioApiLinks Links { get; set; } = new();
  488. /// <summary>
  489. /// 创建任务响应状态。
  490. /// </summary>
  491. [JsonPropertyName("status")]
  492. public string? Status { get; set; }
  493. }
  494. /// <summary>
  495. /// Snov.io 域名潜在客户搜索创建任务元数据。
  496. /// </summary>
  497. public sealed class SnovioDomainProspectsSearchStartMeta
  498. {
  499. /// <summary>
  500. /// 本次搜索的公司域名。
  501. /// </summary>
  502. [JsonPropertyName("domain")]
  503. public string? Domain { get; set; }
  504. /// <summary>
  505. /// 结果类型,潜客搜索通常为 prospects。
  506. /// </summary>
  507. [JsonPropertyName("tab")]
  508. public string? Tab { get; set; }
  509. /// <summary>
  510. /// 异步域名潜客搜索任务唯一标识。
  511. /// </summary>
  512. [JsonPropertyName("task_hash")]
  513. public string? TaskHash { get; set; }
  514. /// <summary>
  515. /// 本次搜索请求的结果页码。
  516. /// </summary>
  517. [JsonPropertyName("page")]
  518. public int Page { get; set; }
  519. /// <summary>
  520. /// 用于筛选潜客的职位列表。
  521. /// </summary>
  522. [JsonPropertyName("positions")]
  523. public List<string> Positions { get; set; } = new();
  524. }
  525. /// <summary>
  526. /// Snov.io 域名潜在客户搜索结果响应体。
  527. /// </summary>
  528. public sealed class SnovioDomainProspectsSearchResultResponse
  529. {
  530. /// <summary>
  531. /// 当前页的潜在客户列表。
  532. /// </summary>
  533. [JsonPropertyName("data")]
  534. public List<SnovioDomainProspect> Data { get; set; } = new();
  535. /// <summary>
  536. /// 域名潜在客户搜索结果元数据。
  537. /// </summary>
  538. [JsonPropertyName("meta")]
  539. public SnovioDomainProspectsSearchResultMeta Meta { get; set; } = new();
  540. /// <summary>
  541. /// 下一页结果链接信息。
  542. /// </summary>
  543. [JsonPropertyName("links")]
  544. public SnovioApiLinks Links { get; set; } = new();
  545. /// <summary>
  546. /// 任务处理状态,例如 completed 或 in_progress。
  547. /// </summary>
  548. [JsonPropertyName("status")]
  549. public string? Status { get; set; }
  550. }
  551. /// <summary>
  552. /// Snov.io 域名潜在客户搜索结果中的潜客信息。
  553. /// </summary>
  554. public sealed class SnovioDomainProspect
  555. {
  556. /// <summary>
  557. /// 潜客名。
  558. /// </summary>
  559. [JsonPropertyName("first_name")]
  560. public string? FirstName { get; set; }
  561. /// <summary>
  562. /// 潜客姓。
  563. /// </summary>
  564. [JsonPropertyName("last_name")]
  565. public string? LastName { get; set; }
  566. /// <summary>
  567. /// 潜客职位。
  568. /// </summary>
  569. [JsonPropertyName("position")]
  570. public string? Position { get; set; }
  571. /// <summary>
  572. /// Snov.io 获取该潜客信息的来源页面。
  573. /// </summary>
  574. [JsonPropertyName("source_page")]
  575. public string? SourcePage { get; set; }
  576. /// <summary>
  577. /// 启动该潜客邮箱检索任务的地址,地址末段包含 prospect_hash。
  578. /// </summary>
  579. [JsonPropertyName("search_emails_start")]
  580. public string? SearchEmailsStart { get; set; }
  581. }
  582. /// <summary>
  583. /// Snov.io 域名潜在客户搜索结果元数据。
  584. /// </summary>
  585. public sealed class SnovioDomainProspectsSearchResultMeta
  586. {
  587. /// <summary>
  588. /// 本次搜索的公司域名。
  589. /// </summary>
  590. [JsonPropertyName("domain")]
  591. public string? Domain { get; set; }
  592. /// <summary>
  593. /// 结果类型,潜客搜索通常为 prospects。
  594. /// </summary>
  595. [JsonPropertyName("tab")]
  596. public string? Tab { get; set; }
  597. /// <summary>
  598. /// 异步域名潜客搜索任务唯一标识。
  599. /// </summary>
  600. [JsonPropertyName("task_hash")]
  601. public string? TaskHash { get; set; }
  602. /// <summary>
  603. /// 当前结果页码。
  604. /// </summary>
  605. [JsonPropertyName("page")]
  606. public int Page { get; set; }
  607. /// <summary>
  608. /// 本次搜索使用的职位筛选列表。
  609. /// </summary>
  610. [JsonPropertyName("positions")]
  611. public List<string> Positions { get; set; } = new();
  612. /// <summary>
  613. /// 符合条件的潜客总数。
  614. /// </summary>
  615. [JsonPropertyName("total_count")]
  616. public int TotalCount { get; set; }
  617. }
  618. /// <summary>
  619. /// Snov.io 潜客邮箱检索请求体。
  620. /// </summary>
  621. public sealed class SnovioProspectEmailSearchRequest
  622. {
  623. /// <summary>
  624. /// 可选的异步任务回调地址。
  625. /// </summary>
  626. [JsonPropertyName("webhook_url")]
  627. public string? WebhookUrl { get; set; }
  628. }
  629. /// <summary>
  630. /// Snov.io 潜客邮箱检索创建任务响应体。
  631. /// </summary>
  632. public sealed class SnovioProspectEmailSearchStartResponse
  633. {
  634. /// <summary>
  635. /// 创建任务时通常为空数组。
  636. /// </summary>
  637. [JsonPropertyName("data")]
  638. public List<JsonElement> Data { get; set; } = new();
  639. /// <summary>
  640. /// 创建任务的元数据,包含邮箱检索 task_hash。
  641. /// </summary>
  642. [JsonPropertyName("meta")]
  643. public SnovioProspectEmailSearchStartMeta Meta { get; set; } = new();
  644. /// <summary>
  645. /// Snov.io 返回的邮箱结果查询地址。
  646. /// </summary>
  647. [JsonPropertyName("links")]
  648. public SnovioApiLinks Links { get; set; } = new();
  649. /// <summary>
  650. /// 创建任务响应状态。
  651. /// </summary>
  652. [JsonPropertyName("status")]
  653. public string? Status { get; set; }
  654. }
  655. /// <summary>
  656. /// Snov.io 潜客邮箱检索创建任务元数据。
  657. /// </summary>
  658. public sealed class SnovioProspectEmailSearchStartMeta
  659. {
  660. /// <summary>
  661. /// 异步邮箱检索任务唯一标识,用于查询邮箱结果。
  662. /// </summary>
  663. [JsonPropertyName("task_hash")]
  664. public string? TaskHash { get; set; }
  665. }
  666. /// <summary>
  667. /// Snov.io 潜客邮箱检索结果响应体。
  668. /// </summary>
  669. public sealed class SnovioProspectEmailSearchResultResponse
  670. {
  671. /// <summary>
  672. /// 潜客邮箱检索结果数据。
  673. /// </summary>
  674. [JsonPropertyName("data")]
  675. public SnovioProspectEmailSearchResultData Data { get; set; } = new();
  676. /// <summary>
  677. /// 邮箱检索任务元数据。
  678. /// </summary>
  679. [JsonPropertyName("meta")]
  680. public SnovioProspectEmailSearchResultMeta Meta { get; set; } = new();
  681. // 文档示例中该字段返回空数组,因此使用 JsonElement 列表兼容实际响应。
  682. [JsonPropertyName("links")]
  683. public List<JsonElement> Links { get; set; } = new();
  684. /// <summary>
  685. /// 任务处理状态,例如 completed 或 in_progress。
  686. /// </summary>
  687. [JsonPropertyName("status")]
  688. public string? Status { get; set; }
  689. }
  690. /// <summary>
  691. /// Snov.io 潜客邮箱检索结果数据。
  692. /// </summary>
  693. [System.Text.Json.Serialization.JsonConverter(typeof(SnovioProspectEmailSearchResultDataConverter))]
  694. public sealed class SnovioProspectEmailSearchResultData
  695. {
  696. /// <summary>
  697. /// 邮箱检索任务开始时间。
  698. /// </summary>
  699. [JsonPropertyName("searching_date")]
  700. public string? SearchingDate { get; set; }
  701. /// <summary>
  702. /// 当前潜客检索到的邮箱列表。
  703. /// </summary>
  704. [JsonPropertyName("emails")]
  705. public List<SnovioProspectEmail> Emails { get; set; } = new();
  706. }
  707. /// <summary>
  708. /// 兼容 Snov.io 潜客邮箱结果中 data 为对象或空数组的响应转换器。
  709. /// </summary>
  710. internal sealed class SnovioProspectEmailSearchResultDataConverter
  711. : System.Text.Json.Serialization.JsonConverter<SnovioProspectEmailSearchResultData>
  712. {
  713. public override SnovioProspectEmailSearchResultData Read(
  714. ref Utf8JsonReader reader,
  715. Type typeToConvert,
  716. JsonSerializerOptions options)
  717. {
  718. if (reader.TokenType == JsonTokenType.Null)
  719. return new SnovioProspectEmailSearchResultData();
  720. if (reader.TokenType == JsonTokenType.StartObject)
  721. {
  722. using var document = JsonDocument.ParseValue(ref reader);
  723. var root = document.RootElement;
  724. var result = new SnovioProspectEmailSearchResultData();
  725. if (root.TryGetProperty("searching_date", out var searchingDate) &&
  726. searchingDate.ValueKind == JsonValueKind.String)
  727. {
  728. result.SearchingDate = searchingDate.GetString();
  729. }
  730. if (root.TryGetProperty("emails", out var emailsElement) &&
  731. emailsElement.ValueKind == JsonValueKind.Array)
  732. {
  733. result.Emails = emailsElement.Deserialize<List<SnovioProspectEmail>>(options)
  734. ?? new List<SnovioProspectEmail>();
  735. }
  736. return result;
  737. }
  738. if (reader.TokenType == JsonTokenType.StartArray)
  739. {
  740. using var document = JsonDocument.ParseValue(ref reader);
  741. var emails = document.RootElement.Deserialize<List<SnovioProspectEmail>>(options);
  742. return new SnovioProspectEmailSearchResultData
  743. {
  744. Emails = emails ?? new List<SnovioProspectEmail>()
  745. };
  746. }
  747. throw new System.Text.Json.JsonException(
  748. "Snov.io 潜客邮箱检索响应中的 data 必须是对象、数组或 null。");
  749. }
  750. public override void Write(
  751. Utf8JsonWriter writer,
  752. SnovioProspectEmailSearchResultData value,
  753. JsonSerializerOptions options)
  754. {
  755. writer.WriteStartObject();
  756. if (value.SearchingDate != null)
  757. writer.WriteString("searching_date", value.SearchingDate);
  758. writer.WritePropertyName("emails");
  759. System.Text.Json.JsonSerializer.Serialize(writer, value.Emails, options);
  760. writer.WriteEndObject();
  761. }
  762. }
  763. /// <summary>
  764. /// Snov.io 潜客邮箱信息。
  765. /// </summary>
  766. public sealed class SnovioProspectEmail
  767. {
  768. /// <summary>
  769. /// 潜客邮箱地址。
  770. /// </summary>
  771. [JsonPropertyName("email")]
  772. public string? Email { get; set; }
  773. /// <summary>
  774. /// SMTP 校验状态,例如 valid 或 unknown。
  775. /// </summary>
  776. [JsonPropertyName("smtp_status")]
  777. public string? SmtpStatus { get; set; }
  778. }
  779. /// <summary>
  780. /// Snov.io 潜客邮箱检索结果元数据。
  781. /// </summary>
  782. public sealed class SnovioProspectEmailSearchResultMeta
  783. {
  784. /// <summary>
  785. /// 异步邮箱检索任务唯一标识。
  786. /// </summary>
  787. [JsonPropertyName("task_hash")]
  788. public string? TaskHash { get; set; }
  789. }
  790. /// <summary>
  791. /// Snov.io 异步任务结果链接。
  792. /// </summary>
  793. public sealed class SnovioApiLinks
  794. {
  795. /// <summary>
  796. /// 当前异步任务的结果查询地址。
  797. /// </summary>
  798. [JsonPropertyName("result")]
  799. public string? Result { get; set; }
  800. /// <summary>
  801. /// 下一页结果地址;没有下一页时通常为空字符串或 null。
  802. /// </summary>
  803. [JsonPropertyName("next")]
  804. public string? Next { get; set; }
  805. }