chzzk-python 0.8.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 (124) hide show
  1. chzzk_python-0.9.1/.env.example +51 -0
  2. chzzk_python-0.9.1/.github/workflows/build.yml +85 -0
  3. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/.github/workflows/ci.yml +2 -2
  4. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/.github/workflows/publish.yml +37 -3
  5. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/.gitignore +5 -0
  6. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/PKG-INFO +17 -3
  7. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/README.md +14 -0
  8. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/README_KO.md +14 -0
  9. chzzk_python-0.9.1/chzzk.spec +128 -0
  10. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/docs/unofficial-chat-websocket-protocol.md +170 -1
  11. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/pyproject.toml +3 -2
  12. chzzk_python-0.9.1/scripts/build.py +126 -0
  13. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/_version.py +2 -2
  14. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/cli/commands/auth.py +2 -40
  15. chzzk_python-0.9.1/src/chzzk/cli/commands/chat.py +827 -0
  16. chzzk_python-0.9.1/src/chzzk/cli/formatter.py +332 -0
  17. chzzk_python-0.9.1/src/chzzk/cli/logging.py +48 -0
  18. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/cli/main.py +57 -1
  19. chzzk_python-0.9.1/src/chzzk/cli/writers.py +253 -0
  20. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/client.py +54 -20
  21. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/handler.py +10 -1
  22. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/monitor.py +42 -8
  23. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/reconnect.py +1 -0
  24. chzzk_python-0.9.1/tests/cli/__init__.py +1 -0
  25. chzzk_python-0.9.1/tests/cli/test_formatter.py +523 -0
  26. chzzk_python-0.9.1/tests/cli/test_writers.py +325 -0
  27. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/uv.lock +217 -169
  28. chzzk_python-0.8.0/.github/workflows/release-please.yml +0 -15
  29. chzzk_python-0.8.0/CHANGELOG.md +0 -183
  30. chzzk_python-0.8.0/src/chzzk/cli/commands/chat.py +0 -569
  31. chzzk_python-0.8.0/src/chzzk/cli/logging.py +0 -24
  32. chzzk_python-0.8.0/src/chzzk/cli/tui/__init__.py +0 -6
  33. chzzk_python-0.8.0/src/chzzk/cli/tui/apps/__init__.py +0 -7
  34. chzzk_python-0.8.0/src/chzzk/cli/tui/apps/chat_interactive.py +0 -294
  35. chzzk_python-0.8.0/src/chzzk/cli/tui/apps/chat_viewer.py +0 -229
  36. chzzk_python-0.8.0/src/chzzk/cli/tui/apps/login.py +0 -218
  37. chzzk_python-0.8.0/src/chzzk/cli/tui/base.py +0 -52
  38. chzzk_python-0.8.0/src/chzzk/cli/tui/styles/app.tcss +0 -150
  39. chzzk_python-0.8.0/src/chzzk/cli/tui/utils.py +0 -82
  40. chzzk_python-0.8.0/src/chzzk/cli/tui/widgets/__init__.py +0 -6
  41. chzzk_python-0.8.0/src/chzzk/cli/tui/widgets/chat.py +0 -234
  42. chzzk_python-0.8.0/src/chzzk/cli/tui/widgets/input.py +0 -106
  43. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/.python-version +0 -0
  44. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/LICENSE +0 -0
  45. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/examples/.env.example +0 -0
  46. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/examples/oauth_server.py +0 -0
  47. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/examples/realtime_chat.py +0 -0
  48. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/examples/realtime_chat_async.py +0 -0
  49. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/examples/session_management.py +0 -0
  50. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/examples/unofficial_chat.py +0 -0
  51. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/examples/unofficial_chat_async.py +0 -0
  52. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/main.py +0 -0
  53. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/__init__.py +0 -0
  54. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/__init__.py +0 -0
  55. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/base.py +0 -0
  56. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/category.py +0 -0
  57. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/channel.py +0 -0
  58. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/chat.py +0 -0
  59. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/live.py +0 -0
  60. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/restriction.py +0 -0
  61. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/session.py +0 -0
  62. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/api/user.py +0 -0
  63. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/auth/__init__.py +0 -0
  64. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/auth/models.py +0 -0
  65. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/auth/oauth.py +0 -0
  66. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/auth/token.py +0 -0
  67. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/cli/__init__.py +0 -0
  68. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/cli/commands/__init__.py +0 -0
  69. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/cli/commands/live.py +0 -0
  70. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/cli/config.py +0 -0
  71. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/client.py +0 -0
  72. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/constants.py +0 -0
  73. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/exceptions/__init__.py +0 -0
  74. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/exceptions/errors.py +0 -0
  75. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/http/__init__.py +0 -0
  76. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/http/_base.py +0 -0
  77. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/http/client.py +0 -0
  78. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/http/endpoints.py +0 -0
  79. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/logging.py +0 -0
  80. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/__init__.py +0 -0
  81. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/category.py +0 -0
  82. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/channel.py +0 -0
  83. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/chat.py +0 -0
  84. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/common.py +0 -0
  85. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/live.py +0 -0
  86. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/restriction.py +0 -0
  87. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/session.py +0 -0
  88. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/models/user.py +0 -0
  89. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/py.typed +0 -0
  90. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/realtime/__init__.py +0 -0
  91. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/realtime/client.py +0 -0
  92. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/__init__.py +0 -0
  93. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/__init__.py +0 -0
  94. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/base.py +0 -0
  95. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/chat.py +0 -0
  96. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/live.py +0 -0
  97. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/api/user.py +0 -0
  98. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/auth/__init__.py +0 -0
  99. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/auth/cookie.py +0 -0
  100. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/__init__.py +0 -0
  101. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/chat/connection.py +0 -0
  102. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/client.py +0 -0
  103. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/__init__.py +0 -0
  104. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/_base.py +0 -0
  105. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/client.py +0 -0
  106. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/http/endpoints.py +0 -0
  107. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/__init__.py +0 -0
  108. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/chat.py +0 -0
  109. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/live.py +0 -0
  110. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/src/chzzk/unofficial/models/user.py +0 -0
  111. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/__init__.py +0 -0
  112. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/__init__.py +0 -0
  113. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/test_category.py +0 -0
  114. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/test_channel.py +0 -0
  115. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/test_chat.py +0 -0
  116. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/test_live.py +0 -0
  117. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/test_restriction.py +0 -0
  118. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/test_session.py +0 -0
  119. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/api/test_user.py +0 -0
  120. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/auth/__init__.py +0 -0
  121. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/auth/test_oauth.py +0 -0
  122. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/realtime/__init__.py +0 -0
  123. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/realtime/test_client.py +0 -0
  124. {chzzk_python-0.8.0 → chzzk_python-0.9.1}/tests/test_client.py +0 -0
