dy-cli 0.2.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.
- dy_cli/__init__.py +3 -0
- dy_cli/commands/__init__.py +0 -0
- dy_cli/commands/account.py +103 -0
- dy_cli/commands/analytics.py +120 -0
- dy_cli/commands/auth.py +159 -0
- dy_cli/commands/config_cmd.py +67 -0
- dy_cli/commands/download.py +212 -0
- dy_cli/commands/init.py +200 -0
- dy_cli/commands/interact.py +140 -0
- dy_cli/commands/live.py +141 -0
- dy_cli/commands/profile.py +78 -0
- dy_cli/commands/publish.py +123 -0
- dy_cli/commands/search.py +131 -0
- dy_cli/commands/trending.py +82 -0
- dy_cli/engines/__init__.py +0 -0
- dy_cli/engines/api_client.py +665 -0
- dy_cli/engines/playwright_client.py +836 -0
- dy_cli/main.py +144 -0
- dy_cli/utils/__init__.py +0 -0
- dy_cli/utils/config.py +99 -0
- dy_cli/utils/envelope.py +49 -0
- dy_cli/utils/export.py +68 -0
- dy_cli/utils/index_cache.py +83 -0
- dy_cli/utils/output.py +283 -0
- dy_cli/utils/signature.py +183 -0
- dy_cli-0.2.0.dist-info/METADATA +376 -0
- dy_cli-0.2.0.dist-info/RECORD +34 -0
- dy_cli-0.2.0.dist-info/WHEEL +4 -0
- dy_cli-0.2.0.dist-info/entry_points.txt +2 -0
- dy_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
- scripts/chrome_launcher.py +71 -0
- scripts/douyin_analytics.py +99 -0
- scripts/douyin_login.py +64 -0
- scripts/douyin_publisher.py +199 -0
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dy search / detail — 搜索和详情命令。
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from dy_cli.engines.api_client import DouyinAPIClient, DouyinAPIError
|
|
11
|
+
from dy_cli.utils.index_cache import save_index, resolve_id
|
|
12
|
+
from dy_cli.utils.export import export_data
|
|
13
|
+
from dy_cli.utils.output import (
|
|
14
|
+
success, error, info, warning, console,
|
|
15
|
+
print_videos, print_json, print_video_detail, print_comments,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
SORT_MAP = {
|
|
20
|
+
"综合": 0,
|
|
21
|
+
"最多点赞": 1,
|
|
22
|
+
"最新发布": 2,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
TIME_MAP = {
|
|
26
|
+
"不限": 0,
|
|
27
|
+
"一天内": 1,
|
|
28
|
+
"一周内": 7,
|
|
29
|
+
"半年内": 182,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@click.command("search", help="搜索抖音视频")
|
|
34
|
+
@click.argument("keyword")
|
|
35
|
+
@click.option("--sort", type=click.Choice(["综合", "最多点赞", "最新发布"]),
|
|
36
|
+
default="综合", help="排序方式")
|
|
37
|
+
@click.option("--time", "pub_time", type=click.Choice(["不限", "一天内", "一周内", "半年内"]),
|
|
38
|
+
default="不限", help="发布时间")
|
|
39
|
+
@click.option("--type", "search_type", type=click.Choice(["general", "video", "user"]),
|
|
40
|
+
default="general", help="搜索类型")
|
|
41
|
+
@click.option("--count", type=int, default=20, help="结果数量 (默认 20)")
|
|
42
|
+
@click.option("--account", default=None, help="使用指定账号")
|
|
43
|
+
@click.option("--json-output", "as_json", is_flag=True, help="输出 JSON 格式")
|
|
44
|
+
@click.option("-o", "--output", default=None, help="导出到文件 (.json/.csv/.yaml)")
|
|
45
|
+
def search(keyword, sort, pub_time, search_type, count, account, as_json, output):
|
|
46
|
+
"""搜索抖音视频/用户。"""
|
|
47
|
+
client = DouyinAPIClient.from_config(account)
|
|
48
|
+
info(f"正在搜索: {keyword}")
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
result = client.search(
|
|
52
|
+
keyword=keyword,
|
|
53
|
+
sort_type=SORT_MAP.get(sort, 0),
|
|
54
|
+
publish_time=TIME_MAP.get(pub_time, 0),
|
|
55
|
+
search_type=search_type,
|
|
56
|
+
count=count,
|
|
57
|
+
)
|
|
58
|
+
except DouyinAPIError as e:
|
|
59
|
+
error(f"搜索失败: {e}")
|
|
60
|
+
raise SystemExit(1)
|
|
61
|
+
finally:
|
|
62
|
+
client.close()
|
|
63
|
+
|
|
64
|
+
if as_json:
|
|
65
|
+
print_json(result)
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
# Extract video list
|
|
69
|
+
data_list = result.get("data", [])
|
|
70
|
+
videos = []
|
|
71
|
+
for item in data_list:
|
|
72
|
+
aweme_info = item.get("aweme_info")
|
|
73
|
+
if aweme_info:
|
|
74
|
+
videos.append(aweme_info)
|
|
75
|
+
|
|
76
|
+
# 缓存索引 — 支持 dy read 1 / dy download 3
|
|
77
|
+
save_index(videos)
|
|
78
|
+
|
|
79
|
+
# 导出
|
|
80
|
+
if output:
|
|
81
|
+
export_data(videos, output)
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
print_videos(videos, keyword=keyword)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@click.command("detail", help="查看视频详情 (支持短索引: dy detail 1)")
|
|
88
|
+
@click.argument("aweme_id")
|
|
89
|
+
@click.option("--comments", is_flag=True, help="同时加载评论")
|
|
90
|
+
@click.option("--comment-count", type=int, default=20, help="评论数量 (默认 20)")
|
|
91
|
+
@click.option("--account", default=None, help="使用指定账号")
|
|
92
|
+
@click.option("--json-output", "as_json", is_flag=True, help="输出 JSON")
|
|
93
|
+
def detail(aweme_id, comments, comment_count, account, as_json):
|
|
94
|
+
"""查看视频详情和评论。支持短索引 (dy search → dy detail 1)。"""
|
|
95
|
+
try:
|
|
96
|
+
aweme_id = resolve_id(aweme_id)
|
|
97
|
+
except ValueError as e:
|
|
98
|
+
error(str(e))
|
|
99
|
+
raise SystemExit(1)
|
|
100
|
+
client = DouyinAPIClient.from_config(account)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
info(f"正在获取详情: {aweme_id}")
|
|
104
|
+
video_detail = client.get_video_detail(aweme_id)
|
|
105
|
+
|
|
106
|
+
if as_json and not comments:
|
|
107
|
+
print_json(video_detail)
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
print_video_detail(video_detail)
|
|
111
|
+
|
|
112
|
+
# Load comments if requested
|
|
113
|
+
if comments:
|
|
114
|
+
info("正在加载评论...")
|
|
115
|
+
try:
|
|
116
|
+
comment_data = client.get_comments(aweme_id, count=comment_count)
|
|
117
|
+
comment_list = comment_data.get("comments", [])
|
|
118
|
+
|
|
119
|
+
if as_json:
|
|
120
|
+
print_json({"detail": video_detail, "comments": comment_list})
|
|
121
|
+
else:
|
|
122
|
+
print_comments(comment_list)
|
|
123
|
+
except DouyinAPIError as e:
|
|
124
|
+
warning(f"评论加载失败: {e}")
|
|
125
|
+
info("评论 API 需要签名,可单独用 [bold]dy comments[/] 尝试")
|
|
126
|
+
|
|
127
|
+
except DouyinAPIError as e:
|
|
128
|
+
error(f"获取详情失败: {e}")
|
|
129
|
+
raise SystemExit(1)
|
|
130
|
+
finally:
|
|
131
|
+
client.close()
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
dy trending — 抖音热榜命令(抖音特色功能)。
|
|
3
|
+
"""
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from dy_cli.engines.api_client import DouyinAPIClient, DouyinAPIError
|
|
11
|
+
from dy_cli.utils.export import export_data
|
|
12
|
+
from dy_cli.utils.output import success, error, info, warning, console, print_trending, print_json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command("trending", help="🔥 抖音热榜")
|
|
16
|
+
@click.option("--count", type=int, default=50, help="显示条数 (默认 50)")
|
|
17
|
+
@click.option("--watch", is_flag=True, help="实时刷新模式 (每 5 分钟更新)")
|
|
18
|
+
@click.option("--account", default=None, help="使用指定账号")
|
|
19
|
+
@click.option("--json-output", "as_json", is_flag=True, help="输出 JSON")
|
|
20
|
+
@click.option("-o", "--output", default=None, help="导出到文件 (.json/.csv/.yaml)")
|
|
21
|
+
def trending(count, watch, account, as_json, output):
|
|
22
|
+
"""查看抖音热榜。"""
|
|
23
|
+
client = DouyinAPIClient.from_config(account)
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
if watch:
|
|
27
|
+
_watch_trending(client, count, as_json)
|
|
28
|
+
else:
|
|
29
|
+
_show_trending(client, count, as_json, output)
|
|
30
|
+
except KeyboardInterrupt:
|
|
31
|
+
info("已退出热榜监控")
|
|
32
|
+
except DouyinAPIError as e:
|
|
33
|
+
error(f"获取热榜失败: {e}")
|
|
34
|
+
raise SystemExit(1)
|
|
35
|
+
finally:
|
|
36
|
+
client.close()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _show_trending(client: DouyinAPIClient, count: int, as_json: bool, output: str = None):
|
|
40
|
+
"""显示热榜。"""
|
|
41
|
+
info("正在获取抖音热榜...")
|
|
42
|
+
items = client.get_trending()
|
|
43
|
+
|
|
44
|
+
if as_json:
|
|
45
|
+
print_json(items[:count])
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
if output:
|
|
49
|
+
export_data(items[:count], output)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
print_trending(items[:count])
|
|
53
|
+
|
|
54
|
+
if items:
|
|
55
|
+
console.print()
|
|
56
|
+
info(f"共 {len(items)} 条热搜,显示前 {min(count, len(items))} 条")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _watch_trending(client: DouyinAPIClient, count: int, as_json: bool):
|
|
60
|
+
"""实时刷新热榜。"""
|
|
61
|
+
interval = 300 # 5 minutes
|
|
62
|
+
info(f"热榜监控模式 (每 {interval // 60} 分钟刷新,Ctrl+C 退出)")
|
|
63
|
+
console.print()
|
|
64
|
+
|
|
65
|
+
while True:
|
|
66
|
+
try:
|
|
67
|
+
items = client.get_trending()
|
|
68
|
+
if as_json:
|
|
69
|
+
print_json(items[:count])
|
|
70
|
+
else:
|
|
71
|
+
console.clear()
|
|
72
|
+
import datetime
|
|
73
|
+
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
74
|
+
console.rule(f"[bold]🔥 抖音热榜 — {now}[/]")
|
|
75
|
+
print_trending(items[:count])
|
|
76
|
+
console.print()
|
|
77
|
+
info(f"下次刷新: {interval // 60} 分钟后 (Ctrl+C 退出)")
|
|
78
|
+
|
|
79
|
+
time.sleep(interval)
|
|
80
|
+
except DouyinAPIError as e:
|
|
81
|
+
warning(f"刷新失败: {e}, 等待重试...")
|
|
82
|
+
time.sleep(60)
|
|
File without changes
|