simcord 3.0.2__tar.gz → 3.2.0__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.
- {simcord-3.0.2 → simcord-3.2.0}/CHANGELOG.md +53 -1
- {simcord-3.0.2 → simcord-3.2.0}/PKG-INFO +2 -1
- {simcord-3.0.2 → simcord-3.2.0}/docs/api.md +25 -1
- {simcord-3.0.2 → simcord-3.2.0}/docs/concepts.md +25 -1
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/ai-coding-agents.md +6 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/fixtures.md +15 -2
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/intents.md +2 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/interactions.md +40 -9
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/messages.md +36 -0
- simcord-3.2.0/docs/guides/monetization.md +728 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/time-control.md +43 -11
- {simcord-3.0.2 → simcord-3.2.0}/docs/llms.txt +2 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/parity-matrix.md +15 -13
- {simcord-3.0.2 → simcord-3.2.0}/docs/stability.md +12 -0
- {simcord-3.0.2 → simcord-3.2.0}/mkdocs.yml +1 -0
- {simcord-3.0.2 → simcord-3.2.0}/pyproject.toml +2 -2
- simcord-3.2.0/scripts/capture_monetization_reference.py +912 -0
- simcord-3.2.0/scripts/capture_remaining_monetization.py +1370 -0
- simcord-3.2.0/scripts/monetization_reference_checkout.py +537 -0
- simcord-3.2.0/scripts/monetization_reference_expiry.py +192 -0
- simcord-3.2.0/scripts/monetization_reference_lifecycle.py +597 -0
- simcord-3.2.0/scripts/monetization_reference_upgrade.py +537 -0
- simcord-3.2.0/scripts/validate_monetization_reference.py +933 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/__init__.py +3 -0
- simcord-3.2.0/src/simcord/_clock.py +15 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_dpy_internals.py +92 -10
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_runtime.py +73 -45
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_settlement.py +7 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/actors.py +39 -7
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/access.py +7 -1
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/errors.py +41 -4
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/__init__.py +3 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/interaction.py +9 -9
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/message.py +1 -0
- simcord-3.2.0/src/simcord/backend/models/monetization.py +38 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/base.py +53 -34
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/commands.py +12 -1
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/guilds.py +15 -2
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/messages.py +128 -8
- simcord-3.2.0/src/simcord/backend/ops/monetization.py +349 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/permissions.py +0 -12
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/polls.py +11 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/scheduled_events.py +18 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/permissions.py +4 -4
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/serializers.py +82 -5
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/state.py +2 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/builders.py +3 -9
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/env.py +263 -49
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/gateway.py +4 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/_helpers.py +26 -12
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/router.py +1 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/__init__.py +2 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/interactions.py +43 -19
- simcord-3.2.0/src/simcord/http/routes/monetization.py +105 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/intents.py +12 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/interactions.py +1 -1
- simcord-3.2.0/src/simcord/monetization.py +104 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_actions.py +3 -1
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_assets.py +40 -15
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_messages.py +53 -5
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_pages.py +9 -1
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_queries.py +18 -11
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/app.js +3 -3
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/messages.js +70 -53
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/preview.css +5 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/pytest_plugin.py +20 -2
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/results.py +9 -2
- {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/_world.py +5 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_field_honesty.py +2 -2
- {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_route_coverage.py +0 -1
- {simcord-3.0.2 → simcord-3.2.0}/tests/conftest.py +7 -1
- simcord-3.2.0/tests/integration/test_entitlement_journey.py +506 -0
- simcord-3.2.0/tests/integration/test_forwarding.py +482 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_interaction_followups.py +206 -11
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_interactions.py +6 -4
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_members.py +4 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_message_content_rest.py +20 -7
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_message_routes.py +136 -3
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview.py +23 -23
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_additional.py +11 -17
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_capture_dx.py +1 -1
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_edges.py +14 -19
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_message_actions.py +5 -2
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_security.py +142 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_settlement_contract.py +64 -0
- simcord-3.2.0/tests/integration/test_shared_clock.py +320 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_time.py +73 -11
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_command_access.py +2 -2
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_intent_rules.py +39 -0
- simcord-3.2.0/tests/unit/test_monetization_backend.py +230 -0
- simcord-3.2.0/tests/unit/test_monetization_http.py +474 -0
- simcord-3.2.0/tests/unit/test_monetization_reference_checkout.py +308 -0
- simcord-3.2.0/tests/unit/test_monetization_reference_expiry.py +246 -0
- simcord-3.2.0/tests/unit/test_monetization_reference_lifecycle.py +162 -0
- simcord-3.2.0/tests/unit/test_monetization_reference_upgrade.py +511 -0
- simcord-3.2.0/tests/unit/test_monetization_reference_validation.py +582 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_permissions.py +9 -6
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_preview_action_specs.py +22 -1
- simcord-3.2.0/tests/unit/test_pytest_clock.py +48 -0
- simcord-3.2.0/tests/unit/test_remaining_monetization_capture.py +367 -0
- {simcord-3.0.2 → simcord-3.2.0}/uv.lock +7 -5
- {simcord-3.0.2 → simcord-3.2.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.github/ISSUE_TEMPLATE/parity-gap.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.github/dependabot.yml +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/ci.yml +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/dpy-master.yml +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/release.yml +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.github/workflows/verify.yml +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.gitignore +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.pre-commit-config.yaml +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.python-version +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/.readthedocs.yaml +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/CODE_OF_CONDUCT.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/CONTRIBUTING.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/LICENSE +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/README.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/SECURITY.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/benchmarks/_helpers.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/benchmarks/test_dispatch_benchmarks.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/benchmarks/test_perf_guards.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/changes/README.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/architecture.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/adventure/alpine.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/adventure/canyon.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/attachments.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/buttons.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/embeds.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/entities.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/layouts.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/markdown.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/modals.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/polls.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/replies.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/selects.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/preview/slash-commands.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/simcord-showcase-poll-refresh.gif +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/simcord-terminal-demo.gif +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/simcord-transparent.webp +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/assets/social-card.png +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/changelog.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/cookbook.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/faq.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/components.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/diagnostics.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/discord-visual-calibration.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/mocks-vs-simulation.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/moderation-and-guild.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/permissions.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/preview.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/test-without-token.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/testing-discord-py-bots.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/testing-slash-commands.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/guides/threads-reactions-dms.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/index.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/installation.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/performance.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/preview-attestation.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/quickstart.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/docs/robots.txt +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/examples/README.md +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/examples/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/examples/bot.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/examples/conftest.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/examples/preview_example.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/examples/test_bot.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/overrides/main.html +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/capture_discord_reference.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/capture_visual_reference.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/check_discoverability.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/check_package.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/check_reference_modal_offline.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/compare_visual_reference.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/discord_reference_bot.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/preview_dogfood.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/record_readme_demo.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/replay_visual_reference.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/scripts/validate_discord_reference_pack.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/_modal.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/asserts.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/cdn.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/command_access.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/auditlog.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/automod.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/channel.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/expression.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/guild.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/invite.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/member.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/role.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/scheduled_event.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/stage_instance.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/user.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/voice.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/models/webhook.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/audit.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/channels.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/expressions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/invites.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/reactions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/voice.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/backend/ops/webhooks.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/components.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/enums.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/client.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/application.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/auditlogs.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/automod.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/channels.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/commands.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/expressions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/guilds.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/invites.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/messages.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/reactions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/scheduled_events.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/http/routes/stage_instances.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/parity.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_action_plans.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_capture.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_commands.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_component_projection.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_diagnostics.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_identity.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_markdown.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_media.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_media_worker.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_server.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/_snapshot.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/protocol.schema.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/commands.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/components.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/composer.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/dom.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Arabic.txt +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-CJK.txt +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Devanagari.txt +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Emoji.txt +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Hebrew.txt +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto.txt +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/manifest.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-color-emoji-v2.051.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-arabic-v2.012.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-devanagari-v2.007.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-hebrew-v3.001.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-latin-italic-v2.015.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-latin-v2.015.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-mono-v2.014.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/fonts/noto-sans-sc-v2.004.ttf +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/index.html +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/listbox.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/media.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/select-drafts.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/selects.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/text.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/timeline.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/transport.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/LICENSE +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/SHA256SUMS +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/core.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/bash.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/cpp.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/csharp.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/css.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/diff.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/dockerfile.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/go.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/ini.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/java.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/javascript.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/json.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/markdown.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/python.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/rust.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/sql.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/typescript.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/xml.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/highlight/es/languages/yaml.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/lottie/5.12.2/lottie_light_canvas.min.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/lottie/LICENSE +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/vendor/lottie/SHA256SUMS +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/preview/static/workbench.js +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/py.typed +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/src/simcord/types.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_field_honesty_fuzz.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/conformance/test_serializers.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/commands/option_cases.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/catalog.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/coverage.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/measurements.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/moving-square.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/profiles.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/states.json +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/video.mp4 +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/preview/voice.ogg +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/__init__.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/events.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/general.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/interactions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/moderation.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/fixtures/sample_bot/picker.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/preview_helpers.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_app_commands.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_application_emojis.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_asserts.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_audit_logs.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_automod.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_bot_users_and_webhooks.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_channels.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_command_permissions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_command_visibility.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_common_bot_sweep.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_components_v2_interactions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_components_v2_messages.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_coverage_sweep.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_diagnostics.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_edge_cases.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_error_capture_and_edits.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_expressions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_forum.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_gateway_and_automod_exempt.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_guilds.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_intents.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_invites.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_lifecycle.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_messages.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_misc_branches.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_moderation.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_observation_contracts.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_polls.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_capture.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_channel.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_command_picker.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_commands.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_coverage.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_dx.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_fixes.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_fonts.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_preview_identity.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_reactions.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_reference_replay.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_roles_and_ordering.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_scheduled_events.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_setup_hook_suspension.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_sharding.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_stage_instances.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_threads.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_transport_edges.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_voice_state.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/integration/test_world_helpers.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_assert_helpers.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_backend_lookup_errors.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_components_v2_validation.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_dpy_internals.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_errors.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_lazy_import.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_option_validation.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_parity.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_parity_tooling.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_permission_validators.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_pytest_plugin.py +0 -0
- {simcord-3.0.2 → simcord-3.2.0}/tests/unit/test_router.py +0 -0
|
@@ -4,13 +4,65 @@ This changelog is generated with [towncrier](https://towncrier.readthedocs.io/).
|
|
|
4
4
|
|
|
5
5
|
<!-- towncrier release notes start -->
|
|
6
6
|
|
|
7
|
+
## 3.2.0 (2026-10-22)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- Add offline SKU and entitlement simulation: catalog/history/live fixtures, six REST routes, interaction access, gateway lifecycle, consumable fulfilment, replay and restart reconciliation. Publish explicit uncalibrated Discord edge policies and the observations needed to reach parity. Unify virtual UTC with chronological resource/timer advancement and supported SDK clock helpers; allocating IDs no longer moves time. Enforce one live Env per loop and preserve application entitlements when the bot leaves a guild.
|
|
12
|
+
|
|
13
|
+
Retain undelivered automatic-clock settlement failures until a public operation receives them, and select pytest virtual UTC from the unique running environment even when fixtures also expose inactive foreign worlds.
|
|
14
|
+
|
|
15
|
+
Add an opt-in user-subscription reference recorder with hidden token entry, raw protocol evidence, authorized interaction checkpoints, session-owned test-grant cleanup and a local boundary self-check.
|
|
16
|
+
|
|
17
|
+
Correct reference-recorder subscription scope detection using named SDK flags; accept unpublished user SKUs and reject guild-only or ambiguous matches.
|
|
18
|
+
|
|
19
|
+
Fix stalled reference interaction checkpoints by decoding discord.py's supported raw Gateway receive event; exercise probe advancement through a local WebSocket and SDK dispatch in the self-check.
|
|
20
|
+
|
|
21
|
+
Preserve rejected different-SKU and post-deletion recreation probes as reference evidence without aborting the remaining subscription capture sequence.
|
|
22
|
+
|
|
23
|
+
Add a manual Application Test Mode purchase capture mode for consumable/repeated consumption and durable wrong-kind consumption. Require operator test-mode confirmation and new type-4 app/SKU/user-matched entitlements before consumption; retain purchase IDs and leave preexisting/live entitlements untouched.
|
|
24
|
+
|
|
25
|
+
Prepare remaining reference evidence with a paced rejection/duplicate/pagination runner, optional guild and legitimate lifecycle capture, durable ownership journals and safe recovery. Add a raw-evidence validator for new and existing bundles; report unavailable prerequisites without claiming full parity or repeating purchase flows.
|
|
26
|
+
|
|
27
|
+
Extend the follow-up recorder with unpublished same-app guild SKU selection, two-server/two-member projection checks, cross-scope grant ordering and owner validation. Add explicit fresh deleted-unconsumed purchase capture and optional zero-total team live-upgrade capture, with per-server command provenance, raw-evidence validation, resumable manual purchase IDs and persistent real-subscription cancellation obligations.
|
|
28
|
+
|
|
29
|
+
Fix checkout baselines stopping on 100 retained test grants by traversing complete history and validating both cursor branches. Resume blocked checkout stages without replaying completed guild/purchase sections, correlate interaction checkpoints to their accepted invocation, print blocker reasons and numbered checkout steps, and retain deleted-unconsumed evidence without a stale missing-fixture blocker.
|
|
30
|
+
|
|
31
|
+
Recognize observed PURCHASE-type live subscription entitlements alongside APPLICATION_SUBSCRIPTION for selection and cancellation recovery, retaining exact app/SKU/user and type-4 mutation boundaries. Preserve cancellation-only recovery after failed checkout identification and validate cleaned historical commands across recorder resumes.
|
|
32
|
+
|
|
33
|
+
Use complete subscription-list objects as authoritative cancellation proof instead of discarding them when a redundant detail lookup fails. Clear stale blocker reasons after positive verification and retain exact-reference, audited offline recovery without repeating Discord actions or altering raw protocol streams.
|
|
34
|
+
|
|
35
|
+
Add an expiry-only follow-up that derives and hashes an authenticated canceled-subscription source, reuses its before interaction, revalidates the real billing schedule, captures entitlement/subscription events and polls, and needs one post-end command. Automate a provenance-safe post-end sandbox grant probe/cleanup, preserve late-start and unavailable-reporting limits, and resume without repeating successful interactions or known grant outcomes.
|
|
36
|
+
|
|
37
|
+
Add an immediate upgrade/scheduled-downgrade follow-up linked to existing canceled-A evidence. Reuse, resume or freely restart A, capture A→B and cross-tier sandbox overlap, record pending B→A renewal, and verify cancellation for every managed real entitlement. Preserve missing-probe-only recovery, bounded read-only propagation settling and raw downgrade evidence links; reject manual/wrong-type/SKU/owner create receipts before cleanup ownership.
|
|
38
|
+
|
|
39
|
+
Calibrate monetization against bounded Discord reference captures without claiming full parity. Migrate catalog fixtures to explicit subscription groups and matching scope flags; interaction entitlements are stably guild-first, while REST pagination descends for default/before and ascends for after. API test grants retain null dates and guild bot purchasers, cross-tier scoped subscription conflicts, structured owner-validation errors and corrected Invalid SKU/Unknown SKU statuses. Consume/delete now reject repeats or deleted rows with calibrated errors; type-4 purchases permit DELETE without implying recorder cleanup ownership. Correct consumed-field presence and fulfillment/source metadata, preserving observed upgrade UPDATE snapshots separately from committed REST state. Document discord.py 2.7.1 raw-history traversal and distinguish fixture policy, unsupported billing/reporting and unverified lifecycle boundaries.
|
|
40
|
+
|
|
41
|
+
Harden recorder credential redaction and verify test-create receipts before granting cleanup ownership. Require explicit unconsumed state for consumables while preserving omitted consumed fields on durable purchases.
|
|
42
|
+
|
|
43
|
+
### Miscellaneous
|
|
44
|
+
|
|
45
|
+
- Require certifi>=2026.10.19 for documentation dependencies and update the lock, removing the affected eMudhra root certificate from its CA bundle. Existing environments require a separate dependency sync.
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
## 3.1.0 (2026-10-16)
|
|
49
|
+
|
|
50
|
+
### Features
|
|
51
|
+
|
|
52
|
+
- Thinking interaction defers now create fetchable, stable-ID originals; completing them through the first followup preserves defer-time visibility, while later followups are independent. Initial callbacks expire after three seconds (`10062`), separately from 15-minute continuation tokens (`10015`) and deleted originals (`10008`).
|
|
53
|
+
|
|
54
|
+
Add native actor and SDK forwarding with immutable rich snapshots, shared Message Content projection, permissions, and frozen Preview media authorized against the forward rather than the live source. Reject foreign-environment message handles before deterministic-ID resolution. Already-forwarded sources remain explicitly unsupported; snapshot metadata is copied while immutable CDN bytes are reused.
|
|
55
|
+
|
|
56
|
+
Keep strict task-scoped settlement diagnostics, but withdraw the unsafe blanket framework-run adapter recipe: only verified external handoffs should be declared, and no generic pydantic-ai adapter is established.
|
|
57
|
+
|
|
58
|
+
|
|
7
59
|
## 3.0.2 (2026-10-16)
|
|
8
60
|
|
|
9
61
|
### Bug fixes
|
|
10
62
|
|
|
11
63
|
- Honor explicit application Message Content approval on REST message responses independently of Gateway intents, including nested references. Restore ping-enabled replies to regular bot messages without exposing replies to interaction responses, preserve contextual interaction/token access, and keep backend fixtures complete. The default `approved_intents=None` retains unrestricted REST access; explicit absent approval is restricted.
|
|
12
64
|
|
|
13
|
-
Explicitly unapproved applications now receive redacted REST content, so tests that previously relied on unauthorized reads may fail and expose consumer bugs. Selecting a message in an interaction does not authorize later REST reads. Reply wire metadata and edit/deletion details remain offline-tested rather than live-Discord calibrated.
|
|
65
|
+
Explicitly unapproved applications now receive redacted REST content, so tests that previously relied on unauthorized reads may fail and expose consumer bugs. Selecting a message in an interaction does not authorize later REST reads. Reply wire metadata and edit/deletion details remain offline-tested rather than live-Discord calibrated.
|
|
14
66
|
|
|
15
67
|
|
|
16
68
|
## 3.0.1 (2026-10-06)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simcord
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Offline discord.py bot testing with pytest. No network, token, or test server.
|
|
5
5
|
Project-URL: Homepage, https://github.com/SilentHacks/simcord
|
|
6
6
|
Project-URL: Documentation, https://simcord.readthedocs.io/
|
|
@@ -39,6 +39,7 @@ Requires-Dist: pytest>=8; extra == 'dev'
|
|
|
39
39
|
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
40
40
|
Requires-Dist: towncrier>=24; extra == 'dev'
|
|
41
41
|
Provides-Extra: docs
|
|
42
|
+
Requires-Dist: certifi>=2026.10.19; extra == 'docs'
|
|
42
43
|
Requires-Dist: mkdocs-material>=9; extra == 'docs'
|
|
43
44
|
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
44
45
|
Provides-Extra: preview
|
|
@@ -24,7 +24,17 @@ top-level `simcord` package unless a module-qualified path is shown.
|
|
|
24
24
|
`settle_timeout` is the default maximum time spent joining runnable bot work. A direct
|
|
25
25
|
`await env.settle(timeout=...)` override is available; `idle=` is only the polling interval.
|
|
26
26
|
Use `await env.external_wait(awaitable, reason="...")` for one explicitly scoped external
|
|
27
|
-
input wait. Unknown waits remain active and produce
|
|
27
|
+
input wait. It applies to the calling task only. Unknown waits remain active and produce
|
|
28
|
+
a diagnostic timeout. Do not label whole framework runs as external waits; see the
|
|
29
|
+
[framework integration limits](concepts.md#actors-act-as-a-real-user).
|
|
30
|
+
|
|
31
|
+
### `Env(approved_intents=None)`
|
|
32
|
+
|
|
33
|
+
`None` skips explicit privileged-intent approval checks and permits REST Message Content
|
|
34
|
+
access, preserving the permissive fixture default. An explicit `discord.Intents.none()`
|
|
35
|
+
denies that access. REST authorization follows this Portal/API setting independently of
|
|
36
|
+
the bot's requested Gateway mask; normal exemptions and interaction-selected access remain.
|
|
37
|
+
Bot-visible projections never redact authoritative actor/backend fixture state.
|
|
28
38
|
|
|
29
39
|
|
|
30
40
|
## Request observability
|
|
@@ -50,6 +60,20 @@ methods. See [Core concepts → Builders](concepts.md#builders-arrange-the-world
|
|
|
50
60
|
|
|
51
61
|
::: simcord.UserHandle
|
|
52
62
|
|
|
63
|
+
### SKUs and entitlements
|
|
64
|
+
|
|
65
|
+
`Env.create_sku()` arranges explicit catalog entries (including non-grantable groups);
|
|
66
|
+
`seed_entitlement()` arranges history without CREATE; `await grant_entitlement()` emits
|
|
67
|
+
a live grant and settles. Recover a committed grant by ID with `get_entitlement()`.
|
|
68
|
+
See [SKUs & entitlements](guides/monetization.md) for calibrated REST fields/errors,
|
|
69
|
+
cross-tier sandbox conflicts, guild-first access, raw history traversal and the separate
|
|
70
|
+
fixture policies, unsupported subscription billing/reporting and unverified boundaries.
|
|
71
|
+
|
|
72
|
+
::: simcord.SKUHandle
|
|
73
|
+
|
|
74
|
+
::: simcord.EntitlementHandle
|
|
75
|
+
|
|
76
|
+
|
|
53
77
|
## Actors
|
|
54
78
|
|
|
55
79
|
The simulated human that drives your bot. Created by `guild.add_member(...)`. See
|
|
@@ -57,11 +57,19 @@ Common builders:
|
|
|
57
57
|
| `guild.create_text_channel(name, *, overwrites=…, topic=…)` | A `ChannelHandle`. |
|
|
58
58
|
| `guild.create_role(name, *, permissions=…)` | A `RoleHandle`. |
|
|
59
59
|
| `guild.add_member(user, *, roles=…, nick=…)` | A `MemberActor` (the user, now in the guild). |
|
|
60
|
+
| `env.create_sku(name)` | A `SKUHandle`; subscription groups are generated automatically. |
|
|
61
|
+
| `env.seed_entitlement(sku, guild=… / user=…)` | Historical `EntitlementHandle`, without CREATE. |
|
|
62
|
+
| `await env.grant_entitlement(sku, guild=… / user=…)` | Live entitlement, CREATE, and settled bot reactions. |
|
|
60
63
|
|
|
61
64
|
!!! tip "`env.guild` is a shortcut"
|
|
62
65
|
The first guild you create is available as `env.guild`, for the common single-guild
|
|
63
66
|
test. `env.guild.create_text_channel(...)` reads nicely.
|
|
64
67
|
|
|
68
|
+
Live entitlement grants are deliberately async: unlike historical seeding, they deliver
|
|
69
|
+
a causal event and settle it. Both handles expose detached real SDK `snapshot()` models.
|
|
70
|
+
The grant may already be committed when settlement times out; recover by ID, do not retry.
|
|
71
|
+
See [SKUs & entitlements](guides/monetization.md) for lifecycle and parity boundaries.
|
|
72
|
+
|
|
65
73
|
## Actors — act as a real user
|
|
66
74
|
|
|
67
75
|
**Actors are async and permission-checked.** A `MemberActor` (returned by
|
|
@@ -112,6 +120,22 @@ input:
|
|
|
112
120
|
await env.external_wait(stop.wait(), reason="wait for the next message")
|
|
113
121
|
```
|
|
114
122
|
|
|
123
|
+
Declarations are **task-scoped**, not inherited. A framework can have three distinct waits:
|
|
124
|
+
graph-event delivery in its caller, a lifecycle wrapper waiting for that caller to finish,
|
|
125
|
+
and a model/provider task waiting for external input. Declaring only the provider wait does
|
|
126
|
+
not make the other two safe to ignore. Declare only verified external handoffs through
|
|
127
|
+
the framework's public hooks; do not infer safety from coroutine names or suppress
|
|
128
|
+
every sibling task.
|
|
129
|
+
|
|
130
|
+
Do **not** wrap an entire `agent.run()` or lifecycle `wrap_run(handler)` in
|
|
131
|
+
`external_wait`. Ordinary initialization and finalization also run there, and timers
|
|
132
|
+
created by the declaring task become virtual. Even an ordinary initialization sleep
|
|
133
|
+
can then stop progressing without `env.advance_time()`. No safe generic pydantic-ai
|
|
134
|
+
framework adapter is established here: declaring a provider/backoff wait alone does
|
|
135
|
+
not solve its orchestration waits. Keep undeclared workers visible as errors rather
|
|
136
|
+
than weakening settlement to make the integration pass.
|
|
137
|
+
|
|
138
|
+
|
|
115
139
|
Successful settlement means no runnable tracked bot work remains. Public operations reject
|
|
116
140
|
overlap before mutating state, and teardown cancels only bot-owned work; caller tasks survive.
|
|
117
141
|
|
|
@@ -119,7 +143,7 @@ The actor verbs, by area:
|
|
|
119
143
|
|
|
120
144
|
| Area | Verbs |
|
|
121
145
|
| --- | --- |
|
|
122
|
-
| Text | `send`, `edit`, `delete`, `typing`, `send_dm` |
|
|
146
|
+
| Text | `send`, `forward`, `edit`, `delete`, `typing`, `send_dm` |
|
|
123
147
|
| Reactions | `react`, `unreact` |
|
|
124
148
|
| App commands | `slash`, `context_menu`, `autocomplete` |
|
|
125
149
|
| Components | `click`, `select`, `submit_modal` |
|
|
@@ -25,6 +25,7 @@ Copy the following into your project's `AGENTS.md`, `CLAUDE.md`, or equivalent a
|
|
|
25
25
|
- Exercise behavior through `simcord_env` builders and actors. Do not call a
|
|
26
26
|
command callback directly when the contract depends on discord.py dispatch.
|
|
27
27
|
- Slash, autocomplete, and context-menu actor calls enforce command visibility; inspect `available_commands()` or arrange permissions and contexts in the fixture, and use `UserHandle.slash()` / `.autocomplete()` for bot-DM commands.
|
|
28
|
+
- For premium flows use `create_sku`, `seed_entitlement` for history and async `grant_entitlement` for live CREATE; exercise real SDK consume/reconcile paths. Read the monetization guide's open parity checks before assuming undocumented Discord edge behavior.
|
|
28
29
|
- Keep tests offline. Never request, invent, log, or connect with a Discord token.
|
|
29
30
|
- Assert observable results from SimCord handles or the bot's real discord.py cache.
|
|
30
31
|
- Keep pure business logic in ordinary unit tests and mock only application-owned
|
|
@@ -92,6 +93,11 @@ The agent can now implement or repair the command against a stable contract.
|
|
|
92
93
|
|
|
93
94
|
This loop works for prefix commands, slash commands, autocomplete, context menus, views, modals, permissions, events, cooldowns, and sharded bots.
|
|
94
95
|
|
|
96
|
+
For premium access and fulfilment, use [SKUs & entitlements](monetization.md).
|
|
97
|
+
SimCord owns Discord resource simulation, not the bot's plans, allowances, trials or ledger.
|
|
98
|
+
Use `env.now` as an injected application clock and `env.time_context()` for manual-driver
|
|
99
|
+
SDK UTC reads; never replace the application's global datetime module.
|
|
100
|
+
|
|
95
101
|
## Prompt for a coding agent
|
|
96
102
|
|
|
97
103
|
```text
|
|
@@ -67,8 +67,9 @@ asyncio_mode = "auto"
|
|
|
67
67
|
|
|
68
68
|
## Without pytest
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
Use `simcord.run` directly for explicit lifecycle control — an async context manager that
|
|
71
|
+
yields the same `Env`. Only one live environment per event loop is supported; shut it
|
|
72
|
+
down before starting another on that loop.
|
|
72
73
|
|
|
73
74
|
```python
|
|
74
75
|
import simcord
|
|
@@ -108,6 +109,18 @@ migration bridge does not change command registration.
|
|
|
108
109
|
|
|
109
110
|
External waits must be scoped with `await env.external_wait(awaitable, reason="...")`; unknown waits time out. Overlapping actor, builder, lifecycle, and time-control operations reject before mutation. Teardown cancels bot-owned work but leaves caller-owned tasks alive.
|
|
110
111
|
|
|
112
|
+
Supported SDK UTC helpers use the selected Env clock automatically during the actual
|
|
113
|
+
pytest test call (including fixtures aliased under another name) when there is one
|
|
114
|
+
running Env. Direct `async with simcord.run(...)` also selects it. For manual
|
|
115
|
+
`Env.start()` / `shutdown()` use `with env.time_context():` around driver SDK time reads;
|
|
116
|
+
inject `env.now` for your own application clocks. Custom async fixtures should use manual
|
|
117
|
+
start/shutdown rather than retaining a `run` ContextVar token across `yield`, which older
|
|
118
|
+
pytest-asyncio versions can resume in another context.
|
|
119
|
+
|
|
120
|
+
Before startup, `create_sku` and `seed_entitlement` can arrange the state that
|
|
121
|
+
`setup_hook` reconciles over REST. Live `grant_entitlement` and handle transitions require
|
|
122
|
+
a running Env. See [SKUs & entitlements](monetization.md).
|
|
123
|
+
|
|
111
124
|
```python
|
|
112
125
|
# An isolated unit test that doesn't care about sync, and inspects errors itself:
|
|
113
126
|
async with simcord.run(bot, strict_sync=False) as env:
|
|
@@ -108,6 +108,8 @@ the bot's event callbacks or SDK fetches to assert its restricted view. The tran
|
|
|
108
108
|
also retains authoritative event data and is **not** a privacy-redacted export.
|
|
109
109
|
These contracts are covered offline; reply wire metadata and edit/deletion details
|
|
110
110
|
have not been calibrated against a live Discord capture.
|
|
111
|
+
Forward snapshots inherit their containing message's content access: redaction removes
|
|
112
|
+
content-bearing fields while retaining snapshot metadata.
|
|
111
113
|
|
|
112
114
|
## Member chunking
|
|
113
115
|
|
|
@@ -43,13 +43,13 @@ everything the bot did in response:
|
|
|
43
43
|
|
|
44
44
|
```python
|
|
45
45
|
result.acknowledged # did the bot respond at all?
|
|
46
|
-
result.deferred #
|
|
46
|
+
result.deferred # an original is still loading, or a component update was deferred
|
|
47
47
|
result.ephemeral # the response is only visible to the invoker
|
|
48
48
|
result.response # the response message (ResponseMessage | None)
|
|
49
49
|
result.response.content # its text
|
|
50
50
|
result.response.embeds # list[discord.Embed]
|
|
51
51
|
result.response.message # the full discord.Message, when you need it
|
|
52
|
-
result.followups # messages
|
|
52
|
+
result.followups # independent followup messages, excluding completion of a thinking original
|
|
53
53
|
result.modal # the modal payload, if one was shown
|
|
54
54
|
result.autocomplete_choices # choices, for autocomplete interactions
|
|
55
55
|
```
|
|
@@ -83,23 +83,54 @@ matching error-acknowledgement transition.
|
|
|
83
83
|
|
|
84
84
|
The full lifecycle behaves as on Discord:
|
|
85
85
|
|
|
86
|
-
- **
|
|
87
|
-
|
|
86
|
+
- **Initial deadline** — reply or defer before three seconds on the backend clock.
|
|
87
|
+
An unacknowledged callback at or after that deadline fails with `10062` (Unknown
|
|
88
|
+
Interaction), and its continuation token is invalid. Use `env.advance_time()` to
|
|
89
|
+
exercise this boundary without wall-clock sleeps.
|
|
90
|
+
|
|
91
|
+
- **Thinking defers (type 5)** — `await interaction.response.defer(thinking=True)` immediately
|
|
92
|
+
creates a fetchable original with `flags.loading`, its chosen ephemeral flag, and a stable
|
|
93
|
+
message ID. Public originals appear in public history; private ones are visible only to
|
|
94
|
+
the invoker.
|
|
95
|
+
- **Completing a thinking defer** — `edit_original_response()` is the preferred endpoint.
|
|
96
|
+
Discord's deprecated compatibility behavior also lets the first `followup.send()` complete
|
|
97
|
+
that same original. Both clear loading without changing its ID or visibility; a conflicting
|
|
98
|
+
`ephemeral=` flag cannot turn an existing public message private (or vice versa).
|
|
99
|
+
- **Component update defers (type 6)** — `defer()` without `thinking=True` keeps the clicked
|
|
100
|
+
message as the original. A followup is independent and does not consume or replace it.
|
|
88
101
|
- **Double-acknowledging** — responding twice raises the real Discord error `40060`. (A
|
|
89
102
|
callback that *fails* — e.g. a 400 from an oversized embed — does **not** consume the
|
|
90
103
|
interaction, so a retried response still gets through.)
|
|
91
|
-
- **Followups** —
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
- **Followups** — after the original is completed, `interaction.followup.send()` creates an
|
|
105
|
+
independent message with its requested visibility. Only these messages enter
|
|
106
|
+
`result.followups`; completion of a thinking original is available as `result.response`.
|
|
107
|
+
- **`@original` operations** — fetching, editing and deleting resolve the same stored message.
|
|
108
|
+
Deletion returns `result.response` to `None`, and later original reads/edits fail with
|
|
109
|
+
`10008`; a deleted pending original cannot be resurrected by a followup. Deleting an
|
|
110
|
+
already-completed original does not prevent independent followups.
|
|
111
|
+
- **Token expiry** — continuation webhooks expire after 15 minutes on the backend clock.
|
|
112
|
+
Expired tokens fail with `10015`, distinct from an absent/deleted message's `10008`.
|
|
94
113
|
|
|
95
114
|
```python
|
|
96
115
|
async def test_defer_then_followup(simcord_env):
|
|
97
116
|
...
|
|
98
117
|
result = await alice.slash(channel, "slow-task")
|
|
99
|
-
assert result.deferred
|
|
100
|
-
assert result.
|
|
118
|
+
assert not result.deferred
|
|
119
|
+
assert result.response.content == "Done!"
|
|
120
|
+
assert result.followups == []
|
|
101
121
|
```
|
|
102
122
|
|
|
123
|
+
This corrects the former first-followup-as-new-message behavior: migrate assertions from
|
|
124
|
+
`result.followups[0]` to `result.response` when the send completes a thinking original.
|
|
125
|
+
Pending originals also consume a message ID immediately, rather than on completion.
|
|
126
|
+
|
|
127
|
+
discord.py caches `interaction.original_response()` as a snapshot. To observe a later edit,
|
|
128
|
+
fetch fresh state with `interaction.followup.fetch_message(original.id)`; SimCord does not
|
|
129
|
+
patch the SDK cache. Choose private visibility at the initial defer when a response may
|
|
130
|
+
contain sensitive data. If visibility must change, complete the original without sensitive
|
|
131
|
+
content before sending a new, independently visible followup.
|
|
132
|
+
|
|
133
|
+
|
|
103
134
|
## Autocomplete
|
|
104
135
|
|
|
105
136
|
Type into an autocomplete option and get back the choices the bot offered:
|
|
@@ -47,6 +47,42 @@ Editing a message preserves immutable flags: ephemeral messages stay ephemeral,
|
|
|
47
47
|
on. If your test setup denies the action, you get a `SetupError` — see
|
|
48
48
|
[Permissions](permissions.md).
|
|
49
49
|
|
|
50
|
+
## Forwarding immutable message snapshots
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
source = await alice.send(channel, "Evidence", attachments=[("proof.txt", b"original")])
|
|
54
|
+
forwarded = await alice.forward(source, destination)
|
|
55
|
+
assert forwarded.reference.type is discord.MessageReferenceType.forward
|
|
56
|
+
assert forwarded.message_snapshots[0].content == "Evidence"
|
|
57
|
+
assert await forwarded.message_snapshots[0].attachments[0].read() == b"original"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Bot code can use native `await message.forward(destination)` too. Both paths store a
|
|
61
|
+
type-1 reference and a snapshot, rather than a reply's live `referenced_message`. Forwarding
|
|
62
|
+
does not ping the original author. Source visibility/history and destination send/attachment
|
|
63
|
+
permissions are enforced; SDK forwarding also requires application-level content access.
|
|
64
|
+
Actor message handles must belong to the same environment; colliding deterministic
|
|
65
|
+
IDs from another environment do not identify a local source. Cross-guild forwarding
|
|
66
|
+
within one environment remains supported, including SDK interaction originals and
|
|
67
|
+
webhook followups. Message handles retained across a bot restart remain environment-owned.
|
|
68
|
+
|
|
69
|
+
Snapshot metadata is copied when the forward is created and detached on observation. Later
|
|
70
|
+
source edits/deletion and mutations of returned objects do not alter it. Existing immutable
|
|
71
|
+
CDN bytes are reused, not duplicated. Actors and fixture history see authoritative snapshots;
|
|
72
|
+
bot Gateway/REST observations apply the normal Message Content policy.
|
|
73
|
+
|
|
74
|
+
Preview renders the frozen text, embeds, attachments, stickers and components without
|
|
75
|
+
inventing a source author or resolving a live reply. Historical interaction controls are
|
|
76
|
+
disabled; safe link buttons remain links. Snapshot downloads are authorized against the
|
|
77
|
+
forwarding message, not the source: readers of a deliberately shared forward need not have
|
|
78
|
+
source-channel access, but deleting the forward revokes its retained downloads.
|
|
79
|
+
|
|
80
|
+
**Explicit limit:** forwarding an already-forwarded message raises `NotImplementedError`.
|
|
81
|
+
Discord documents a nesting cap but not the transformation of nested snapshots plus outer
|
|
82
|
+
annotations; SimCord does not silently flatten/drop content or fabricate a Discord error.
|
|
83
|
+
Ordinary forwards, including outer annotations sent through the native SDK, are supported.
|
|
84
|
+
|
|
85
|
+
|
|
50
86
|
## Converters and checks run for real
|
|
51
87
|
|
|
52
88
|
Because the cache is populated through discord.py's actual parsers, converters resolve
|