hap-cli 0.8.27__tar.gz → 0.8.28__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.
- {hap_cli-0.8.27 → hap_cli-0.8.28}/PKG-INFO +1 -1
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/chat_cmd.py +80 -4
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/post_cmd.py +69 -9
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/chat.py +153 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/post.py +23 -7
- hap_cli-0.8.28/hap_cli/guide/en/post.md +101 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/index.json +8 -0
- hap_cli-0.8.28/hap_cli/guide/zh-Hans/post.md +89 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/i18n.py +12 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/locale/messages.json +42 -10
- hap_cli-0.8.28/hap_cli/tests/test_chat_recall_cli.py +159 -0
- hap_cli-0.8.28/hap_cli/tests/test_post_scope_cli.py +203 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli.egg-info/SOURCES.txt +4 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/MANIFEST.in +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/README.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/README.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/commands/worksheet_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/context.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/record.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/worksheet.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/en/approval.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/en/chart.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/approval.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/chart.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_core.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/charts.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/config.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/errors.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/executor.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/fields.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/hap.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/schema.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/step.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/steps.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/store.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/setup.cfg +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/setup.py +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.27 → hap_cli-0.8.28}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -274,8 +274,11 @@ def chat_send_to_one(ctx, to, message, files, dry_run):
|
|
|
274
274
|
\b
|
|
275
275
|
hap chat send-to-one -t <accountId> -m "spec attached" -f ./spec.pdf
|
|
276
276
|
|
|
277
|
-
Text and each file arrive as separate messages, in that order.
|
|
278
|
-
|
|
277
|
+
Text and each file arrive as separate messages, in that order.
|
|
278
|
+
|
|
279
|
+
To message yourself, send to the file-transfer assistant with
|
|
280
|
+
-t file-transfer. Your own account id is not a valid target and is
|
|
281
|
+
skipped.
|
|
279
282
|
"""
|
|
280
283
|
if not to:
|
|
281
284
|
ctx.handle_error(click.UsageError(
|
|
@@ -302,9 +305,20 @@ def chat_send_to_one(ctx, to, message, files, dry_run):
|
|
|
302
305
|
return
|
|
303
306
|
try:
|
|
304
307
|
session = ctx.get_session()
|
|
308
|
+
# The file-transfer assistant is a conversation id, not an account
|
|
309
|
+
# id, so the account-addressed endpoint drops it silently. It goes
|
|
310
|
+
# over the socket path the chat panel uses instead.
|
|
311
|
+
socket_targets = [t for t in to if t == chat_mod.FILE_TRANSFER_ID]
|
|
312
|
+
account_targets = [t for t in to if t != chat_mod.FILE_TRANSFER_ID]
|
|
305
313
|
if not sources:
|
|
306
|
-
|
|
307
|
-
|
|
314
|
+
results = {}
|
|
315
|
+
if account_targets:
|
|
316
|
+
results["accounts"] = chat_mod.send_message(
|
|
317
|
+
session, account_targets, message)
|
|
318
|
+
for target in socket_targets:
|
|
319
|
+
results[target] = chat_mod.send_user_message(
|
|
320
|
+
session, target, message)
|
|
321
|
+
ctx.output(results, lambda d: click.echo("Message sent."))
|
|
308
322
|
return
|
|
309
323
|
descriptors = chat_mod.upload_chat_files(session, sources)
|
|
310
324
|
acks = chat_mod.send_files(session, list(to), descriptors, message)
|
|
@@ -430,3 +444,65 @@ def chat_card_detail(ctx, task, post, calendar, kcfile, worksheet, worksheetrow)
|
|
|
430
444
|
except Exception as e:
|
|
431
445
|
ctx.handle_error(e)
|
|
432
446
|
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@chat.command("recall")
|
|
450
|
+
@click.argument("message_id")
|
|
451
|
+
@click.option("--group-id", "group_id", default="", help="Group id the message is in")
|
|
452
|
+
@click.option("--with-user", "with_user", default="",
|
|
453
|
+
help="Peer accountId for a 1-on-1 message")
|
|
454
|
+
@click.option("--time", "msg_time", default="",
|
|
455
|
+
help="Send time of the message. Looked up from the conversation "
|
|
456
|
+
"when omitted.")
|
|
457
|
+
@click.option("--as-admin", is_flag=True,
|
|
458
|
+
help="Recall as a group admin. Admins may recall another "
|
|
459
|
+
"member's message; ordinary recall only covers your own.")
|
|
460
|
+
@click.option("--dry-run", is_flag=True,
|
|
461
|
+
help="Show which message would be recalled, without recalling it.")
|
|
462
|
+
@pass_context
|
|
463
|
+
def chat_recall(ctx, message_id, group_id, with_user, msg_time, as_admin, dry_run):
|
|
464
|
+
"""Recall a message you sent.
|
|
465
|
+
|
|
466
|
+
You can recall your own message within 5 minutes of sending it; a group
|
|
467
|
+
admin can recall another member's message with no time limit. A recall
|
|
468
|
+
cannot be undone.
|
|
469
|
+
"""
|
|
470
|
+
if bool(group_id) == bool(with_user):
|
|
471
|
+
raise click.UsageError("Pass exactly one of --group-id or --with-user.")
|
|
472
|
+
try:
|
|
473
|
+
session = ctx.get_session()
|
|
474
|
+
found = None
|
|
475
|
+
if not msg_time:
|
|
476
|
+
# The recall payload carries the message's send time; look it
|
|
477
|
+
# up so the caller only needs the message id.
|
|
478
|
+
history = (
|
|
479
|
+
chat_mod.get_group_messages(session, group_id, num=50)
|
|
480
|
+
if group_id else
|
|
481
|
+
chat_mod.get_user_messages(session, with_user, num=50)
|
|
482
|
+
) or []
|
|
483
|
+
for m in history:
|
|
484
|
+
if str(m.get("id")) == str(message_id):
|
|
485
|
+
found = m
|
|
486
|
+
msg_time = m.get("time", "")
|
|
487
|
+
break
|
|
488
|
+
if dry_run:
|
|
489
|
+
ctx.output(
|
|
490
|
+
{"messageId": message_id,
|
|
491
|
+
"groupId": group_id, "withUser": with_user,
|
|
492
|
+
"time": msg_time,
|
|
493
|
+
"preview": ((found or {}).get("msg") or {}).get("con"),
|
|
494
|
+
"recalled": False},
|
|
495
|
+
lambda d: click.echo(
|
|
496
|
+
f"Would recall {d['messageId']}"
|
|
497
|
+
+ (f" ({d['preview']!r})" if d.get("preview") else "")),
|
|
498
|
+
)
|
|
499
|
+
return
|
|
500
|
+
admin_id = session.account_id if as_admin else ""
|
|
501
|
+
data = chat_mod.withdraw_message(
|
|
502
|
+
session, message_id,
|
|
503
|
+
group_id=group_id, to_user=with_user,
|
|
504
|
+
time=msg_time, admin_account_id=admin_id,
|
|
505
|
+
)
|
|
506
|
+
ctx.output(data, lambda d: click.echo("Message recalled."))
|
|
507
|
+
except Exception as e:
|
|
508
|
+
ctx.handle_error(e)
|
|
@@ -644,25 +644,74 @@ def post_get(ctx, post_id):
|
|
|
644
644
|
help="Post type: 0=text, 3=link, 5=vote, 7=file",
|
|
645
645
|
)
|
|
646
646
|
@click.option(
|
|
647
|
-
"--
|
|
647
|
+
"--to-group",
|
|
648
648
|
"share_group_ids",
|
|
649
649
|
multiple=True,
|
|
650
|
-
help="
|
|
650
|
+
help="Audience: members of this group can see the post (repeatable). "
|
|
651
|
+
"Naming a group does not also publish to the organization.",
|
|
651
652
|
)
|
|
652
653
|
@click.option(
|
|
653
|
-
"--
|
|
654
|
+
"--to-org",
|
|
654
655
|
"share_org_ids",
|
|
655
656
|
multiple=True,
|
|
656
|
-
help=
|
|
657
|
-
|
|
658
|
-
"goes only to --org-id."
|
|
659
|
-
),
|
|
657
|
+
help="Audience: everyone in this organization can see the post "
|
|
658
|
+
"(repeatable). This is the company-wide feed.",
|
|
660
659
|
)
|
|
660
|
+
@click.option("--share-group", "legacy_group_ids", multiple=True, hidden=True)
|
|
661
|
+
@click.option("--share-org", "legacy_org_ids", multiple=True, hidden=True)
|
|
662
|
+
@click.option("--card-to-group", "card_group_ids", multiple=True,
|
|
663
|
+
help="After publishing, also send a card linking to the post "
|
|
664
|
+
"into this group chat (repeatable). This is a chat "
|
|
665
|
+
"message and does not change who can see the post.")
|
|
666
|
+
@click.option("--dry-run", is_flag=True,
|
|
667
|
+
help="Show the audience and what would be sent, without publishing.")
|
|
661
668
|
@pass_context
|
|
662
|
-
def post_create(ctx, org_id, message, post_type, share_group_ids, share_org_ids
|
|
663
|
-
|
|
669
|
+
def post_create(ctx, org_id, message, post_type, share_group_ids, share_org_ids,
|
|
670
|
+
legacy_group_ids, legacy_org_ids, card_group_ids, dry_run):
|
|
671
|
+
"""Create a new post in the given organization.
|
|
672
|
+
|
|
673
|
+
Audience is set with --to-group / --to-org and defaults to everyone in
|
|
674
|
+
--org-id when neither is given. Who can see a post, and sending a card
|
|
675
|
+
about it to a group chat, are covered in `hap guide post`.
|
|
676
|
+
"""
|
|
664
677
|
try:
|
|
678
|
+
share_group_ids = tuple(share_group_ids) + tuple(legacy_group_ids)
|
|
679
|
+
share_org_ids = tuple(share_org_ids) + tuple(legacy_org_ids)
|
|
680
|
+
if dry_run:
|
|
681
|
+
audience = (
|
|
682
|
+
[f"group:{g}" for g in share_group_ids]
|
|
683
|
+
+ [f"org:{o}" for o in share_org_ids]
|
|
684
|
+
) or [f"org:{org_id or '<current>'} (everyone, default)"]
|
|
685
|
+
ctx.output(
|
|
686
|
+
{"audience": audience, "cardToGroups": list(card_group_ids),
|
|
687
|
+
"message": message, "published": False},
|
|
688
|
+
lambda d: click.echo(
|
|
689
|
+
"Would publish to: " + ", ".join(d["audience"])
|
|
690
|
+
+ (f"; card to groups: {', '.join(d['cardToGroups'])}"
|
|
691
|
+
if d["cardToGroups"] else "")),
|
|
692
|
+
)
|
|
693
|
+
return
|
|
665
694
|
session = ctx.get_session()
|
|
695
|
+
# Only a group with the feed turned on can be a post audience. The
|
|
696
|
+
# server rejects the rest with "选择了没有加入的群组" (you picked a
|
|
697
|
+
# group you have not joined), which is misleading when you are in
|
|
698
|
+
# the group — it is a plain chat group with no feed. Check first so
|
|
699
|
+
# the message names the real reason.
|
|
700
|
+
if share_group_ids:
|
|
701
|
+
from hap_cli.core import group as group_mod
|
|
702
|
+
no_feed = []
|
|
703
|
+
for gid in share_group_ids:
|
|
704
|
+
info = group_mod.get_group_info(session, gid) or {}
|
|
705
|
+
if isinstance(info.get("data"), dict):
|
|
706
|
+
info = info["data"]
|
|
707
|
+
if not info.get("isPost"):
|
|
708
|
+
no_feed.append(info.get("name") or gid)
|
|
709
|
+
if no_feed:
|
|
710
|
+
raise click.ClickException(
|
|
711
|
+
"These groups cannot receive a post because they are chat "
|
|
712
|
+
"groups with no feed enabled: " + ", ".join(no_feed)
|
|
713
|
+
+ ". Pick a group whose feed is on, or send a card to the "
|
|
714
|
+
"chat instead with --card-to-group.")
|
|
666
715
|
pid = require_api_project_id(session, org_id)
|
|
667
716
|
raw = post_mod.add_post(
|
|
668
717
|
session,
|
|
@@ -674,10 +723,21 @@ def post_create(ctx, org_id, message, post_type, share_group_ids, share_org_ids)
|
|
|
674
723
|
)
|
|
675
724
|
post_obj = raw.get("post") if isinstance(raw, dict) else None
|
|
676
725
|
summary = _simplify_post(post_obj) if post_obj else {"raw": raw}
|
|
726
|
+
if card_group_ids and summary.get("id"):
|
|
727
|
+
from hap_cli.core import chat as chat_mod
|
|
728
|
+
sent = []
|
|
729
|
+
for gid in card_group_ids:
|
|
730
|
+
chat_mod.send_group_post_card(
|
|
731
|
+
session, gid, summary["id"], message,
|
|
732
|
+
post_type=post_type)
|
|
733
|
+
sent.append(gid)
|
|
734
|
+
summary["cardSentToGroups"] = sent
|
|
677
735
|
def _render(d):
|
|
678
736
|
click.echo(f"Post created: {d.get('id')}")
|
|
679
737
|
if d.get("text"):
|
|
680
738
|
click.echo(f" {d['text']}")
|
|
739
|
+
if d.get("cardSentToGroups"):
|
|
740
|
+
click.echo(f" Card sent to: {', '.join(d['cardSentToGroups'])}")
|
|
681
741
|
ctx.output(summary, _render)
|
|
682
742
|
except Exception as e:
|
|
683
743
|
ctx.handle_error(e)
|
|
@@ -438,6 +438,159 @@ def send_group_message(
|
|
|
438
438
|
return ack_data
|
|
439
439
|
|
|
440
440
|
|
|
441
|
+
# The file-transfer assistant (文件传输助手) is a real 1-on-1 conversation
|
|
442
|
+
# with a fixed pseudo-account id, not a shortcut for your own account
|
|
443
|
+
# (pd-openweb src/pages/chat/utils/constant.js `FILE_TRANSFER.id`). It is
|
|
444
|
+
# how the web client lets you message yourself.
|
|
445
|
+
FILE_TRANSFER_ID = "file-transfer"
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def send_user_message(
|
|
449
|
+
session: Session,
|
|
450
|
+
to_user: str,
|
|
451
|
+
message: str,
|
|
452
|
+
) -> Any:
|
|
453
|
+
"""Send a 1-on-1 chat message over the socket, as the chat panel does.
|
|
454
|
+
|
|
455
|
+
``send_message`` posts to the main-site ``Message.SendMessageToAccountIds``,
|
|
456
|
+
which addresses real *account ids* and silently drops anything else —
|
|
457
|
+
the caller's own id, and the file-transfer assistant, both come back
|
|
458
|
+
as ``successCount: 0`` with nothing stored.
|
|
459
|
+
|
|
460
|
+
The chat panel never uses that endpoint: it emits the socket.io event
|
|
461
|
+
``'send message'`` with ``touser`` set to the conversation id
|
|
462
|
+
(``src/pages/chat/utils/socket.js`` ``Message.send`` USER branch;
|
|
463
|
+
``containers/ChatPanelSession/index.js:152,334,357`` set
|
|
464
|
+
``sendMsg.touser = session.id``). Because the target is a
|
|
465
|
+
conversation id rather than an account id, this path also reaches
|
|
466
|
+
:data:`FILE_TRANSFER_ID`, which is what "send to myself" means in the
|
|
467
|
+
web client.
|
|
468
|
+
"""
|
|
469
|
+
import uuid
|
|
470
|
+
|
|
471
|
+
if not to_user:
|
|
472
|
+
raise ValueError("to_user is required")
|
|
473
|
+
if not message:
|
|
474
|
+
raise ValueError("message must not be empty")
|
|
475
|
+
payload = {
|
|
476
|
+
"type": MSGTYPE_TEXT,
|
|
477
|
+
"msg": message,
|
|
478
|
+
"touser": to_user,
|
|
479
|
+
"waitingid": str(uuid.uuid4()),
|
|
480
|
+
}
|
|
481
|
+
ack = _socket_emit(session, "send message", payload, strict=True)
|
|
482
|
+
if isinstance(ack, tuple) and len(ack) >= 2:
|
|
483
|
+
return ack[1]
|
|
484
|
+
return ack
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def withdraw_message(
|
|
488
|
+
session: Session,
|
|
489
|
+
message_id: str,
|
|
490
|
+
*,
|
|
491
|
+
group_id: str = "",
|
|
492
|
+
to_user: str = "",
|
|
493
|
+
time: str = "",
|
|
494
|
+
admin_account_id: str = "",
|
|
495
|
+
) -> Any:
|
|
496
|
+
"""Recall (撤回) a message you sent, in a group or a 1-on-1 chat.
|
|
497
|
+
|
|
498
|
+
Recall has no REST endpoint — pd-openweb emits a socket.io event
|
|
499
|
+
(``src/pages/chat/utils/socket.js`` ``Message.sendWithdrawMessgae``):
|
|
500
|
+
``'withdraw group message'`` for a group, ``'withdraw user message'``
|
|
501
|
+
for a 1-on-1. The parameter object is built by
|
|
502
|
+
``containers/Message/index.js::handleWithdrawMessage`` as
|
|
503
|
+
``{messageId, groupid | touser, time}`` — note the lowercase
|
|
504
|
+
``groupid`` / ``touser`` keys — plus ``adminid`` when a group admin
|
|
505
|
+
recalls somebody else's message.
|
|
506
|
+
|
|
507
|
+
Server-side limits, surfaced by the UI rather than by us: an author
|
|
508
|
+
can only recall within 5 minutes of sending
|
|
509
|
+
(``components/Message/MessageToolbar/index.js:285``), while a group
|
|
510
|
+
admin may recall another member's message with no time limit
|
|
511
|
+
(``:275-276``). A recall past the window is refused by the server.
|
|
512
|
+
|
|
513
|
+
Exactly one of ``group_id`` / ``to_user`` identifies the conversation.
|
|
514
|
+
"""
|
|
515
|
+
if not message_id:
|
|
516
|
+
raise ValueError("message_id is required")
|
|
517
|
+
if bool(group_id) == bool(to_user):
|
|
518
|
+
raise ValueError("pass exactly one of group_id or to_user")
|
|
519
|
+
param: dict[str, Any] = {"messageId": message_id, "time": time or ""}
|
|
520
|
+
if group_id:
|
|
521
|
+
param["groupid"] = group_id
|
|
522
|
+
event = "withdraw group message"
|
|
523
|
+
else:
|
|
524
|
+
param["touser"] = to_user
|
|
525
|
+
event = "withdraw user message"
|
|
526
|
+
if admin_account_id:
|
|
527
|
+
param["adminid"] = admin_account_id
|
|
528
|
+
ack = _socket_emit(session, event, param, strict=True)
|
|
529
|
+
if isinstance(ack, tuple) and len(ack) >= 2:
|
|
530
|
+
return ack[1]
|
|
531
|
+
return ack
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def send_group_post_card(
|
|
535
|
+
session: Session,
|
|
536
|
+
group_id: str,
|
|
537
|
+
post_id: str,
|
|
538
|
+
title: str,
|
|
539
|
+
*,
|
|
540
|
+
post_type: int = 0,
|
|
541
|
+
server_url: str = "",
|
|
542
|
+
) -> Any:
|
|
543
|
+
"""Send a card linking to a post into a group chat.
|
|
544
|
+
|
|
545
|
+
This is a chat message about a post; it does not change who can see
|
|
546
|
+
the post itself (that is the post's own audience — see
|
|
547
|
+
``hap_cli.core.post.add_post``).
|
|
548
|
+
|
|
549
|
+
Wire shape mirrors ``pd-openweb/src/pages/chat/utils/cardSender.js``
|
|
550
|
+
(``_initPost``): message ``type`` 5 (``Constant.MSGTYPE_CARD``) with a
|
|
551
|
+
``card`` of ``{md, entityid, url, title, text}``, where ``md`` is
|
|
552
|
+
``vote`` for a vote post (postType 7) and ``post`` otherwise, and the
|
|
553
|
+
url is ``/feeddetail?itemID=<postId>``. The title is capped at 300
|
|
554
|
+
characters, as the frontend does.
|
|
555
|
+
|
|
556
|
+
The ``msg`` line is the conversation-list summary, built the way
|
|
557
|
+
``ChatPanelSession/index.js:277-278`` builds it: ``[<kind>] <title>``,
|
|
558
|
+
where the kind comes from the *localized* card name
|
|
559
|
+
(``cardDisposeName`` → ``_l('动态')`` / ``_l('投票')``). It is read by
|
|
560
|
+
other people in the chat, so it goes through the content-default
|
|
561
|
+
catalog rather than being hardcoded in one language.
|
|
562
|
+
"""
|
|
563
|
+
import uuid
|
|
564
|
+
|
|
565
|
+
from hap_cli.i18n import content_default
|
|
566
|
+
|
|
567
|
+
if not group_id:
|
|
568
|
+
raise ValueError("group_id is required")
|
|
569
|
+
if not post_id:
|
|
570
|
+
raise ValueError("post_id is required")
|
|
571
|
+
card_title = (title or "")[:300]
|
|
572
|
+
is_vote = str(post_type) == "7"
|
|
573
|
+
kind = content_default("chat.vote" if is_vote else "chat.post")
|
|
574
|
+
base = (server_url or getattr(session, "server_url", "") or "").rstrip("/")
|
|
575
|
+
payload = {
|
|
576
|
+
"type": 5,
|
|
577
|
+
"msg": f"{kind} {card_title}",
|
|
578
|
+
"card": {
|
|
579
|
+
"md": "vote" if is_vote else "post",
|
|
580
|
+
"entityid": post_id,
|
|
581
|
+
"url": f"{base}/feeddetail?itemID={post_id}",
|
|
582
|
+
"title": card_title,
|
|
583
|
+
"text": "",
|
|
584
|
+
},
|
|
585
|
+
"togroup": group_id,
|
|
586
|
+
"waitingid": str(uuid.uuid4()),
|
|
587
|
+
}
|
|
588
|
+
ack = _socket_emit(session, "send group message", payload, strict=True)
|
|
589
|
+
if isinstance(ack, tuple) and len(ack) >= 2:
|
|
590
|
+
return ack[1]
|
|
591
|
+
return ack
|
|
592
|
+
|
|
593
|
+
|
|
441
594
|
def send_group_files(
|
|
442
595
|
session: Session,
|
|
443
596
|
group_id: str,
|
|
@@ -207,19 +207,35 @@ def add_post(
|
|
|
207
207
|
) -> dict[str, Any]:
|
|
208
208
|
"""Create a post via ``Post.AddPost``.
|
|
209
209
|
|
|
210
|
-
``scope`` is a ``PostShareScopeReq`` object — not an int
|
|
211
|
-
frontend
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
210
|
+
``scope`` is a ``PostShareScopeReq`` object — not an int — and it is
|
|
211
|
+
what decides **who can see the post**. The frontend
|
|
212
|
+
(``pd-openweb/src/pages/feed/components/createFeed/index.js``) posts
|
|
213
|
+
``{radioProjectIds, shareGroupIds, shareProjectIds}``:
|
|
214
|
+
|
|
215
|
+
* ``shareProjectIds: [<orgId>]`` — everyone in that organization.
|
|
216
|
+
This is the org-wide feed (index.js:267-270).
|
|
217
|
+
* ``shareGroupIds: [<groupId>]`` with ``shareProjectIds: []`` — only
|
|
218
|
+
that group's members. Posting into a group builds exactly this
|
|
219
|
+
(index.js:647-650).
|
|
220
|
+
|
|
221
|
+
``project_id`` is the organization the post belongs to and is always
|
|
222
|
+
sent as ``projectId``; on its own it is *not* an audience. Audience
|
|
223
|
+
defaults to the whole organization only when the caller names no
|
|
224
|
+
audience at all — naming a group must never silently widen the post
|
|
225
|
+
to everyone, which is what an unconditional ``or [project_id]``
|
|
226
|
+
fallback used to do.
|
|
215
227
|
|
|
216
228
|
Returns the raw ``{post, success}`` envelope.
|
|
217
229
|
"""
|
|
218
230
|
if scope is None:
|
|
231
|
+
groups = list(share_group_ids or [])
|
|
232
|
+
orgs = list(share_project_ids or [])
|
|
233
|
+
if not groups and not orgs:
|
|
234
|
+
orgs = [project_id]
|
|
219
235
|
scope = {
|
|
220
236
|
"radioProjectIds": radio_project_ids,
|
|
221
|
-
"shareGroupIds":
|
|
222
|
-
"shareProjectIds":
|
|
237
|
+
"shareGroupIds": groups,
|
|
238
|
+
"shareProjectIds": orgs,
|
|
223
239
|
}
|
|
224
240
|
return session.api_call(
|
|
225
241
|
"Post", "AddPost",
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Posting: audience and group-chat cards
|
|
2
|
+
|
|
3
|
+
`hap post create` publishes a post. This topic covers the one thing that most
|
|
4
|
+
easily goes wrong: the **audience** — **who can see the post** — and how that
|
|
5
|
+
differs from sending a card about it into a group chat.
|
|
6
|
+
|
|
7
|
+
> 🚨 Core risk of this topic: **getting the audience wrong is outward-facing
|
|
8
|
+
> and cannot be taken back.** Confirm who will receive it before publishing;
|
|
9
|
+
> never guess.
|
|
10
|
+
|
|
11
|
+
## 1. Audience is who can see it
|
|
12
|
+
|
|
13
|
+
Two options, both repeatable, and they can be combined:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
--to-group <groupId> # members of that group can see it
|
|
17
|
+
--to-org <orgId> # everyone in that organization can see it (company-wide)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**With neither given, the post goes to everyone in the `--org-id`
|
|
21
|
+
organization.** That is the widest audience, so posting to a single group
|
|
22
|
+
always requires an explicit `--to-group`.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# one group only
|
|
26
|
+
hap post create -m "text" --to-group 9ccde712-xxxx
|
|
27
|
+
|
|
28
|
+
# everyone in the company
|
|
29
|
+
hap post create -m "text" --to-org fe288386-xxxx
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`--org-id` is **the organization the post belongs to**; on its own it is not an
|
|
33
|
+
audience. Naming a `--to-group` does **not** additionally publish company-wide.
|
|
34
|
+
|
|
35
|
+
### Only a group with the feed enabled can be an audience
|
|
36
|
+
|
|
37
|
+
A plain chat group cannot receive a post. The server refuses with
|
|
38
|
+
"选择了没有加入的群组" (you picked a group you have not joined), which is
|
|
39
|
+
**misleading** — you are in the group; it simply has no feed. The CLI checks
|
|
40
|
+
first and names the group instead. To check one: `hap group info <groupId>`
|
|
41
|
+
and look at `isPost`. To merely drop a link into such a group, use
|
|
42
|
+
`--card-to-group` below.
|
|
43
|
+
|
|
44
|
+
To see who would receive it before sending:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
hap post create -m "text" --to-group 9ccde712-xxxx --dry-run
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### ⚠️ You cannot infer the audience from an existing post
|
|
51
|
+
|
|
52
|
+
The read model returns `org_ids` on **every** post — it identifies the network
|
|
53
|
+
the post belongs to and does **not** mean the post went company-wide. A
|
|
54
|
+
group-only post and a company-wide post read back with the same `org_ids` and
|
|
55
|
+
`groups`, so the two are **indistinguishable**.
|
|
56
|
+
|
|
57
|
+
That means a request like "post it to the same audience as last time" cannot be
|
|
58
|
+
satisfied by reading the previous post; the information is not there. Ask, or
|
|
59
|
+
use `--dry-run` to put the audience in front of the person for confirmation.
|
|
60
|
+
|
|
61
|
+
## 2. A group-chat card is a different thing
|
|
62
|
+
|
|
63
|
+
Sending a card that links to a post into a group chat has **nothing to do with
|
|
64
|
+
the audience**. It is just a chat message, and it does not let anyone see a post
|
|
65
|
+
they could not already see.
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# publish, and also drop a card into a group chat
|
|
69
|
+
hap post create -m "text" --to-group 9ccde712-xxxx --card-to-group 7f694ec7-xxxx
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
`--card-to-group` is repeatable. The card renders in the chat as
|
|
73
|
+
"[Post] <title>" (the label follows the active language) and links through
|
|
74
|
+
to the post.
|
|
75
|
+
|
|
76
|
+
One line to remember the difference: `--to-group` decides **who can see the
|
|
77
|
+
post**; `--card-to-group` only **drops a link card into a chat**.
|
|
78
|
+
|
|
79
|
+
**Deleting the post does not take back the card.** The two are independent: the
|
|
80
|
+
card stays in the chat and then points at a post that no longer exists. To
|
|
81
|
+
remove it too, recall that message with
|
|
82
|
+
`hap chat recall <messageId> --group-id <groupId>` (your own messages within 5
|
|
83
|
+
minutes of sending; a group admin can recall a member's message with no limit).
|
|
84
|
+
|
|
85
|
+
## 3. Topic tags
|
|
86
|
+
|
|
87
|
+
Writing `#tag#` in the body registers a topic (for example `#今日工作#`), which
|
|
88
|
+
shows up in the post's categories. List available topics with `hap post topics`.
|
|
89
|
+
|
|
90
|
+
## 4. Other common commands
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
hap post list # list posts
|
|
94
|
+
hap post search -k "kw" --scope myself # only your own posts
|
|
95
|
+
hap post get <postId> # one post with its comments
|
|
96
|
+
hap post comment <postId> -m "..." # comment
|
|
97
|
+
hap post delete <postId> # delete
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`post search --scope` accepts: `org`, `user`, `myself`, `group` / `groups`,
|
|
101
|
+
`fav`.
|
|
@@ -24,6 +24,14 @@
|
|
|
24
24
|
"zh-Hans": "顺着通知或讨论找到它所属的那条应用记录。"
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
|
+
{
|
|
28
|
+
"name": "post",
|
|
29
|
+
"title": {"en": "Posting and who sees it", "zh-Hans": "发动态与发布范围"},
|
|
30
|
+
"summary": {
|
|
31
|
+
"en": "Audience decides who can see a post, and you cannot read it back off an existing one. Group-chat cards are a separate thing.",
|
|
32
|
+
"zh-Hans": "发布范围决定谁能看到,且无法从旧动态读出来。群聊卡片是另一回事。"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
27
35
|
{
|
|
28
36
|
"name": "approval",
|
|
29
37
|
"title": {"en": "Todos and approvals", "zh-Hans": "待办与审批"},
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# 发动态:发布范围与群聊卡片
|
|
2
|
+
|
|
3
|
+
`hap post create` 发布动态。本主题只讲一件最容易出事的事:**发布范围**——也就是
|
|
4
|
+
**哪些人能看到这条动态**,以及它和「在群聊里发一张动态卡片」的区别。
|
|
5
|
+
|
|
6
|
+
> 🚨 本主题核心风险:**发布范围一旦发错,是对外可见且不可撤回的。**
|
|
7
|
+
> 发之前务必确认收件范围,不要靠猜。
|
|
8
|
+
|
|
9
|
+
## 1. 发布范围 = 谁能看到
|
|
10
|
+
|
|
11
|
+
两个选项,可以重复指定,也可以混用:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
--to-group <群组ID> # 该群组的成员能看到
|
|
15
|
+
--to-org <组织ID> # 该组织的全体同事能看到(公司范围)
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
**两个都不给时,默认发给 `--org-id` 组织的全体同事。** 这是最大的那个范围,
|
|
19
|
+
所以只发给某个群时必须显式写 `--to-group`。
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 只发给某个群
|
|
23
|
+
hap post create -m "内容" --to-group 9ccde712-xxxx
|
|
24
|
+
|
|
25
|
+
# 发给全体同事
|
|
26
|
+
hap post create -m "内容" --to-org fe288386-xxxx
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`--org-id` 是**这条动态归属的组织**,本身不是发布范围。指定了 `--to-group` 就
|
|
30
|
+
**不会**再额外发给全体同事。
|
|
31
|
+
|
|
32
|
+
### 只有开启了动态的群组能作为发布范围
|
|
33
|
+
|
|
34
|
+
纯聊天群不能接收动态。服务端对此的报错是「选择了没有加入的群组」,**具有误导性**
|
|
35
|
+
——你明明在群里,真实原因是这个群没开启动态功能。CLI 会先行检查并直接报出群名。
|
|
36
|
+
想查某个群是否可用:`hap group info <群组ID>`,看 `isPost` 是否为 true。
|
|
37
|
+
只是想在这种群里贴个链接,用下面的 `--card-to-group`。
|
|
38
|
+
|
|
39
|
+
发之前想确认发给谁,先干跑:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
hap post create -m "内容" --to-group 9ccde712-xxxx --dry-run
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### ⚠️ 不能靠读旧动态来推断范围
|
|
46
|
+
|
|
47
|
+
读接口返回的 `org_ids` **每条动态都有**,它表示这条动态属于哪个网络,
|
|
48
|
+
**不代表发给了全体同事**。只发群组的动态和发全体同事的动态,读出来的
|
|
49
|
+
`org_ids` / `groups` 是一样的,**无法区分**。
|
|
50
|
+
|
|
51
|
+
所以「照着上一条日报的范围发」这个需求,**不能**通过读上一条动态来实现——
|
|
52
|
+
读不出来。要么问清楚,要么用 `--dry-run` 把范围列出来让人确认。
|
|
53
|
+
|
|
54
|
+
## 2. 群聊动态卡片:另一回事
|
|
55
|
+
|
|
56
|
+
在群聊里发一张指向动态的卡片,和发布范围**没有关系**。它只是一条聊天消息,
|
|
57
|
+
不会让原本看不到这条动态的人看到它。
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# 发布并同时在某个群聊里发一张卡片
|
|
61
|
+
hap post create -m "内容" --to-group 9ccde712-xxxx --card-to-group 7f694ec7-xxxx
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`--card-to-group` 可以重复指定多个群。卡片在群里显示为「[动态] 标题」(该标签随语言设置变化),点击跳转到该动态。
|
|
65
|
+
|
|
66
|
+
两者的区别记住一句话:`--to-group` 决定**谁能看到动态本身**,
|
|
67
|
+
`--card-to-group` 只是**在群聊里贴一个链接卡片**。
|
|
68
|
+
|
|
69
|
+
**删掉动态不会撤回卡片。** 两者是彼此独立的,删除动态后群里的卡片仍在,点进去
|
|
70
|
+
指向一条已不存在的动态。要一并清掉,用 `hap chat recall <消息ID> --group-id <群组ID>`
|
|
71
|
+
撤回那条卡片消息(自己的消息限发送后 5 分钟内,群管理员撤回他人消息不限时)。
|
|
72
|
+
|
|
73
|
+
## 3. 话题标签
|
|
74
|
+
|
|
75
|
+
正文里写 `#标签#` 会被识别为话题(例如 `#今日工作#`),发布后出现在动态的
|
|
76
|
+
分类里。可用的话题用 `hap post topics` 查。
|
|
77
|
+
|
|
78
|
+
## 4. 其它常用
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
hap post list # 动态列表
|
|
82
|
+
hap post search -k "关键字" --scope myself # 只搜自己发的
|
|
83
|
+
hap post get <动态ID> # 查看某条动态及评论
|
|
84
|
+
hap post comment <动态ID> -m "..." # 评论
|
|
85
|
+
hap post delete <动态ID> # 删除
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`post search` 的 `--scope` 取值:`org` 组织、`user` 某人、`myself` 自己、
|
|
89
|
+
`group` / `groups` 群组、`fav` 收藏。
|
|
@@ -95,6 +95,18 @@ CONTENT_DEFAULTS: dict[str, dict[str, str]] = {
|
|
|
95
95
|
"en": "[Video]", "zh-Hans": "[视频]", "zh-Hant": "[影片]",
|
|
96
96
|
"ja": "[動画]",
|
|
97
97
|
},
|
|
98
|
+
# Card messages carry the same kind of one-line summary, built by
|
|
99
|
+
# pd-openweb as `[<kind>] <title>` from the localized card name
|
|
100
|
+
# (chat/utils/index.js `cardDisposeName`, used at
|
|
101
|
+
# ChatPanelSession/index.js:277-278).
|
|
102
|
+
"chat.post": {
|
|
103
|
+
"en": "[Post]", "zh-Hans": "[动态]", "zh-Hant": "[動態]",
|
|
104
|
+
"ja": "[投稿]",
|
|
105
|
+
},
|
|
106
|
+
"chat.vote": {
|
|
107
|
+
"en": "[Vote]", "zh-Hans": "[投票]", "zh-Hant": "[投票]",
|
|
108
|
+
"ja": "[投票]",
|
|
109
|
+
},
|
|
98
110
|
# Name given to the reverse control synthesized when a two-way
|
|
99
111
|
# relation is paired and the caller supplied no name.
|
|
100
112
|
"control.backRelation": {
|