@@ -0,0 +1,51 @@
1
+ # Chzzk CLI Configuration
2
+ # Copy this file to .env and fill in your values
3
+
4
+ # Authentication (Naver Cookie)
5
+ # 브라우저에서 네이버 로그인 후 개발자 도구에서 쿠키 확인
6
+ # 1. 브라우저에서 chzzk.naver.com에 로그인
7
+ # 2. 개발자 도구 (F12) -> Application -> Cookies -> chzzk.naver.com
8
+ # 3. NID_AUT와 NID_SES 값을 복사
9
+ CHZZK_NID_AUT=
10
+ CHZZK_NID_SES=
11
+
12
+ # Logging Configuration
13
+ # Log level: DEBUG, INFO, WARNING, ERROR
14
+ CHZZK_LOG_LEVEL=WARNING
15
+
16
+ # Log file path (optional)
17
+ CHZZK_LOG_FILE=
18
+
19
+ # Disable console (stderr) logging output (set to 1 to disable)
20
+ CHZZK_NO_CONSOLE_LOG=
21
+
22
+ # Chat Format Configuration (Rich markup supported)
23
+ # Available variables: {time}, {server_time}, {badge}, {verified}, {role}, {name}, {msg}
24
+ # Example: "{time} {badge}{verified}{name}: {msg}"
25
+ CHZZK_CHAT_FORMAT=
26
+
27
+ # Donation Format (Rich markup supported)
28
+ # Available variables: {time}, {server_time}, {badge}, {verified}, {role}, {name}, {msg}, {amount}, {pay_type}, {donation_type}, {continuous_days}
29
+ # Example: "{time} {amount}원({pay_type}) {badge}{name}: {msg}"
30
+ CHZZK_DONATION_FORMAT=
31
+
32
+ # Sent Message Format (Rich markup supported)
33
+ # Available variables: {time}, {msg}
34
+ # Example: "{time} > {msg}"
35
+ CHZZK_SENT_FORMAT=
36
+
37
+ # Time format (strftime format string)
38
+ # Example: "%H:%M:%S" (24-hour), "%I:%M:%S %p" (12-hour with AM/PM)
39
+ CHZZK_TIME_FORMAT=
40
+
41
+ # Chat Output Configuration
42
+ # Save chat messages to file (explicit path)
43
+ CHZZK_CHAT_OUTPUT=
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
+
50
+ # Output format: jsonl, txt (default: jsonl)
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/*
@@ -24,7 +24,7 @@ jobs:
24
24
  python-version: "3.12"
25
25
 
26
26
  - name: Install dependencies
27
- run: uv sync --dev
27
+ run: uv sync --dev --all-extras
28
28
 
29
29
  - name: Run ruff format check
30
30
  run: uv run ruff format --check .
@@ -53,7 +53,7 @@ jobs:
53
53
  python-version: ${{ matrix.python-version }}
54
54
 
55
55
  - name: Install dependencies
56
- run: uv sync --dev
56
+ run: uv sync --dev --all-extras
57
57
 
58
58
  - name: Run tests
59
59
  run: uv run pytest -v
@@ -1,8 +1,9 @@
1
1
  name: Publish to PyPI
2
2
 
3
3
  on:
4
- release:
5
- types: [published]
4
+ push:
5
+ tags:
6
+ - "v*"
6
7
  workflow_dispatch:
7
8
 
8
9
  jobs:
@@ -36,9 +37,42 @@ jobs:
36
37
  name: dist
37
38
  path: dist/
38
39
 
39
- publish-pypi:
40
+ release:
40
41
  needs: build
41
42
  runs-on: ubuntu-latest
43
+ if: startsWith(github.ref, 'refs/tags/v')
44
+ permissions:
45
+ contents: write
46
+ steps:
47
+ - uses: actions/checkout@v6
48
+ with:
49
+ fetch-depth: 0
50
+
51
+ - name: Generate changelog
52
+ uses: orhun/git-cliff-action@v4
53
+ with:
54
+ config: github
55
+ args: --latest --strip header
56
+ env:
57
+ OUTPUT: CHANGES.md
58
+ GITHUB_REPO: ${{ github.repository }}
59
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60
+
61
+ - name: Download artifacts
62
+ uses: actions/download-artifact@v4
63
+ with:
64
+ name: dist
65
+ path: dist/
66
+
67
+ - name: Create Release
68
+ uses: softprops/action-gh-release@v2
69
+ with:
70
+ body_path: CHANGES.md
71
+ files: dist/*
72
+
73
+ publish-pypi:
74
+ needs: release
75
+ runs-on: ubuntu-latest
42
76
  environment:
43
77
  name: pypi
44
78
  url: https://pypi.org/p/chzzk-python
@@ -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,3 +42,7 @@ htmlcov/
41
42
  CLAUDE.md
42
43
  .claude/
43
44
 
45
+ chat_logs/
46
+ *.log
47
+ *.txt
48
+ *.jsonl
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chzzk-python
3
- Version: 0.8.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
+ )
@@ -1,6 +1,6 @@
1
1
  # Chzzk 비공식 채팅 WebSocket 프로토콜 분석
2
2
 
3
- > 최종 업데이트: 2026-01-23
3
+ > 최종 업데이트: 2026-01-25
4
4
  > 분석 방법: Chrome DevTools WebSocket 메시지 캡처
5
5
 
6
6
  ## 개요
@@ -255,6 +255,174 @@ GET https://api.chzzk.naver.com/service/v1/user/status
255
255
 
256
256
  ---
257
257
 
258
+ ## 수신 메시지 구조
259
+
260
+ ### 6. CHAT (cmd: 93101)
261
+
262
+ 실시간 채팅 메시지 수신.
263
+
264
+ ```json
265
+ {
266
+ "ver": "3",
267
+ "cmd": 93101,
268
+ "svcid": "game",
269
+ "cid": "N2FOy2",
270
+ "bdy": [
271
+ {
272
+ "svcid": "game",
273
+ "cid": "N2FOy2",
274
+ "mbrCnt": 1234,
275
+ "uid": "14f67c97d654f655afc0c9b3********",
276
+ "profile": "{\"userIdHash\":\"14f67c97...\",\"nickname\":\"닉네임\",\"profileImageUrl\":\"...\",\"userRoleCode\":\"common_user\",\"badge\":null,\"title\":null,\"verifiedMark\":false,\"activityBadges\":[{\"badgeNo\":123,\"badgeId\":\"구독자\",\"name\":\"구독자\",\"imageUrl\":\"...\"}],\"streamingProperty\":{}}",
277
+ "msg": "안녕하세요!",
278
+ "msgTypeCode": 1,
279
+ "msgStatusType": "NORMAL",
280
+ "extras": "{\"chatType\":\"STREAMING\",\"osType\":\"PC\",\"emojis\":{}}",
281
+ "ctime": 1769139665965,
282
+ "utime": 1769139665965,
283
+ "msgTime": 1769139665965
284
+ }
285
+ ]
286
+ }
287
+ ```
288
+
289
+ **bdy 배열 내 메시지 필드:**
290
+
291
+ | 필드 | 설명 |
292
+ |------|------|
293
+ | `uid` | 사용자 ID 해시 |
294
+ | `profile` | 사용자 프로필 (JSON 문자열, 아래 참조) |
295
+ | `msg` | 메시지 내용 |
296
+ | `msgTypeCode` | 메시지 타입 (`1` = 일반 텍스트, `10` = 후원 메시지) |
297
+ | `msgStatusType` | 메시지 상태 (`NORMAL`, `HIDDEN` 등) |
298
+ | `extras` | 추가 데이터 (JSON 문자열) |
299
+ | `ctime` | 생성 시간 (밀리초) |
300
+ | `msgTime` | 메시지 시간 (밀리초) |
301
+ | `mbrCnt` | 현재 시청자 수 |
302
+
303
+ ### 7. DONATION (cmd: 93102)
304
+
305
+ 후원 메시지 수신.
306
+
307
+ ```json
308
+ {
309
+ "ver": "3",
310
+ "cmd": 93102,
311
+ "svcid": "game",
312
+ "cid": "N2FOy2",
313
+ "bdy": [
314
+ {
315
+ "uid": "14f67c97d654f655afc0c9b3********",
316
+ "profile": "{...}",
317
+ "msg": "응원합니다!",
318
+ "msgTypeCode": 10,
319
+ "msgStatusType": "NORMAL",
320
+ "extras": "{\"payType\":\"CURRENCY\",\"payAmount\":1000,\"donationType\":\"CHAT\",...}",
321
+ "msgTime": 1769139665965
322
+ }
323
+ ]
324
+ }
325
+ ```
326
+
327
+ **중요**: 후원 관련 필드(`payType`, `payAmount`, `donationType`)는 **`extras` JSON 문자열 내부**에 있습니다. 최상위 필드가 아닙니다.
328
+
329
+ **extras 필드 파싱 후 전체 구조:**
330
+
331
+ ```json
332
+ {
333
+ "emojis": {},
334
+ "streamingChannelId": "a9b3377345a2a37e68a6072ba5e77fec",
335
+ "donationId": "6alUxGqGBsQvsGNE1eoa0lV9JGEBl",
336
+ "donationType": "CHAT",
337
+ "payType": "CURRENCY",
338
+ "payAmount": 1000,
339
+ "nickname": "닉네임",
340
+ "osType": "PC",
341
+ "chatType": "STREAMING",
342
+ "isAnonymous": false,
343
+ "continuousDonationDays": 1,
344
+ "weeklyRankList": [...],
345
+ "donationUserWeeklyRank": {...}
346
+ }
347
+ ```
348
+
349
+ **extras 필드 내 DONATION 전용 필드:**
350
+
351
+ | 필드 | 타입 | 설명 |
352
+ |------|------|------|
353
+ | `payType` | string | 결제 타입 (`CURRENCY` = 치즈, `CHEESE` 등) |
354
+ | `payAmount` | int | 결제 금액 (KRW, 정수) |
355
+ | `donationType` | string | 후원 타입 (`CHAT`, `VIDEO` 등) |
356
+ | `donationId` | string | 후원 고유 ID |
357
+ | `isAnonymous` | boolean | 익명 여부 |
358
+ | `nickname` | string | 후원자 닉네임 (extras 내에도 포함) |
359
+ | `continuousDonationDays` | int | 연속 후원 일수 |
360
+ | `weeklyRankList` | array | 주간 후원 랭킹 목록 (상위 10명) |
361
+ | `donationUserWeeklyRank` | object | 후원자의 주간 랭킹 정보 |
362
+
363
+ ---
364
+
365
+ ## profile 필드 구조
366
+
367
+ `profile` 필드는 JSON 문자열로 인코딩되어 있으며, 파싱 후 다음 구조를 가짐:
368
+
369
+ ```json
370
+ {
371
+ "userIdHash": "14f67c97d654f655afc0c9b3********",
372
+ "nickname": "닉네임",
373
+ "profileImageUrl": "https://...",
374
+ "userRoleCode": "common_user",
375
+ "badge": null,
376
+ "title": null,
377
+ "verifiedMark": false,
378
+ "activityBadges": [
379
+ {
380
+ "badgeNo": 1708498,
381
+ "badgeId": "donation_accumulate_amount_lv1",
382
+ "name": "치즈 후원자",
383
+ "imageUrl": "https://ssl.pstatic.net/static/nng/glive/icon/cheese01.png",
384
+ "activated": true
385
+ }
386
+ ],
387
+ "streamingProperty": {
388
+ "nicknameColor": {"colorCode": "CC000"},
389
+ "activatedAchievementBadgeIds": []
390
+ },
391
+ "viewerBadges": [
392
+ {
393
+ "type": "STANDARD",
394
+ "badge": {
395
+ "badgeId": "donation_accumulate_amount_lv1",
396
+ "scope": "CHANNEL",
397
+ "imageUrl": "https://ssl.pstatic.net/static/nng/glive/badge/recent_cheese01.png"
398
+ }
399
+ }
400
+ ]
401
+ }
402
+ ```
403
+
404
+ **주요 필드 설명:**
405
+
406
+ | 필드 | 타입 | 설명 |
407
+ |------|------|------|
408
+ | `userIdHash` | string | 사용자 ID 해시 |
409
+ | `nickname` | string | 닉네임 |
410
+ | `profileImageUrl` | string | 프로필 이미지 URL |
411
+ | `userRoleCode` | string | 권한 코드 (`common_user`, `streamer`, `manager` 등) |
412
+ | `badge` | object \| null | 대표 배지 (`name`, `imageUrl` 등) |
413
+ | `title` | object \| null | 칭호 (`name`, `color` 등) |
414
+ | `verifiedMark` | boolean | 인증 마크 여부 |
415
+ | `activityBadges` | array | 활동 배지 목록 (구독, 후원 레벨 등) |
416
+ | `streamingProperty` | object | 스트리밍 속성 (닉네임 색상 등) |
417
+ | `viewerBadges` | array | 시청자 배지 목록 (채널별/글로벌 배지) |
418
+
419
+ **참고**: `badge`와 `activityBadges`는 서로 다른 필드입니다:
420
+ - `badge`: 단일 대표 배지 (스트리머가 지정한 배지, 대부분 null)
421
+ - `activityBadges`: 활동 기반 배지 목록 (구독자, 후원 레벨 등)
422
+ - `viewerBadges`: 시청자 배지 (채널/글로벌 스코프)
423
+
424
+ ---
425
+
258
426
  ## 주의사항
259
427
 
260
428
  1. **프로토콜 버전**: 모든 메시지에 `"ver": "3"` 사용
@@ -269,4 +437,5 @@ GET https://api.chzzk.naver.com/service/v1/user/status
269
437
 
270
438
  | 날짜 | 변경 내용 |
271
439
  |------|----------|
440
+ | 2026-01-25 | CHAT/DONATION 메시지 구조 추가, profile 필드 상세 문서화 |
272
441
  | 2026-01-23 | 최초 작성. Chrome DevTools WebSocket 캡처 기반 프로토콜 분석 |
@@ -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",