telepost-bot 2.84.1__tar.gz → 2.84.2__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.
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/CHANGELOG.md +9 -0
- {telepost_bot-2.84.1/telepost_bot.egg-info → telepost_bot-2.84.2}/PKG-INFO +1 -1
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/callback_handlers.py +58 -15
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/review.py +325 -42
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/main.py +3 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/build_info.py +1 -1
- telepost_bot-2.84.2/telepost/telegram/callback_feedback.py +153 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/review_keyboard.py +59 -3
- {telepost_bot-2.84.1 → telepost_bot-2.84.2/telepost_bot.egg-info}/PKG-INFO +1 -1
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost_bot.egg-info/SOURCES.txt +2 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review.py +24 -12
- telepost_bot-2.84.2/tests/test_review_card_recovery.py +726 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review_media_lifecycle.py +8 -2
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_update_inbox.py +1 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/ui/locales/en/LC_MESSAGES/telepost.mo +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/ui/locales/en/LC_MESSAGES/telepost.po +45 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/LICENSE +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/MANIFEST.in +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/README.en.md +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/README.md +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/config/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/config/fly-telepost-policy.example.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/config/review_policy.md +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/config/settings.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/config.ini.example +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/database/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/database/db_manager.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/api_commands.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/automation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/botconfig.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/channel_listener.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/command_handlers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/conversation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/error_handler.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/index_handlers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/mode_selection.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/moderation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/preview_handlers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/publish.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/reaction_stats.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/recovery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/schedule_status.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/search_handlers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/stats_handlers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/handlers/upload.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/mcp_server/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/mcp_server/review_client.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/mcp_server/server.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/models/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/models/state.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/SOURCES.sha256 +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/error-mapping.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/ackresult.candidate_search.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/capabilities.pixivflow.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/event.job.expired.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/event.job.succeeded.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/eventpage.candidate_search.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/job.cancelled.candidate_search.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/job.failed.no_candidate.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/job.queued.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/job.running.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/job.succeeded.candidate_search.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/jobpage.candidate_search.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/task.candidate_search.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/fixtures/task.candidate_search.target_only.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/protocol/v1/protocol.schema.json +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/pyproject.toml +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/run.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/services/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/services/review_service.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/setup.cfg +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/api/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/admin_ops.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/automation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/delivery_planner.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/editorial.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/hot.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/novel_fallback_card.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/novel_preview.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/pixivflow_jobs.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/post_media.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/posts.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/publication.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/reaction_backfill.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/recovery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/refetch.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/rerender.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/resubmit.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/review_queue.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/submitter_notify.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/application/telepress_provider.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/automation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/delivery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/editorial.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/errors.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/hot.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/media.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/navigation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/novel_preview.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/packing.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/presentation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/presentation_policy.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/refetch_state.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/review.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/review_chat.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/domain/submission.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/miniapp/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/miniapp/auth.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/miniapp/rbac.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/miniapp/role_bindings.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/miniapp/session.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/observability/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/observability/audit.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/observability/cli.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/observability/doctor.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/observability/errors.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/observability/redact.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/automation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/columns.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/editorial.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/inbox.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/ledger.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/media_assets.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/moderation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/novel_preview.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/posts.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/protocol_events.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/recovery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/refetch.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/reviews.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/storage/sqlite/submitter_notifications.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/capture.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/discussion.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/executor.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/gateway.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/legacy_runner.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/planner.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/preparation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/registry.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/delivery/sender.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/inbox_worker.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/review_expiry.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/review_media.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost/telegram/review_stager.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost_bot.egg-info/dependency_links.txt +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost_bot.egg-info/entry_points.txt +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost_bot.egg-info/requires.txt +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/telepost_bot.egg-info/top_level.txt +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_admin_api.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_anonymous.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_api_commands.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_api_server.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_api_version.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_architecture_application.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_architecture_core.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_automation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_backfill_attribution.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_blacklist.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_bot_language.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_bot_token_logging.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_botconfig.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_boundary.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_callback_contract.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_caption_mentions.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_compatibility.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_config_encoding.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_console_encoding.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_conversation_flow.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_data_class_cleanup.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_database.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_database_first_start.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_database_operations.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_delivery_planner.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_disposition.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_doctor.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_editorial.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_file_validator.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_fly_deploy_contract.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_frozen_support.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_handlers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_heat_calculator.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_helper_functions.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_hot_domain.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_identity_provenance.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_install_scripts.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_integration.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_legacy_delivery_port_navigation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_logic_flow.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_logical_submissions.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_maintenance_jobs.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_manager_notifications.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_media_decision_audit.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_media_delivery_pipeline.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_media_domain.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_media_preparation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_media_proxy_delivery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_menu_shortcut_single_reply.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_messages.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_miniapp_api.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_miniapp_auth.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_miniapp_content.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_miniapp_entries.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_miniapp_history.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_miniapp_rbac.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_mixed_media_integrity.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_moderation_and_admin_ux.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_navigation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_no_owner_probe.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_novel_cover_preview.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_novel_delivery_semantics.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_novel_preview.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_observability.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_observability_lifecycle.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_online_reading_root_button.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_packing_ssot.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_polling_server.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_preview_pagination.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_production_handler_graph.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_protocol_contract.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_publication_presentation.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_publish_idempotency.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_publish_regressions.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_publish_reply.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_reaction_backfill.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_reaction_stats.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_recovery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_refetch.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_refetch_card_state.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_refetch_events.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_refetch_job_lifecycle.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_refetch_reason_normalization.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_refetch_replacement.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_remote_fetch_fallback.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_rerender.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_resubmit.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review_album_payload.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review_chat_contract.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review_consistency.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review_expiry_recovery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review_http_mcp.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_review_service.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_robustness.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_run_dispatcher.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_run_mode.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_run_supervisor.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_schedule_outcomes.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_search_disabled_hint.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_search_recovery.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_security.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_security_headers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_session_database_handles.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_shutdown.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_streaming_uploads.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_submission_timeout_idempotency.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_telegram_golden_messages.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_telegram_media_cache.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_upload.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_user_friendly_messages.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_webhook_payload_admission.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_webhook_ready.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_webhook_router.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_webhook_secret.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_webhook_secret_logging.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_webhook_static.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/tests/test_work_id_wire.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/ui/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/ui/i18n.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/ui/keyboards.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/ui/messages.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/api_server.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/api_tokens.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/blacklist.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/cache.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/config_wizard.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/console.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/database.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/file_validator.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/heat_calculator.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/helper_functions.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/index_manager.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/logging_config.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/maintenance_jobs.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/polling_server.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/run_mode.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/runtime_policy.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/schedule_watchdog.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/search_engine.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/security_headers.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/submission.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/utils/webhook_server.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/webapp/__init__.py +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/webapp/dist/assets/2d82b92e720462f8dd3b-Bh4Fahcw.svg +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/webapp/dist/assets/index-CLfkW8eZ.css +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/webapp/dist/assets/index-CfjBQN_e.js +0 -0
- {telepost_bot-2.84.1 → telepost_bot-2.84.2}/webapp/dist/index.html +0 -0
|
@@ -8,6 +8,15 @@
|
|
|
8
8
|
> 发布说明面向普通用户:写「对用户有什么改变」,不写内部实现细节(模块名、函数名、配置键);
|
|
9
9
|
> 技术细节放进代码注释或 `docs/`。每个 Release 的正文由本文件对应版本段自动生成。
|
|
10
10
|
|
|
11
|
+
## [2.84.2](https://github.com/redtidev1918/TelePost/compare/v2.84.1...v2.84.2) (2026-10-22)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **review:** answer each callback once and make failure feedback non-destructive ([223b1c1](https://github.com/redtidev1918/TelePost/commit/223b1c1f4e718273253a140c416e8eea58458989))
|
|
17
|
+
* **review:** keep review cards actionable when a callback fails ([455ba69](https://github.com/redtidev1918/TelePost/commit/455ba690e0ac15a95b986c8e83eeaadf6e5375bf))
|
|
18
|
+
* **review:** keep review cards actionable when a callback fails ([1827946](https://github.com/redtidev1918/TelePost/commit/1827946759284ddf9143f913bb22ae2b1a6408dc))
|
|
19
|
+
|
|
11
20
|
## [2.84.1](https://github.com/redtidev1918/TelePost/compare/v2.84.0...v2.84.1) (2026-10-22)
|
|
12
21
|
|
|
13
22
|
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
"""
|
|
4
4
|
from ui.i18n import tr
|
|
5
5
|
import logging
|
|
6
|
+
import re
|
|
6
7
|
from telegram import Update
|
|
7
8
|
from telegram.ext import CallbackContext, ConversationHandler
|
|
8
9
|
from telegram.constants import ParseMode
|
|
9
10
|
|
|
11
|
+
from telepost.telegram.callback_feedback import answer_callback, has_answered
|
|
10
12
|
from ui.keyboards import Keyboards
|
|
11
13
|
from ui.messages import MessageFormatter
|
|
12
14
|
from database.db_manager import get_db
|
|
@@ -33,16 +35,15 @@ GLOBAL_CALLBACK_PATTERNS = (
|
|
|
33
35
|
|
|
34
36
|
async def _safe_answer(query, text: str = None, **kwargs):
|
|
35
37
|
"""
|
|
36
|
-
|
|
38
|
+
安全应答回调查询(§review-card-recovery)。
|
|
37
39
|
|
|
38
|
-
每个 callback query
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
每个 callback query 只能被应答一次,重复应答会被 Telegram 拒绝。这里统一
|
|
41
|
+
委托给 ``callback_feedback.answer_callback``:先到的应答生效(例如带
|
|
42
|
+
show_alert 的提示),后续重复调用直接跳过且不抛错。
|
|
41
43
|
"""
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
logger.debug(f"回调应答失败(通常为重复应答或已过期): {e}")
|
|
44
|
+
await answer_callback(
|
|
45
|
+
query, text, show_alert=bool(kwargs.get("show_alert", False)),
|
|
46
|
+
)
|
|
46
47
|
|
|
47
48
|
|
|
48
49
|
async def handle_callback_query(update: Update, context: CallbackContext):
|
|
@@ -173,17 +174,59 @@ async def handle_callback_query(update: Update, context: CallbackContext):
|
|
|
173
174
|
|
|
174
175
|
except Exception as e:
|
|
175
176
|
logger.error(f"处理回调查询时出错: {e}", exc_info=True)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
# §review-card-recovery: no text-based "UI-only" classification here. The
|
|
178
|
+
# global catch is the *outer* boundary: a Telegram edit failure inside a
|
|
179
|
+
# handler is classified at that call site (is_ui_only_error requires a
|
|
180
|
+
# BadRequest raised by the edit itself), while anything reaching this
|
|
181
|
+
# level may have been raised by a channel publish, media upload or the
|
|
182
|
+
# review DB. Treating it as display noise would mark the update done and
|
|
183
|
+
# silently drop a retryable business failure.
|
|
184
|
+
#
|
|
185
|
+
# Feedback is answer-only when the callback has not been answered yet
|
|
186
|
+
# (handlers answer exactly once, before long work); otherwise the card is
|
|
187
|
+
# rebuilt from the row — a second answer would be dropped by Telegram.
|
|
188
|
+
if not has_answered(query):
|
|
189
|
+
await _safe_answer(
|
|
190
|
+
query, tr('操作失败,已记录;卡片与审核状态保持不变。'),
|
|
191
|
+
show_alert=True,
|
|
179
192
|
)
|
|
180
|
-
|
|
181
|
-
|
|
193
|
+
await _restore_card_after_failure(context, data)
|
|
194
|
+
# Re-raise so PTB's error observers and the durable inbox worker record a
|
|
195
|
+
# bounded retry instead of a silent "done".
|
|
196
|
+
raise
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
_REVIEW_CALLBACK_DATA = re.compile(
|
|
200
|
+
r"^review_(?:approve|reject|spoiler|preview|refetch|block_user|block_api):(\d+)$"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
async def _restore_card_after_failure(context, data: str) -> None:
|
|
205
|
+
"""Best-effort card rebuild for a review callback that just failed."""
|
|
206
|
+
match = _REVIEW_CALLBACK_DATA.match(data or "")
|
|
207
|
+
if not match:
|
|
208
|
+
return
|
|
209
|
+
bot = getattr(context, "bot", None)
|
|
210
|
+
if bot is None:
|
|
211
|
+
return
|
|
212
|
+
try:
|
|
213
|
+
from handlers.review import restore_review_card
|
|
214
|
+
await restore_review_card(bot, int(match.group(1)))
|
|
215
|
+
except Exception:
|
|
216
|
+
logger.debug("失败后恢复审核卡出错: data=%s", data, exc_info=True)
|
|
182
217
|
|
|
183
218
|
|
|
184
219
|
async def handle_unknown_callback(update: Update, context: CallbackContext):
|
|
185
|
-
"""Final fallback for callback data unclaimed by every business namespace.
|
|
186
|
-
|
|
220
|
+
"""Final fallback for callback data unclaimed by every business namespace.
|
|
221
|
+
|
|
222
|
+
§review-card-recovery: an unroutable click (stale menu, old client, a card
|
|
223
|
+
whose namespace was removed) is answered with a popup. The message is NOT
|
|
224
|
+
edited — editing it here used to strip the keyboard off a message that may
|
|
225
|
+
still carry working buttons.
|
|
226
|
+
"""
|
|
227
|
+
query = update.callback_query
|
|
228
|
+
logger.warning("未路由的回调查询: %s", query.data)
|
|
229
|
+
await _safe_answer(query, tr('❌ 未知操作'), show_alert=True)
|
|
187
230
|
|
|
188
231
|
|
|
189
232
|
async def handle_hot_filter(update: Update, context: CallbackContext):
|
|
@@ -49,6 +49,10 @@ from telepost.telegram.delivery.preparation import PHOTO_MAX_BYTES
|
|
|
49
49
|
from telepost.telegram.delivery.sender import file_id_of as _file_id_of
|
|
50
50
|
from telepost.telegram.review_stager import TelegramReviewStager
|
|
51
51
|
from telepost.telegram import review_keyboard
|
|
52
|
+
from telepost.telegram.callback_feedback import (
|
|
53
|
+
answer_callback,
|
|
54
|
+
is_ui_only_error,
|
|
55
|
+
)
|
|
52
56
|
|
|
53
57
|
logger = logging.getLogger(__name__)
|
|
54
58
|
|
|
@@ -1572,6 +1576,214 @@ async def refresh_refetch_card(bot, review_id: int, *,
|
|
|
1572
1576
|
return False
|
|
1573
1577
|
|
|
1574
1578
|
|
|
1579
|
+
async def _record_card_restored(review_id: int, outcome: str, status: str,
|
|
1580
|
+
actor=None) -> None:
|
|
1581
|
+
try:
|
|
1582
|
+
from telepost.observability import audit
|
|
1583
|
+
await audit.record_event(
|
|
1584
|
+
"review.card_restored", review_id=int(review_id), actor=actor,
|
|
1585
|
+
detail={"status": status, "result": outcome},
|
|
1586
|
+
)
|
|
1587
|
+
except Exception:
|
|
1588
|
+
logger.debug("记录审核卡恢复审计失败: review_id=%s", review_id, exc_info=True)
|
|
1589
|
+
|
|
1590
|
+
|
|
1591
|
+
async def restore_review_card(bot, review_id: int, *, dry_run: bool = False,
|
|
1592
|
+
actor=None) -> dict:
|
|
1593
|
+
"""Re-render a review's control card from its DB row (§review-card-recovery).
|
|
1594
|
+
|
|
1595
|
+
The row is the source of truth and Telegram is a projection: a card that a
|
|
1596
|
+
failed callback replaced with an error text (or that lost its buttons on a
|
|
1597
|
+
replay) is rebuilt in place from ``pending_reviews``. Only an actionable
|
|
1598
|
+
review gets moderation buttons — terminal rows come back READ-ONLY — and
|
|
1599
|
+
review state is NEVER touched here. Nothing is ever reposted: the edit always
|
|
1600
|
+
targets the durable ``control_message_id``.
|
|
1601
|
+
|
|
1602
|
+
Never raises; returns a small JSON-serializable plan/result dict.
|
|
1603
|
+
"""
|
|
1604
|
+
result: dict = {"review_id": int(review_id), "ok": False}
|
|
1605
|
+
try:
|
|
1606
|
+
from telepost.storage.sqlite.reviews import ReviewRepository
|
|
1607
|
+
row = await ReviewRepository().get(int(review_id))
|
|
1608
|
+
except Exception:
|
|
1609
|
+
logger.warning("恢复审核卡:读取审核记录失败 review_id=%s", review_id, exc_info=True)
|
|
1610
|
+
result["reason"] = "read_failed"
|
|
1611
|
+
return result
|
|
1612
|
+
if row is None:
|
|
1613
|
+
result["reason"] = "not_found"
|
|
1614
|
+
return result
|
|
1615
|
+
|
|
1616
|
+
status = _row_value(row, "status") or ""
|
|
1617
|
+
control = _row_value(row, "control_message_id")
|
|
1618
|
+
chat_id = _row_value(row, "review_chat_id") or REVIEW_CHAT_ID
|
|
1619
|
+
result.update({"status": status, "control_message_id": control,
|
|
1620
|
+
"chat_id": str(chat_id)})
|
|
1621
|
+
if not control:
|
|
1622
|
+
# A card was never durably recorded: the queue's
|
|
1623
|
+
# reconcile_incomplete_reviews path owns that row. Never repost here.
|
|
1624
|
+
result["reason"] = "no_control_message"
|
|
1625
|
+
return result
|
|
1626
|
+
|
|
1627
|
+
if status == "pending":
|
|
1628
|
+
if dry_run:
|
|
1629
|
+
result.update({"ok": True, "dry_run": True, "card": "pending"})
|
|
1630
|
+
return result
|
|
1631
|
+
if await refresh_refetch_card(bot, int(review_id)):
|
|
1632
|
+
result.update({"ok": True, "card": "pending", "edit": "edited"})
|
|
1633
|
+
await _record_card_restored(review_id, "edited", status, actor)
|
|
1634
|
+
else:
|
|
1635
|
+
result.update({"ok": False, "reason": "edit_failed", "card": "pending"})
|
|
1636
|
+
return result
|
|
1637
|
+
|
|
1638
|
+
text, markup = review_keyboard.card_from_row(row)
|
|
1639
|
+
result.update({"card": "read_only", "text": text})
|
|
1640
|
+
if dry_run:
|
|
1641
|
+
result.update({"ok": True, "dry_run": True})
|
|
1642
|
+
return result
|
|
1643
|
+
|
|
1644
|
+
from telepost.telegram.callback_feedback import edit_card_message
|
|
1645
|
+
try:
|
|
1646
|
+
outcome = await edit_card_message(bot, chat_id, control, text, markup)
|
|
1647
|
+
except Exception:
|
|
1648
|
+
logger.warning("恢复审核卡:编辑消息失败 review_id=%s", review_id, exc_info=True)
|
|
1649
|
+
result.update({"ok": False, "reason": "edit_failed"})
|
|
1650
|
+
await _record_card_restored(review_id, "failed", status, actor)
|
|
1651
|
+
return result
|
|
1652
|
+
result.update({"ok": outcome in ("edited", "unchanged"), "edit": outcome})
|
|
1653
|
+
if outcome == "gone":
|
|
1654
|
+
result["reason"] = "message_gone"
|
|
1655
|
+
await _record_card_restored(review_id, outcome, status, actor)
|
|
1656
|
+
return result
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
async def _notify_state_mismatch(query, context, review_id: int, text: str) -> None:
|
|
1660
|
+
"""§review-card-recovery: report a decided/changed review WITHOUT killing the card.
|
|
1661
|
+
|
|
1662
|
+
The callback was already answered before the business call, so a second
|
|
1663
|
+
``answerCallbackQuery`` would be ignored by Telegram and the operator would
|
|
1664
|
+
see nothing. Instead the card is rebuilt from the row: it shows the real
|
|
1665
|
+
state (read-only when terminal) plus the original-link button, which is the
|
|
1666
|
+
non-destructive feedback the operator needs. The reason is logged either way.
|
|
1667
|
+
"""
|
|
1668
|
+
logger.info(
|
|
1669
|
+
"审核操作与当前状态不一致(已应答,改用卡片状态同步): review_id=%s reason=%s",
|
|
1670
|
+
review_id, text,
|
|
1671
|
+
)
|
|
1672
|
+
bot = getattr(context, "bot", None)
|
|
1673
|
+
if bot is None:
|
|
1674
|
+
return
|
|
1675
|
+
await restore_review_card(bot, int(review_id))
|
|
1676
|
+
|
|
1677
|
+
|
|
1678
|
+
_TERMINAL_REVIEW_STATUSES = ("rejected", "expired", "superseded")
|
|
1679
|
+
|
|
1680
|
+
|
|
1681
|
+
async def _terminal_state_alert(query, context, review_id: int) -> bool:
|
|
1682
|
+
"""Answer a click on an already-decided review, BEFORE any business call.
|
|
1683
|
+
|
|
1684
|
+
This is the only place a state conflict still gets a popup: the callback has
|
|
1685
|
+
not been answered yet, so the alert actually reaches the client. The database
|
|
1686
|
+
state machine stays authoritative — this pre-flight only decides *feedback*;
|
|
1687
|
+
a pending/failed/publishing row still goes through the service CAS, and a
|
|
1688
|
+
published row keeps the publish-reuse ledger path (``duplicate_suppressed``)
|
|
1689
|
+
instead of being short-circuited here.
|
|
1690
|
+
Returns True when the handler must stop.
|
|
1691
|
+
"""
|
|
1692
|
+
row = await review_service.get_row(review_id)
|
|
1693
|
+
if row is None:
|
|
1694
|
+
await _answer(query, tr('审核记录不存在'), show_alert=True)
|
|
1695
|
+
return True
|
|
1696
|
+
status = _row_value(row, "status") or ""
|
|
1697
|
+
if status not in _TERMINAL_REVIEW_STATUSES:
|
|
1698
|
+
return False
|
|
1699
|
+
await _answer(query, f"该投稿当前状态:{status}", show_alert=True)
|
|
1700
|
+
bot = getattr(context, "bot", None)
|
|
1701
|
+
if bot is not None:
|
|
1702
|
+
await restore_review_card(bot, int(review_id))
|
|
1703
|
+
return True
|
|
1704
|
+
|
|
1705
|
+
|
|
1706
|
+
async def _edit_card_or_recover(query, context, review_id: int, text: str,
|
|
1707
|
+
markup) -> None:
|
|
1708
|
+
"""Update a card AFTER a business decision has been committed.
|
|
1709
|
+
|
|
1710
|
+
A display failure here must never repeat the business action (publish,
|
|
1711
|
+
reject, refetch) — the inbox would replay the whole handler. The card is
|
|
1712
|
+
therefore best-effort: Telegram-side edit failures are logged and the card
|
|
1713
|
+
is re-rendered read-only from the row; non-UI failures are surfaced in the
|
|
1714
|
+
log and recovered the same way instead of being re-raised.
|
|
1715
|
+
"""
|
|
1716
|
+
try:
|
|
1717
|
+
await query.edit_message_text(
|
|
1718
|
+
text, reply_markup=markup, disable_web_page_preview=True,
|
|
1719
|
+
)
|
|
1720
|
+
return
|
|
1721
|
+
except Exception as exc: # noqa: BLE001 - post-commit display must not raise
|
|
1722
|
+
if is_ui_only_error(exc):
|
|
1723
|
+
logger.info(
|
|
1724
|
+
"审核卡更新未生效(消息不可编辑/未变化,业务已完成): review_id=%s reason=%s",
|
|
1725
|
+
review_id, exc,
|
|
1726
|
+
)
|
|
1727
|
+
else:
|
|
1728
|
+
logger.warning(
|
|
1729
|
+
"审核卡更新失败(业务已完成,不回滚、不重试): review_id=%s",
|
|
1730
|
+
review_id, exc_info=True,
|
|
1731
|
+
)
|
|
1732
|
+
bot = getattr(context, "bot", None)
|
|
1733
|
+
if bot is not None:
|
|
1734
|
+
await restore_review_card(bot, review_id)
|
|
1735
|
+
|
|
1736
|
+
|
|
1737
|
+
async def restore_card_command(update, context):
|
|
1738
|
+
"""`/restore_card <review_id> [--dry-run]` — admin recovery of a lost card.
|
|
1739
|
+
|
|
1740
|
+
The command only ever touches the database of the Bot it is sent to: every
|
|
1741
|
+
Bot owns its own SQLite file and review ids collide across Bots, so an admin
|
|
1742
|
+
must send it to the target Bot itself. Cross-Bot addressing is unsupported
|
|
1743
|
+
and rejected explicitly.
|
|
1744
|
+
"""
|
|
1745
|
+
user = update.effective_user
|
|
1746
|
+
if user is None or user.id not in ADMIN_IDS:
|
|
1747
|
+
await update.message.reply_text(tr('你没有审核权限'))
|
|
1748
|
+
return
|
|
1749
|
+
args = list(getattr(context, "args", None) or [])
|
|
1750
|
+
# Exactly: one review id (``202`` or ``#202``) + optionally one ``--dry-run``.
|
|
1751
|
+
# ``--bot``, unknown flags, extra tokens and duplicated flags are rejected
|
|
1752
|
+
# without touching any Telegram card.
|
|
1753
|
+
valid = (
|
|
1754
|
+
len(args) in (1, 2)
|
|
1755
|
+
and re.fullmatch(r"#?\d+", args[0]) is not None
|
|
1756
|
+
and (len(args) == 1 or args[1] == "--dry-run")
|
|
1757
|
+
)
|
|
1758
|
+
if not valid:
|
|
1759
|
+
await update.message.reply_text(
|
|
1760
|
+
tr('用法:/restore_card <审核ID> [--dry-run](只重建卡片,不改变审核状态)')
|
|
1761
|
+
)
|
|
1762
|
+
return
|
|
1763
|
+
review_id = int(args[0].lstrip("#"))
|
|
1764
|
+
dry_run = len(args) == 2
|
|
1765
|
+
result = await restore_review_card(
|
|
1766
|
+
context.bot, review_id, dry_run=dry_run, actor=user.id,
|
|
1767
|
+
)
|
|
1768
|
+
if not result.get("ok"):
|
|
1769
|
+
await update.message.reply_text(
|
|
1770
|
+
tr('恢复审核卡失败:{p0}').format(p0=result.get("reason") or 'unknown')
|
|
1771
|
+
)
|
|
1772
|
+
return
|
|
1773
|
+
if dry_run:
|
|
1774
|
+
await update.message.reply_text(
|
|
1775
|
+
tr('审核 #{p0} 卡片预览(状态:{p1},未修改任何内容):\n\n{p2}').format(
|
|
1776
|
+
p0=review_id, p1=result.get("status"), p2=result.get("text") or tr('(待审核,将按重抓状态重建)'),
|
|
1777
|
+
)
|
|
1778
|
+
)
|
|
1779
|
+
return
|
|
1780
|
+
await update.message.reply_text(
|
|
1781
|
+
tr('已恢复审核 #{p0} 的卡片(状态:{p1},未改变审核状态)。').format(
|
|
1782
|
+
p0=review_id, p1=result.get("status"),
|
|
1783
|
+
)
|
|
1784
|
+
)
|
|
1785
|
+
|
|
1786
|
+
|
|
1575
1787
|
async def reconcile_incomplete_reviews(bot, *, stale_seconds: float = 60.0) -> int:
|
|
1576
1788
|
"""Repair crash-left review rows by restoring a durable control message."""
|
|
1577
1789
|
repaired = await queue_service.reconcile_incomplete(
|
|
@@ -1677,10 +1889,13 @@ async def _apply_review_group_mask(bot, row, mask_on: bool):
|
|
|
1677
1889
|
|
|
1678
1890
|
# ---- callback handlers ----------------------------------------------------
|
|
1679
1891
|
async def _answer(query, text=None, **kwargs):
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1892
|
+
# §review-card-recovery: Telegram accepts exactly ONE answer per callback
|
|
1893
|
+
# query. Bookkeeping lives in ``callback_feedback.answer_callback`` so every
|
|
1894
|
+
# handler (and the global fallback) shares it; a second answer would be
|
|
1895
|
+
# silently dropped by Telegram, losing the operator-visible message.
|
|
1896
|
+
await answer_callback(
|
|
1897
|
+
query, text, show_alert=bool(kwargs.get("show_alert", False)),
|
|
1898
|
+
)
|
|
1684
1899
|
|
|
1685
1900
|
|
|
1686
1901
|
async def toggle_review_spoiler(update, context):
|
|
@@ -1728,24 +1943,17 @@ async def toggle_review_spoiler(update, context):
|
|
|
1728
1943
|
media_cleared=_row_value(row, "preview_state") == "cleared",
|
|
1729
1944
|
)
|
|
1730
1945
|
)
|
|
1731
|
-
except Exception:
|
|
1732
|
-
# §review-
|
|
1733
|
-
#
|
|
1734
|
-
#
|
|
1735
|
-
#
|
|
1736
|
-
#
|
|
1737
|
-
# (a popup, not a new chat message, so repeated toggles do not spam).
|
|
1946
|
+
except Exception as exc: # noqa: BLE001 - business already committed
|
|
1947
|
+
# §review-card-recovery: the spoiler flag + the review-group mask are
|
|
1948
|
+
# already applied; a keyboard refresh failure must not be re-raised (the
|
|
1949
|
+
# inbox would replay the handler and flip the mask again). Log and rebuild
|
|
1950
|
+
# the card from the row — the callback was answered earlier, so a second
|
|
1951
|
+
# answer would be dropped by Telegram anyway.
|
|
1738
1952
|
logger.warning(
|
|
1739
|
-
"
|
|
1740
|
-
|
|
1741
|
-
review_id,
|
|
1742
|
-
exc_info=True,
|
|
1743
|
-
)
|
|
1744
|
-
await _answer(
|
|
1745
|
-
query,
|
|
1746
|
-
tr('已遮罩,但按钮无法刷新(状态已{p0})。').format(p0=tr('开启') if new_spoiler else tr('关闭')),
|
|
1747
|
-
show_alert=True,
|
|
1953
|
+
"刷新审核键盘失败,已按库内状态重建审核卡: review_id=%s ui_only=%s",
|
|
1954
|
+
review_id, is_ui_only_error(exc), exc_info=True,
|
|
1748
1955
|
)
|
|
1956
|
+
await restore_review_card(bot, review_id)
|
|
1749
1957
|
|
|
1750
1958
|
|
|
1751
1959
|
async def restore_review_preview(update, context):
|
|
@@ -1793,13 +2001,30 @@ async def restore_review_preview(update, context):
|
|
|
1793
2001
|
await _answer(query, tr('预览正在恢复中,请稍候。'))
|
|
1794
2002
|
return
|
|
1795
2003
|
|
|
2004
|
+
# Answer BEFORE the staging uploads: regeneration sends media to Telegram
|
|
2005
|
+
# and can outlive the callback answer window, and a later answer would be
|
|
2006
|
+
# rejected by Telegram (one answer per callback). The restored media itself
|
|
2007
|
+
# is the visible outcome; the card keeps its handle either way.
|
|
2008
|
+
await _answer(query, tr('预览正在恢复中,请稍候。'))
|
|
2009
|
+
|
|
1796
2010
|
from telepost.telegram.review_media import regenerate_review_preview
|
|
1797
2011
|
result = await regenerate_review_preview(context.bot, row)
|
|
1798
2012
|
if not result["ok"]:
|
|
1799
|
-
|
|
2013
|
+
logger.warning(
|
|
2014
|
+
"恢复审核预览失败: review_id=%s reason=%s",
|
|
2015
|
+
review_id, result.get("message"),
|
|
2016
|
+
)
|
|
2017
|
+
chat_id = getattr(getattr(query, "message", None), "chat_id", None)
|
|
2018
|
+
if chat_id is not None:
|
|
2019
|
+
try:
|
|
2020
|
+
await context.bot.send_message(
|
|
2021
|
+
chat_id=chat_id,
|
|
2022
|
+
text=f"⚠️ 审核 #{review_id} 预览恢复失败:{result['message']}",
|
|
2023
|
+
)
|
|
2024
|
+
except Exception: # noqa: BLE001 - notice is best-effort
|
|
2025
|
+
logger.debug("发送预览恢复失败提示失败", exc_info=True)
|
|
2026
|
+
await restore_review_card(context.bot, review_id)
|
|
1800
2027
|
return
|
|
1801
|
-
await _answer(query, tr('预览已恢复({p0} 条消息),将按同样的时限自动清理。').format(
|
|
1802
|
-
p0=len(result["message_ids"])))
|
|
1803
2028
|
try:
|
|
1804
2029
|
await query.edit_message_reply_markup(
|
|
1805
2030
|
reply_markup=_review_keyboard(
|
|
@@ -2004,12 +2229,21 @@ async def approve_review(update, context):
|
|
|
2004
2229
|
if update.effective_user.id not in ADMIN_IDS:
|
|
2005
2230
|
await _answer(query, tr('你没有审核权限'), show_alert=True)
|
|
2006
2231
|
return
|
|
2007
|
-
await _answer(query)
|
|
2008
2232
|
try:
|
|
2009
2233
|
review_id = int(query.data.split(":", 1)[1])
|
|
2010
2234
|
except (ValueError, IndexError):
|
|
2011
|
-
|
|
2235
|
+
# §review-card-recovery: invalid payload is answerable — alert, never
|
|
2236
|
+
# overwrite the card with an error text.
|
|
2237
|
+
await _answer(query, tr('无效的审核记录'), show_alert=True)
|
|
2012
2238
|
return
|
|
2239
|
+
# Pre-flight: an already-decided review is answered HERE (the callback is
|
|
2240
|
+
# still answerable, so the popup actually reaches the operator). The review
|
|
2241
|
+
# state machine (service CAS) stays authoritative for anything not terminal.
|
|
2242
|
+
if await _terminal_state_alert(query, context, review_id):
|
|
2243
|
+
return
|
|
2244
|
+
# Single acknowledgement before the (possibly tens-of-seconds) publish. All
|
|
2245
|
+
# later feedback must be card state sync, not a second answer.
|
|
2246
|
+
await _answer(query)
|
|
2013
2247
|
|
|
2014
2248
|
async def _show_publishing(row):
|
|
2015
2249
|
try:
|
|
@@ -2019,7 +2253,8 @@ async def approve_review(update, context):
|
|
|
2019
2253
|
reply_markup=InlineKeyboardMarkup([]),
|
|
2020
2254
|
)
|
|
2021
2255
|
except Exception:
|
|
2022
|
-
|
|
2256
|
+
logger.debug("发布中卡片更新失败(不改写业务状态): review_id=%s", review_id,
|
|
2257
|
+
exc_info=True)
|
|
2023
2258
|
|
|
2024
2259
|
try:
|
|
2025
2260
|
result = await review_service.approve(
|
|
@@ -2028,20 +2263,25 @@ async def approve_review(update, context):
|
|
|
2028
2263
|
on_claim=_show_publishing,
|
|
2029
2264
|
)
|
|
2030
2265
|
except ReviewNotFoundError:
|
|
2031
|
-
|
|
2266
|
+
logger.info("审核不存在(已应答,改用卡片同步): review_id=%s", review_id)
|
|
2267
|
+
await restore_review_card(context.bot, review_id)
|
|
2032
2268
|
return
|
|
2033
2269
|
except ReviewBusyError:
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
await
|
|
2270
|
+
# Still being published by another claim: the card already carries the
|
|
2271
|
+
# in-progress shape; re-render it instead of a second answer.
|
|
2272
|
+
logger.info("审核正在发布中(已应答): review_id=%s", review_id)
|
|
2273
|
+
await restore_review_card(context.bot, review_id)
|
|
2038
2274
|
return
|
|
2039
2275
|
except PublishFailedError as error:
|
|
2040
2276
|
row = await _load_review_for_action(query, review_id)
|
|
2041
|
-
|
|
2277
|
+
if row is None:
|
|
2278
|
+
await restore_review_card(context.bot, review_id)
|
|
2279
|
+
return
|
|
2280
|
+
await _edit_card_or_recover(
|
|
2281
|
+
query, context, review_id,
|
|
2042
2282
|
tr("""⚠️ 审核 #{p0} {p1}:
|
|
2043
2283
|
{p2}""").format(p0=review_id, p1=error.retry_hint, p2=error.message),
|
|
2044
|
-
|
|
2284
|
+
_review_keyboard(
|
|
2045
2285
|
review_id, row["link"],
|
|
2046
2286
|
spoiler=bool(row["spoiler"]), source=row["source"],
|
|
2047
2287
|
work_id=_work_id_from_link(row["link"] or ""),
|
|
@@ -2052,11 +2292,21 @@ async def approve_review(update, context):
|
|
|
2052
2292
|
),
|
|
2053
2293
|
)
|
|
2054
2294
|
return
|
|
2295
|
+
except ReviewStateError as error:
|
|
2296
|
+
await _notify_state_mismatch(query, context, review_id, error.message)
|
|
2297
|
+
return
|
|
2298
|
+
except ReviewError as error:
|
|
2299
|
+
await _notify_state_mismatch(
|
|
2300
|
+
query, context, review_id,
|
|
2301
|
+
getattr(error, "message", None) or tr('该投稿已处理,无法发布'),
|
|
2302
|
+
)
|
|
2303
|
+
return
|
|
2055
2304
|
|
|
2056
|
-
await
|
|
2305
|
+
await _edit_card_or_recover(
|
|
2306
|
+
query, context, review_id,
|
|
2057
2307
|
tr("""✅ 审核 #{p0} 已发布
|
|
2058
2308
|
{p1}""").format(p0=review_id, p1=result.link or ''),
|
|
2059
|
-
|
|
2309
|
+
None,
|
|
2060
2310
|
)
|
|
2061
2311
|
|
|
2062
2312
|
|
|
@@ -2065,12 +2315,14 @@ async def reject_review(update, context):
|
|
|
2065
2315
|
if update.effective_user.id not in ADMIN_IDS:
|
|
2066
2316
|
await _answer(query, tr('你没有审核权限'), show_alert=True)
|
|
2067
2317
|
return
|
|
2068
|
-
await _answer(query)
|
|
2069
2318
|
try:
|
|
2070
2319
|
review_id = int(query.data.split(":", 1)[1])
|
|
2071
2320
|
except (ValueError, IndexError):
|
|
2072
|
-
await query
|
|
2321
|
+
await _answer(query, tr('无效的审核记录'), show_alert=True)
|
|
2322
|
+
return
|
|
2323
|
+
if await _terminal_state_alert(query, context, review_id):
|
|
2073
2324
|
return
|
|
2325
|
+
await _answer(query)
|
|
2074
2326
|
|
|
2075
2327
|
try:
|
|
2076
2328
|
await review_service.reject(
|
|
@@ -2078,13 +2330,23 @@ async def reject_review(update, context):
|
|
|
2078
2330
|
actor=update.effective_user.id, source="telegram",
|
|
2079
2331
|
)
|
|
2080
2332
|
except ReviewNotFoundError:
|
|
2081
|
-
|
|
2333
|
+
logger.info("审核不存在(已应答,改用卡片同步): review_id=%s", review_id)
|
|
2334
|
+
await restore_review_card(context.bot, review_id)
|
|
2082
2335
|
return
|
|
2083
2336
|
except ReviewStateError as error:
|
|
2084
|
-
await query
|
|
2337
|
+
await _notify_state_mismatch(query, context, review_id, error.message)
|
|
2338
|
+
return
|
|
2339
|
+
except ReviewError as error:
|
|
2340
|
+
await _notify_state_mismatch(
|
|
2341
|
+
query, context, review_id,
|
|
2342
|
+
getattr(error, "message", None) or tr('该投稿已处理,无法拒绝'),
|
|
2343
|
+
)
|
|
2085
2344
|
return
|
|
2086
2345
|
|
|
2087
|
-
await
|
|
2346
|
+
await _edit_card_or_recover(
|
|
2347
|
+
query, context, review_id,
|
|
2348
|
+
tr('❌ 审核 #{p0} 已拒绝').format(p0=review_id), None,
|
|
2349
|
+
)
|
|
2088
2350
|
|
|
2089
2351
|
|
|
2090
2352
|
async def block_review_user(update, context):
|
|
@@ -2121,10 +2383,22 @@ async def block_review_user(update, context):
|
|
|
2121
2383
|
)
|
|
2122
2384
|
await _answer(query, tr('已封禁该投稿人:后续投稿将被自动拒绝'))
|
|
2123
2385
|
try:
|
|
2386
|
+
# §review-card-recovery: append the audit note WITHOUT dropping the
|
|
2387
|
+
# keyboard — losing 发布/拒绝 on a still-pending review is exactly the
|
|
2388
|
+
# reported production failure, only here it would be self-inflicted.
|
|
2124
2389
|
await query.edit_message_text(
|
|
2125
2390
|
tr("""{p0}
|
|
2126
2391
|
|
|
2127
|
-
🚫 投稿人封禁已由管理员 {p1} 记录""").format(p0=query.message.text, p1=update.effective_user.id)
|
|
2392
|
+
🚫 投稿人封禁已由管理员 {p1} 记录""").format(p0=query.message.text, p1=update.effective_user.id),
|
|
2393
|
+
reply_markup=_review_keyboard(
|
|
2394
|
+
int(review_id), row["link"],
|
|
2395
|
+
spoiler=bool(row["spoiler"]), source=row["source"],
|
|
2396
|
+
work_id=_work_id_from_link(row["link"] or ""),
|
|
2397
|
+
submitter_user_id=_row_value(row, "submitter_user_id"),
|
|
2398
|
+
actor_kind=_row_value(row, "actor_kind") or "user",
|
|
2399
|
+
actor_subject=_row_value(row, "actor_subject") or "",
|
|
2400
|
+
media_cleared=_row_value(row, "preview_state") == "cleared",
|
|
2401
|
+
),
|
|
2128
2402
|
)
|
|
2129
2403
|
except Exception as exc:
|
|
2130
2404
|
logger.debug("封禁后更新审核卡失败: %s", exc)
|
|
@@ -2166,7 +2440,16 @@ async def block_review_api(update, context):
|
|
|
2166
2440
|
await query.edit_message_text(
|
|
2167
2441
|
tr("""{p0}
|
|
2168
2442
|
|
|
2169
|
-
🔑 API token 禁用已由管理员 {p1} 记录""").format(p0=query.message.text, p1=update.effective_user.id)
|
|
2443
|
+
🔑 API token 禁用已由管理员 {p1} 记录""").format(p0=query.message.text, p1=update.effective_user.id),
|
|
2444
|
+
reply_markup=_review_keyboard(
|
|
2445
|
+
int(review_id), row["link"],
|
|
2446
|
+
spoiler=bool(row["spoiler"]), source=row["source"],
|
|
2447
|
+
work_id=_work_id_from_link(row["link"] or ""),
|
|
2448
|
+
submitter_user_id=_row_value(row, "submitter_user_id"),
|
|
2449
|
+
actor_kind=_row_value(row, "actor_kind") or "user",
|
|
2450
|
+
actor_subject=_row_value(row, "actor_subject") or "",
|
|
2451
|
+
media_cleared=_row_value(row, "preview_state") == "cleared",
|
|
2452
|
+
),
|
|
2170
2453
|
)
|
|
2171
2454
|
except Exception as exc:
|
|
2172
2455
|
logger.debug("禁用API后更新审核卡失败: %s", exc)
|
|
@@ -66,6 +66,7 @@ from handlers.review import (
|
|
|
66
66
|
poll_refetch_jobs,
|
|
67
67
|
reconcile_incomplete_reviews,
|
|
68
68
|
recover_refetch_jobs,
|
|
69
|
+
restore_card_command,
|
|
69
70
|
)
|
|
70
71
|
|
|
71
72
|
# 错误处理
|
|
@@ -737,6 +738,8 @@ def setup_application(application):
|
|
|
737
738
|
application.add_handler(CommandHandler('blacklist_list', blacklist_list), group=-998)
|
|
738
739
|
application.add_handler(CommandHandler('ban_user', ban_user_command), group=-998)
|
|
739
740
|
application.add_handler(CommandHandler('ban_api', ban_api_command), group=-998)
|
|
741
|
+
# §review-card-recovery: read-only rebuild of a destroyed review card.
|
|
742
|
+
application.add_handler(CommandHandler('restore_card', restore_card_command), group=-998)
|
|
740
743
|
application.add_handler(CommandHandler('botconfig', botconfig), group=-998)
|
|
741
744
|
# 不再注册高优先级的cancel命令,只在ConversationHandler的fallbacks中注册
|
|
742
745
|
# application.add_handler(CommandHandler('cancel', cancel), group=-998) # 注释掉这行
|
|
@@ -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.
|
|
11
|
+
RELEASE_VERSION = "2.84.2" # x-release-please-version
|
|
12
12
|
RELEASE_COMMIT = "dev"
|
|
13
13
|
BUILD_DATE = "dev"
|
|
14
14
|
SERVICE = "telepost"
|