rednote-cli 0.1.0__py3-none-any.whl

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 (81) hide show
  1. rednote_cli/__init__.py +5 -0
  2. rednote_cli/_runtime/__init__.py +0 -0
  3. rednote_cli/_runtime/common/__init__.py +0 -0
  4. rednote_cli/_runtime/common/app_utils.py +77 -0
  5. rednote_cli/_runtime/common/config.py +83 -0
  6. rednote_cli/_runtime/common/enums.py +17 -0
  7. rednote_cli/_runtime/common/errors.py +22 -0
  8. rednote_cli/_runtime/core/__init__.py +0 -0
  9. rednote_cli/_runtime/core/account_manager.py +349 -0
  10. rednote_cli/_runtime/core/browser/__init__.py +0 -0
  11. rednote_cli/_runtime/core/browser/manager.py +247 -0
  12. rednote_cli/_runtime/core/database/__init__.py +0 -0
  13. rednote_cli/_runtime/core/database/manager.py +334 -0
  14. rednote_cli/_runtime/platforms/__init__.py +0 -0
  15. rednote_cli/_runtime/platforms/base.py +62 -0
  16. rednote_cli/_runtime/platforms/factory.py +55 -0
  17. rednote_cli/_runtime/platforms/publishing/__init__.py +12 -0
  18. rednote_cli/_runtime/platforms/publishing/media.py +275 -0
  19. rednote_cli/_runtime/platforms/publishing/models.py +59 -0
  20. rednote_cli/_runtime/platforms/publishing/validator.py +124 -0
  21. rednote_cli/_runtime/services/__init__.py +1 -0
  22. rednote_cli/_runtime/services/scraper_service.py +235 -0
  23. rednote_cli/adapters/__init__.py +1 -0
  24. rednote_cli/adapters/output/__init__.py +1 -0
  25. rednote_cli/adapters/output/event_stream.py +29 -0
  26. rednote_cli/adapters/output/formatter_json.py +23 -0
  27. rednote_cli/adapters/output/formatter_table.py +39 -0
  28. rednote_cli/adapters/output/writer.py +17 -0
  29. rednote_cli/adapters/persistence/__init__.py +1 -0
  30. rednote_cli/adapters/persistence/file_account_repo.py +51 -0
  31. rednote_cli/adapters/platform/__init__.py +1 -0
  32. rednote_cli/adapters/platform/rednote/__init__.py +1 -0
  33. rednote_cli/adapters/platform/rednote/extractor.py +65 -0
  34. rednote_cli/adapters/platform/rednote/publisher.py +26 -0
  35. rednote_cli/adapters/platform/rednote/runtime_extractor.py +818 -0
  36. rednote_cli/adapters/platform/rednote/runtime_publisher.py +373 -0
  37. rednote_cli/adapters/platform/rednote/runtime_registration.py +20 -0
  38. rednote_cli/application/__init__.py +1 -0
  39. rednote_cli/application/dto/__init__.py +1 -0
  40. rednote_cli/application/dto/input_models.py +121 -0
  41. rednote_cli/application/dto/output_models.py +78 -0
  42. rednote_cli/application/use_cases/__init__.py +1 -0
  43. rednote_cli/application/use_cases/account_list.py +9 -0
  44. rednote_cli/application/use_cases/account_mutation.py +22 -0
  45. rednote_cli/application/use_cases/auth_login.py +64 -0
  46. rednote_cli/application/use_cases/auth_status.py +96 -0
  47. rednote_cli/application/use_cases/doctor.py +49 -0
  48. rednote_cli/application/use_cases/init_runtime.py +20 -0
  49. rednote_cli/application/use_cases/note_get.py +22 -0
  50. rednote_cli/application/use_cases/note_search.py +26 -0
  51. rednote_cli/application/use_cases/publish_note.py +25 -0
  52. rednote_cli/application/use_cases/user_get.py +18 -0
  53. rednote_cli/application/use_cases/user_search.py +8 -0
  54. rednote_cli/application/use_cases/user_self.py +8 -0
  55. rednote_cli/cli/__init__.py +1 -0
  56. rednote_cli/cli/__main__.py +5 -0
  57. rednote_cli/cli/commands/__init__.py +1 -0
  58. rednote_cli/cli/commands/account.py +204 -0
  59. rednote_cli/cli/commands/doctor.py +20 -0
  60. rednote_cli/cli/commands/init.py +20 -0
  61. rednote_cli/cli/commands/note.py +101 -0
  62. rednote_cli/cli/commands/publish.py +147 -0
  63. rednote_cli/cli/commands/search.py +185 -0
  64. rednote_cli/cli/commands/user.py +113 -0
  65. rednote_cli/cli/main.py +163 -0
  66. rednote_cli/cli/options.py +13 -0
  67. rednote_cli/cli/runtime.py +142 -0
  68. rednote_cli/cli/utils.py +74 -0
  69. rednote_cli/domain/__init__.py +1 -0
  70. rednote_cli/domain/errors.py +50 -0
  71. rednote_cli/domain/note_search_filters.py +155 -0
  72. rednote_cli/infra/__init__.py +1 -0
  73. rednote_cli/infra/exit_codes.py +30 -0
  74. rednote_cli/infra/logger.py +11 -0
  75. rednote_cli/infra/paths.py +31 -0
  76. rednote_cli/infra/platforms.py +4 -0
  77. rednote_cli-0.1.0.dist-info/METADATA +81 -0
  78. rednote_cli-0.1.0.dist-info/RECORD +81 -0
  79. rednote_cli-0.1.0.dist-info/WHEEL +5 -0
  80. rednote_cli-0.1.0.dist-info/entry_points.txt +2 -0
  81. rednote_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,235 @@
