chzzk-python 0.9.0__tar.gz → 0.9.1__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 (121) hide show
  1. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/.env.example +6 -1
  2. chzzk_python-0.9.1/.github/workflows/build.yml +85 -0
  3. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/.gitignore +4 -1
  4. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/PKG-INFO +17 -3
  5. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/README.md +14 -0
  6. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/README_KO.md +14 -0
  7. chzzk_python-0.9.1/chzzk.spec +128 -0
  8. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/pyproject.toml +3 -2
  9. chzzk_python-0.9.1/scripts/build.py +126 -0
  10. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/_version.py +2 -2
  11. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/commands/auth.py +2 -40
  12. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/commands/chat.py +228 -204
  13. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/formatter.py +0 -50
  14. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/writers.py +38 -0
  15. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/cli/test_formatter.py +0 -42
  16. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/uv.lock +217 -169
  17. chzzk_python-0.9.0/CHANGELOG.md +0 -183
  18. chzzk_python-0.9.0/src/chzzk/cli/tui/__init__.py +0 -6
  19. chzzk_python-0.9.0/src/chzzk/cli/tui/apps/__init__.py +0 -7
  20. chzzk_python-0.9.0/src/chzzk/cli/tui/apps/chat_interactive.py +0 -308
  21. chzzk_python-0.9.0/src/chzzk/cli/tui/apps/chat_viewer.py +0 -241
  22. chzzk_python-0.9.0/src/chzzk/cli/tui/apps/login.py +0 -218
  23. chzzk_python-0.9.0/src/chzzk/cli/tui/base.py +0 -52
  24. chzzk_python-0.9.0/src/chzzk/cli/tui/styles/app.tcss +0 -150
  25. chzzk_python-0.9.0/src/chzzk/cli/tui/utils.py +0 -82
  26. chzzk_python-0.9.0/src/chzzk/cli/tui/widgets/__init__.py +0 -6
  27. chzzk_python-0.9.0/src/chzzk/cli/tui/widgets/chat.py +0 -289
  28. chzzk_python-0.9.0/src/chzzk/cli/tui/widgets/input.py +0 -106
  29. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/.github/workflows/ci.yml +0 -0
  30. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/.github/workflows/publish.yml +0 -0
  31. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/.python-version +0 -0
  32. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/LICENSE +0 -0
  33. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/docs/unofficial-chat-websocket-protocol.md +0 -0
  34. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/examples/.env.example +0 -0
  35. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/examples/oauth_server.py +0 -0
  36. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/examples/realtime_chat.py +0 -0
  37. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/examples/realtime_chat_async.py +0 -0
  38. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/examples/session_management.py +0 -0
  39. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/examples/unofficial_chat.py +0 -0
  40. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/examples/unofficial_chat_async.py +0 -0
  41. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/main.py +0 -0
  42. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/__init__.py +0 -0
  43. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/__init__.py +0 -0
  44. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/base.py +0 -0
  45. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/category.py +0 -0
  46. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/channel.py +0 -0
  47. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/chat.py +0 -0
  48. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/live.py +0 -0
  49. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/restriction.py +0 -0
  50. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/session.py +0 -0
  51. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/api/user.py +0 -0
  52. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/auth/__init__.py +0 -0
  53. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/auth/models.py +0 -0
  54. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/auth/oauth.py +0 -0
  55. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/auth/token.py +0 -0
  56. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/__init__.py +0 -0
  57. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/commands/__init__.py +0 -0
  58. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/commands/live.py +0 -0
  59. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/config.py +0 -0
  60. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/logging.py +0 -0
  61. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/cli/main.py +0 -0
  62. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/client.py +0 -0
  63. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/constants.py +0 -0
  64. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/exceptions/__init__.py +0 -0
  65. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/exceptions/errors.py +0 -0
  66. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/http/__init__.py +0 -0
  67. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/http/_base.py +0 -0
  68. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/http/client.py +0 -0
  69. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/http/endpoints.py +0 -0
  70. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/logging.py +0 -0
  71. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/__init__.py +0 -0
  72. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/category.py +0 -0
  73. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/channel.py +0 -0
  74. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/chat.py +0 -0
  75. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/common.py +0 -0
  76. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/live.py +0 -0
  77. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/restriction.py +0 -0
  78. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/session.py +0 -0
  79. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/models/user.py +0 -0
  80. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/py.typed +0 -0
  81. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/realtime/__init__.py +0 -0
  82. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/realtime/client.py +0 -0
  83. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/__init__.py +0 -0
  84. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/__init__.py +0 -0
  85. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/base.py +0 -0
  86. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/chat.py +0 -0
  87. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/live.py +0 -0
  88. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/user.py +0 -0
  89. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/auth/__init__.py +0 -0
  90. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/auth/cookie.py +0 -0
  91. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/__init__.py +0 -0
  92. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/client.py +0 -0
  93. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/connection.py +0 -0
  94. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/handler.py +0 -0
  95. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/monitor.py +0 -0
  96. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/client.py +0 -0
  97. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/__init__.py +0 -0
  98. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/_base.py +0 -0
  99. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/client.py +0 -0
  100. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/endpoints.py +0 -0
  101. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/__init__.py +0 -0
  102. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/chat.py +0 -0
  103. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/live.py +0 -0
  104. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/reconnect.py +0 -0
  105. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/user.py +0 -0
  106. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/__init__.py +0 -0
  107. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/__init__.py +0 -0
  108. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/test_category.py +0 -0
  109. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/test_channel.py +0 -0
  110. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/test_chat.py +0 -0
  111. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/test_live.py +0 -0
  112. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/test_restriction.py +0 -0
  113. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/test_session.py +0 -0
  114. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/api/test_user.py +0 -0
  115. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/auth/__init__.py +0 -0
  116. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/auth/test_oauth.py +0 -0
  117. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/cli/__init__.py +0 -0
  118. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/cli/test_writers.py +0 -0
  119. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/realtime/__init__.py +0 -0
  120. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/realtime/test_client.py +0 -0
  121. {chzzk_python-0.9.0 → chzzk_python-0.9.1}/tests/test_client.py +0 -0
