rednote-cli 0.1.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.
Files changed (86) hide show
  1. rednote_cli-0.1.0/PKG-INFO +81 -0
  2. rednote_cli-0.1.0/README.md +65 -0
  3. rednote_cli-0.1.0/pyproject.toml +34 -0
  4. rednote_cli-0.1.0/setup.cfg +4 -0
  5. rednote_cli-0.1.0/src/rednote_cli/__init__.py +5 -0
  6. rednote_cli-0.1.0/src/rednote_cli/_runtime/__init__.py +0 -0
  7. rednote_cli-0.1.0/src/rednote_cli/_runtime/common/__init__.py +0 -0
  8. rednote_cli-0.1.0/src/rednote_cli/_runtime/common/app_utils.py +77 -0
  9. rednote_cli-0.1.0/src/rednote_cli/_runtime/common/config.py +83 -0
  10. rednote_cli-0.1.0/src/rednote_cli/_runtime/common/enums.py +17 -0
  11. rednote_cli-0.1.0/src/rednote_cli/_runtime/common/errors.py +22 -0
  12. rednote_cli-0.1.0/src/rednote_cli/_runtime/core/__init__.py +0 -0
  13. rednote_cli-0.1.0/src/rednote_cli/_runtime/core/account_manager.py +349 -0
  14. rednote_cli-0.1.0/src/rednote_cli/_runtime/core/browser/__init__.py +0 -0
  15. rednote_cli-0.1.0/src/rednote_cli/_runtime/core/browser/manager.py +247 -0
  16. rednote_cli-0.1.0/src/rednote_cli/_runtime/core/database/__init__.py +0 -0
  17. rednote_cli-0.1.0/src/rednote_cli/_runtime/core/database/manager.py +334 -0
  18. rednote_cli-0.1.0/src/rednote_cli/_runtime/platforms/__init__.py +0 -0
  19. rednote_cli-0.1.0/src/rednote_cli/_runtime/platforms/base.py +62 -0
  20. rednote_cli-0.1.0/src/rednote_cli/_runtime/platforms/factory.py +55 -0
  21. rednote_cli-0.1.0/src/rednote_cli/_runtime/platforms/publishing/__init__.py +12 -0
  22. rednote_cli-0.1.0/src/rednote_cli/_runtime/platforms/publishing/media.py +275 -0
  23. rednote_cli-0.1.0/src/rednote_cli/_runtime/platforms/publishing/models.py +59 -0
  24. rednote_cli-0.1.0/src/rednote_cli/_runtime/platforms/publishing/validator.py +124 -0
  25. rednote_cli-0.1.0/src/rednote_cli/_runtime/services/__init__.py +1 -0
  26. rednote_cli-0.1.0/src/rednote_cli/_runtime/services/scraper_service.py +235 -0
  27. rednote_cli-0.1.0/src/rednote_cli/adapters/__init__.py +1 -0
  28. rednote_cli-0.1.0/src/rednote_cli/adapters/output/__init__.py +1 -0
  29. rednote_cli-0.1.0/src/rednote_cli/adapters/output/event_stream.py +29 -0
  30. rednote_cli-0.1.0/src/rednote_cli/adapters/output/formatter_json.py +23 -0
  31. rednote_cli-0.1.0/src/rednote_cli/adapters/output/formatter_table.py +39 -0
  32. rednote_cli-0.1.0/src/rednote_cli/adapters/output/writer.py +17 -0
  33. rednote_cli-0.1.0/src/rednote_cli/adapters/persistence/__init__.py +1 -0
  34. rednote_cli-0.1.0/src/rednote_cli/adapters/persistence/file_account_repo.py +51 -0
  35. rednote_cli-0.1.0/src/rednote_cli/adapters/platform/__init__.py +1 -0
  36. rednote_cli-0.1.0/src/rednote_cli/adapters/platform/rednote/__init__.py +1 -0
  37. rednote_cli-0.1.0/src/rednote_cli/adapters/platform/rednote/extractor.py +65 -0
  38. rednote_cli-0.1.0/src/rednote_cli/adapters/platform/rednote/publisher.py +26 -0
  39. rednote_cli-0.1.0/src/rednote_cli/adapters/platform/rednote/runtime_extractor.py +818 -0
  40. rednote_cli-0.1.0/src/rednote_cli/adapters/platform/rednote/runtime_publisher.py +373 -0
  41. rednote_cli-0.1.0/src/rednote_cli/adapters/platform/rednote/runtime_registration.py +20 -0
  42. rednote_cli-0.1.0/src/rednote_cli/application/__init__.py +1 -0
  43. rednote_cli-0.1.0/src/rednote_cli/application/dto/__init__.py +1 -0
  44. rednote_cli-0.1.0/src/rednote_cli/application/dto/input_models.py +121 -0
  45. rednote_cli-0.1.0/src/rednote_cli/application/dto/output_models.py +78 -0
  46. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/__init__.py +1 -0
  47. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/account_list.py +9 -0
  48. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/account_mutation.py +22 -0
  49. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/auth_login.py +64 -0
  50. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/auth_status.py +96 -0
  51. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/doctor.py +49 -0
  52. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/init_runtime.py +20 -0
  53. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/note_get.py +22 -0
  54. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/note_search.py +26 -0
  55. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/publish_note.py +25 -0
  56. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/user_get.py +18 -0
  57. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/user_search.py +8 -0
  58. rednote_cli-0.1.0/src/rednote_cli/application/use_cases/user_self.py +8 -0
  59. rednote_cli-0.1.0/src/rednote_cli/cli/__init__.py +1 -0
  60. rednote_cli-0.1.0/src/rednote_cli/cli/__main__.py +5 -0
  61. rednote_cli-0.1.0/src/rednote_cli/cli/commands/__init__.py +1 -0
  62. rednote_cli-0.1.0/src/rednote_cli/cli/commands/account.py +204 -0
  63. rednote_cli-0.1.0/src/rednote_cli/cli/commands/doctor.py +20 -0
  64. rednote_cli-0.1.0/src/rednote_cli/cli/commands/init.py +20 -0
  65. rednote_cli-0.1.0/src/rednote_cli/cli/commands/note.py +101 -0
  66. rednote_cli-0.1.0/src/rednote_cli/cli/commands/publish.py +147 -0
  67. rednote_cli-0.1.0/src/rednote_cli/cli/commands/search.py +185 -0
  68. rednote_cli-0.1.0/src/rednote_cli/cli/commands/user.py +113 -0
  69. rednote_cli-0.1.0/src/rednote_cli/cli/main.py +163 -0
  70. rednote_cli-0.1.0/src/rednote_cli/cli/options.py +13 -0
  71. rednote_cli-0.1.0/src/rednote_cli/cli/runtime.py +142 -0
  72. rednote_cli-0.1.0/src/rednote_cli/cli/utils.py +74 -0
  73. rednote_cli-0.1.0/src/rednote_cli/domain/__init__.py +1 -0
  74. rednote_cli-0.1.0/src/rednote_cli/domain/errors.py +50 -0
  75. rednote_cli-0.1.0/src/rednote_cli/domain/note_search_filters.py +155 -0
  76. rednote_cli-0.1.0/src/rednote_cli/infra/__init__.py +1 -0
  77. rednote_cli-0.1.0/src/rednote_cli/infra/exit_codes.py +30 -0
  78. rednote_cli-0.1.0/src/rednote_cli/infra/logger.py +11 -0
  79. rednote_cli-0.1.0/src/rednote_cli/infra/paths.py +31 -0
  80. rednote_cli-0.1.0/src/rednote_cli/infra/platforms.py +4 -0
  81. rednote_cli-0.1.0/src/rednote_cli.egg-info/PKG-INFO +81 -0
  82. rednote_cli-0.1.0/src/rednote_cli.egg-info/SOURCES.txt +84 -0
  83. rednote_cli-0.1.0/src/rednote_cli.egg-info/dependency_links.txt +1 -0
  84. rednote_cli-0.1.0/src/rednote_cli.egg-info/entry_points.txt +2 -0
  85. rednote_cli-0.1.0/src/rednote_cli.egg-info/requires.txt +9 -0
  86. rednote_cli-0.1.0/src/rednote_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1,81 @@
