telepost-bot 2.84.3__tar.gz → 2.84.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/CHANGELOG.md +9 -0
  2. {telepost_bot-2.84.3/telepost_bot.egg-info → telepost_bot-2.84.4}/PKG-INFO +1 -1
  3. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/publish.py +1 -1
  4. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/delivery_planner.py +31 -3
  5. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/build_info.py +1 -1
  6. {telepost_bot-2.84.3 → telepost_bot-2.84.4/telepost_bot.egg-info}/PKG-INFO +1 -1
  7. telepost_bot-2.84.4/tests/test_delivery_planner.py +210 -0
  8. telepost_bot-2.84.3/tests/test_delivery_planner.py +0 -112
  9. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/LICENSE +0 -0
  10. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/MANIFEST.in +0 -0
  11. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/README.en.md +0 -0
  12. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/README.md +0 -0
  13. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/config/__init__.py +0 -0
  14. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/config/fly-telepost-policy.example.json +0 -0
  15. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/config/review_policy.md +0 -0
  16. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/config/settings.py +0 -0
  17. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/config.ini.example +0 -0
  18. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/database/__init__.py +0 -0
  19. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/database/db_manager.py +0 -0
  20. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/__init__.py +0 -0
  21. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/api_commands.py +0 -0
  22. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/automation.py +0 -0
  23. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/botconfig.py +0 -0
  24. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/callback_handlers.py +0 -0
  25. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/channel_listener.py +0 -0
  26. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/command_handlers.py +0 -0
  27. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/conversation.py +0 -0
  28. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/error_handler.py +0 -0
  29. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/index_handlers.py +0 -0
  30. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/mode_selection.py +0 -0
  31. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/moderation.py +0 -0
  32. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/preview_handlers.py +0 -0
  33. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/reaction_stats.py +0 -0
  34. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/recovery.py +0 -0
  35. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/review.py +0 -0
  36. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/schedule_status.py +0 -0
  37. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/search_handlers.py +0 -0
  38. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/stats_handlers.py +0 -0
  39. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/handlers/upload.py +0 -0
  40. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/main.py +0 -0
  41. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/mcp_server/__init__.py +0 -0
  42. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/mcp_server/review_client.py +0 -0
  43. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/mcp_server/server.py +0 -0
  44. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/models/__init__.py +0 -0
  45. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/models/state.py +0 -0
  46. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/__init__.py +0 -0
  47. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/SOURCES.sha256 +0 -0
  48. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/__init__.py +0 -0
  49. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/error-mapping.json +0 -0
  50. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/ackresult.candidate_search.json +0 -0
  51. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/capabilities.pixivflow.json +0 -0
  52. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/event.job.expired.json +0 -0
  53. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/event.job.succeeded.json +0 -0
  54. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/eventpage.candidate_search.json +0 -0
  55. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/job.cancelled.candidate_search.json +0 -0
  56. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/job.failed.no_candidate.json +0 -0
  57. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/job.queued.json +0 -0
  58. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/job.running.json +0 -0
  59. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/job.succeeded.candidate_search.json +0 -0
  60. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/jobpage.candidate_search.json +0 -0
  61. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/task.candidate_search.json +0 -0
  62. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/fixtures/task.candidate_search.target_only.json +0 -0
  63. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/protocol/v1/protocol.schema.json +0 -0
  64. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/pyproject.toml +0 -0
  65. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/run.py +0 -0
  66. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/services/__init__.py +0 -0
  67. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/services/review_service.py +0 -0
  68. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/setup.cfg +0 -0
  69. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/__init__.py +0 -0
  70. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/api/__init__.py +0 -0
  71. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/__init__.py +0 -0
  72. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/admin_ops.py +0 -0
  73. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/automation.py +0 -0
  74. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/editorial.py +0 -0
  75. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/hot.py +0 -0
  76. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/novel_fallback_card.py +0 -0
  77. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/novel_preview.py +0 -0
  78. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/pixivflow_jobs.py +0 -0
  79. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/post_media.py +0 -0
  80. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/posts.py +0 -0
  81. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/publication.py +0 -0
  82. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/reaction_backfill.py +0 -0
  83. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/recovery.py +0 -0
  84. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/refetch.py +0 -0
  85. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/rerender.py +0 -0
  86. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/resubmit.py +0 -0
  87. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/review_queue.py +0 -0
  88. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/submitter_notify.py +0 -0
  89. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/application/telepress_provider.py +0 -0
  90. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/__init__.py +0 -0
  91. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/automation.py +0 -0
  92. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/delivery.py +0 -0
  93. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/editorial.py +0 -0
  94. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/errors.py +0 -0
  95. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/hot.py +0 -0
  96. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/media.py +0 -0
  97. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/navigation.py +0 -0
  98. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/novel_preview.py +0 -0
  99. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/packing.py +0 -0
  100. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/presentation.py +0 -0
  101. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/presentation_policy.py +0 -0
  102. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/refetch_state.py +0 -0
  103. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/review.py +0 -0
  104. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/review_chat.py +0 -0
  105. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/domain/submission.py +0 -0
  106. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/miniapp/__init__.py +0 -0
  107. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/miniapp/auth.py +0 -0
  108. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/miniapp/rbac.py +0 -0
  109. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/miniapp/role_bindings.py +0 -0
  110. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/miniapp/session.py +0 -0
  111. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/observability/__init__.py +0 -0
  112. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/observability/audit.py +0 -0
  113. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/observability/cli.py +0 -0
  114. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/observability/doctor.py +0 -0
  115. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/observability/errors.py +0 -0
  116. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/observability/redact.py +0 -0
  117. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/__init__.py +0 -0
  118. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/__init__.py +0 -0
  119. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/automation.py +0 -0
  120. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/columns.py +0 -0
  121. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/editorial.py +0 -0
  122. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/inbox.py +0 -0
  123. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/ledger.py +0 -0
  124. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/media_assets.py +0 -0
  125. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/moderation.py +0 -0
  126. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/novel_preview.py +0 -0
  127. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/posts.py +0 -0
  128. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/protocol_events.py +0 -0
  129. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/recovery.py +0 -0
  130. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/refetch.py +0 -0
  131. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/reviews.py +0 -0
  132. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/storage/sqlite/submitter_notifications.py +0 -0
  133. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/__init__.py +0 -0
  134. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/callback_feedback.py +0 -0
  135. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/capture.py +0 -0
  136. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/__init__.py +0 -0
  137. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/discussion.py +0 -0
  138. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/executor.py +0 -0
  139. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/gateway.py +0 -0
  140. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/legacy_runner.py +0 -0
  141. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/planner.py +0 -0
  142. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/preparation.py +0 -0
  143. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/registry.py +0 -0
  144. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/delivery/sender.py +0 -0
  145. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/inbox_worker.py +0 -0
  146. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/review_expiry.py +0 -0
  147. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/review_keyboard.py +0 -0
  148. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/review_media.py +0 -0
  149. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost/telegram/review_stager.py +0 -0
  150. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost_bot.egg-info/SOURCES.txt +0 -0
  151. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost_bot.egg-info/dependency_links.txt +0 -0
  152. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost_bot.egg-info/entry_points.txt +0 -0
  153. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost_bot.egg-info/requires.txt +0 -0
  154. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/telepost_bot.egg-info/top_level.txt +0 -0
  155. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_admin_api.py +0 -0
  156. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_anonymous.py +0 -0
  157. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_api_commands.py +0 -0
  158. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_api_server.py +0 -0
  159. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_api_version.py +0 -0
  160. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_architecture_application.py +0 -0
  161. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_architecture_core.py +0 -0
  162. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_automation.py +0 -0
  163. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_backfill_attribution.py +0 -0
  164. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_blacklist.py +0 -0
  165. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_bot_language.py +0 -0
  166. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_bot_token_logging.py +0 -0
  167. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_botconfig.py +0 -0
  168. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_boundary.py +0 -0
  169. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_callback_contract.py +0 -0
  170. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_caption_mentions.py +0 -0
  171. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_compatibility.py +0 -0
  172. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_config_encoding.py +0 -0
  173. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_console_encoding.py +0 -0
  174. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_conversation_flow.py +0 -0
  175. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_data_class_cleanup.py +0 -0
  176. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_database.py +0 -0
  177. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_database_first_start.py +0 -0
  178. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_database_operations.py +0 -0
  179. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_discussion_uncertainty.py +0 -0
  180. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_disposition.py +0 -0
  181. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_doctor.py +0 -0
  182. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_editorial.py +0 -0
  183. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_file_validator.py +0 -0
  184. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_fly_deploy_contract.py +0 -0
  185. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_frozen_support.py +0 -0
  186. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_handlers.py +0 -0
  187. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_heat_calculator.py +0 -0
  188. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_helper_functions.py +0 -0
  189. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_hot_domain.py +0 -0
  190. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_identity_provenance.py +0 -0
  191. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_install_scripts.py +0 -0
  192. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_integration.py +0 -0
  193. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_legacy_delivery_port_navigation.py +0 -0
  194. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_logic_flow.py +0 -0
  195. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_logical_submissions.py +0 -0
  196. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_maintenance_jobs.py +0 -0
  197. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_manager_notifications.py +0 -0
  198. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_media_decision_audit.py +0 -0
  199. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_media_delivery_pipeline.py +0 -0
  200. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_media_domain.py +0 -0
  201. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_media_preparation.py +0 -0
  202. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_media_proxy_delivery.py +0 -0
  203. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_menu_shortcut_single_reply.py +0 -0
  204. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_messages.py +0 -0
  205. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_miniapp_api.py +0 -0
  206. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_miniapp_auth.py +0 -0
  207. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_miniapp_content.py +0 -0
  208. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_miniapp_entries.py +0 -0
  209. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_miniapp_history.py +0 -0
  210. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_miniapp_rbac.py +0 -0
  211. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_mixed_media_integrity.py +0 -0
  212. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_moderation_and_admin_ux.py +0 -0
  213. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_navigation.py +0 -0
  214. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_no_owner_probe.py +0 -0
  215. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_novel_cover_preview.py +0 -0
  216. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_novel_delivery_semantics.py +0 -0
  217. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_novel_preview.py +0 -0
  218. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_observability.py +0 -0
  219. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_observability_lifecycle.py +0 -0
  220. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_online_reading_root_button.py +0 -0
  221. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_packing_ssot.py +0 -0
  222. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_polling_server.py +0 -0
  223. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_preview_pagination.py +0 -0
  224. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_production_handler_graph.py +0 -0
  225. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_protocol_contract.py +0 -0
  226. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_publication_presentation.py +0 -0
  227. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_publish_idempotency.py +0 -0
  228. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_publish_regressions.py +0 -0
  229. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_publish_reply.py +0 -0
  230. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_reaction_backfill.py +0 -0
  231. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_reaction_stats.py +0 -0
  232. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_recovery.py +0 -0
  233. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_refetch.py +0 -0
  234. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_refetch_card_state.py +0 -0
  235. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_refetch_events.py +0 -0
  236. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_refetch_job_lifecycle.py +0 -0
  237. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_refetch_reason_normalization.py +0 -0
  238. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_refetch_replacement.py +0 -0
  239. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_remote_fetch_fallback.py +0 -0
  240. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_rerender.py +0 -0
  241. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_resubmit.py +0 -0
  242. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review.py +0 -0
  243. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_album_payload.py +0 -0
  244. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_card_recovery.py +0 -0
  245. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_chat_contract.py +0 -0
  246. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_consistency.py +0 -0
  247. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_expiry_recovery.py +0 -0
  248. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_http_mcp.py +0 -0
  249. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_media_lifecycle.py +0 -0
  250. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_review_service.py +0 -0
  251. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_robustness.py +0 -0
  252. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_run_dispatcher.py +0 -0
  253. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_run_mode.py +0 -0
  254. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_run_supervisor.py +0 -0
  255. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_schedule_outcomes.py +0 -0
  256. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_search_disabled_hint.py +0 -0
  257. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_search_recovery.py +0 -0
  258. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_security.py +0 -0
  259. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_security_headers.py +0 -0
  260. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_session_database_handles.py +0 -0
  261. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_shutdown.py +0 -0
  262. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_streaming_uploads.py +0 -0
  263. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_submission_timeout_idempotency.py +0 -0
  264. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_telegram_golden_messages.py +0 -0
  265. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_telegram_media_cache.py +0 -0
  266. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_update_inbox.py +0 -0
  267. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_upload.py +0 -0
  268. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_user_friendly_messages.py +0 -0
  269. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_webhook_payload_admission.py +0 -0
  270. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_webhook_ready.py +0 -0
  271. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_webhook_router.py +0 -0
  272. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_webhook_secret.py +0 -0
  273. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_webhook_secret_logging.py +0 -0
  274. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_webhook_static.py +0 -0
  275. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/tests/test_work_id_wire.py +0 -0
  276. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/ui/__init__.py +0 -0
  277. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/ui/i18n.py +0 -0
  278. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/ui/keyboards.py +0 -0
  279. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/ui/locales/en/LC_MESSAGES/telepost.mo +0 -0
  280. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/ui/locales/en/LC_MESSAGES/telepost.po +0 -0
  281. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/ui/messages.py +0 -0
  282. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/__init__.py +0 -0
  283. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/api_server.py +0 -0
  284. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/api_tokens.py +0 -0
  285. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/blacklist.py +0 -0
  286. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/cache.py +0 -0
  287. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/config_wizard.py +0 -0
  288. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/console.py +0 -0
  289. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/database.py +0 -0
  290. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/file_validator.py +0 -0
  291. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/heat_calculator.py +0 -0
  292. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/helper_functions.py +0 -0
  293. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/index_manager.py +0 -0
  294. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/logging_config.py +0 -0
  295. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/maintenance_jobs.py +0 -0
  296. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/polling_server.py +0 -0
  297. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/run_mode.py +0 -0
  298. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/runtime_policy.py +0 -0
  299. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/schedule_watchdog.py +0 -0
  300. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/search_engine.py +0 -0
  301. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/security_headers.py +0 -0
  302. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/submission.py +0 -0
  303. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/utils/webhook_server.py +0 -0
  304. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/webapp/__init__.py +0 -0
  305. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/webapp/dist/assets/2d82b92e720462f8dd3b-Bh4Fahcw.svg +0 -0
  306. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/webapp/dist/assets/index-CLfkW8eZ.css +0 -0
  307. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/webapp/dist/assets/index-CfjBQN_e.js +0 -0
  308. {telepost_bot-2.84.3 → telepost_bot-2.84.4}/webapp/dist/index.html +0 -0