@@ -39,8 +39,13 @@ CHZZK_SENT_FORMAT=
39
39
  CHZZK_TIME_FORMAT=
40
40
 
41
41
  # Chat Output Configuration
42
- # Save chat messages to file
42
+ # Save chat messages to file (explicit path)
43
43
  CHZZK_CHAT_OUTPUT=
44
44
 
45
+ # Directory for auto-generated chat log filenames
46
+ # Filename format: {channel_id}_{live_id}_{YYYYMMDD}.{format}
47
+ # Note: --output and --output-dir are mutually exclusive
48
+ CHZZK_CHAT_OUTPUT_DIR=
49
+
45
50
  # Output format: jsonl, txt (default: jsonl)
46
51
  CHZZK_CHAT_OUTPUT_FORMAT=
@@ -0,0 +1,85 @@
1
+ name: Build Executables
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build:
11
+ strategy:
12
+ matrix:
13
+ include:
14
+ - os: ubuntu-latest
15
+ artifact_name: chzzk-linux
16
+ - os: windows-latest
17
+ artifact_name: chzzk-windows.exe
18
+ - os: macos-latest
19
+ artifact_name: chzzk-macos
20
+
21
+ runs-on: ${{ matrix.os }}
22
+
23
+ steps:
24
+ - uses: actions/checkout@v6
25
+ with:
26
+ fetch-depth: 0
27
+
28
+ - name: Install uv
29
+ uses: astral-sh/setup-uv@v7
30
+ with:
31
+ version: "latest"
32
+
33
+ - name: Set up Python
34
+ uses: actions/setup-python@v6
35
+ with:
36
+ python-version: "3.12"
37
+
38
+ - name: Install dependencies
39
+ run: uv sync --dev --all-extras
40
+
41
+ - name: Build executable
42
+ run: uv run python -m PyInstaller chzzk.spec --clean
43
+
44
+ - name: Verify executable (Unix)
45
+ if: runner.os != 'Windows'
46
+ run: |
47
+ chmod +x dist/${{ matrix.artifact_name }}
48
+ dist/${{ matrix.artifact_name }} --help
49
+
50
+ - name: Verify executable (Windows)
51
+ if: runner.os == 'Windows'
52
+ run: dist/${{ matrix.artifact_name }} --help
53
+
54
+ - name: Upload artifact
55
+ uses: actions/upload-artifact@v4
56
+ with:
57
+ name: ${{ matrix.artifact_name }}
58
+ path: dist/${{ matrix.artifact_name }}
59
+ if-no-files-found: error
60
+
61
+ release:
62
+ needs: build
63
+ runs-on: ubuntu-latest
64
+ if: startsWith(github.ref, 'refs/tags/v')
65
+ permissions:
66
+ contents: write
67
+
68
+ steps:
69
+ - name: Download all artifacts
70
+ uses: actions/download-artifact@v4
71
+ with:
72
+ path: artifacts
73
+
74
+ - name: Prepare release files
75
+ run: |
76
+ mkdir -p release
77
+ cp artifacts/chzzk-linux/chzzk-linux release/
78
+ cp artifacts/chzzk-windows.exe/chzzk-windows.exe release/
79
+ cp artifacts/chzzk-macos/chzzk-macos release/
80
+ chmod +x release/chzzk-linux release/chzzk-macos
81
+
82
+ - name: Add executables to release
83
+ uses: softprops/action-gh-release@v2
84
+ with:
85
+ files: release/*
@@ -5,6 +5,7 @@ build/
5
5
  dist/
6
6
  wheels/
7
7
  *.egg-info
8
+ *.spec.bak
8
9
 
9
10
  # Version file (auto-generated by hatch-vcs)
10
11
  src/chzzk/_version.py
@@ -41,5 +42,7 @@ htmlcov/
41
42
  CLAUDE.md
42
43
  .claude/
43
44
 
45
+ chat_logs/
44
46
  *.log
45
- *.txt
47
+ *.txt
48
+ *.jsonl
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chzzk-python
3
- Version: 0.9.0
3
+ Version: 0.9.1
4
4
  Summary: Unofficial Python SDK for Chzzk (NAVER Live Streaming Platform) API
5
5
  Project-URL: Homepage, https://github.com/hypn4/chzzk-python
6
6
  Project-URL: Repository, https://github.com/hypn4/chzzk-python
@@ -29,8 +29,8 @@ Requires-Dist: python-socketio[asyncio-client]<5.0.0,>=4.6.0
29
29
  Requires-Dist: websocket-client>=1.9.0
30
30
  Requires-Dist: websockets>=12.0
31
31
  Provides-Extra: cli
32
- Requires-Dist: rich>=13.0.0; extra == 'cli'
33
- Requires-Dist: textual>=7.3.0; extra == 'cli'
32
+ Requires-Dist: prompt-toolkit>=3.0.52; extra == 'cli'
33
+ Requires-Dist: rich>=14.3.1; extra == 'cli'
34
34
  Requires-Dist: typer>=0.15.0; extra == 'cli'
35
35
  Description-Content-Type: text/markdown
36
36
 
@@ -416,6 +416,14 @@ chzzk chat watch CHANNEL_ID
416
416
  # Watch chat even when offline
417
417
  chzzk chat watch CHANNEL_ID --offline
418
418
 
419
+ # Save chat to file
420
+ chzzk chat watch CHANNEL_ID --output chat.jsonl
421
+ chzzk chat watch CHANNEL_ID --output chat.txt --output-format txt
422
+
423
+ # Auto-generate filename based on stream info (recommended)
424
+ # Creates: {channel_id}_{live_id}_{YYYYMMDD}.jsonl
425
+ chzzk chat watch CHANNEL_ID --output-dir ./logs
426
+
419
427
  # Send a single message (requires authentication)
420
428
  chzzk chat send CHANNEL_ID "Hello!"
421
429
 
@@ -427,6 +435,9 @@ chzzk chat send CHANNEL_ID --interactive
427
435
  # or
428
436
  chzzk chat send CHANNEL_ID -i
429
437
 
438
+ # Interactive mode with chat logging
439
+ chzzk chat send CHANNEL_ID -i --output-dir ./logs
440
+
430
441
  # Interactive mode with offline channel
431
442
  chzzk chat send CHANNEL_ID -i --offline
432
443
  ```
@@ -447,6 +458,9 @@ chzzk chat send CHANNEL_ID -i --offline
447
458
  | `CHZZK_NID_AUT` | NID_AUT cookie value |
448
459
  | `CHZZK_NID_SES` | NID_SES cookie value |
449
460
  | `CHZZK_LOG_LEVEL` | Default log level |
461
+ | `CHZZK_CHAT_OUTPUT` | Default chat output file path |
462
+ | `CHZZK_CHAT_OUTPUT_DIR` | Default chat output directory (auto-generates filename) |
463
+ | `CHZZK_CHAT_OUTPUT_FORMAT` | Default chat output format (jsonl, txt) |
450
464
 
451
465
  ## Examples
452
466
 
@@ -380,6 +380,14 @@ chzzk chat watch CHANNEL_ID
380
380
  # Watch chat even when offline
381
381
  chzzk chat watch CHANNEL_ID --offline
382
382
 
383
+ # Save chat to file
384
+ chzzk chat watch CHANNEL_ID --output chat.jsonl
385
+ chzzk chat watch CHANNEL_ID --output chat.txt --output-format txt
386
+
387
+ # Auto-generate filename based on stream info (recommended)
388
+ # Creates: {channel_id}_{live_id}_{YYYYMMDD}.jsonl
389
+ chzzk chat watch CHANNEL_ID --output-dir ./logs
390
+
383
391
  # Send a single message (requires authentication)
384
392
  chzzk chat send CHANNEL_ID "Hello!"
385
393
 
@@ -391,6 +399,9 @@ chzzk chat send CHANNEL_ID --interactive
391
399
  # or
392
400
  chzzk chat send CHANNEL_ID -i
393
401
 
402
+ # Interactive mode with chat logging
403
+ chzzk chat send CHANNEL_ID -i --output-dir ./logs
404
+
394
405
  # Interactive mode with offline channel
395
406
  chzzk chat send CHANNEL_ID -i --offline
396
407
  ```
@@ -411,6 +422,9 @@ chzzk chat send CHANNEL_ID -i --offline
411
422
  | `CHZZK_NID_AUT` | NID_AUT cookie value |
412
423
  | `CHZZK_NID_SES` | NID_SES cookie value |
413
424
  | `CHZZK_LOG_LEVEL` | Default log level |
425
+ | `CHZZK_CHAT_OUTPUT` | Default chat output file path |
426
+ | `CHZZK_CHAT_OUTPUT_DIR` | Default chat output directory (auto-generates filename) |
427
+ | `CHZZK_CHAT_OUTPUT_FORMAT` | Default chat output format (jsonl, txt) |
414
428
 
415
429
  ## Examples
416
430
 
@@ -380,6 +380,14 @@ chzzk chat watch CHANNEL_ID
380
380
  # 오프라인 상태에서도 채팅 보기
381
381
  chzzk chat watch CHANNEL_ID --offline
382
382
 
383
+ # 채팅을 파일로 저장
384
+ chzzk chat watch CHANNEL_ID --output chat.jsonl
385
+ chzzk chat watch CHANNEL_ID --output chat.txt --output-format txt
386
+
387
+ # 방송 정보 기반 자동 파일명 생성 (권장)
388
+ # 생성 형식: {channel_id}_{live_id}_{YYYYMMDD}.jsonl
389
+ chzzk chat watch CHANNEL_ID --output-dir ./logs
390
+
383
391
  # 단일 메시지 전송 (인증 필요)
384
392
  chzzk chat send CHANNEL_ID "안녕하세요!"
385
393
 
@@ -391,6 +399,9 @@ chzzk chat send CHANNEL_ID --interactive
391
399
  # 또는
392
400
  chzzk chat send CHANNEL_ID -i
393
401
 
402
+ # 대화형 모드에서 채팅 로그 저장
403
+ chzzk chat send CHANNEL_ID -i --output-dir ./logs
404
+
394
405
  # 오프라인 채널에서 대화형 모드
395
406
  chzzk chat send CHANNEL_ID -i --offline
396
407
  ```
@@ -411,6 +422,9 @@ chzzk chat send CHANNEL_ID -i --offline
411
422
  | `CHZZK_NID_AUT` | NID_AUT 쿠키 값 |
412
423
  | `CHZZK_NID_SES` | NID_SES 쿠키 값 |
413
424
  | `CHZZK_LOG_LEVEL` | 기본 로그 레벨 |
425
+ | `CHZZK_CHAT_OUTPUT` | 기본 채팅 출력 파일 경로 |
426
+ | `CHZZK_CHAT_OUTPUT_DIR` | 기본 채팅 출력 디렉토리 (파일명 자동 생성) |
427
+ | `CHZZK_CHAT_OUTPUT_FORMAT` | 기본 채팅 출력 형식 (jsonl, txt) |
414
428
 
415
429
  ## 예제 코드
416
430
 
@@ -0,0 +1,128 @@
1
+ # -*- mode: python ; coding: utf-8 -*-
2
+ """PyInstaller spec file for chzzk CLI."""
3
+
4
+ import platform
5
+ from pathlib import Path
6
+
7
+ from PyInstaller.utils.hooks import collect_data_files, collect_submodules
8
+
9
+ # Determine output name based on platform
10
+ system = platform.system().lower()
11
+ if system == "linux":
12
+ exe_name = "chzzk-linux"
13
+ elif system == "windows":
14
+ exe_name = "chzzk-windows"
15
+ elif system == "darwin":
16
+ exe_name = "chzzk-macos"
17
+ else:
18
+ exe_name = f"chzzk-{system}"
19
+
20
+ # Collect hidden imports for all required packages
21
+ hidden_imports = [
22
+ # CLI framework
23
+ *collect_submodules("typer"),
24
+ *collect_submodules("click"),
25
+ # Rich console
26
+ *collect_submodules("rich"),
27
+ # Textual TUI
28
+ *collect_submodules("textual"),
29
+ # HTTP client
30
+ *collect_submodules("httpx"),
31
+ *collect_submodules("httpcore"),
32
+ # WebSocket
33
+ *collect_submodules("websockets"),
34
+ *collect_submodules("websocket"),
35
+ # Socket.IO
36
+ *collect_submodules("socketio"),
37
+ *collect_submodules("engineio"),
38
+ # Data validation
39
+ *collect_submodules("pydantic"),
40
+ *collect_submodules("pydantic_core"),
41
+ # Async support
42
+ *collect_submodules("anyio"),
43
+ # SSL certificates
44
+ "certifi",
45
+ # Encoding
46
+ "idna",
47
+ # Environment variables
48
+ "dotenv",
49
+ # Markdown for textual
50
+ "markdown_it",
51
+ ]
52
+
53
+ # Collect data files
54
+ datas = [
55
+ # SSL certificates
56
+ *collect_data_files("certifi"),
57
+ # Textual CSS and themes
58
+ *collect_data_files("textual"),
59
+ ]
60
+
61
+ # Add app.tcss from chzzk CLI
62
+ tcss_path = Path("src/chzzk/cli/tui/styles/app.tcss")
63
+ if tcss_path.exists():
64
+ datas.append((str(tcss_path), "chzzk/cli/tui/styles"))
65
+
66
+ # Excluded modules to reduce binary size
67
+ excludes = [
68
+ "tkinter",
69
+ "_tkinter",
70
+ "unittest",
71
+ "email",
72
+ "xml",
73
+ "pydoc",
74
+ "doctest",
75
+ "argparse",
76
+ "difflib",
77
+ "inspect",
78
+ "pdb",
79
+ "profile",
80
+ "pstats",
81
+ "calendar",
82
+ "gettext",
83
+ "pickle",
84
+ "PIL",
85
+ "numpy",
86
+ "pandas",
87
+ "matplotlib",
88
+ "scipy",
89
+ "test",
90
+ "tests",
91
+ ]
92
+
93
+ a = Analysis(
94
+ ["src/chzzk/cli/main.py"],
95
+ pathex=[],
96
+ binaries=[],
97
+ datas=datas,
98
+ hiddenimports=hidden_imports,
99
+ hookspath=[],
100
+ hooksconfig={},
101
+ runtime_hooks=[],
102
+ excludes=excludes,
103
+ noarchive=False,
104
+ optimize=0,
105
+ )
106
+
107
+ pyz = PYZ(a.pure)
108
+
109
+ exe = EXE(
110
+ pyz,
111
+ a.scripts,
112
+ a.binaries,
113
+ a.datas,
114
+ [],
115
+ name=exe_name,
116
+ debug=False,
117
+ bootloader_ignore_signals=False,
118
+ strip=False,
119
+ upx=True,
120
+ upx_exclude=[],
121
+ runtime_tmpdir=None,
122
+ console=True,
123
+ disable_windowed_traceback=False,
124
+ argv_emulation=False,
125
+ target_arch=None,
126
+ codesign_identity=None,
127
+ entitlements_file=None,
128
+ )
@@ -36,8 +36,8 @@ dependencies = [
36
36
  [project.optional-dependencies]
37
37
  cli = [
38
38
  "typer>=0.15.0",
39
- "rich>=13.0.0",
40
- "textual>=7.3.0",
39
+ "rich>=14.3.1",
40
+ "prompt-toolkit>=3.0.52",
41
41
  ]
42
42
 
43
43
  [project.scripts]
@@ -54,6 +54,7 @@ Changelog = "https://github.com/hypn4/chzzk-python/blob/main/CHANGELOG.md"
54
54
  dev = [
55
55
  "flask>=3.1.2",
56
56
  "mypy>=1.19.1",
57
+ "pyinstaller>=6.18.0",
57
58
  "pytest>=9.0.2",
58
59
  "pytest-asyncio>=1.3.0",
59
60
  "pytest-httpx>=0.35.0",
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env python
2
+ """Local build script for chzzk CLI executable."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ import platform
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ logging.basicConfig(
14
+ level=logging.INFO,
15
+ format="%(message)s",
16
+ )
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def get_executable_name() -> str:
21
+ """Get the expected executable name based on platform."""
22
+ system = platform.system().lower()
23
+ names = {
24
+ "linux": "chzzk-linux",
25
+ "windows": "chzzk-windows.exe",
26
+ "darwin": "chzzk-macos",
27
+ }
28
+ return names.get(system, f"chzzk-{system}")
29
+
30
+
31
+ def clean_directories() -> None:
32
+ """Clean previous build and dist directories."""
33
+ root = Path(__file__).parent.parent
34
+ build_dir = root / "build"
35
+ dist_dir = root / "dist"
36
+
37
+ for directory in [build_dir, dist_dir]:
38
+ if directory.exists():
39
+ logger.info("Cleaning %s...", directory)
40
+ shutil.rmtree(directory)
41
+
42
+
43
+ def build_executable() -> Path | None:
44
+ """Build the executable using PyInstaller."""
45
+ root = Path(__file__).parent.parent
46
+ spec_file = root / "chzzk.spec"
47
+
48
+ if not spec_file.exists():
49
+ logger.error("Error: %s not found", spec_file)
50
+ return None
51
+
52
+ logger.info("Building executable with PyInstaller...")
53
+ result = subprocess.run(
54
+ [sys.executable, "-m", "PyInstaller", str(spec_file), "--clean"],
55
+ cwd=root,
56
+ )
57
+
58
+ if result.returncode != 0:
59
+ logger.error("Error: PyInstaller build failed")
60
+ return None
61
+
62
+ # Find the output executable
63
+ dist_dir = root / "dist"
64
+ exe_name = get_executable_name()
65
+ exe_path = dist_dir / exe_name
66
+
67
+ if exe_path.exists():
68
+ return exe_path
69
+
70
+ # Try without extension on Windows (in case .exe wasn't added)
71
+ if platform.system().lower() == "windows":
72
+ exe_path_no_ext = dist_dir / exe_name.replace(".exe", "")
73
+ if exe_path_no_ext.exists():
74
+ return exe_path_no_ext
75
+
76
+ return None
77
+
78
+
79
+ def format_size(size_bytes: int) -> str:
80
+ """Format file size in human-readable format."""
81
+ for unit in ["B", "KB", "MB", "GB"]:
82
+ if size_bytes < 1024:
83
+ return f"{size_bytes:.2f} {unit}"
84
+ size_bytes /= 1024
85
+ return f"{size_bytes:.2f} TB"
86
+
87
+
88
+ def main() -> int:
89
+ """Main build function."""
90
+ logger.info("=== chzzk CLI Build Script ===\n")
91
+
92
+ # Clean previous builds
93
+ clean_directories()
94
+
95
+ # Build executable
96
+ exe_path = build_executable()
97
+
98
+ if exe_path is None:
99
+ logger.error("\nBuild failed!")
100
+ return 1
101
+
102
+ # Report results
103
+ file_size = exe_path.stat().st_size
104
+ logger.info("\n=== Build Complete ===")
105
+ logger.info("Output: %s", exe_path)
106
+ logger.info("Size: %s", format_size(file_size))
107
+
108
+ # Quick verification
109
+ logger.info("\nVerifying executable...")
110
+ result = subprocess.run(
111
+ [str(exe_path), "--version"],
112
+ capture_output=True,
113
+ text=True,
114
+ )
115
+ if result.returncode == 0:
116
+ logger.info("Version: %s", result.stdout.strip())
117
+ else:
118
+ logger.warning("Warning: Could not verify executable version")
119
+ if result.stderr:
120
+ logger.warning("stderr: %s", result.stderr.strip())
121
+
122
+ return 0
123
+
124
+
125
+ if __name__ == "__main__":
126
+ sys.exit(main())
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.9.0'
32
- __version_tuple__ = version_tuple = (0, 9, 0)
31
+ __version__ = version = '0.9.1'
32
+ __version_tuple__ = version_tuple = (0, 9, 1)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -9,9 +9,6 @@ import typer
9
9
  from rich.console import Console
10
10
  from rich.panel import Panel
11
11
 
12
- from chzzk.cli.tui import can_run_tui, run_tui
13
- from chzzk.cli.tui.apps import LoginApp
14
-
15
12
  if TYPE_CHECKING:
16
13
  from chzzk.cli.config import ConfigManager
17
14
 
@@ -55,13 +52,6 @@ def login(
55
52
  help="NID_SES cookie value from Naver login",
56
53
  ),
57
54
  ] = None,
58
- no_tui: Annotated[
59
- bool,
60
- typer.Option(
61
- "--no-tui",
62
- help="Disable TUI and use simple prompts",
63
- ),
64
- ] = False,
65
55
  ) -> None:
66
56
  """Save Naver authentication cookies.
67
57
 
@@ -69,14 +59,11 @@ def login(
69
59
  1. Open browser DevTools (F12)
70
60
  2. Go to Application > Cookies > naver.com
71
61
  3. Find NID_AUT and NID_SES values
72
-
73
- By default, opens an interactive TUI for entering cookies.
74
- Use --no-tui to use simple prompts instead.
75
62
  """
76
63
  config = get_config(ctx)
77
64
  json_output = ctx.obj.get("json_output", False)
78
65
 
79
- # If both values provided via CLI, skip TUI/prompts
66
+ # If both values provided via CLI, skip prompts
80
67
  if nid_aut and nid_ses:
81
68
  config.save_cookies(nid_aut, nid_ses)
82
69
  if json_output:
@@ -91,32 +78,7 @@ def login(
91
78
  )
92
79
  return
93
80
 
94
- # Try TUI if available and not disabled
95
- if not json_output and not no_tui and can_run_tui():
96
- login_app = LoginApp(config=config, nid_aut=nid_aut, nid_ses=nid_ses)
97
- run_tui(login_app)
98
-
99
- if login_app.result.cancelled:
100
- console.print("[yellow]Login cancelled[/yellow]")
101
- raise typer.Exit(0)
102
-
103
- if login_app.result.success:
104
- if json_output:
105
- console.print(json.dumps({"status": "success", "message": "Cookies saved"}))
106
- else:
107
- console.print(
108
- Panel(
109
- f"Cookies saved to [cyan]{config.config_dir}[/cyan]",
110
- title="[green]Login successful[/green]",
111
- border_style="green",
112
- )
113
- )
114
- else:
115
- console.print("[red]Login failed[/red]")
116
- raise typer.Exit(1)
117
- return
118
-
119
- # Fallback to simple prompts
81
+ # Use simple prompts
120
82
  try:
121
83
  final_nid_aut, final_nid_ses = _prompt_login_fallback(config)
122
84
  except (KeyboardInterrupt, EOFError):