1
+ Metadata-Version: 2.4
2
+ Name: rednote-cli
3
+ Version: 0.1.0
4
+ Summary: Rednote platform CLI
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+ Requires-Dist: playwright==1.57.0
8
+ Requires-Dist: playwright-stealth==2.0.0
9
+ Requires-Dist: yt-dlp==2025.12.8
10
+ Requires-Dist: pydantic==2.12.2
11
+ Requires-Dist: typer>=0.12.0
12
+ Requires-Dist: rich>=13.7.0
13
+ Requires-Dist: loguru==0.7.3
14
+ Requires-Dist: croniter==6.0.0
15
+ Requires-Dist: fake_useragent==2.2.0
16
+
17
+ # rednote-cli
18
+
19
+ Rednote 平台专用 CLI 包(独立安装、独立升级)。
20
+
21
+ ## 安装
22
+
23
+ ```bash
24
+ pipx install rednote-cli
25
+ ```
26
+
27
+ ## 快速开始
28
+
29
+ ```bash
30
+ rednote-cli init runtime
31
+ rednote-cli doctor run --format json
32
+ rednote-cli account login
33
+ rednote-cli account status --format json
34
+ rednote-cli account list --only-active true
35
+ rednote-cli account list --only-active false
36
+ rednote-cli search note --keyword 旅行 --size 10 --sort-by latest --note-type image_text --format json
37
+ rednote-cli publish --target image --account user_001 --image-list /abs/a.jpg,/abs/b.jpg --title 标题 --content 正文 --format json
38
+ rednote-cli publish --target video --account user_001 --video /abs/demo.mp4 --title 标题 --content 正文 --format json
39
+ ```
40
+
41
+ 命令结构(当前版本):
42
+ - `note` / `user` 作为详情查询入口,`search note|user` 作为统一搜索入口。
43
+ - 旧层级 `note get|search`、`user get|search` 已移除。
44
+
45
+ 空参数行为(统一约定):
46
+ - `rednote-cli note`、`rednote-cli publish`、`rednote-cli account activate` 等缺少业务参数时,输出帮助文本并返回退出码 `0`。
47
+ - 非空调用但参数非法时,返回 JSON `INVALID_ARGS`(退出码 `2`)。
48
+
49
+ `account status` 会实时打开浏览器检查登录态并刷新账号存储(`is_logged_in/check_time/storage_state`),输出结构与 `account list --only-active false` 对齐。
50
+
51
+ `account list` 的 `--only-active` 支持显式 `true|false`(默认 `true`)。
52
+
53
+ 发布能力说明:
54
+ - `publish --target image`:图文发布,支持本地/URL 图片,`--account` 必填。
55
+ - `publish --target video`:视频发布,支持本地/URL 视频(仅 `mp4/mov`),`--account` 必填。
56
+
57
+ ## 自动化建议
58
+
59
+ ```bash
60
+ rednote-cli --format json --trace-id req-001 <command> ...
61
+ ```
62
+
63
+ `--input`(面向 AI/Agent):
64
+ - 作用:从 JSON 文件或 stdin 读取参数,适合复杂命令的程序化调用。
65
+ - 优先级:显式 CLI 参数 > `--input` JSON > 默认值。
66
+ - 范围:`--input` 不是全局参数,仅 `search note|user`、`note`、`user`、`publish` 支持。
67
+
68
+ 示例:
69
+
70
+ ```bash
71
+ rednote-cli --format json --trace-id req-001 search note --input payload.json
72
+ cat payload.json | rednote-cli --format json --trace-id req-002 publish --input - --account user_001
73
+ ```
74
+
75
+ ## 文档
76
+
77
+ 完整文档请查看仓库根目录:
78
+ - `README.md`
79
+ - `DESIGN.md`
80
+ - `DEV.md`
81
+ - `USER_GUIDE.md`
@@ -0,0 +1,65 @@
1
+ # rednote-cli
2
+
3
+ Rednote 平台专用 CLI 包(独立安装、独立升级)。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ pipx install rednote-cli
9
+ ```
10
+
11
+ ## 快速开始
12
+
13
+ ```bash
14
+ rednote-cli init runtime
15
+ rednote-cli doctor run --format json
16
+ rednote-cli account login
17
+ rednote-cli account status --format json
18
+ rednote-cli account list --only-active true
19
+ rednote-cli account list --only-active false
20
+ rednote-cli search note --keyword 旅行 --size 10 --sort-by latest --note-type image_text --format json
21
+ rednote-cli publish --target image --account user_001 --image-list /abs/a.jpg,/abs/b.jpg --title 标题 --content 正文 --format json
22
+ rednote-cli publish --target video --account user_001 --video /abs/demo.mp4 --title 标题 --content 正文 --format json
23
+ ```
24
+
25
+ 命令结构(当前版本):
26
+ - `note` / `user` 作为详情查询入口,`search note|user` 作为统一搜索入口。
27
+ - 旧层级 `note get|search`、`user get|search` 已移除。
28
+
29
+ 空参数行为(统一约定):
30
+ - `rednote-cli note`、`rednote-cli publish`、`rednote-cli account activate` 等缺少业务参数时,输出帮助文本并返回退出码 `0`。
31
+ - 非空调用但参数非法时,返回 JSON `INVALID_ARGS`(退出码 `2`)。
32
+
33
+ `account status` 会实时打开浏览器检查登录态并刷新账号存储(`is_logged_in/check_time/storage_state`),输出结构与 `account list --only-active false` 对齐。
34
+
35
+ `account list` 的 `--only-active` 支持显式 `true|false`(默认 `true`)。
36
+
37
+ 发布能力说明:
38
+ - `publish --target image`:图文发布,支持本地/URL 图片,`--account` 必填。
39
+ - `publish --target video`:视频发布,支持本地/URL 视频(仅 `mp4/mov`),`--account` 必填。
40
+
41
+ ## 自动化建议
42
+
43
+ ```bash
44
+ rednote-cli --format json --trace-id req-001 <command> ...
45
+ ```
46
+
47
+ `--input`(面向 AI/Agent):
48
+ - 作用:从 JSON 文件或 stdin 读取参数,适合复杂命令的程序化调用。
49
+ - 优先级:显式 CLI 参数 > `--input` JSON > 默认值。
50
+ - 范围:`--input` 不是全局参数,仅 `search note|user`、`note`、`user`、`publish` 支持。
51
+
52
+ 示例:
53
+
54
+ ```bash
55
+ rednote-cli --format json --trace-id req-001 search note --input payload.json
56
+ cat payload.json | rednote-cli --format json --trace-id req-002 publish --input - --account user_001
57
+ ```
58
+
59
+ ## 文档
60
+
61
+ 完整文档请查看仓库根目录:
62
+ - `README.md`
63
+ - `DESIGN.md`
64
+ - `DEV.md`
65
+ - `USER_GUIDE.md`
@@ -0,0 +1,34 @@
1
+ [build-system]
2
+ requires = [
3
+ "setuptools>=68",
4
+ "wheel",
5
+ ]
6
+ build-backend = "setuptools.build_meta"
7
+
8
+ [project]
9
+ name = "rednote-cli"
10
+ version = "0.1.0"
11
+ description = "Rednote platform CLI"
12
+ readme = "README.md"
13
+ requires-python = ">=3.11"
14
+ dependencies = [
15
+ "playwright==1.57.0",
16
+ "playwright-stealth==2.0.0",
17
+ "yt-dlp==2025.12.8",
18
+ "pydantic==2.12.2",
19
+ "typer>=0.12.0",
20
+ "rich>=13.7.0",
21
+ "loguru==0.7.3",
22
+ "croniter==6.0.0",
23
+ "fake_useragent==2.2.0",
24
+ ]
25
+
26
+ [project.scripts]
27
+ rednote-cli = "rednote_cli.cli.main:run"
28
+
29
+ [tool.setuptools]
30
+ include-package-data = true
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+ include = ["rednote_cli*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """Rednote Operator CLI package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
File without changes
@@ -0,0 +1,77 @@
1
+ import os
2
+ import platform
3
+ import sys
4
+ from datetime import datetime
5
+
6
+
7
+ def get_app_path(relative_path):
8
+ """
9
+ 获取资源绝对路径(兼容 Dev 和 Frozen 环境)
10
+
11
+ 在 PyInstaller --onedir 模式下,macOS .app 的结构通常是:
12
+ OperatorRuntime.app/
13
+ Contents/
14
+ MacOS/
15
+ OperatorRuntime (可执行文件)
16
+ assets/ (如果 spec 中配置在同级)
17
+ Resources/ (标准 Mac 资源目录)
18
+
19
+ 我们现在的 spec 配置是将 assets 放在可执行文件同级目录。
20
+ """
21
+ if getattr(sys, 'frozen', False):
22
+ # 打包后的环境
23
+ bundle_dir = os.path.dirname(sys.executable)
24
+ return os.path.join(bundle_dir, relative_path)
25
+ else:
26
+ # 开发环境
27
+ return os.path.join(os.path.abspath("."), relative_path)
28
+
29
+
30
+ def get_system_chrome_path():
31
+ """
32
+ 获取系统已安装的 Chrome/Edge 路径,避免打包几百兆的浏览器。
33
+ 优先查找 Chrome,其次 Edge。
34
+ """
35
+ system = platform.system()
36
+ paths = []
37
+
38
+ if system == "Darwin": # macOS
39
+ paths = [
40
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
41
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
42
+ # 用户可能安装在用户目录下
43
+ os.path.expanduser("~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"),
44
+ ]
45
+ elif system == "Windows":
46
+ paths = [
47
+ r"C:\Program Files\Google\Chrome\Application\chrome.exe",
48
+ r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
49
+ r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
50
+ r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe",
51
+ ]
52
+
53
+ for p in paths:
54
+ if os.path.exists(p):
55
+ return p
56
+
57
+ return None
58
+
59
+
60
+ def timestamp_to_str(ts, fmt='%Y-%m-%d %H:%M:%S', tz=None):
61
+ """
62
+ 自动识别10位或13位时间戳并转为日期字符串
63
+ """
64
+ if not ts: return ts
65
+
66
+ # 如果是字符串类型的输入,先转为数字
67
+ try:
68
+ ts = int(ts)
69
+ except Exception:
70
+ return ts
71
+
72
+ # 核心逻辑:如果数值大于 1e11 (即 100,000,000,000),通常被认为是毫秒级
73
+ # 10位时间戳最大到 2286年 (9999999999)
74
+ if ts > 10000000000:
75
+ ts = ts / 1000
76
+
77
+ return datetime.fromtimestamp(ts, tz=tz).strftime(fmt)
@@ -0,0 +1,83 @@
1
+ import os
2
+ import platform
3
+ from pathlib import Path
4
+
5
+
6
+ def _read_bool_env(name: str, default: bool) -> bool:
7
+ raw = os.getenv(name)
8
+ if raw is None:
9
+ return default
10
+ text = str(raw).strip().lower()
11
+ if text in {"1", "true", "yes", "on"}:
12
+ return True
13
+ if text in {"0", "false", "no", "off"}:
14
+ return False
15
+ return default
16
+
17
+
18
+ def get_app_data_dir(app_name="OperatorRuntime"):
19
+ """
20
+ Get the platform-specific user data directory.
21
+ """
22
+ system = platform.system()
23
+ if system == "Windows":
24
+ return Path(os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming")) / app_name
25
+ elif system == "Darwin":
26
+ return Path.home() / "Library" / "Application Support" / app_name
27
+ else:
28
+ # Linux / Unix
29
+ return Path.home() / ".local" / "share" / app_name
30
+
31
+
32
+ # Initialize paths
33
+ APP_DATA_DIR = get_app_data_dir()
34
+ try:
35
+ APP_DATA_DIR.mkdir(parents=True, exist_ok=True)
36
+ except PermissionError:
37
+ # Sandbox-friendly fallback for restricted environments (tests/CI).
38
+ APP_DATA_DIR = Path.cwd() / ".operator_runtime_data"
39
+ APP_DATA_DIR.mkdir(parents=True, exist_ok=True)
40
+
41
+ # Database path
42
+ DB_FILE = APP_DATA_DIR / "operator_runtime.db"
43
+
44
+ # Log path
45
+ LOG_DIR = APP_DATA_DIR / "logs"
46
+ LOG_DIR.mkdir(parents=True, exist_ok=True)
47
+ LOG_FILE = LOG_DIR / "app.log"
48
+
49
+ # --- 调试配置 ---
50
+ # 默认行为:无头(生产)
51
+ # 开发调试可通过环境变量强制有头:
52
+ # - REDNOTE_CLI_HEADFUL=1
53
+ # - BROWSER_HEADFUL=1
54
+ BROWSER_HEADLESS = _read_bool_env("BROWSER_HEADLESS", True)
55
+ _BROWSER_HEADFUL_FORCED = _read_bool_env("REDNOTE_CLI_HEADFUL", False) or _read_bool_env("BROWSER_HEADFUL", False)
56
+
57
+
58
+ def is_browser_headful_forced() -> bool:
59
+ return _BROWSER_HEADFUL_FORCED
60
+
61
+
62
+ def resolve_browser_headless(requested: bool | None = None) -> bool:
63
+ if _BROWSER_HEADFUL_FORCED:
64
+ return False
65
+ if requested is None:
66
+ return BROWSER_HEADLESS
67
+ return bool(requested)
68
+
69
+
70
+ def resolve_login_mode_headless(mode: str) -> bool:
71
+ normalized_mode = str(mode or "").strip().lower()
72
+ if normalized_mode == "local":
73
+ # Local login is interactive by design.
74
+ return resolve_browser_headless(False)
75
+ if normalized_mode == "remote":
76
+ # Remote login defaults to headless, but follows BROWSER_HEADLESS env.
77
+ return resolve_browser_headless(None)
78
+ return resolve_browser_headless()
79
+
80
+ # --- 浏览器环境配置 ---
81
+ # 建议与账号真实访问地区保持一致,减少环境指纹漂移。
82
+ BROWSER_LOCALE = os.getenv("BROWSER_LOCALE", "zh-CN")
83
+ BROWSER_TIMEZONE = os.getenv("BROWSER_TIMEZONE", "Asia/Shanghai")
@@ -0,0 +1,17 @@
1
+ from enum import Enum
2
+
3
+
4
+ class Platform(Enum):
5
+ REDNOTE = "Rednote"
6
+ DOUYIN = "Douyin"
7
+ TIKTOK = "TikTok"
8
+
9
+
10
+ class XsecSource(Enum):
11
+ PC_FEED = "pc_feed" # 发现页 → 笔记;发现页 → 达人
12
+ PC_SEARCH = "pc_search" # 搜索页 → 笔记;搜索页 → 达人
13
+ PC_COLLECT = "pc_collect" # 达人-收藏页 → 笔记;达人-收藏页 → 达人
14
+ PC_LIKE = "pc_like" # 达人-点赞页 → 笔记;达人-点赞页 → 达人
15
+ PC_USER = "pc_user" # 达人-笔记页 → 笔记
16
+ PC_NOTE = "pc_note" # 笔记页 → 达人
17
+
@@ -0,0 +1,22 @@
1
+ class PublishNoteException(Exception):
2
+ """Base exception for publish-note workflow."""
3
+
4
+
5
+ class InvalidPublishParameterError(PublishNoteException, ValueError):
6
+ """Raised when publish-note inputs are invalid."""
7
+
8
+
9
+ class UnsupportedPublishTargetError(PublishNoteException):
10
+ """Raised when publish target is unsupported."""
11
+
12
+
13
+ class PublishWorkflowNotReadyError(PublishNoteException):
14
+ """Raised when target exists but workflow is not implemented yet."""
15
+
16
+
17
+ class PublishMediaPreparationError(PublishNoteException):
18
+ """Raised when media validation/download fails."""
19
+
20
+
21
+ class PublishExecutionError(PublishNoteException):
22
+ """Raised when publish execution fails unexpectedly."""