@@ -8,6 +8,15 @@
8
8
  > 发布说明面向普通用户:写「对用户有什么改变」,不写内部实现细节(模块名、函数名、配置键);
9
9
  > 技术细节放进代码注释或 `docs/`。每个 Release 的正文由本文件对应版本段自动生成。
10
10
 
11
+ ## [2.84.4](https://github.com/redtidev1918/TelePost/compare/v2.84.3...v2.84.4) (2026-10-24)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * drop chain asset refs that belong to a different work ([f277843](https://github.com/redtidev1918/TelePost/commit/f2778430929694219321c49864ec10e6bd31e16e))
17
+ * keep staged media kind when chain assets downgrade to photo ([d6ae2d1](https://github.com/redtidev1918/TelePost/commit/d6ae2d1c3c316278c2364d98966802ebac841b39))
18
+ * plan replacement publications with the real staged media type ([59f0af9](https://github.com/redtidev1918/TelePost/commit/59f0af92b76c959ea653111674933db3a603e94f))
19
+
11
20
  ## [2.84.3](https://github.com/redtidev1918/TelePost/compare/v2.84.2...v2.84.3) (2026-10-24)
12
21
 
13
22
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telepost-bot
3
- Version: 2.84.3
3
+ Version: 2.84.4
4
4
  Summary: Telegram submission, review, and publication bot with a Mini App, HTTP API, and multi-bot supervisor.
5
5
  Author: redtidev1918
6
6
  License-Expression: MIT
@@ -745,7 +745,7 @@ async def publish_from_file_ids(bot, media, documents, *, tags="", title="",
745
745
  assets = [MediaAsset.coerce(a) for a in (media_assets or [])]
746
746
 
747
747
  if assets:
748
- plan = plan_review_media(media, assets, documents=documents)
748
+ plan = plan_review_media(media, assets, documents=documents, work_id=pid)
749
749
  items = plan.to_media_items(spoiler=spoiler)
750
750
  else:
751
751
  items = _items_from_dicts([
@@ -111,6 +111,23 @@ def _asset_kind_to_media_kind(asset_kind: str) -> str:
111
111
  return str(asset_kind).strip().lower()
112
112
 
113
113
 
114
+ def _asset_belongs_to_other_work(asset_id: str, work_id: str) -> bool:
115
+ """True only for a ``pixiv:<work_id>:...`` ref whose work is NOT this one.
116
+
117
+ Unparseable / non-pixiv asset ids stay (conservative): filtering them would
118
+ silently drop legitimate uploads. A chain's refs are replaced per
119
+ submission, but a refetch replacement that arrives without its own asset
120
+ manifest leaves the SUPERSEDED work's refs on the chain — publishing must
121
+ never ship another work's pictures as this submission's content (review
122
+ 208: a 4-page illustration refetch-replaced by a ugoira).
123
+ """
124
+ parts = str(asset_id or "").split(":")
125
+ if len(parts) < 3 or parts[0] != "pixiv":
126
+ return False
127
+ ref_work = parts[1].strip()
128
+ return bool(ref_work) and bool(work_id) and ref_work != work_id
129
+
130
+
114
131
  def _overall_strategy(entries: List[MediaPlanEntry]) -> str:
115
132
  if not entries:
116
133
  return "empty"
@@ -126,7 +143,8 @@ def _overall_strategy(entries: List[MediaPlanEntry]) -> str:
126
143
 
127
144
  def plan_review_media(media: List[Dict[str, Any]],
128
145
  media_assets: List[Dict[str, Any]],
129
- *, documents: Optional[List[Dict[str, Any]]] = None) -> MediaDeliveryPlan:
146
+ *, documents: Optional[List[Dict[str, Any]]] = None,
147
+ work_id: str = "") -> MediaDeliveryPlan:
130
148
  """Build a delivery plan from a review's local media + canonical asset refs.
131
149
 
132
150
  ``media`` / ``documents`` mirror the raw ``pending_reviews`` JSON arrays
@@ -137,8 +155,14 @@ def plan_review_media(media: List[Dict[str, Any]],
137
155
  Positional pairing (same index) is used because PixivFlow manifests are
138
156
  ordered and match the Telegram file list order; a mismatch can ONLY fall
139
157
  back to ``remote_url`` and never produces a bogus file_id.
158
+
159
+ ``work_id`` drops chain refs that provably belong to a DIFFERENT pixiv
160
+ work (a refetch replacement may leave the superseded work's refs behind).
140
161
  """
141
162
  assets = [MediaAsset.coerce(a) for a in (media_assets or [])]
163
+ if work_id:
164
+ assets = [a for a in assets
165
+ if not _asset_belongs_to_other_work(a.asset_id, work_id)]
142
166
  media_items = list(media or [])
143
167
  document_items = list(documents or [])
144
168
  local_items = media_items + document_items
@@ -182,8 +206,10 @@ def plan_review_media(media: List[Dict[str, Any]],
182
206
  else:
183
207
  local = local_items[index] if index < len(local_items) else None
184
208
  # The indexed local item is authoritative for presentation: a Telegram
185
- # TXT document must never be sent as a photo, even though the matching
186
- # canonical asset ref is kind=image (novel covers + TXT document case).
209
+ # TXT document must never be sent as a photo, and a staged animation /
210
+ # video / audio file_id must keep its real Telegram type even when the
211
+ # canonical asset ref (or a stale refetch-chain ref) says kind=image
212
+ # (review 208: sendPhoto on an animation file_id is a certain reject).
187
213
  local_kind = (
188
214
  str(local.get("type") or local.get("kind") or "")
189
215
  .strip().lower().replace("-", "_")
@@ -198,6 +224,8 @@ def plan_review_media(media: List[Dict[str, Any]],
198
224
  )
199
225
  if is_document:
200
226
  kind = MediaKind.DOCUMENT.value
227
+ elif local_kind:
228
+ kind = _asset_kind_to_media_kind(local_kind)
201
229
  else:
202
230
  kind = MediaKind.PHOTO.value
203
231
  local_file_id = (str(local.get("file_id") or "").strip()
@@ -8,7 +8,7 @@ to the entry points; when present it overrides these committed defaults.
8
8
  # Bumped automatically by release-please (extra-files in
9
9
  # release-please-config.json). Release bundles and images override it at build
10
10
  # time with the exact version/commit; this constant is the local-dev fallback.
11
- RELEASE_VERSION = "2.84.3" # x-release-please-version
11
+ RELEASE_VERSION = "2.84.4" # x-release-please-version
12
12
  RELEASE_COMMIT = "dev"
13
13
  BUILD_DATE = "dev"
14
14
  SERVICE = "telepost"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telepost-bot
3
- Version: 2.84.3
3
+ Version: 2.84.4
4
4
  Summary: Telegram submission, review, and publication bot with a Mini App, HTTP API, and multi-bot supervisor.
5
5
  Author: redtidev1918
6
6
  License-Expression: MIT
@@ -0,0 +1,210 @@
1
+ """Step 11 DeliveryPlanner: TelePost decides how to deliver review media."""
2
+ import pytest
3
+
4
+ from telepost.application.delivery_planner import (
5
+ STRATEGY_FILE_ID,
6
+ STRATEGY_REMOTE_URL,
7
+ plan_review_media,
8
+ )
9
+
10
+
11
+ class TestPlanReviewMedia:
12
+ def test_no_assets_uses_local_file_ids(self):
13
+ plan = plan_review_media(
14
+ [{"type": "photo", "file_id": "AAA"}],
15
+ [],
16
+ )
17
+ assert plan.strategy == STRATEGY_FILE_ID
18
+ assert len(plan.entries) == 1
19
+ assert plan.entries[0].strategy == STRATEGY_FILE_ID
20
+ assert plan.entries[0].file_id == "AAA"
21
+
22
+ def test_asset_with_file_id_prefers_zero_reupload(self):
23
+ plan = plan_review_media(
24
+ [{"type": "photo", "file_id": "AAA"}],
25
+ [{"asset_id": "pixiv-1", "kind": "image",
26
+ "source_url": "https://i.pximg.net/1.jpg"}],
27
+ )
28
+ assert plan.strategy == STRATEGY_FILE_ID
29
+ assert plan.entries[0].strategy == STRATEGY_FILE_ID
30
+ assert plan.entries[0].asset_id == "pixiv-1"
31
+
32
+ def test_asset_without_file_id_falls_back_to_remote_url(self):
33
+ plan = plan_review_media(
34
+ [],
35
+ [{"asset_id": "pixiv-1", "kind": "image",
36
+ "source_url": "https://proxy.example/pixiv/1.jpg"}],
37
+ )
38
+ assert plan.strategy == STRATEGY_REMOTE_URL
39
+ assert plan.entries[0].strategy == STRATEGY_REMOTE_URL
40
+ assert plan.entries[0].source_url == "https://proxy.example/pixiv/1.jpg"
41
+
42
+ def test_mixed_plan(self):
43
+ plan = plan_review_media(
44
+ [{"type": "photo", "file_id": "AAA"}],
45
+ [
46
+ {"asset_id": "a1", "kind": "image", "source_url": "https://x/a"},
47
+ {"asset_id": "a2", "kind": "image", "source_url": "https://x/b"},
48
+ ],
49
+ )
50
+ assert plan.strategy == "mixed"
51
+ strategies = [e.strategy for e in plan.entries]
52
+ assert strategies == [STRATEGY_FILE_ID, STRATEGY_REMOTE_URL]
53
+
54
+ def test_stale_chain_asset_never_downgrades_local_animation_to_photo(self):
55
+ """Regression (review 208): a refetch replacement staged an animation
56
+ file_id while the chain's media_asset_refs still declared the OLD
57
+ multi-page work as kind=image. The indexed local item is authoritative:
58
+ sendPhoto on an animation file_id is a certain Telegram reject."""
59
+ plan = plan_review_media(
60
+ [{"type": "animation", "file_id": "UGOIRA_FID"}],
61
+ [
62
+ {"asset_id": "pixiv:1:illust:page-1", "kind": "image",
63
+ "source_url": "https://x/1"},
64
+ {"asset_id": "pixiv:1:illust:page-2", "kind": "image",
65
+ "source_url": "https://x/2"},
66
+ ],
67
+ )
68
+ assert plan.entries[0].kind == "animation"
69
+ assert plan.entries[0].strategy == STRATEGY_FILE_ID
70
+ assert plan.entries[0].file_id == "UGOIRA_FID"
71
+ # The asset-only tail keeps the documented remote fallback.
72
+ assert plan.entries[1].kind == "photo"
73
+ assert plan.entries[1].strategy == STRATEGY_REMOTE_URL
74
+
75
+ def test_local_video_and_audio_keep_their_kind_with_image_assets(self):
76
+ plan = plan_review_media(
77
+ [
78
+ {"type": "video", "file_id": "VID"},
79
+ {"type": "audio", "file_id": "AUD"},
80
+ ],
81
+ [
82
+ {"asset_id": "a1", "kind": "image", "source_url": "https://x/1"},
83
+ {"asset_id": "a2", "kind": "image", "source_url": "https://x/2"},
84
+ ],
85
+ )
86
+ assert [e.kind for e in plan.entries] == ["video", "audio"]
87
+ assert all(e.file_id for e in plan.entries)
88
+
89
+ def test_foreign_work_refs_are_dropped_from_the_plan(self):
90
+ """Regression (review 208): a ugoira refetch-replaced a 4-page
91
+ illustration; the chain kept the OLD work's pixiv refs. With work_id
92
+ the plan must contain ONLY this work's staged animation — never the
93
+ superseded work's pages as remote photos."""
94
+ plan = plan_review_media(
95
+ [{"type": "animation", "file_id": "UGOIRA_FID"}],
96
+ [
97
+ {"asset_id": "pixiv:151182115:illust:page-1", "kind": "image",
98
+ "source_url": "https://x/1"},
99
+ {"asset_id": "pixiv:151182115:illust:page-2", "kind": "image",
100
+ "source_url": "https://x/2"},
101
+ {"asset_id": "pixiv:151182115:illust:page-3", "kind": "image",
102
+ "source_url": "https://x/3"},
103
+ {"asset_id": "pixiv:151182115:illust:page-4", "kind": "image",
104
+ "source_url": "https://x/4"},
105
+ ],
106
+ work_id="151190970",
107
+ )
108
+ assert len(plan.entries) == 1
109
+ entry = plan.entries[0]
110
+ assert entry.kind == "animation"
111
+ assert entry.strategy == STRATEGY_FILE_ID
112
+ assert entry.file_id == "UGOIRA_FID"
113
+
114
+ def test_same_work_and_non_pixiv_refs_survive_the_filter(self):
115
+ plan = plan_review_media(
116
+ [{"type": "document", "file_id": "TXT"}],
117
+ [
118
+ {"asset_id": "pixiv:29430449:novelcover", "kind": "image",
119
+ "source_url": "https://x/cover"},
120
+ {"asset_id": "uploadedimage:abc", "kind": "image",
121
+ "source_url": "https://x/up"},
122
+ {"asset_id": "pixiv:29430449:illust:page-1", "kind": "image",
123
+ "source_url": "https://x/1"},
124
+ ],
125
+ work_id="29430449",
126
+ )
127
+ kinds = [e.kind for e in plan.entries]
128
+ # same-work cover + upload pair first, TXT document keeps its slot.
129
+ assert "photo" in kinds and "document" in kinds
130
+ assert not any(e.asset_id and e.asset_id.startswith("pixiv:999") for e in plan.entries)
131
+
132
+ def test_no_work_id_keeps_legacy_unfiltered_behavior(self):
133
+ plan = plan_review_media(
134
+ [{"type": "animation", "file_id": "UGOIRA_FID"}],
135
+ [
136
+ {"asset_id": "pixiv:151182115:illust:page-1", "kind": "image",
137
+ "source_url": "https://x/1"},
138
+ {"asset_id": "pixiv:151182115:illust:page-2", "kind": "image",
139
+ "source_url": "https://x/2"},
140
+ ],
141
+ )
142
+ assert [e.kind for e in plan.entries] == ["animation", "photo"]
143
+ assert plan.entries[1].strategy == STRATEGY_REMOTE_URL
144
+
145
+ def test_local_photo_stays_photo_with_image_asset(self):
146
+ plan = plan_review_media(
147
+ [{"type": "photo", "file_id": "AAA"}],
148
+ [{"asset_id": "a1", "kind": "image", "source_url": "https://x/a"}],
149
+ )
150
+ assert plan.entries[0].kind == "photo"
151
+
152
+ def test_indexed_document_is_not_photo(self):
153
+ """Regression: novel cover assets + indexed TXT file_id."""
154
+ plan = plan_review_media(
155
+ [],
156
+ [
157
+ {"asset_id": "a1", "kind": "image",
158
+ "source_url": "https://x/a"},
159
+ {"asset_id": "txt", "kind": "image",
160
+ "source_url": "https://x/txt"},
161
+ ],
162
+ documents=[{"file_id": "DDD", "filename": "novel.txt"}],
163
+ )
164
+ assert [e.kind for e in plan.entries] == ["photo", "document"]
165
+ assert plan.strategy == "mixed"
166
+ items = plan.to_media_items()
167
+ assert items[0].kind.value == "photo"
168
+ assert items[1].kind.value == "document"
169
+ assert items[1].telegram_file_id == "DDD"
170
+
171
+ def test_single_cover_asset_pairs_with_indexed_txt_document(self):
172
+ """Regression: a novel with one cover asset and one TXT must not plan
173
+ the TXT as a photo; that made novel_images_preview_only remove every
174
+ item and the publication failed with "delivery returned no messages"."""
175
+ plan = plan_review_media(
176
+ [],
177
+ [{"asset_id": "cover", "kind": "image",
178
+ "source_url": "https://i.pximg.net/cover.png"}],
179
+ documents=[{"file_id": "TXT_FILE_ID", "filename": "novel.txt"}],
180
+ )
181
+ assert [e.kind for e in plan.entries] == ["document"]
182
+ assert [e.strategy for e in plan.entries] == [STRATEGY_FILE_ID]
183
+ assert plan.entries[0].file_id == "TXT_FILE_ID"
184
+ assert plan.entries[0].filename == "novel.txt"
185
+
186
+ def test_leftover_document_keeps_file_id(self):
187
+ plan = plan_review_media(
188
+ [{"type": "photo", "file_id": "AAA"}],
189
+ [{"asset_id": "a1", "kind": "image", "source_url": "https://x/a"}],
190
+ documents=[{"file_id": "DDD", "filename": "novel.txt"}],
191
+ )
192
+ assert [e.kind for e in plan.entries] == ["photo", "document"]
193
+ assert [e.strategy for e in plan.entries] == [STRATEGY_FILE_ID, STRATEGY_FILE_ID]
194
+ assert plan.entries[1].file_id == "DDD"
195
+
196
+ def test_empty_plan(self):
197
+ plan = plan_review_media([], [])
198
+ assert plan.strategy == "empty"
199
+ assert plan.to_media_items() == []
200
+
201
+ def test_to_media_items(self):
202
+ plan = plan_review_media(
203
+ [],
204
+ [{"asset_id": "a1", "kind": "image",
205
+ "source_url": "https://proxy.example/pixiv/1.jpg"}],
206
+ )
207
+ items = plan.to_media_items()
208
+ assert len(items) == 1
209
+ assert items[0].source.url == "https://proxy.example/pixiv/1.jpg"
210
+ assert items[0].telegram_file_id is None
@@ -1,112 +0,0 @@
1
- """Step 11 DeliveryPlanner: TelePost decides how to deliver review media."""
2
- import pytest
3
-
4
- from telepost.application.delivery_planner import (
5
- STRATEGY_FILE_ID,
6
- STRATEGY_REMOTE_URL,
7
- plan_review_media,
8
- )
9
-
10
-
11
- class TestPlanReviewMedia:
12
- def test_no_assets_uses_local_file_ids(self):
13
- plan = plan_review_media(
14
- [{"type": "photo", "file_id": "AAA"}],
15
- [],
16
- )
17
- assert plan.strategy == STRATEGY_FILE_ID
18
- assert len(plan.entries) == 1
19
- assert plan.entries[0].strategy == STRATEGY_FILE_ID
20
- assert plan.entries[0].file_id == "AAA"
21
-
22
- def test_asset_with_file_id_prefers_zero_reupload(self):
23
- plan = plan_review_media(
24
- [{"type": "photo", "file_id": "AAA"}],
25
- [{"asset_id": "pixiv-1", "kind": "image",
26
- "source_url": "https://i.pximg.net/1.jpg"}],
27
- )
28
- assert plan.strategy == STRATEGY_FILE_ID
29
- assert plan.entries[0].strategy == STRATEGY_FILE_ID
30
- assert plan.entries[0].asset_id == "pixiv-1"
31
-
32
- def test_asset_without_file_id_falls_back_to_remote_url(self):
33
- plan = plan_review_media(
34
- [],
35
- [{"asset_id": "pixiv-1", "kind": "image",
36
- "source_url": "https://proxy.example/pixiv/1.jpg"}],
37
- )
38
- assert plan.strategy == STRATEGY_REMOTE_URL
39
- assert plan.entries[0].strategy == STRATEGY_REMOTE_URL
40
- assert plan.entries[0].source_url == "https://proxy.example/pixiv/1.jpg"
41
-
42
- def test_mixed_plan(self):
43
- plan = plan_review_media(
44
- [{"type": "photo", "file_id": "AAA"}],
45
- [
46
- {"asset_id": "a1", "kind": "image", "source_url": "https://x/a"},
47
- {"asset_id": "a2", "kind": "image", "source_url": "https://x/b"},
48
- ],
49
- )
50
- assert plan.strategy == "mixed"
51
- strategies = [e.strategy for e in plan.entries]
52
- assert strategies == [STRATEGY_FILE_ID, STRATEGY_REMOTE_URL]
53
-
54
- def test_indexed_document_is_not_photo(self):
55
- """Regression: novel cover assets + indexed TXT file_id."""
56
- plan = plan_review_media(
57
- [],
58
- [
59
- {"asset_id": "a1", "kind": "image",
60
- "source_url": "https://x/a"},
61
- {"asset_id": "txt", "kind": "image",
62
- "source_url": "https://x/txt"},
63
- ],
64
- documents=[{"file_id": "DDD", "filename": "novel.txt"}],
65
- )
66
- assert [e.kind for e in plan.entries] == ["photo", "document"]
67
- assert plan.strategy == "mixed"
68
- items = plan.to_media_items()
69
- assert items[0].kind.value == "photo"
70
- assert items[1].kind.value == "document"
71
- assert items[1].telegram_file_id == "DDD"
72
-
73
- def test_single_cover_asset_pairs_with_indexed_txt_document(self):
74
- """Regression: a novel with one cover asset and one TXT must not plan
75
- the TXT as a photo; that made novel_images_preview_only remove every
76
- item and the publication failed with "delivery returned no messages"."""
77
- plan = plan_review_media(
78
- [],
79
- [{"asset_id": "cover", "kind": "image",
80
- "source_url": "https://i.pximg.net/cover.png"}],
81
- documents=[{"file_id": "TXT_FILE_ID", "filename": "novel.txt"}],
82
- )
83
- assert [e.kind for e in plan.entries] == ["document"]
84
- assert [e.strategy for e in plan.entries] == [STRATEGY_FILE_ID]
85
- assert plan.entries[0].file_id == "TXT_FILE_ID"
86
- assert plan.entries[0].filename == "novel.txt"
87
-
88
- def test_leftover_document_keeps_file_id(self):
89
- plan = plan_review_media(
90
- [{"type": "photo", "file_id": "AAA"}],
91
- [{"asset_id": "a1", "kind": "image", "source_url": "https://x/a"}],
92
- documents=[{"file_id": "DDD", "filename": "novel.txt"}],
93
- )
94
- assert [e.kind for e in plan.entries] == ["photo", "document"]
95
- assert [e.strategy for e in plan.entries] == [STRATEGY_FILE_ID, STRATEGY_FILE_ID]
96
- assert plan.entries[1].file_id == "DDD"
97
-
98
- def test_empty_plan(self):
99
- plan = plan_review_media([], [])
100
- assert plan.strategy == "empty"
101
- assert plan.to_media_items() == []
102
-
103
- def test_to_media_items(self):
104
- plan = plan_review_media(
105
- [],
106
- [{"asset_id": "a1", "kind": "image",
107
- "source_url": "https://proxy.example/pixiv/1.jpg"}],
108
- )
109
- items = plan.to_media_items()
110
- assert len(items) == 1
111
- assert items[0].source.url == "https://proxy.example/pixiv/1.jpg"
112
- assert items[0].telegram_file_id is None
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes