content_mode.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. """Business-level content mode helpers for acquisition."""
  2. from __future__ import annotations
  3. from dataclasses import dataclass
  4. from typing import Any, Literal
  5. ContentMode = Literal["image_post", "video_post", "article", "unsupported"]
  6. IMAGE_POST: ContentMode = "image_post"
  7. VIDEO_POST: ContentMode = "video_post"
  8. ARTICLE: ContentMode = "article"
  9. UNSUPPORTED: ContentMode = "unsupported"
  10. @dataclass(frozen=True)
  11. class ContentModeGuard:
  12. can_process: bool
  13. reason: str = ""
  14. label: str = ""
  15. metadata: dict[str, Any] | None = None
  16. def _has_any(values: list[str] | None) -> bool:
  17. return any(bool(value) for value in values or [])
  18. def _raw_type(raw: dict[str, Any] | None) -> str:
  19. if not isinstance(raw, dict):
  20. return ""
  21. candidates = [
  22. raw.get("type"),
  23. raw.get("content_type"),
  24. raw.get("note_type"),
  25. (raw.get("note_card") or {}).get("type") if isinstance(raw.get("note_card"), dict) else None,
  26. raw.get("aweme_type"),
  27. ]
  28. for value in candidates:
  29. if value is not None and str(value).strip():
  30. return str(value).strip().lower()
  31. return ""
  32. def infer_content_mode(
  33. *,
  34. platform: str,
  35. content_type: str | None = None,
  36. body_text: str | None = None,
  37. image_urls: list[str] | None = None,
  38. video_urls: list[str] | None = None,
  39. raw: dict[str, Any] | None = None,
  40. ) -> ContentMode:
  41. """Infer the business mode without overwriting the platform raw type."""
  42. platform_key = (platform or "").lower()
  43. raw_kind = _raw_type(raw)
  44. raw_content_type = (content_type or "").strip().lower()
  45. has_image = _has_any(image_urls)
  46. has_video = _has_any(video_urls)
  47. has_text = bool((body_text or "").strip())
  48. if platform_key == "weixin":
  49. return ARTICLE
  50. if has_video:
  51. return VIDEO_POST
  52. if has_image or has_text:
  53. return IMAGE_POST
  54. if raw_kind in {"normal", "note", "image", "image_post", "图文"}:
  55. return IMAGE_POST
  56. if raw_content_type in {"normal", "note", "image", "image_post", "图文"}:
  57. return IMAGE_POST
  58. return UNSUPPORTED
  59. def guard_content_for_processing(
  60. *,
  61. content_mode: str | None,
  62. video_urls: list[str] | None = None,
  63. ) -> ContentModeGuard:
  64. """Return whether this item should enter media/classify/decode processing."""
  65. if content_mode == UNSUPPORTED:
  66. return ContentModeGuard(
  67. can_process=False,
  68. reason="unsupported_content_mode",
  69. label="unsupported_content_mode",
  70. metadata={"skip_reason": "unsupported_content_mode"},
  71. )
  72. if content_mode == VIDEO_POST and not _has_any(video_urls):
  73. return ContentModeGuard(
  74. can_process=False,
  75. reason="video_url_missing",
  76. label="video_missing",
  77. metadata={"skip_reason": "video_url_missing", "video_url_missing": True},
  78. )
  79. return ContentModeGuard(can_process=True)
  80. __all__ = [
  81. "ARTICLE",
  82. "ContentMode",
  83. "ContentModeGuard",
  84. "IMAGE_POST",
  85. "UNSUPPORTED",
  86. "VIDEO_POST",
  87. "guard_content_for_processing",
  88. "infer_content_mode",
  89. ]