1
+ from typing import List, Optional, Union
2
+
3
+ from rednote_cli._runtime.common.enums import Platform, XsecSource
4
+ from rednote_cli._runtime.common.errors import PublishExecutionError, PublishNoteException
5
+ from rednote_cli._runtime.core.account_manager import AccountLeaseManager, AccountManager
6
+ from rednote_cli._runtime.core.browser.manager import browser_manager
7
+ from rednote_cli._runtime.platforms.factory import PlatformFactory
8
+ from rednote_cli._runtime.platforms.base import RiskControlException
9
+ from rednote_cli._runtime.platforms.publishing.media import MediaPreprocessor
10
+ from rednote_cli._runtime.platforms.publishing.models import PublishResult, PublishStage, PublishTarget
11
+ from rednote_cli._runtime.platforms.publishing import normalize_publish_request
12
+
13
+
14
+ def _normalize_xsec_source(xsec_source: Union[XsecSource, str, None]) -> XsecSource:
15
+ if isinstance(xsec_source, XsecSource):
16
+ return xsec_source
17
+ if isinstance(xsec_source, str) and xsec_source:
18
+ for source in XsecSource:
19
+ if source.value == xsec_source:
20
+ return source
21
+ return XsecSource.PC_FEED
22
+
23
+
24
+ def _normalize_platform(platform: Union[Platform, str, None]) -> Platform:
25
+ if isinstance(platform, Platform):
26
+ return platform
27
+ if isinstance(platform, str) and platform:
28
+ for item in Platform:
29
+ if item.value == platform:
30
+ return item
31
+ return Platform.REDNOTE
32
+
33
+
34
+ def _strip_sensitive_account_fields(accounts: List[dict]) -> List[dict]:
35
+ cleaned = []
36
+ for account in accounts:
37
+ item = dict(account)
38
+ item.pop("storage_state", None)
39
+ cleaned.append(item)
40
+ return cleaned
41
+
42
+
43
+ def _build_publish_result_from_raw(raw_result) -> PublishResult:
44
+ if isinstance(raw_result, dict):
45
+ uploaded_count = int(raw_result.get("uploaded_count", 0) or 0)
46
+ status = str(raw_result.get("status", "")).strip().lower()
47
+ if status in {"uploaded", "submitted", "success", "verified"}:
48
+ if status == "uploaded":
49
+ stage = PublishStage.UPLOAD_DONE
50
+ elif status == "verified":
51
+ stage = PublishStage.VERIFIED
52
+ else:
53
+ stage = PublishStage.SUBMITTED
54
+ return PublishResult(
55
+ success=True,
56
+ stage=stage,
57
+ uploaded_count=uploaded_count,
58
+ publish_url=str(raw_result.get("publish_url") or ""),
59
+ publish_id=str(raw_result.get("publish_id") or ""),
60
+ message=str(raw_result.get("message") or ""),
61
+ data=raw_result,
62
+ )
63
+ return PublishResult(
64
+ success=False,
65
+ stage=PublishStage.FAILED,
66
+ uploaded_count=uploaded_count,
67
+ message=str(raw_result.get("message") or "发布失败"),
68
+ data=raw_result,
69
+ )
70
+
71
+ return PublishResult(
72
+ success=False,
73
+ stage=PublishStage.FAILED,
74
+ message=f"无法识别的发布返回值类型: {type(raw_result).__name__}",
75
+ )
76
+
77
+
78
+ async def search_notes(
79
+ keyword: str,
80
+ size: int = 20,
81
+ sort_by: Optional[str] = None,
82
+ note_type: Optional[str] = None,
83
+ publish_time: Optional[str] = None,
84
+ search_scope: Optional[str] = None,
85
+ location: Optional[str] = None,
86
+ account_uid: Optional[str] = None,
87
+ platform: Union[Platform, str, None] = Platform.REDNOTE
88
+ ) -> List[dict]:
89
+ platform_enum = _normalize_platform(platform)
90
+ async with AccountLeaseManager.lease_account(platform_enum, account_uid) as account:
91
+ storage_state = account.get("storage_state")
92
+ async with browser_manager.get_page(storage_state, account_key=account.get("user_no")) as page:
93
+ extractor = PlatformFactory.get_extractor(platform_enum, page)
94
+ notes = await extractor.search_notes(
95
+ keyword=keyword,
96
+ size=size,
97
+ sort_by=sort_by,
98
+ note_type=note_type,
99
+ publish_time=publish_time,
100
+ search_scope=search_scope,
101
+ location=location,
102
+ )
103
+ return notes if isinstance(notes, list) else []
104
+
105
+
106
+ async def search_users(
107
+ keyword: str,
108
+ size: int = 20,
109
+ account_uid: Optional[str] = None,
110
+ platform: Union[Platform, str, None] = Platform.REDNOTE
111
+ ) -> List[dict]:
112
+ platform_enum = _normalize_platform(platform)
113
+ async with AccountLeaseManager.lease_account(platform_enum, account_uid) as account:
114
+ storage_state = account.get("storage_state")
115
+ async with browser_manager.get_page(storage_state, account_key=account.get("user_no")) as page:
116
+ extractor = PlatformFactory.get_extractor(platform_enum, page)
117
+ users = await extractor.search_users(keyword, size)
118
+ return users if isinstance(users, list) else []
119
+
120
+
121
+ async def get_user_info(
122
+ user_id: str,
123
+ xsec_token: Optional[str] = None,
124
+ xsec_source: Union[XsecSource, str, None] = XsecSource.PC_FEED,
125
+ account_uid: Optional[str] = None,
126
+ platform: Union[Platform, str, None] = Platform.REDNOTE
127
+ ) -> dict:
128
+ platform_enum = _normalize_platform(platform)
129
+ normalized_source = _normalize_xsec_source(xsec_source)
130
+ async with AccountLeaseManager.lease_account(platform_enum, account_uid) as account:
131
+ storage_state = account.get("storage_state")
132
+ async with browser_manager.get_page(storage_state, account_key=account.get("user_no")) as page:
133
+ extractor = PlatformFactory.get_extractor(platform_enum, page)
134
+ return await extractor.get_user_info(user_id, xsec_token, normalized_source)
135
+
136
+
137
+ async def get_note_info(
138
+ note_id: str,
139
+ xsec_token: Optional[str] = None,
140
+ xsec_source: Union[XsecSource, str, None] = XsecSource.PC_FEED,
141
+ account_uid: Optional[str] = None,
142
+ comment_size: int = 10,
143
+ sub_comment_size: int = 5,
144
+ platform: Union[Platform, str, None] = Platform.REDNOTE,
145
+ ) -> dict:
146
+ platform_enum = _normalize_platform(platform)
147
+ normalized_source = _normalize_xsec_source(xsec_source)
148
+ async with AccountLeaseManager.lease_account(platform_enum, account_uid) as account:
149
+ storage_state = account.get("storage_state")
150
+ async with browser_manager.get_page(storage_state, account_key=account.get("user_no")) as page:
151
+ extractor = PlatformFactory.get_extractor(platform_enum, page)
152
+ return await extractor.get_note_info(
153
+ note_id=note_id,
154
+ xsec_token=xsec_token,
155
+ xsec_source=normalized_source,
156
+ comment_size=comment_size,
157
+ sub_comment_size=sub_comment_size,
158
+ )
159
+
160
+
161
+ async def get_self_info(
162
+ account_uid: Optional[str] = None,
163
+ platform: Union[Platform, str, None] = Platform.REDNOTE,
164
+ ) -> dict:
165
+ platform_enum = _normalize_platform(platform)
166
+ async with AccountLeaseManager.lease_account(platform_enum, account_uid) as account:
167
+ storage_state = account.get("storage_state")
168
+ async with browser_manager.get_page(storage_state, account_key=account.get("user_no")) as page:
169
+ extractor = PlatformFactory.get_extractor(platform_enum, page)
170
+ return await extractor.get_self_info()
171
+
172
+
173
+ async def publish_note(
174
+ target: str,
175
+ image_list: Optional[list] = None,
176
+ title: str = "",
177
+ content: str = "",
178
+ tags: Optional[list] = None,
179
+ schedule_at: Optional[str] = None,
180
+ account_uid: Optional[str] = None,
181
+ platform: Union[Platform, str, None] = Platform.REDNOTE,
182
+ ) -> dict:
183
+ platform_enum = _normalize_platform(platform)
184
+ request = normalize_publish_request(
185
+ target=target,
186
+ title=title,
187
+ content=content,
188
+ media_list=image_list,
189
+ tags=tags,
190
+ schedule_at=schedule_at,
191
+ account_uid=account_uid,
192
+ )
193
+ stage = PublishStage.INIT
194
+ try:
195
+ async with MediaPreprocessor() as preprocessor:
196
+ if request.target == PublishTarget.IMAGE:
197
+ prepared_media = await preprocessor.prepare_images(request.media_list)
198
+ elif request.target == PublishTarget.VIDEO:
199
+ prepared_media = await preprocessor.prepare_videos(request.media_list)
200
+ else:
201
+ prepared_media = list(request.media_list or [])
202
+ stage = PublishStage.MEDIA_READY
203
+
204
+ async with AccountLeaseManager.lease_account(platform_enum, request.account_uid) as account:
205
+ storage_state = account.get("storage_state")
206
+ account_key = account.get("user_no")
207
+ async with browser_manager.get_page(storage_state, account_key=account_key) as page:
208
+ extractor = PlatformFactory.get_extractor(platform_enum, page)
209
+ raw_result = await extractor.publish_note(
210
+ target=request.target.value,
211
+ image_list=prepared_media,
212
+ title=request.title,
213
+ content=request.content,
214
+ tags=request.tags,
215
+ schedule_at=request.schedule_at,
216
+ )
217
+ result = _build_publish_result_from_raw(raw_result)
218
+ if result.stage == PublishStage.UPLOAD_DONE:
219
+ result.data["pipeline_stage"] = PublishStage.UPLOAD_DONE.value
220
+ return result.to_dict()
221
+ except RiskControlException:
222
+ raise
223
+ except PublishNoteException:
224
+ raise
225
+ except Exception as exc:
226
+ raise PublishExecutionError(f"发布编排执行失败(stage={stage.value}): {exc}") from exc
227
+
228
+
229
+ async def list_accounts(
230
+ platform: str = Platform.REDNOTE.value,
231
+ only_active: bool = True
232
+ ) -> List[dict]:
233
+ accounts = AccountManager.list_accounts(only_active=only_active)
234
+ filtered = [acc for acc in accounts if acc.get("platform") == platform]
235
+ return _strip_sensitive_account_fields(filtered)
@@ -0,0 +1 @@
1
+ """Adapters layer."""
@@ -0,0 +1 @@
1
+ """Output adapters."""
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from datetime import datetime, timezone
5
+ from typing import Any
6
+
7
+ from rednote_cli.adapters.output.writer import write_error
8
+
9
+
10
+ def _now_iso() -> str:
11
+ return datetime.now(timezone.utc).isoformat()
12
+
13
+
14
+ def emit_event(
15
+ *,
16
+ command: str,
17
+ event: str,
18
+ data: dict[str, Any] | None = None,
19
+ trace_id: str | None = None,
20
+ ) -> None:
21
+ payload = {
22
+ "type": "event",
23
+ "command": command,
24
+ "event": event,
25
+ "timestamp": _now_iso(),
26
+ "trace_id": trace_id,
27
+ "data": data or {},
28
+ }
29
+ write_error(json.dumps(payload, ensure_ascii=False, separators=(",", ":")))
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import Any
5
+
6
+ from pydantic import BaseModel
7
+
8
+
9
+ def format_json(payload: Any, ensure_ascii: bool = False, indent: int = 2) -> str:
10
+ if isinstance(payload, BaseModel):
11
+ data = payload.model_dump(mode="json")
12
+ else:
13
+ data = payload
14
+ return json.dumps(data, ensure_ascii=ensure_ascii, indent=indent)
15
+
16
+
17
+ def format_jsonl(payloads: list[Any], ensure_ascii: bool = False) -> str:
18
+ lines = []
19
+ for item in payloads:
20
+ if isinstance(item, BaseModel):
21
+ item = item.model_dump(mode="json")
22
+ lines.append(json.dumps(item, ensure_ascii=ensure_ascii))
23
+ return "\n".join(lines)
@@ -0,0 +1,39 @@
1
+ from __future__ import annotations
2
+
3
+ from io import StringIO
4
+ from typing import Any
5
+
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+
9
+
10
+ def _normalize_rows(data: Any) -> list[dict]:
11
+ if isinstance(data, list):
12
+ rows = []
13
+ for item in data:
14
+ if isinstance(item, dict):
15
+ rows.append(item)
16
+ else:
17
+ rows.append({"value": item})
18
+ return rows
19
+
20
+ if isinstance(data, dict):
21
+ return [data]
22
+
23
+ return [{"value": data}]
24
+
25
+
26
+ def format_table(data: Any) -> str:
27
+ rows = _normalize_rows(data)
28
+ columns = sorted({key for row in rows for key in row.keys()})
29
+ table = Table(show_header=True, header_style="bold cyan")
30
+ for col in columns:
31
+ table.add_column(col, overflow="fold")
32
+
33
+ for row in rows:
34
+ table.add_row(*[str(row.get(col, "")) for col in columns])
35
+
36
+ buffer = StringIO()
37
+ console = Console(file=buffer, force_terminal=False, color_system=None)
38
+ console.print(table)
39
+ return buffer.getvalue().rstrip()
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ import sys
4
+ from pathlib import Path
5
+
6
+
7
+ def write_output(content: str, out_file: str | None = None) -> None:
8
+ print(content)
9
+ if out_file:
10
+ path = Path(out_file).expanduser().resolve()
11
+ path.parent.mkdir(parents=True, exist_ok=True)
12
+ path.write_text(content + "\n", encoding="utf-8")
13
+
14
+
15
+ def write_error(content: str) -> None:
16
+ sys.stderr.write(content + "\n")
17
+ sys.stderr.flush()
@@ -0,0 +1 @@
1
+ """Persistence adapters."""
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from rednote_cli._runtime.core.database.manager import DatabaseManager
6
+
7
+
8
+ class FileAccountRepository:
9
+ @staticmethod
10
+ def _resolve_account(platform: str, account_uid: str) -> dict[str, Any] | None:
11
+ target = str(account_uid or "").strip()
12
+ if not target:
13
+ return None
14
+ accounts = DatabaseManager.get_all_accounts(only_active=False)
15
+ for item in accounts:
16
+ if item.get("platform") != platform:
17
+ continue
18
+ if str(item.get("user_no") or "").strip() == target:
19
+ return item
20
+ return None
21
+
22
+ @staticmethod
23
+ def _to_cli_account(item: dict[str, Any]) -> dict[str, Any]:
24
+ output = dict(item)
25
+ output.pop("storage_state", None)
26
+ output["account_uid"] = output.get("user_no")
27
+ return output
28
+
29
+ def list_accounts(self, platform: str = "Rednote", only_active: bool = True) -> list[dict[str, Any]]:
30
+ accounts = DatabaseManager.get_all_accounts(only_active=only_active)
31
+ filtered = [acc for acc in accounts if acc.get("platform") == platform]
32
+ return [self._to_cli_account(item) for item in filtered]
33
+
34
+ def delete(self, platform: str, account_uid: str) -> bool:
35
+ record = self._resolve_account(platform=platform, account_uid=account_uid)
36
+ if not record:
37
+ return False
38
+ DatabaseManager.delete_account(platform, record["user_no"])
39
+ return True
40
+
41
+ def activate(self, platform: str, account_uid: str) -> bool:
42
+ record = self._resolve_account(platform=platform, account_uid=account_uid)
43
+ if not record:
44
+ return False
45
+ return DatabaseManager.set_account_login_status(platform=platform, user_no=record["user_no"], is_logged_in=True)
46
+
47
+ def deactivate(self, platform: str, account_uid: str) -> bool:
48
+ record = self._resolve_account(platform=platform, account_uid=account_uid)
49
+ if not record:
50
+ return False
51
+ return DatabaseManager.set_account_login_status(platform=platform, user_no=record["user_no"], is_logged_in=False)
@@ -0,0 +1 @@
1
+ """Platform adapters."""
@@ -0,0 +1 @@
1
+ """Rednote platform adapters."""
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from rednote_cli._runtime.services import scraper_service
4
+
5
+
6
+ class RednoteExtractorAdapter:
7
+ async def search_notes(
8
+ self,
9
+ keyword: str,
10
+ size: int,
11
+ sort_by: str | None = None,
12
+ note_type: str | None = None,
13
+ publish_time: str | None = None,
14
+ search_scope: str | None = None,
15
+ location: str | None = None,
16
+ account_uid: str | None = None,
17
+ ) -> list[dict]:
18
+ return await scraper_service.search_notes(
19
+ keyword=keyword,
20
+ size=size,
21
+ sort_by=sort_by,
22
+ note_type=note_type,
23
+ publish_time=publish_time,
24
+ search_scope=search_scope,
25
+ location=location,
26
+ account_uid=account_uid,
27
+ )
28
+
29
+ async def search_users(self, keyword: str, size: int, account_uid: str | None = None) -> list[dict]:
30
+ return await scraper_service.search_users(keyword=keyword, size=size, account_uid=account_uid)
31
+
32
+ async def get_user_info(
33
+ self,
34
+ user_id: str,
35
+ xsec_token: str | None = None,
36
+ xsec_source: str | None = "pc_feed",
37
+ account_uid: str | None = None,
38
+ ) -> dict:
39
+ return await scraper_service.get_user_info(
40
+ user_id=user_id,
41
+ xsec_token=xsec_token,
42
+ xsec_source=xsec_source,
43
+ account_uid=account_uid,
44
+ )
45
+
46
+ async def get_note_info(
47
+ self,
48
+ note_id: str,
49
+ xsec_token: str | None = None,
50
+ xsec_source: str | None = "pc_feed",
51
+ comment_size: int = 10,
52
+ sub_comment_size: int = 5,
53
+ account_uid: str | None = None,
54
+ ) -> dict:
55
+ return await scraper_service.get_note_info(
56
+ note_id=note_id,
57
+ xsec_token=xsec_token,
58
+ xsec_source=xsec_source,
59
+ comment_size=comment_size,
60
+ sub_comment_size=sub_comment_size,
61
+ account_uid=account_uid,
62
+ )
63
+
64
+ async def get_self_info(self, account_uid: str | None = None) -> dict:
65
+ return await scraper_service.get_self_info(account_uid=account_uid)
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ from rednote_cli._runtime.services import scraper_service
4
+
5
+
6
+ class RednotePublisherAdapter:
7
+ async def publish_note(
8
+ self,
9
+ *,
10
+ target: str,
11
+ image_list: list[str],
12
+ title: str = "",
13
+ content: str = "",
14
+ tags: list[str] | None = None,
15
+ schedule_at: str | None = None,
16
+ account_uid: str | None = None,
17
+ ) -> dict:
18
+ return await scraper_service.publish_note(
19
+ target=target,
20
+ image_list=image_list,
21
+ title=title,
22
+ content=content,
23
+ tags=tags,
24
+ schedule_at=schedule_at,
25
+ account_uid=account_uid,
26
+ )