simcord 3.2.2__tar.gz → 4.0.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-4.0.0/.github/workflows/dpy-master.yml +25 -0
- {simcord-3.2.2 → simcord-4.0.0}/.github/workflows/release.yml +1 -1
- {simcord-3.2.2 → simcord-4.0.0}/.github/workflows/verify.yml +2 -1
- {simcord-3.2.2 → simcord-4.0.0}/CHANGELOG.md +36 -0
- {simcord-3.2.2 → simcord-4.0.0}/CONTRIBUTING.md +11 -0
- {simcord-3.2.2 → simcord-4.0.0}/PKG-INFO +4 -4
- {simcord-3.2.2 → simcord-4.0.0}/docs/api.md +35 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/components.md +19 -3
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/diagnostics.md +119 -2
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/fixtures.md +15 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/permissions.md +74 -2
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/testing-slash-commands.md +7 -1
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/threads-reactions-dms.md +23 -2
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/time-control.md +59 -3
- simcord-4.0.0/docs/parity-matrix.md +703 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/stability.md +81 -5
- {simcord-3.2.2 → simcord-4.0.0}/pyproject.toml +5 -4
- simcord-4.0.0/scripts/behavior_reference/__init__.py +4 -0
- simcord-4.0.0/scripts/behavior_reference/__main__.py +249 -0
- simcord-4.0.0/scripts/behavior_reference/gateway.py +107 -0
- simcord-4.0.0/scripts/behavior_reference/ledger.py +372 -0
- simcord-4.0.0/scripts/behavior_reference/offline.py +152 -0
- simcord-4.0.0/scripts/behavior_reference/oracle.py +86 -0
- simcord-4.0.0/scripts/behavior_reference/publish.py +86 -0
- simcord-4.0.0/scripts/behavior_reference/runner.py +527 -0
- simcord-4.0.0/scripts/behavior_reference/scenarios.py +580 -0
- simcord-4.0.0/scripts/behavior_reference/transport.py +388 -0
- simcord-4.0.0/src/simcord/_clock.py +35 -0
- simcord-4.0.0/src/simcord/_diagnostics.py +160 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/_dpy_internals.py +98 -6
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/_settlement.py +13 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/actors.py +21 -5
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/access.py +29 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/command_access.py +7 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/channel.py +2 -1
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/base.py +116 -19
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/channels.py +38 -2
- simcord-4.0.0/src/simcord/backend/ops/commands.py +227 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/guilds.py +45 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/messages.py +12 -6
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/permissions.py +11 -4
- simcord-4.0.0/src/simcord/backend/permissions.py +134 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/serializers.py +7 -3
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/enums.py +1 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/env.py +173 -35
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/gateway.py +209 -1
- simcord-4.0.0/src/simcord/http/_access.py +14 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/_helpers.py +172 -8
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/client.py +3 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/router.py +36 -8
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/channels.py +127 -21
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/commands.py +37 -9
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/guilds.py +19 -2
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/messages.py +62 -26
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/reactions.py +20 -8
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_actions.py +2 -1
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_commands.py +19 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_messages.py +1 -3
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_snapshot.py +4 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/protocol.schema.json +1 -1
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/backend-diagnostics.json +7 -0
- simcord-4.0.0/src/simcord/pytest_plugin.py +382 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/types.py +2 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/conformance/_world.py +4 -0
- simcord-4.0.0/tests/conformance/test_behavior_ledger.py +22 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/conformance/test_field_honesty.py +4 -6
- {simcord-3.2.2 → simcord-4.0.0}/tests/conformance/test_field_honesty_fuzz.py +5 -6
- simcord-4.0.0/tests/fixtures/behavior/known_gaps.json +505 -0
- simcord-4.0.0/tests/fixtures/behavior/ledger.json +14206 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_access_boundaries.py +4 -4
- simcord-4.0.0/tests/integration/test_channel_behavior_fidelity.py +183 -0
- simcord-4.0.0/tests/integration/test_channel_obfuscation.py +317 -0
- simcord-4.0.0/tests/integration/test_command_fidelity.py +171 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_command_visibility.py +18 -44
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_forum.py +110 -1
- simcord-4.0.0/tests/integration/test_member_fidelity.py +87 -0
- simcord-4.0.0/tests/integration/test_message_fidelity.py +486 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_message_routes.py +30 -1
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_messages.py +2 -2
- simcord-4.0.0/tests/integration/test_native_typing.py +200 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_commands.py +39 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_fixes.py +17 -7
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_message_actions.py +4 -1
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_security.py +2 -1
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_runtime_executor_ownership.py +2 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_settlement_contract.py +50 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_shared_clock.py +128 -3
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_threads.py +21 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_time.py +97 -0
- simcord-4.0.0/tests/unit/test_behavior_reference.py +516 -0
- simcord-4.0.0/tests/unit/test_channel_obfuscation_wire.py +93 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_command_access.py +14 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_dpy_internals.py +3 -3
- simcord-4.0.0/tests/unit/test_nsfw_channel_policy.py +37 -0
- simcord-4.0.0/tests/unit/test_permission_diagnostics.py +317 -0
- simcord-4.0.0/tests/unit/test_permission_sweep.py +521 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_pytest_clock.py +13 -1
- simcord-4.0.0/tests/unit/test_sweep_error_handlers.py +159 -0
- {simcord-3.2.2 → simcord-4.0.0}/uv.lock +26 -50
- simcord-3.2.2/.github/workflows/dpy-master.yml +0 -22
- simcord-3.2.2/docs/parity-matrix.md +0 -416
- simcord-3.2.2/src/simcord/_clock.py +0 -15
- simcord-3.2.2/src/simcord/backend/ops/commands.py +0 -122
- simcord-3.2.2/src/simcord/backend/permissions.py +0 -71
- simcord-3.2.2/src/simcord/pytest_plugin.py +0 -93
- {simcord-3.2.2 → simcord-4.0.0}/.github/ISSUE_TEMPLATE/bug.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.github/ISSUE_TEMPLATE/feature.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.github/ISSUE_TEMPLATE/parity-gap.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.github/dependabot.yml +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.github/workflows/ci.yml +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.gitignore +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.pre-commit-config.yaml +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.python-version +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/.readthedocs.yaml +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/CODE_OF_CONDUCT.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/LICENSE +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/README.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/SECURITY.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/benchmarks/_helpers.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/benchmarks/simulation_attribution.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/benchmarks/test_dispatch_benchmarks.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/benchmarks/test_perf_guards.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/benchmarks/test_simulation_benchmarks.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/changes/README.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/architecture.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/adventure/alpine.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/adventure/canyon.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/attachments.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/buttons.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/embeds.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/entities.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/layouts.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/markdown.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/modals.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/polls.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/replies.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/selects.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/preview/slash-commands.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/simcord-showcase-poll-refresh.gif +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/simcord-terminal-demo.gif +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/simcord-transparent.webp +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/assets/social-card.png +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/changelog.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/concepts.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/cookbook.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/faq.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/ai-coding-agents.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/discord-visual-calibration.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/intents.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/interactions.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/messages.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/mocks-vs-simulation.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/moderation-and-guild.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/monetization.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/preview.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/test-without-token.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/guides/testing-discord-py-bots.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/index.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/installation.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/llms.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/performance.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/preview-attestation.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/quickstart.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/docs/robots.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/examples/README.md +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/examples/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/examples/bot.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/examples/conftest.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/examples/preview_example.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/examples/test_bot.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/jsconfig.preview.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/mkdocs.yml +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/overrides/main.html +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/package-lock.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/package.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/capture_discord_reference.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/capture_monetization_reference.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/capture_remaining_monetization.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/capture_visual_reference.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/check_discoverability.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/check_package.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/check_reference_modal_offline.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/compare_visual_reference.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/discord_reference_bot.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/monetization_reference_checkout.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/monetization_reference_config.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/monetization_reference_expiry.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/monetization_reference_journal.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/monetization_reference_lifecycle.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/monetization_reference_transport.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/monetization_reference_upgrade.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/preview_dogfood.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/record_readme_demo.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/replay_visual_reference.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/validate_discord_reference_pack.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/scripts/validate_monetization_reference.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/_modal.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/_payload.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/_runtime.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/asserts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/_deadlines.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/cdn.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/errors.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/auditlog.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/automod.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/expression.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/guild.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/interaction.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/invite.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/member.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/message.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/monetization.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/role.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/scheduled_event.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/stage_instance.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/user.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/voice.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/models/webhook.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/audit.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/expressions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/invites.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/monetization.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/polls.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/reactions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/scheduled_events.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/voice.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/ops/webhooks.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/backend/state.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/builders.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/components.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/application.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/auditlogs.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/automod.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/expressions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/interactions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/invites.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/monetization.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/scheduled_events.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/http/routes/stage_instances.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/intents.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/interactions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/monetization.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/parity.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_action_plans.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_assets.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_capture.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_component_projection.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_dependencies.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_diagnostics.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_identity.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_markdown.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_media.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_media_worker.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_pages.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_queries.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/_server.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/actions.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/app.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/assets.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/command-catalog.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/command-drafts.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/commands.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/components.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/composer.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/connection.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/diagnostics.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/dom.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/focus.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Arabic.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-CJK.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Devanagari.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Emoji.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto-Hebrew.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/OFL-1.1-Noto.txt +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/manifest.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-color-emoji-v2.051.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-sans-arabic-v2.012.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-sans-devanagari-v2.007.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-sans-hebrew-v3.001.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-sans-latin-italic-v2.015.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-sans-latin-v2.015.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-sans-mono-v2.014.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/fonts/noto-sans-sc-v2.004.ttf +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/geometry.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/highlight-types.d.ts +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/index.html +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/listbox.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/media.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/messages.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/modal.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/presentation.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/preview-types.d.ts +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/preview.css +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/select-drafts.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/selects.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/session.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/surfaces.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/text.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/timeline.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/transport.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/LICENSE +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/SHA256SUMS +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/core.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/bash.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/cpp.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/csharp.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/css.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/diff.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/dockerfile.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/go.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/ini.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/java.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/javascript.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/json.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/markdown.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/python.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/rust.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/sql.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/typescript.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/xml.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/highlight/es/languages/yaml.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/lottie/5.12.2/lottie_light_canvas.min.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/lottie/LICENSE +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/vendor/lottie/SHA256SUMS +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/preview/static/workbench.js +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/py.typed +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/src/simcord/results.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/conformance/test_route_coverage.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/conformance/test_serializers.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/conftest.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/commands/option_cases.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/catalog.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/coverage.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/measurements.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/moving-square.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/profiles.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/states.json +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/video.mp4 +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/preview/voice.ogg +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/sample_bot/__init__.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/sample_bot/events.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/sample_bot/general.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/sample_bot/interactions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/sample_bot/moderation.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/fixtures/sample_bot/picker.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/preview_helpers.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_app_commands.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_application_emojis.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_asserts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_atomic_mutations.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_audit_logs.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_automod.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_bot_users_and_webhooks.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_callback_thread_safety.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_channels.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_command_permissions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_common_bot_sweep.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_components_v2_interactions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_components_v2_messages.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_coverage_sweep.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_diagnostics.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_edge_cases.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_entitlement_journey.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_error_capture_and_edits.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_expressions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_forwarding.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_gateway_and_automod_exempt.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_guilds.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_intents.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_interaction_followups.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_interactions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_invites.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_lifecycle.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_members.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_message_content_rest.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_misc_branches.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_moderation.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_observation_contracts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_polls.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_action_admission.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_additional.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_browser_ownership.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_capture.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_capture_dx.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_channel.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_command_picker.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_components_v2.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_coverage.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_dx.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_edges.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_entity_contracts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_fonts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_identity.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_modal_contracts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_navigation_contracts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_observations.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_provenance.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_render_contracts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_support_report.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_viewport_ownership.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_preview_workbench_contracts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_reactions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_reference_replay.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_roles_and_ordering.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_scheduled_events.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_sdk_compatibility.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_setup_hook_suspension.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_sharding.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_stage_instances.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_transport_edges.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_visual_reference_geometry.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_voice_state.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/integration/test_world_helpers.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_assert_helpers.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_automod_preparation.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_backend_lookup_errors.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_backend_performance_contracts.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_components_v2_validation.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_errors.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_intent_rules.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_lazy_import.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_local_eliminations.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_backend.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_http.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_reference_capture.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_reference_checkout.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_reference_expiry.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_reference_lifecycle.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_reference_upgrade.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_monetization_reference_validation.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_option_validation.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_parity.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_parity_tooling.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_payload_ownership.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_permission_validators.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_permissions.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_preview_action_specs.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_pytest_plugin.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_remaining_monetization_capture.py +0 -0
- {simcord-3.2.2 → simcord-4.0.0}/tests/unit/test_router.py +0 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Early warning for discord.py internal changes: run the suite against the
|
|
2
|
+
# upstream development branch every week (and on demand).
|
|
3
|
+
name: discord.py master
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
schedule:
|
|
7
|
+
- cron: "0 6 * * 1"
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
test:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v7
|
|
18
|
+
- uses: astral-sh/setup-uv@v7
|
|
19
|
+
- run: |
|
|
20
|
+
uv sync --locked --extra dev --extra screenshot
|
|
21
|
+
uv pip install --no-deps -U "discord.py @ git+https://github.com/Rapptz/discord.py"
|
|
22
|
+
# Do not let project sync replace the upstream checkout with locked discord.py.
|
|
23
|
+
- run: uv run --no-sync python -m playwright install --with-deps chromium
|
|
24
|
+
- run: uv run --no-sync python -c "import discord; print(discord.__version__)"
|
|
25
|
+
- run: uv run --no-sync pytest tests examples -q
|
|
@@ -11,7 +11,7 @@ jobs:
|
|
|
11
11
|
runs-on: ubuntu-latest
|
|
12
12
|
steps:
|
|
13
13
|
- uses: actions/checkout@v7
|
|
14
|
-
- uses: actions/setup-node@
|
|
14
|
+
- uses: actions/setup-node@v7
|
|
15
15
|
with:
|
|
16
16
|
node-version: "24"
|
|
17
17
|
cache: npm
|
|
@@ -40,6 +40,7 @@ jobs:
|
|
|
40
40
|
- run: uv sync --locked --extra dev --extra screenshot
|
|
41
41
|
- run: uv run python -m playwright install --with-deps chromium
|
|
42
42
|
- run: uv run coverage run -m pytest tests examples -q
|
|
43
|
+
- run: uv run coverage combine
|
|
43
44
|
- run: uv run coverage report
|
|
44
45
|
|
|
45
46
|
docs:
|
|
@@ -4,6 +4,42 @@ This changelog is generated with [towncrier](https://towncrier.readthedocs.io/).
|
|
|
4
4
|
|
|
5
5
|
<!-- towncrier release notes start -->
|
|
6
6
|
|
|
7
|
+
## 4.0.0 (2026-11-09)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- Add an independently recorded 184-scenario Discord behavior ledger, receipt-safe manual recorder and recovery tooling, permission-endpoint cross-checks, strict offline mismatch/stale-gap ratchets, and a generated dated evidence table. Deliberate metadata and pre-rollout REST differences remain explicitly documented rather than counted as parity.
|
|
12
|
+
- Add permission explanations, structured denial details on HTTP request records and transcripts, observed-permission reports, and explicit degradation assertions. Nonthrowing history and pin-list checks contribute observed flags without inventing HTTP errors. `pytest --simcord-sweep` reruns passing function-scoped tests with authentic permission revocation, reports unhandled bot exceptions/timeouts and application assertions with source locations, and distinguishes unsupported configurations from exercised coverage, including xdist runs.
|
|
13
|
+
- Project the bot's gateway cache through Discord channel obfuscation and omit hidden channels from guild REST channel listings. Role, overwrite and bot-membership changes refresh visibility; authoritative test handles retain their real data. Deferred gateway delivery retains emission-time access rather than authorizing old events against later permission changes. There is no legacy-visibility opt-out. See the permissions guide for the distinction between documented behavior and unrecorded field details.
|
|
14
|
+
|
|
15
|
+
### Bug fixes
|
|
16
|
+
|
|
17
|
+
- Match independently recorded Discord behavior for message payload permissions, reply/history access, reactions, pins and crossposts; archived and locked threads, creator edit authority, voice/stage text access, mentions and silent effects. Require `pin_messages` rather than `manage_messages` for bot and simulated-user pin/unpin operations. Add announcement threads and required forum tags, enforce captured validation boundaries and error trees, reject invalid timeouts and message targets, and normalize stored application commands with atomic bulk validation. See the 3.x-to-4.0 migration guide and dated behavior ledger for deliberate gaps and bot-versus-actor boundaries.
|
|
18
|
+
Keep permission reads side-effect-free during sweeps, report unsupported role-sharing transitions explicitly, preserve visible voice departures without exposing hidden destinations, and round-trip forum pin flags. Reject NSFW enabling on commands and channels while keeping unrelated previews usable; observe requested permission-dependent send effects and preserve exact Administrator permission explanations.
|
|
19
|
+
Skipped revocation lifecycles no longer count as exercised coverage, and previously captured unhandled bot failures retain precedence over skips.
|
|
20
|
+
|
|
21
|
+
### Miscellaneous
|
|
22
|
+
|
|
23
|
+
- Allow PyAV 17 and 18 in the optional Preview and screenshot extras, and refresh the locked media dependency to PyAV 18.1.0.
|
|
24
|
+
- Repair the weekly discord.py development-branch check by installing Preview/browser dependencies and retaining the upstream SDK installation instead of resynchronizing it back to the locked release.
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
## 3.3.0 (2026-11-02)
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
- Add the aware `start_time` option to `Env`, `simcord.run` and pytest markers, applying a validated UTC origin before fixtures and startup while preserving the deterministic default and elapsed time across bot restarts. Application and SQL clocks still require explicit application clock injection.
|
|
32
|
+
|
|
33
|
+
### Bug fixes
|
|
34
|
+
|
|
35
|
+
- Recognize discord.py's native typing interval as a virtual-only wait while preserving the initial typing request, every five-second refresh, HTTP work, and normal cancellation and lifecycle cleanup.
|
|
36
|
+
- Deliver already-real-due native asyncio cancellation deadlines through blocked input during automatic clock advancement, preserving chronological resource/timer processing, strict settlement, executor ownership, and undelivered settlement failures.
|
|
37
|
+
|
|
38
|
+
### Documentation
|
|
39
|
+
|
|
40
|
+
- Clarify the guild-versus-DM handle scopes enforced since 3.2.1, with actionable setup errors and migration examples: use `member.user.send_dm(...)` for DM messages and `member.user` for DM slash commands, autocomplete, components, and modals. The existing `member.send_dm(...)` convenience remains supported; accepted and rejected actions are unchanged.
|
|
41
|
+
|
|
42
|
+
|
|
7
43
|
## 3.2.2 (2026-10-31)
|
|
8
44
|
|
|
9
45
|
### Bug fixes
|
|
@@ -16,6 +16,13 @@ uv run ruff check src tests examples benchmarks scripts && uv run ruff format --
|
|
|
16
16
|
uv run pyright src
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
Coverage includes the pytest plugin's real subprocess runs. To measure the full
|
|
20
|
+
suite, run `uv run coverage run -m pytest tests examples`, then
|
|
21
|
+
`uv run coverage combine` before `uv run coverage report`. The
|
|
22
|
+
[native subprocess patch](https://coverage.readthedocs.io/en/stable/subprocess.html)
|
|
23
|
+
requires coverage.py 7.10 or newer; do not replace those lifecycle checks with
|
|
24
|
+
mocked pytest calls or lower the coverage ratchet.
|
|
25
|
+
|
|
19
26
|
The Python hooks run ruff on commit and pyright on push using the project's
|
|
20
27
|
pinned tools. Run the frontend check below separately; passing Python hooks
|
|
21
28
|
does not replace the JavaScript CI gate.
|
|
@@ -32,6 +39,10 @@ uv sync --extra dev --extra screenshot # matches the CI lint job
|
|
|
32
39
|
uv run playwright install --with-deps chromium # needed once for browser capture tests + the example
|
|
33
40
|
```
|
|
34
41
|
|
|
42
|
+
The weekly `discord.py master` workflow installs the same screenshot dependencies
|
|
43
|
+
and Chromium before exercising upstream discord.py. After installing that checkout,
|
|
44
|
+
use `uv run --no-sync` so the project lock does not replace it with the released SDK.
|
|
45
|
+
|
|
35
46
|
Frontend contributors also need Node 24:
|
|
36
47
|
|
|
37
48
|
```bash
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: simcord
|
|
3
|
-
Version:
|
|
3
|
+
Version: 4.0.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/
|
|
@@ -29,7 +29,7 @@ Requires-Dist: pytest-asyncio>=0.24; extra == 'bench'
|
|
|
29
29
|
Requires-Dist: pytest-benchmark>=4; extra == 'bench'
|
|
30
30
|
Requires-Dist: pytest>=8; extra == 'bench'
|
|
31
31
|
Provides-Extra: dev
|
|
32
|
-
Requires-Dist: coverage[toml]>=7; extra == 'dev'
|
|
32
|
+
Requires-Dist: coverage[toml]>=7.10; extra == 'dev'
|
|
33
33
|
Requires-Dist: hypothesis>=6; extra == 'dev'
|
|
34
34
|
Requires-Dist: jsonschema>=4; extra == 'dev'
|
|
35
35
|
Requires-Dist: pre-commit>=3; extra == 'dev'
|
|
@@ -45,7 +45,7 @@ Requires-Dist: mkdocs-material>=9; extra == 'docs'
|
|
|
45
45
|
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
46
46
|
Provides-Extra: preview
|
|
47
47
|
Requires-Dist: aiohttp>=3.9; extra == 'preview'
|
|
48
|
-
Requires-Dist: av<
|
|
48
|
+
Requires-Dist: av<19,>=14; extra == 'preview'
|
|
49
49
|
Requires-Dist: linkify-it-py>=2.0.3; extra == 'preview'
|
|
50
50
|
Requires-Dist: markdown-it-py>=4.1; extra == 'preview'
|
|
51
51
|
Requires-Dist: pillow>=10; extra == 'preview'
|
|
@@ -56,7 +56,7 @@ Requires-Dist: pytest-asyncio>=0.24; extra == 'pytest'
|
|
|
56
56
|
Requires-Dist: pytest>=8; extra == 'pytest'
|
|
57
57
|
Provides-Extra: screenshot
|
|
58
58
|
Requires-Dist: aiohttp>=3.9; extra == 'screenshot'
|
|
59
|
-
Requires-Dist: av<
|
|
59
|
+
Requires-Dist: av<19,>=14; extra == 'screenshot'
|
|
60
60
|
Requires-Dist: linkify-it-py>=2.0.3; extra == 'screenshot'
|
|
61
61
|
Requires-Dist: markdown-it-py>=4.1; extra == 'screenshot'
|
|
62
62
|
Requires-Dist: pillow>=10; extra == 'screenshot'
|
|
@@ -19,6 +19,17 @@ top-level `simcord` package unless a module-qualified path is shown.
|
|
|
19
19
|
|
|
20
20
|
::: simcord.Env
|
|
21
21
|
|
|
22
|
+
### `Env(start_time=None)`
|
|
23
|
+
|
|
24
|
+
Sets the immutable initial virtual UTC instant before backend IDs, fixtures and startup.
|
|
25
|
+
`None` preserves 2026-01-01 UTC. Supply an aware `datetime`; offsets normalize to UTC.
|
|
26
|
+
Invalid values raise `SetupError`. Supported UTC instants are at least
|
|
27
|
+
2015-01-01 00:00:00 and strictly before 2154-05-15 07:35:11.104.
|
|
28
|
+
Elapsed time advances normally and survives bot restarts. `simcord.run` and
|
|
29
|
+
`@pytest.mark.simcord` accept the same option. This does not synchronize application or
|
|
30
|
+
SQL wall clocks; bind application time seams to `env.now` before startup when needed.
|
|
31
|
+
See [time control](guides/time-control.md#choosing-the-initial-utc-instant).
|
|
32
|
+
|
|
22
33
|
### `Env(settle_timeout=5.0)`
|
|
23
34
|
|
|
24
35
|
`settle_timeout` is the default maximum time spent joining runnable bot work. A direct
|
|
@@ -47,6 +58,30 @@ encoded headers; uploaded files are not captured. The `json` field preserves the
|
|
|
47
58
|
JSON-compatible shape, including top-level arrays. `params` and `json` are detached deep
|
|
48
59
|
snapshots, so later mutations to source containers do not alter a record.
|
|
49
60
|
|
|
61
|
+
Permission refusals additionally populate `denial` with structured authorization
|
|
62
|
+
detail; successful requests leave it `None`. This diagnostic is separate from the
|
|
63
|
+
authentic Discord error body.
|
|
64
|
+
|
|
65
|
+
### Permission diagnostics
|
|
66
|
+
|
|
67
|
+
`Env.explain_permissions(actor, channel)` returns a JSON-friendly mapping with
|
|
68
|
+
`actor_id`, `guild_id`, the requested `channel_id`, `permission_channel_id`,
|
|
69
|
+
ordered `steps`, and final integer `effective` bits. Each step includes `source`,
|
|
70
|
+
`before`, `allow`, `deny` and `after`, satisfying `after = (before & ~deny) | allow`;
|
|
71
|
+
roles and overwrites include their target identifiers. Administrator resets to the
|
|
72
|
+
canonical all-permissions mask, discarding unknown bits. Thread explanations resolve
|
|
73
|
+
inherited parent overwrites.
|
|
74
|
+
|
|
75
|
+
`Env.permission_checks` is a detached snapshot of bot authorization checks.
|
|
76
|
+
`Env.observed_permissions()` returns a `discord.Permissions` union of exercised
|
|
77
|
+
flags, including denied checks and nonthrowing checks for requested send effects:
|
|
78
|
+
enabled TTS, known external emoji references, and policy-permitted mass mentions
|
|
79
|
+
or unmentionable role pings. Refused effects still degrade successfully and do not
|
|
80
|
+
populate a successful request's `denial`. Plain text, suppressed mentions, and DMs
|
|
81
|
+
do not add irrelevant guild effect flags. This reports exercised paths, not the
|
|
82
|
+
permissions needed by every possible bot execution. See
|
|
83
|
+
[permission diagnostics](guides/diagnostics.md#permission-diagnostics).
|
|
84
|
+
|
|
50
85
|
## Builders
|
|
51
86
|
|
|
52
87
|
Synchronous, omnipotent handles for arranging the virtual Discord. Returned by `env`/`guild`
|
|
@@ -26,9 +26,25 @@ inspect remote media dimensions or contents.
|
|
|
26
26
|
|
|
27
27
|
Nested buttons and selects dispatch through their real discord.py callbacks. The same
|
|
28
28
|
`click`, `select`, and `submit_modal` methods are available on `UserHandle` for component
|
|
29
|
-
flows in DMs.
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
flows in DMs. Since 3.2.1, use `member.user` for these DM actions rather than the
|
|
30
|
+
guild-scoped `MemberActor`:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
opened = await alice.user.click(dm_message, custom_id="feedback:open")
|
|
34
|
+
submitted = await alice.user.submit_modal(opened, {"comment": "Great bot"})
|
|
35
|
+
# For a DM select menu:
|
|
36
|
+
selected = await alice.user.select(dm_menu, ["green"], custom_id="color")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
DM callbacks receive SDK interactions with `interaction.guild is None`. Keep using
|
|
40
|
+
`alice.click(...)`, `alice.select(...)`, and `alice.submit_modal(...)` for interactions in
|
|
41
|
+
`alice`'s guild. A `UserHandle` cannot act on guild messages; use a `MemberActor` belonging
|
|
42
|
+
to the target guild instead. These are SimCord handle scopes, not limitations on what the
|
|
43
|
+
same human can do in Discord.
|
|
44
|
+
|
|
45
|
+
Use `component_text=` and `component_custom_id=` with `assert_message`, `assert_sent`,
|
|
46
|
+
or `assert_responded` to inspect layouts without treating TextDisplay text as legacy
|
|
47
|
+
message content.
|
|
32
48
|
|
|
33
49
|
## Clicking buttons
|
|
34
50
|
|
|
@@ -131,14 +131,131 @@ assert len(posts) == 1
|
|
|
131
131
|
assert posts[0].json["content"] == "Pong!"
|
|
132
132
|
```
|
|
133
133
|
|
|
134
|
-
Each record exposes `method`, `path`, `params`, `json`, and `
|
|
135
|
-
arguments discord.py
|
|
134
|
+
Each record exposes `method`, `path`, `params`, `json`, `reason`, and `denial`.
|
|
135
|
+
Transport arguments retain discord.py's values: query parameters are not wire-normalized
|
|
136
136
|
strings, and `reason` is not an encoded HTTP header value. Uploaded files are not captured.
|
|
137
137
|
The `json` field preserves every supplied JSON-compatible shape, including top-level arrays.
|
|
138
138
|
`params` and `json` are detached deep snapshots, so later mutations to source containers do
|
|
139
139
|
not alter a record. The request record is appended before fault injection and route handling,
|
|
140
140
|
so failed and unimplemented requests remain observable.
|
|
141
141
|
|
|
142
|
+
## Permission diagnostics
|
|
143
|
+
|
|
144
|
+
Explain effective permission bits independently of a failed operation:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
trace = simcord_env.explain_permissions(simcord_env.bot.user, channel)
|
|
148
|
+
effective = discord.Permissions(trace["effective"])
|
|
149
|
+
for step in trace["steps"]:
|
|
150
|
+
print(step["source"], step["before"], step["after"])
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The ordered trace covers base roles, owner/Administrator bypasses, `@everyone`,
|
|
154
|
+
aggregated role and member overwrites, and active timeout masking. Threads use
|
|
155
|
+
their parent's overwrites. This explains permission bits, not every REST policy:
|
|
156
|
+
thread state, channel access and role hierarchy can independently refuse an action.
|
|
157
|
+
|
|
158
|
+
Bot refusals add `DENIAL` entries to the transcript and structured `denial` detail
|
|
159
|
+
to the failing HTTP request record. Discord exception text and error JSON remain
|
|
160
|
+
unchanged. `env.permission_checks` returns detached check records with actor,
|
|
161
|
+
guild and channel IDs, required/missing flags, effective bits, outcome and reason.
|
|
162
|
+
Non-permission authorization refusals, such as role hierarchy, are attributed too.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
observed = simcord_env.observed_permissions()
|
|
166
|
+
print([name for name, used in observed if used])
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Observed flags include refused checks. They describe only paths this environment
|
|
170
|
+
exercised, not a least-privilege guarantee for your entire bot.
|
|
171
|
+
Implicit guild-channel access contributes `view_channel` (reported as
|
|
172
|
+
`read_messages` when iterating discord.py's native permissions). DM checks do not
|
|
173
|
+
invent guild permission requirements.
|
|
174
|
+
History and pin listings also record their nonthrowing `read_message_history`
|
|
175
|
+
checks. Without that permission, the listings still succeed with an empty result;
|
|
176
|
+
the check is refused, but no HTTP exception or request denial is invented.
|
|
177
|
+
|
|
178
|
+
### Silent degradations
|
|
179
|
+
|
|
180
|
+
Some successful requests lose an effect without a Discord error:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
for degradation in simcord_env.degradations:
|
|
184
|
+
print(degradation["kind"])
|
|
185
|
+
simcord_env.assert_no_degradations() # opt-in assertion
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Records are detached snapshots. Modeled ordinary-send cases are missing TTS
|
|
189
|
+
permission (`tts_permission_dropped`), unmentionable role pings
|
|
190
|
+
(`unmentionable_role_ping_dropped`), ignored ephemeral flags
|
|
191
|
+
(`normal_message_ephemeral_ignored`) and known external custom emojis without
|
|
192
|
+
the external-emoji permission (`external_emoji_permission_stripped`).
|
|
193
|
+
An explicit allowed-mentions policy suppressing a ping is intentional, not a
|
|
194
|
+
degradation. Unknown emoji IDs are not guessed or rewritten. Successful TTS
|
|
195
|
+
creates retain `tts=True` in the create result/event but ordinary bot messages
|
|
196
|
+
read later have `tts=False`, matching the captured behavior.
|
|
197
|
+
|
|
198
|
+
### Suite reports and failure sweeps
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
pytest --simcord-observed-permissions
|
|
202
|
+
pytest --simcord-sweep
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The observed-permissions option aggregates exercised flags across environments
|
|
206
|
+
and xdist workers. The sweep first runs each test normally, then reruns passing
|
|
207
|
+
tests with fresh function-scoped fixtures once per observed permission.
|
|
208
|
+
It discovers both `simcord_env` and environments created directly or through
|
|
209
|
+
`simcord.run`.
|
|
210
|
+
|
|
211
|
+
Revocation changes real bot-exclusive roles and bot member overwrites, not HTTP
|
|
212
|
+
fault responses. REST enforcement, interaction `app_permissions` and cached
|
|
213
|
+
`channel.permissions_for(guild.me)` therefore agree. A channel overwrite can
|
|
214
|
+
revoke a channel permission while `guild.me.guild_permissions` correctly retains
|
|
215
|
+
the guild-level grant.
|
|
216
|
+
|
|
217
|
+
Existing worlds are projected once when registered. Canonical builder and REST
|
|
218
|
+
permission mutations project the affected guild/channel before returning models,
|
|
219
|
+
serializing responses or publishing events, including silent construction.
|
|
220
|
+
Permission reads never project, mutate state or emit events. Raw model/dictionary
|
|
221
|
+
permission writes are unsupported; use the builder or backend mutation methods.
|
|
222
|
+
|
|
223
|
+
Test-driver assertions that no longer hold under revocation are ignored.
|
|
224
|
+
Assertions raised by application code are findings, like other unhandled bot
|
|
225
|
+
exceptions and timeouts, and are reported with the permission and source location;
|
|
226
|
+
findings make pytest exit unsuccessfully. Handled refusals are not findings.
|
|
227
|
+
Baseline failures remain ordinary test failures and are not swept.
|
|
228
|
+
|
|
229
|
+
Successful custom event, prefix-command, application-command, View and Modal
|
|
230
|
+
error handlers consume their errors for sweep purposes. A handler that raises
|
|
231
|
+
or reraises still produces a finding. Routed errors remain in `Env.errors` for
|
|
232
|
+
normal diagnostics and its existing assertion policy; the sweep distinguishes
|
|
233
|
+
those observations from unhandled runtime failures.
|
|
234
|
+
|
|
235
|
+
Env-owned callback/task errors and settlement timeouts carry bot-ownership
|
|
236
|
+
evidence. Direct product calls can also produce findings when their traceback
|
|
237
|
+
reaches application code under pytest's root, outside the configured `testpaths`.
|
|
238
|
+
Keep test helpers under those test paths: a direct SDK helper refusal, or a
|
|
239
|
+
failure without application ownership evidence, is unsupported rather than an
|
|
240
|
+
invented bot failure. Broader external application code is still covered when
|
|
241
|
+
executed as Env-owned bot work. A baseline using only permissionless interaction
|
|
242
|
+
callbacks may observe no flags; that is not permission-revocation coverage.
|
|
243
|
+
|
|
244
|
+
The report separates exercised and unsupported cases. Broader-scoped user
|
|
245
|
+
fixtures, reused environments, bot-owned guilds, Administrator bypasses,
|
|
246
|
+
and guild-scoped grants that cannot be removed without changing other members
|
|
247
|
+
are unsupported. Removing Administrator could revoke unrelated permissions, so
|
|
248
|
+
it is never presented as a faithful one-flag sweep. Sharing a role after its
|
|
249
|
+
guild-scoped grant was suppressed restores that owned bit and stops the rerun
|
|
250
|
+
as unsupported; pytest still tears down its fixtures. Explicit later permission
|
|
251
|
+
edits relinquish ownership of the old suppressed bit; unrelated role/overwrite
|
|
252
|
+
edits are retained. No roles are cloned and no whole-world rollback is used.
|
|
253
|
+
Harness/setup failures are unsupported rather than invented bot failures.
|
|
254
|
+
Skipped revocation setup or test calls are unsupported, not exercised coverage;
|
|
255
|
+
any already-captured unhandled bot errors remain findings.
|
|
256
|
+
An unsupported case is not evidence that your bot handles revocation.
|
|
257
|
+
Missing xdist worker reports explicitly mark coverage incomplete.
|
|
258
|
+
|
|
142
259
|
## Injecting API failures
|
|
143
260
|
|
|
144
261
|
Real bots have to survive Discord hiccups. `env.inject_error` makes matching REST calls fail
|
|
@@ -100,6 +100,7 @@ Both `simcord.run(bot, **options)` and the underlying `Env` accept these keyword
|
|
|
100
100
|
| `approved_intents` | `None` (all approved) | Simulates developer-portal privileged-intent toggles and REST Message Content access, independently of the Gateway request bit. Explicit `Intents.none()` denies approval. |
|
|
101
101
|
| `shard_count` | client setting | Supplies the Get Gateway Bot recommendation when an `AutoShardedClient` does not configure `shard_count` itself. |
|
|
102
102
|
| `settle_timeout` | `5.0` seconds | Default maximum time for joining runnable bot work; a direct settle `timeout=` overrides it. |
|
|
103
|
+
| `start_time` | `None` (2026-01-01 UTC) | Aware initial UTC instant, normalized from any offset before fixtures/startup; see [time control](time-control.md#choosing-the-initial-utc-instant) for supported bounds and application clock wiring. |
|
|
103
104
|
|
|
104
105
|
`settle_timeout` does not change startup's fixed five-second READY settlement budget.
|
|
105
106
|
Restart drains existing work using `settle_timeout`, then gets a fresh fixed five-second
|
|
@@ -181,6 +182,20 @@ async def test_command_logic_in_isolation(simcord_env):
|
|
|
181
182
|
...
|
|
182
183
|
```
|
|
183
184
|
|
|
185
|
+
The same marker accepts an initial clock origin:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from datetime import UTC, datetime
|
|
189
|
+
|
|
190
|
+
@pytest.mark.simcord(start_time=datetime(2030, 4, 5, tzinfo=UTC))
|
|
191
|
+
async def test_future_entitlement(simcord_env):
|
|
192
|
+
assert simcord_env.now() == datetime(2030, 4, 5, tzinfo=UTC)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
For application clock injection needed by `setup_hook`, construct `Env` and bind
|
|
196
|
+
`env.now` before manual startup in your fixture; the
|
|
197
|
+
[time-control example](time-control.md#choosing-the-initial-utc-instant) includes teardown.
|
|
198
|
+
|
|
184
199
|
In 3.0, error snapshots with prefix acknowledgement and detached mutable result
|
|
185
200
|
payloads are unconditional. Remove the temporary 2.3 `future_behavior` marker/run
|
|
186
201
|
option; see [Migration](../stability.md#migrating-from-23-to-30).
|
|
@@ -61,8 +61,12 @@ state and cross-guild role/member targets are rejected before channel creation.
|
|
|
61
61
|
(`50013`). It also can't delete the `@everyone` role.
|
|
62
62
|
- **Editing others' messages** — the bot can't edit another user's message (`50005`).
|
|
63
63
|
- **Timeouts** — a timed-out member loses everything except viewing and reading history.
|
|
64
|
-
- **
|
|
65
|
-
existing reaction
|
|
64
|
+
- **Bot reactions** — `add_reactions` is required to introduce a new emoji, not
|
|
65
|
+
to join an existing reaction or remove your own. Read Message History is not
|
|
66
|
+
required on these bot REST paths. Simulated users and Preview retain their
|
|
67
|
+
separate history-access checks.
|
|
68
|
+
- **Pins** — pinning and unpinning require `pin_messages`, for both bots and
|
|
69
|
+
simulated users. `manage_messages` does not grant this permission.
|
|
66
70
|
|
|
67
71
|
```python
|
|
68
72
|
async def test_cannot_ban_higher_role(simcord_env):
|
|
@@ -81,6 +85,74 @@ async def test_cannot_ban_higher_role(simcord_env):
|
|
|
81
85
|
assert guild.get_ban(boss) is None
|
|
82
86
|
```
|
|
83
87
|
|
|
88
|
+
## Bot-visible channels
|
|
89
|
+
|
|
90
|
+
The bot's cache follows Discord's [channel-obfuscation contract](https://docs.discord.com/developers/resources/channel#channel-object-obfuscated-channels):
|
|
91
|
+
channels it cannot view have the name `___hidden___`, the obfuscated flag, neutral
|
|
92
|
+
sensitive fields and a synthetic `@everyone` overwrite denying `view_channel`.
|
|
93
|
+
Guild channel listings omit those channels; fetching one directly is denied.
|
|
94
|
+
Role and overwrite changes update this projection, including threads under a
|
|
95
|
+
hidden parent. Categories remain visible when a child is visible, and a connected
|
|
96
|
+
voice channel remains visible to the bot.
|
|
97
|
+
|
|
98
|
+
Builders, actors and backend state retain their authoritative data. Use a
|
|
99
|
+
`ChannelHandle` to inspect test setup, not the bot cache. Interaction wire payloads
|
|
100
|
+
remain unobfuscated; discord.py 2.7.1 may nevertheless resolve `Interaction.channel`
|
|
101
|
+
from its existing cache, and does not expose the wire obfuscation flag on every
|
|
102
|
+
channel class.
|
|
103
|
+
|
|
104
|
+
Our [live evidence](../parity-matrix.md#behavior-evidence) includes opt-in gateway
|
|
105
|
+
captures for text, voice, forum and category channels. Structural fields such as
|
|
106
|
+
slowmode and voice limits are retained. REST list filtering follows Discord's
|
|
107
|
+
documented mandatory behavior; it was not yet enforced in the dated live capture
|
|
108
|
+
and is an explicit known gap, not a claim of matching that pre-rollout response.
|
|
109
|
+
Uncaptured neutral-field details and category/voice visibility edge rules remain
|
|
110
|
+
documented simulator policy.
|
|
111
|
+
|
|
112
|
+
Visible voice occupants are projected per connection at event admission, including
|
|
113
|
+
events waiting for FIFO parser delivery. Moving into a hidden channel (or losing
|
|
114
|
+
visibility while stationary) clears the SDK voice cache with a departure built
|
|
115
|
+
only from the last authorized snapshot and `channel_id: null`. Hidden-to-hidden
|
|
116
|
+
movement discloses nothing; a reveal restores current authorized occupants.
|
|
117
|
+
This departure wire shape is an intentional privacy/cache-safety inference, not
|
|
118
|
+
a recorded live Discord observation. Canonical backend voice state is unchanged,
|
|
119
|
+
and normal voice intent and shard routing still apply.
|
|
120
|
+
|
|
121
|
+
## Bot REST behavior
|
|
122
|
+
|
|
123
|
+
- Embeds, uploads and polls on ordinary messages require `embed_links`,
|
|
124
|
+
`attach_files` and `send_polls`. Replies require history (`160002`); fetching a
|
|
125
|
+
message, pinning and crossposting also check history. Listing pins without
|
|
126
|
+
history succeeds with an empty list.
|
|
127
|
+
- Voice and stage text actions require `connect` (`50001`). Pinning there is
|
|
128
|
+
refused with `50019`, even when `pin_messages` is granted.
|
|
129
|
+
- Archived-thread edit, delete, bulk delete, reaction and pin actions fail with
|
|
130
|
+
`50083`. Sending automatically unarchives an unlocked thread after validating
|
|
131
|
+
the payload. Locked sends need `manage_threads`; locked unarchive without it
|
|
132
|
+
fails with `160005`.
|
|
133
|
+
- Thread creators may rename, archive and lock their threads; other members
|
|
134
|
+
need `manage_threads`. Edits and unarchive also need
|
|
135
|
+
`send_messages_in_threads`. **Deleting requires `manage_threads`, including
|
|
136
|
+
for the creator**, as confirmed by our own recording. Leaving needs no view
|
|
137
|
+
access.
|
|
138
|
+
- Forum thread pinning and unpinning require `manage_threads`, even for the
|
|
139
|
+
creator. The pinned flag is returned consistently by edits, fetches, gateway
|
|
140
|
+
events and active/archived listings. Pinning non-forum threads and changing
|
|
141
|
+
unsupported thread flag bits are rejected before mutation; existing unrelated
|
|
142
|
+
bits are preserved.
|
|
143
|
+
- NSFW channels are not modeled: channel creation/edit and thread creation reject
|
|
144
|
+
enabling NSFW. Omitted or literal `False` metadata is accepted inertly; there is
|
|
145
|
+
no way to enable NSFW.
|
|
146
|
+
- `AllowedMentions` controls the returned mention arrays. Missing TTS permission,
|
|
147
|
+
unmentionable role pings, ignored ephemeral flags on normal sends and denied
|
|
148
|
+
known external custom emojis are modeled as successful requests with reduced
|
|
149
|
+
effects, reported through [`env.degradations`](diagnostics.md#silent-degradations).
|
|
150
|
+
|
|
151
|
+
These bot recordings do not establish the rules for simulated human actors.
|
|
152
|
+
Actor behavior is unchanged except for the documented pin-permission split and
|
|
153
|
+
the docs-based locked-thread send guard.
|
|
154
|
+
|
|
155
|
+
|
|
84
156
|
## Application command visibility
|
|
85
157
|
|
|
86
158
|
Before an actor dispatches a slash command, autocomplete request, or context menu, SimCord checks whether that user could see the command in that guild and channel. This is separate from callback checks such as `@app_commands.checks.has_permissions(...)`; hidden commands raise `SetupError` instead of dispatching an interaction. See [slash command visibility](testing-slash-commands.md#command-visibility) for scope/context rules, permission overrides, reason codes, and fixture setup.
|
|
@@ -90,13 +90,19 @@ uploaded = await alice.slash(channel, "upload", file=("photo.png", b"image bytes
|
|
|
90
90
|
|
|
91
91
|
## Bot-DM slash commands and visible command lists
|
|
92
92
|
|
|
93
|
-
`UserHandle.slash(name, **options)` and `UserHandle.autocomplete(name, option, value, **filled)` invoke global commands whose contexts allow `BOT_DM`. Their interactions have `context == 1`:
|
|
93
|
+
`UserHandle.slash(name, **options)` and `UserHandle.autocomplete(name, option, value, **filled)` invoke global commands whose contexts allow `BOT_DM`. Their interactions have `context == 1`, and the SDK callback receives `interaction.guild is None`:
|
|
94
94
|
|
|
95
95
|
```python
|
|
96
96
|
user = simcord_env.create_user("alice")
|
|
97
97
|
await user.slash("help") # The bot's callback receives interaction.context == 1.
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
For an existing guild member, use `await member.user.slash("help")` or
|
|
101
|
+
`await member.user.autocomplete("tag", "name", "py")` for DM commands. Since 3.2.1,
|
|
102
|
+
passing `member.user.dm_channel` to `member.slash(...)` or `member.autocomplete(...)`
|
|
103
|
+
is rejected: `MemberActor` is guild-scoped. Keep `member.slash(channel, ...)` and
|
|
104
|
+
`member.autocomplete(channel, ...)` for its guild.
|
|
105
|
+
|
|
100
106
|
Use `member.available_commands(channel)` or `user.available_commands()` to get a tuple of leaf invocation strings that are visible there and accepted by `slash()` (for example, `"config set"`):
|
|
101
107
|
|
|
102
108
|
```python
|
|
@@ -70,11 +70,32 @@ reply = alice.user.dm_channel.history() # what the bot DM'd back
|
|
|
70
70
|
assert reply[-1].content.startswith("Hi")
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
`alice` above is a `MemberActor`; `alice.send_dm(...)` remains a supported convenience
|
|
74
|
+
that delegates to `alice.user.send_dm(...)`. Other guild-scoped actor methods do not
|
|
75
|
+
target DMs. Use the `UserHandle` (`alice.user`) for DM messages, commands, components,
|
|
76
|
+
and modals; keep `alice` for operations in its guild.
|
|
77
|
+
|
|
78
|
+
Since 3.2.1, migrate calls that passed a DM channel to a guild actor:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
# Before 3.2.1: a guild actor was used to target a DM.
|
|
82
|
+
await alice.send(alice.user.dm_channel, "hello bot")
|
|
83
|
+
|
|
84
|
+
# Now: send through the user's DM-scoped handle.
|
|
85
|
+
await alice.user.send_dm("hello bot")
|
|
86
|
+
# This existing convenience also remains supported:
|
|
87
|
+
await alice.send_dm("hello bot")
|
|
88
|
+
|
|
89
|
+
# Guild operations still use the guild member.
|
|
90
|
+
await alice.send(channel, "hello guild")
|
|
91
|
+
```
|
|
92
|
+
|
|
73
93
|
`alice.user.dm_channel` is the user's DM channel with the bot; `.history()` returns the
|
|
74
94
|
conversation as real `discord.Message` objects. A `UserHandle` can also invoke global slash
|
|
75
95
|
commands and autocomplete in that DM when their command contexts allow `BOT_DM`; these
|
|
76
|
-
interactions carry `context == 1
|
|
77
|
-
|
|
96
|
+
interactions carry `context == 1` and the SDK callback receives `interaction.guild is None`.
|
|
97
|
+
Use `user.available_commands()` to list the slash invocations visible there. See
|
|
98
|
+
[slash command visibility and DM commands](testing-slash-commands.md#bot-dm-slash-commands-and-visible-command-lists).
|
|
78
99
|
|
|
79
100
|
Guild actors can edit/delete their messages and pin with `set_pinned`. In the user's bot DM,
|
|
80
101
|
`UserHandle` supports their own message edits/deletes, desired reactions, and poll votes:
|
|
@@ -9,6 +9,47 @@ Time-dependent behaviour — `View` timeouts, command cooldowns, `asyncio.sleep`
|
|
|
9
9
|
— is normally painful to test: you either wait in real time (slow, flaky) or mock the clock
|
|
10
10
|
by hand (fiddly, leaky). SimCord gives you a **virtual clock** you fast-forward in one call.
|
|
11
11
|
|
|
12
|
+
## Choosing the initial UTC instant
|
|
13
|
+
|
|
14
|
+
`Env(bot, start_time=...)` and `simcord.run(bot, start_time=...)` accept an aware
|
|
15
|
+
`datetime`. The default remains **2026-01-01 00:00:00 UTC**. Offsets normalize to UTC;
|
|
16
|
+
naive datetimes, non-datetimes and unsupported instants raise `SetupError` before backend
|
|
17
|
+
IDs or runtime hooks are created. The supported initial UTC range is
|
|
18
|
+
**2015-01-01 00:00:00 inclusive through 2154-05-15 07:35:11.104 exclusive**.
|
|
19
|
+
The origin is fixed at construction, already applies to pre-start fixtures and backend
|
|
20
|
+
timestamps, and survives bot restarts along with elapsed virtual time. There is no rewind
|
|
21
|
+
or `set_time` operation.
|
|
22
|
+
|
|
23
|
+
Bind your application's existing clock seam **before** startup so `setup_hook` and
|
|
24
|
+
fixture writes use the same clock. For example, a custom pytest fixture can use manual
|
|
25
|
+
lifecycle control:
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from datetime import UTC, datetime
|
|
29
|
+
|
|
30
|
+
import pytest_asyncio
|
|
31
|
+
import simcord
|
|
32
|
+
|
|
33
|
+
@pytest_asyncio.fixture
|
|
34
|
+
async def world():
|
|
35
|
+
bot = create_bot()
|
|
36
|
+
env = simcord.Env(bot, start_time=datetime(2030, 4, 5, 12, tzinfo=UTC))
|
|
37
|
+
bot.service.now = env.now # application-owned callable used by startup and writes
|
|
38
|
+
await env.start()
|
|
39
|
+
try:
|
|
40
|
+
yield env
|
|
41
|
+
finally:
|
|
42
|
+
await env.shutdown()
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The standard pytest fixture also accepts
|
|
46
|
+
`@pytest.mark.simcord(start_time=datetime(2030, 4, 5, tzinfo=UTC))`.
|
|
47
|
+
Use a manual fixture when application wiring must happen before `env.start()`.
|
|
48
|
+
Selecting an initial origin does **not** synchronize Python wall time, SQL
|
|
49
|
+
`CURRENT_TIMESTAMP`/`now()`, database defaults or application clocks. Inject `env.now`
|
|
50
|
+
through existing application time seams, including relevant persisted writes, rather
|
|
51
|
+
than globally monkeypatching datetime, SQL or SDK functions.
|
|
52
|
+
|
|
12
53
|
## `env.advance_time(seconds)`
|
|
13
54
|
|
|
14
55
|
```python
|
|
@@ -35,6 +76,9 @@ Anything the bot scheduled against the loop's clock:
|
|
|
35
76
|
- **Resource deadlines** — polls, scheduled events and explicitly scheduled entitlement ends.
|
|
36
77
|
- **Supported SDK UTC helpers** — entitlement expiry, `discord.utils.sleep_until`, and
|
|
37
78
|
relative/calendar `discord.ext.tasks` loops use the selected virtual clock.
|
|
79
|
+
- **Typing refresh intervals** — the native SDK typing task's direct five-second sleep
|
|
80
|
+
is virtual-only. Its initial HTTP request and every refresh still execute and join
|
|
81
|
+
normally; HTTP waits and retries are not treated as parked typing intervals.
|
|
38
82
|
|
|
39
83
|
```python
|
|
40
84
|
async def test_cooldown_resets(simcord_env):
|
|
@@ -109,13 +153,17 @@ and retry shutdown.
|
|
|
109
153
|
|
|
110
154
|
## Determinism
|
|
111
155
|
|
|
112
|
-
Snowflake IDs and UTC start from
|
|
113
|
-
time: IDs use a sequence within the current millisecond
|
|
114
|
-
against collisions. Exhausting the sequence fails instead
|
|
156
|
+
Snowflake IDs and UTC start from the configured origin (the fixed default is unchanged).
|
|
157
|
+
ID allocation does not advance time: IDs use a sequence within the current millisecond
|
|
158
|
+
and explicit IDs are reserved against collisions. Exhausting the sequence fails instead
|
|
159
|
+
of silently moving UTC.
|
|
115
160
|
Shard-pinned guild fixtures can use an earlier timestamp so they route to the requested
|
|
116
161
|
shard without time travel. Their `created_at` can precede arrangement time, and fixture
|
|
117
162
|
creation order need not match numeric ID order. Normal snowflake timestamps have
|
|
118
163
|
millisecond precision, while backend UTC retains microseconds.
|
|
164
|
+
At the Discord epoch, allocation skips ID zero. A shard request requiring a timestamp
|
|
165
|
+
before that epoch raises `SetupError` without reserving IDs. Allocation beyond the
|
|
166
|
+
64-bit snowflake timestamp range also rejects rather than generating an invalid ID.
|
|
119
167
|
Settlement joins runnable work before and after each timer step. Deadline timers behind
|
|
120
168
|
recognized waits — `wait_for` timeouts on listener waits, View/Modal expiry, `ext.tasks`
|
|
121
169
|
loop intervals and wake-up timers scheduled inside `env.external_wait` — never fire on the
|
|
@@ -124,6 +172,14 @@ recognized only when its live loop timer resolves the awaited future; unrelated
|
|
|
124
172
|
timers do not make an unknown wait look parked. `advance_time()` is itself exclusive with
|
|
125
173
|
other public operations and rejects invalid or negative durations.
|
|
126
174
|
|
|
175
|
+
Unrecognized waits can have real-due timers handled by the automatic coordinator. That
|
|
176
|
+
fallback advances through its due horizon even when an unrelated input remains blocked,
|
|
177
|
+
still processing resource deadlines before tied bot timers. It does not declare the
|
|
178
|
+
blocked input settled, relax executor ownership or suppress settlement failures.
|
|
179
|
+
Recognized intervals such as typing refresh sleeps remain virtual-only. Explicit
|
|
180
|
+
`advance_time()` still uses strict settlement and reports committed partial progress on
|
|
181
|
+
failure.
|
|
182
|
+
|
|
127
183
|
If an advance times out, earlier transitions remain committed. The exception notes
|
|
128
184
|
report requested and reached UTC instants. Resolve the outstanding work and advance
|
|
129
185
|
only the remaining duration; do not repeat the original duration or replay an action.
|