chzzk-python 0.12.0__tar.gz → 0.13.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 (117) hide show
  1. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.env.example +18 -0
  2. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.github/workflows/docker.yml +6 -3
  3. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/PKG-INFO +27 -1
  4. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/README.md +26 -0
  5. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/README_KO.md +26 -0
  6. chzzk_python-0.13.0/docker-compose.yml +140 -0
  7. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/_version.py +2 -2
  8. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/chat.py +4 -4
  9. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/formatter.py +4 -3
  10. chzzk_python-0.13.0/src/chzzk/cli/timezone.py +49 -0
  11. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/writers.py +10 -9
  12. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.dockerignore +0 -0
  13. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.github/workflows/build.yml +0 -0
  14. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.github/workflows/ci.yml +0 -0
  15. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.github/workflows/publish.yml +0 -0
  16. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.gitignore +0 -0
  17. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/.python-version +0 -0
  18. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/Dockerfile +0 -0
  19. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/LICENSE +0 -0
  20. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/chzzk.spec +0 -0
  21. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/docs/unofficial-chat-websocket-protocol.md +0 -0
  22. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/examples/.env.example +0 -0
  23. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/examples/oauth_server.py +0 -0
  24. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/examples/realtime_chat.py +0 -0
  25. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/examples/realtime_chat_async.py +0 -0
  26. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/examples/session_management.py +0 -0
  27. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/examples/unofficial_chat.py +0 -0
  28. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/examples/unofficial_chat_async.py +0 -0
  29. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/main.py +0 -0
  30. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/pyproject.toml +0 -0
  31. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/scripts/build.py +0 -0
  32. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/__init__.py +0 -0
  33. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/__init__.py +0 -0
  34. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/base.py +0 -0
  35. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/category.py +0 -0
  36. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/channel.py +0 -0
  37. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/chat.py +0 -0
  38. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/live.py +0 -0
  39. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/restriction.py +0 -0
  40. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/session.py +0 -0
  41. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/api/user.py +0 -0
  42. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/auth/__init__.py +0 -0
  43. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/auth/models.py +0 -0
  44. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/auth/oauth.py +0 -0
  45. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/auth/token.py +0 -0
  46. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/__init__.py +0 -0
  47. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/__init__.py +0 -0
  48. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/auth.py +0 -0
  49. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/live.py +0 -0
  50. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/config.py +0 -0
  51. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/logging.py +0 -0
  52. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/cli/main.py +0 -0
  53. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/client.py +0 -0
  54. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/constants.py +0 -0
  55. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/exceptions/__init__.py +0 -0
  56. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/exceptions/errors.py +0 -0
  57. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/http/__init__.py +0 -0
  58. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/http/_base.py +0 -0
  59. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/http/client.py +0 -0
  60. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/http/endpoints.py +0 -0
  61. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/logging.py +0 -0
  62. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/__init__.py +0 -0
  63. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/category.py +0 -0
  64. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/channel.py +0 -0
  65. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/chat.py +0 -0
  66. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/common.py +0 -0
  67. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/live.py +0 -0
  68. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/restriction.py +0 -0
  69. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/session.py +0 -0
  70. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/models/user.py +0 -0
  71. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/py.typed +0 -0
  72. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/realtime/__init__.py +0 -0
  73. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/realtime/client.py +0 -0
  74. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/__init__.py +0 -0
  75. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/__init__.py +0 -0
  76. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/base.py +0 -0
  77. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/chat.py +0 -0
  78. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/live.py +0 -0
  79. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/user.py +0 -0
  80. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/auth/__init__.py +0 -0
  81. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/auth/cookie.py +0 -0
  82. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/__init__.py +0 -0
  83. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/client.py +0 -0
  84. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/connection.py +0 -0
  85. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/handler.py +0 -0
  86. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/monitor.py +0 -0
  87. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/client.py +0 -0
  88. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/__init__.py +0 -0
  89. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/_base.py +0 -0
  90. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/client.py +0 -0
  91. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/endpoints.py +0 -0
  92. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/__init__.py +0 -0
  93. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/chat.py +0 -0
  94. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/live.py +0 -0
  95. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/reconnect.py +0 -0
  96. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/user.py +0 -0
  97. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/__init__.py +0 -0
  98. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/__init__.py +0 -0
  99. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/test_category.py +0 -0
  100. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/test_channel.py +0 -0
  101. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/test_chat.py +0 -0
  102. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/test_live.py +0 -0
  103. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/test_restriction.py +0 -0
  104. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/test_session.py +0 -0
  105. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/api/test_user.py +0 -0
  106. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/auth/__init__.py +0 -0
  107. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/auth/test_oauth.py +0 -0
  108. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/cli/__init__.py +0 -0
  109. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/cli/test_formatter.py +0 -0
  110. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/cli/test_writers.py +0 -0
  111. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/realtime/__init__.py +0 -0
  112. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/realtime/test_client.py +0 -0
  113. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/test_client.py +0 -0
  114. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/unofficial/__init__.py +0 -0
  115. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/unofficial/test_client.py +0 -0
  116. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/tests/unofficial/test_monitor.py +0 -0
  117. {chzzk_python-0.12.0 → chzzk_python-0.13.0}/uv.lock +0 -0
@@ -1,6 +1,14 @@
1
1
  # Chzzk CLI Configuration
2
2
  # Copy this file to .env and fill in your values
3
3
 
4
+ # Timezone Configuration
5
+ # Docker 컨테이너 시스템 시간대 (default: Asia/Seoul)
6
+ TZ=Asia/Seoul
7
+
8
+ # 채팅 타임스탬프 및 로그 파일명에 사용되는 시간대 (default: Asia/Seoul)
9
+ # TZ와 별도로 설정 가능하며, 로그 출력에만 영향
10
+ CHZZK_TIMEZONE=Asia/Seoul
11
+
4
12
  # Authentication (Naver Cookie)
5
13
  # 브라우저에서 네이버 로그인 후 개발자 도구에서 쿠키 확인
6
14
  # 1. 브라우저에서 chzzk.naver.com에 로그인
@@ -9,6 +17,16 @@
9
17
  CHZZK_NID_AUT=
10
18
  CHZZK_NID_SES=
11
19
 
20
+ # Channel ID
21
+ # 채팅을 모니터링할 채널 ID
22
+ CHZZK_CHANNEL_ID=
23
+
24
+ # Channel IDs (Docker Compose only)
25
+ # Docker Compose 다중 채널 모니터링 전용 (chat-watch-1, chat-watch-2, chat-watch-3)
26
+ CHZZK_CHANNEL_ID_1=
27
+ CHZZK_CHANNEL_ID_2=
28
+ CHZZK_CHANNEL_ID_3=
29
+
12
30
  # Logging Configuration
13
31
  # Log level: DEBUG, INFO, WARNING, ERROR
14
32
  CHZZK_LOG_LEVEL=WARNING
@@ -145,13 +145,16 @@ jobs:
145
145
  docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
146
146
  $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
147
147
 
148
- - name: Inspect image
148
+ - name: Get image digest
149
+ id: digest
149
150
  run: |
150
- docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
151
+ DIGEST=$(docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} --format '{{json .Manifest.Digest}}' | tr -d '"')
152
+ echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"
153
+ echo "Image digest: $DIGEST"
151
154
 
152
155
  - name: Generate artifact attestation
153
156
  uses: actions/attest-build-provenance@v2
154
157
  with:
155
158
  subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
156
- subject-digest: ${{ steps.meta.outputs.version }}
159
+ subject-digest: ${{ steps.digest.outputs.digest }}
157
160
  push-to-registry: true
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chzzk-python
3
- Version: 0.12.0
3
+ Version: 0.13.0
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
@@ -90,6 +90,30 @@ docker run --rm -it \
90
90
 
91
91
  Available tags: `latest`, `X.Y.Z`, `X.Y`, `X`
92
92
 
93
+ ### Docker Compose
94
+
95
+ ```bash
96
+ # Copy and configure environment variables
97
+ cp .env.example .env
98
+ # Edit .env with your credentials and channel ID
99
+
100
+ # Run CLI commands
101
+ docker compose run --rm chzzk --help
102
+ docker compose run --rm chzzk live info CHANNEL_ID
103
+
104
+ # Watch chat (background service with auto-restart)
105
+ docker compose --profile chat up -d chat-watch
106
+
107
+ # Interactive chat mode
108
+ docker compose --profile interactive run --rm chat-interactive
109
+
110
+ # View logs
111
+ docker compose --profile chat logs -f chat-watch
112
+
113
+ # Stop services
114
+ docker compose --profile chat down
115
+ ```
116
+
93
117
  ## Quick Start
94
118
 
95
119
  ```python
@@ -552,6 +576,8 @@ chzzk chat send CHANNEL_ID -i --offline
552
576
  | `CHZZK_CHAT_OUTPUT_FORMAT` | Default chat output format (jsonl, txt) |
553
577
  | `CHZZK_POLL_INTERVAL` | Live status polling interval in seconds (default: 10) |
554
578
  | `CHZZK_AUTO_RECONNECT` | Enable auto-reconnection (default: true, set "false" to disable) |
579
+ | `CHZZK_TIMEZONE` | Timezone for chat timestamps and log filenames (default: Asia/Seoul) |
580
+ | `TZ` | System timezone for Docker containers (default: Asia/Seoul) |
555
581
 
556
582
  ## Examples
557
583
 
@@ -53,6 +53,30 @@ docker run --rm -it \
53
53
 
54
54
  Available tags: `latest`, `X.Y.Z`, `X.Y`, `X`
55
55
 
56
+ ### Docker Compose
57
+
58
+ ```bash
59
+ # Copy and configure environment variables
60
+ cp .env.example .env
61
+ # Edit .env with your credentials and channel ID
62
+
63
+ # Run CLI commands
64
+ docker compose run --rm chzzk --help
65
+ docker compose run --rm chzzk live info CHANNEL_ID
66
+
67
+ # Watch chat (background service with auto-restart)
68
+ docker compose --profile chat up -d chat-watch
69
+
70
+ # Interactive chat mode
71
+ docker compose --profile interactive run --rm chat-interactive
72
+
73
+ # View logs
74
+ docker compose --profile chat logs -f chat-watch
75
+
76
+ # Stop services
77
+ docker compose --profile chat down
78
+ ```
79
+
56
80
  ## Quick Start
57
81
 
58
82
  ```python
@@ -515,6 +539,8 @@ chzzk chat send CHANNEL_ID -i --offline
515
539
  | `CHZZK_CHAT_OUTPUT_FORMAT` | Default chat output format (jsonl, txt) |
516
540
  | `CHZZK_POLL_INTERVAL` | Live status polling interval in seconds (default: 10) |
517
541
  | `CHZZK_AUTO_RECONNECT` | Enable auto-reconnection (default: true, set "false" to disable) |
542
+ | `CHZZK_TIMEZONE` | Timezone for chat timestamps and log filenames (default: Asia/Seoul) |
543
+ | `TZ` | System timezone for Docker containers (default: Asia/Seoul) |
518
544
 
519
545
  ## Examples
520
546
 
@@ -53,6 +53,30 @@ docker run --rm -it \
53
53
 
54
54
  사용 가능한 태그: `latest`, `X.Y.Z`, `X.Y`, `X`
55
55
 
56
+ ### Docker Compose
57
+
58
+ ```bash
59
+ # 환경 변수 파일 복사 및 설정
60
+ cp .env.example .env
61
+ # .env 파일에 인증 정보와 채널 ID 입력
62
+
63
+ # CLI 명령어 실행
64
+ docker compose run --rm chzzk --help
65
+ docker compose run --rm chzzk live info CHANNEL_ID
66
+
67
+ # 채팅 모니터링 (백그라운드 서비스, 자동 재시작)
68
+ docker compose --profile chat up -d chat-watch
69
+
70
+ # 대화형 채팅 모드
71
+ docker compose --profile interactive run --rm chat-interactive
72
+
73
+ # 로그 확인
74
+ docker compose --profile chat logs -f chat-watch
75
+
76
+ # 서비스 종료
77
+ docker compose --profile chat down
78
+ ```
79
+
56
80
  ## 빠른 시작
57
81
 
58
82
  ```python
@@ -515,6 +539,8 @@ chzzk chat send CHANNEL_ID -i --offline
515
539
  | `CHZZK_CHAT_OUTPUT_FORMAT` | 기본 채팅 출력 형식 (jsonl, txt) |
516
540
  | `CHZZK_POLL_INTERVAL` | 라이브 상태 폴링 간격 (초, 기본값: 10) |
517
541
  | `CHZZK_AUTO_RECONNECT` | 자동 재연결 활성화 (기본값: true, 비활성화: "false") |
542
+ | `CHZZK_TIMEZONE` | 채팅 타임스탬프 및 로그 파일명 시간대 (기본값: Asia/Seoul) |
543
+ | `TZ` | Docker 컨테이너 시스템 시간대 (기본값: Asia/Seoul) |
518
544
 
519
545
  ## 예제 코드
520
546
 
@@ -0,0 +1,140 @@
1
+ # =============================================================================
2
+ # chzzk-python Docker Compose
3
+ # =============================================================================
4
+ #
5
+ # Setup:
6
+ # cp .env.example .env
7
+ # # Edit .env with your CHZZK_CHANNEL_ID_1, CHZZK_CHANNEL_ID_2, ...
8
+ #
9
+ # Usage:
10
+ # # QR 로그인으로 쿠키 저장 (최초 1회)
11
+ # mkdir -p .chzzk .logs && chmod 777 .chzzk .logs
12
+ # docker compose run --rm -v ./.chzzk:/home/chzzk/.chzzk chzzk auth qr
13
+ # # 또는 docker run 사용:
14
+ # docker run --rm -it -v ./.chzzk:/home/chzzk/.chzzk ghcr.io/hypn4/chzzk-python:latest auth qr
15
+ # # 또는 호스트 사용자 권한으로 실행:
16
+ # docker run --rm -it --user $(id -u):$(id -g) -v ./.chzzk:/home/chzzk/.chzzk ghcr.io/hypn4/chzzk-python:latest auth qr
17
+ #
18
+ # # CLI 명령어 실행
19
+ # docker compose run --rm chzzk --help
20
+ # docker compose run --rm chzzk live info CHANNEL_ID
21
+ #
22
+ # # 채팅 모니터링 (백그라운드)
23
+ # # .env에 CHZZK_CHANNEL_ID_1, CHZZK_CHANNEL_ID_2, ... 설정 후:
24
+ # docker compose --profile chat up -d
25
+ # docker compose --profile chat logs -f
26
+ # docker compose --profile chat down
27
+ # # 특정 채널만 실행:
28
+ # docker compose --profile chat up -d chat-watch-1
29
+ #
30
+ # # 대화형 채팅
31
+ # docker compose --profile interactive run --rm chat-interactive
32
+ #
33
+ # =============================================================================
34
+
35
+ # YAML Anchor: 채팅 모니터링 서비스 공통 설정
36
+ x-chat-watch-base: &chat-watch-base
37
+ image: ghcr.io/hypn4/chzzk-python:latest
38
+ restart: unless-stopped
39
+ profiles:
40
+ - chat
41
+ environment:
42
+ - TZ=${TZ:-Asia/Seoul}
43
+ - CHZZK_TIMEZONE=${CHZZK_TIMEZONE:-Asia/Seoul}
44
+ - CHZZK_NID_AUT=${CHZZK_NID_AUT:-}
45
+ - CHZZK_NID_SES=${CHZZK_NID_SES:-}
46
+ - CHZZK_LOG_LEVEL=${CHZZK_LOG_LEVEL:-INFO}
47
+ - CHZZK_POLL_INTERVAL=${CHZZK_POLL_INTERVAL:-10}
48
+ volumes:
49
+ - ./.logs:/home/chzzk/logs
50
+ - ./.chzzk:/home/chzzk/.chzzk
51
+
52
+ services:
53
+ chzzk:
54
+ image: ghcr.io/hypn4/chzzk-python:latest
55
+ container_name: chzzk
56
+ restart: "no"
57
+ stdin_open: true
58
+ tty: true
59
+ environment:
60
+ - TZ=${TZ:-Asia/Seoul}
61
+ - CHZZK_TIMEZONE=${CHZZK_TIMEZONE:-Asia/Seoul}
62
+ - CHZZK_NID_AUT=${CHZZK_NID_AUT:-}
63
+ - CHZZK_NID_SES=${CHZZK_NID_SES:-}
64
+ - CHZZK_LOG_LEVEL=${CHZZK_LOG_LEVEL:-INFO}
65
+ - CHZZK_POLL_INTERVAL=${CHZZK_POLL_INTERVAL:-10}
66
+ - CHZZK_AUTO_RECONNECT=${CHZZK_AUTO_RECONNECT:-true}
67
+ volumes:
68
+ - ./.logs:/home/chzzk/logs
69
+ # Uncomment to persist cookies (run: docker compose run --rm chzzk auth qr)
70
+ # - ./.chzzk:/home/chzzk/.chzzk
71
+ # Override command as needed:
72
+ # command: ["chat", "watch", "CHANNEL_ID"]
73
+ # command: ["chat", "watch", "CHANNEL_ID", "--output-dir", "/home/chzzk/logs"]
74
+ # command: ["live", "info", "CHANNEL_ID"]
75
+
76
+ # Multi-channel chat monitoring (uses x-chat-watch-base anchor)
77
+ # 각 서비스는 CHZZK_CHANNEL_ID_N 환경변수를 사용하여 채널 지정
78
+ chat-watch-1:
79
+ <<: *chat-watch-base
80
+ container_name: chzzk-chat-watch-1
81
+ command:
82
+ - chat
83
+ - watch
84
+ - ${CHZZK_CHANNEL_ID_1:-}
85
+ - --output-dir
86
+ - /home/chzzk/logs
87
+ - --offline
88
+ - --auto-reconnect
89
+
90
+ chat-watch-2:
91
+ <<: *chat-watch-base
92
+ container_name: chzzk-chat-watch-2
93
+ command:
94
+ - chat
95
+ - watch
96
+ - ${CHZZK_CHANNEL_ID_2:-}
97
+ - --output-dir
98
+ - /home/chzzk/logs
99
+ - --offline
100
+ - --auto-reconnect
101
+
102
+ chat-watch-3:
103
+ <<: *chat-watch-base
104
+ container_name: chzzk-chat-watch-3
105
+ command:
106
+ - chat
107
+ - watch
108
+ - ${CHZZK_CHANNEL_ID_3:-}
109
+ - --output-dir
110
+ - /home/chzzk/logs
111
+ - --offline
112
+ - --auto-reconnect
113
+
114
+ # Example: Interactive chat mode
115
+ chat-interactive:
116
+ image: ghcr.io/hypn4/chzzk-python:latest
117
+ container_name: chzzk-chat-interactive
118
+ restart: "no"
119
+ profiles:
120
+ - interactive
121
+ stdin_open: true
122
+ tty: true
123
+ environment:
124
+ - TZ=${TZ:-Asia/Seoul}
125
+ - CHZZK_TIMEZONE=${CHZZK_TIMEZONE:-Asia/Seoul}
126
+ - CHZZK_NID_AUT=${CHZZK_NID_AUT:-}
127
+ - CHZZK_NID_SES=${CHZZK_NID_SES:-}
128
+ - CHZZK_LOG_LEVEL=${CHZZK_LOG_LEVEL:-INFO}
129
+ volumes:
130
+ - ./.logs:/home/chzzk/logs
131
+ # - ./.chzzk:/home/chzzk/.chzzk
132
+ command:
133
+ - chat
134
+ - send
135
+ - ${CHZZK_CHANNEL_ID:-}
136
+ - --interactive
137
+ - --output-dir
138
+ - /home/chzzk/logs
139
+ - --offline
140
+ - --auto-reconnect
@@ -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.12.0'
32
- __version_tuple__ = version_tuple = (0, 12, 0)
31
+ __version__ = version = '0.13.0'
32
+ __version_tuple__ = version_tuple = (0, 13, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -7,7 +7,6 @@ import contextlib
7
7
  import json
8
8
  import logging
9
9
  import signal
10
- from datetime import datetime
11
10
  from pathlib import Path
12
11
  from typing import Annotated
13
12
 
@@ -16,6 +15,7 @@ from prompt_toolkit import PromptSession
16
15
  from prompt_toolkit.patch_stdout import patch_stdout
17
16
  from rich.console import Console
18
17
 
18
+ from chzzk.cli import timezone as tz
19
19
  from chzzk.cli.formatter import ChatFormatter, FormatConfig
20
20
  from chzzk.cli.writers import ChatWriter, OutputFormat, create_writer, generate_chat_log_filename
21
21
  from chzzk.constants import StatusText
@@ -57,7 +57,7 @@ def format_chat_message_json(msg: ChatMessage) -> str:
57
57
  return json.dumps(
58
58
  {
59
59
  "type": "chat",
60
- "timestamp": datetime.now().isoformat(),
60
+ "timestamp": tz.now().isoformat(),
61
61
  "user_id_hash": msg.user_id_hash,
62
62
  "nickname": msg.nickname,
63
63
  "content": msg.content,
@@ -71,7 +71,7 @@ def format_donation_message_json(msg: DonationMessage) -> str:
71
71
  return json.dumps(
72
72
  {
73
73
  "type": "donation",
74
- "timestamp": datetime.now().isoformat(),
74
+ "timestamp": tz.now().isoformat(),
75
75
  "user_id_hash": msg.user_id_hash,
76
76
  "nickname": msg.nickname,
77
77
  "content": msg.content,
@@ -85,7 +85,7 @@ def format_sent_message_json(content: str) -> str:
85
85
  return json.dumps(
86
86
  {
87
87
  "type": "sent",
88
- "timestamp": datetime.now().isoformat(),
88
+ "timestamp": tz.now().isoformat(),
89
89
  "content": content,
90
90
  }
91
91
  )
@@ -3,11 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  from dataclasses import dataclass
6
- from datetime import datetime
7
6
  from typing import TYPE_CHECKING, Any
8
7
 
9
8
  from rich.markup import escape
10
9
 
10
+ from chzzk.cli import timezone as tz
11
+
11
12
  if TYPE_CHECKING:
12
13
  from chzzk.unofficial import ChatMessage, ChatProfile, DonationMessage
13
14
 
@@ -119,7 +120,7 @@ class ChatFormatter:
119
120
 
120
121
  def _get_timestamp(self) -> str:
121
122
  """Get formatted timestamp string."""
122
- return datetime.now().strftime(self.config.time_format)
123
+ return tz.now().strftime(self.config.time_format)
123
124
 
124
125
  def _escape_markup(self, text: str) -> str:
125
126
  """Escape Rich markup characters in text.
@@ -214,7 +215,7 @@ class ChatFormatter:
214
215
  """
215
216
  if not message_time:
216
217
  return ""
217
- dt = datetime.fromtimestamp(message_time / 1000)
218
+ dt = tz.from_timestamp(message_time / 1000)
218
219
  return dt.strftime(self.config.time_format)
219
220
 
220
221
  def _get_pay_type_str(self, pay_type: str | None) -> str:
@@ -0,0 +1,49 @@
1
+ """Timezone utilities for CLI output.
2
+
3
+ This module provides centralized timezone handling for consistent timestamp
4
+ display across different environments (local, Docker, etc.).
5
+
6
+ The timezone can be configured via the CHZZK_TIMEZONE environment variable.
7
+ Default timezone is Asia/Seoul (KST).
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import os
13
+ from datetime import datetime
14
+ from zoneinfo import ZoneInfo
15
+
16
+ DEFAULT_TIMEZONE = "Asia/Seoul"
17
+
18
+
19
+ def get_timezone() -> ZoneInfo:
20
+ """Get the configured timezone.
21
+
22
+ Returns:
23
+ ZoneInfo object for the configured timezone.
24
+ Uses CHZZK_TIMEZONE environment variable if set,
25
+ otherwise defaults to Asia/Seoul.
26
+ """
27
+ tz_name = os.environ.get("CHZZK_TIMEZONE", DEFAULT_TIMEZONE)
28
+ return ZoneInfo(tz_name)
29
+
30
+
31
+ def now() -> datetime:
32
+ """Get current datetime in the configured timezone.
33
+
34
+ Returns:
35
+ Current datetime with timezone info.
36
+ """
37
+ return datetime.now(get_timezone())
38
+
39
+
40
+ def from_timestamp(timestamp: float) -> datetime:
41
+ """Convert Unix timestamp to datetime in the configured timezone.
42
+
43
+ Args:
44
+ timestamp: Unix timestamp (seconds since epoch).
45
+
46
+ Returns:
47
+ Datetime object in the configured timezone.
48
+ """
49
+ return datetime.fromtimestamp(timestamp, get_timezone())
@@ -4,11 +4,12 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  from abc import ABC, abstractmethod
7
- from datetime import datetime
8
7
  from enum import StrEnum
9
8
  from pathlib import Path
10
9
  from typing import TYPE_CHECKING
11
10
 
11
+ from chzzk.cli import timezone as tz
12
+
12
13
  if TYPE_CHECKING:
13
14
  from chzzk.unofficial import ChatMessage, DonationMessage
14
15
 
@@ -98,7 +99,7 @@ class JsonlWriter(ChatWriter):
98
99
 
99
100
  data = {
100
101
  "type": "chat",
101
- "timestamp": datetime.now().isoformat(),
102
+ "timestamp": tz.now().isoformat(),
102
103
  "user_id_hash": msg.user_id_hash,
103
104
  "nickname": msg.nickname,
104
105
  "content": msg.content,
@@ -111,7 +112,7 @@ class JsonlWriter(ChatWriter):
111
112
  """Write a donation message in JSONL format."""
112
113
  data = {
113
114
  "type": "donation",
114
- "timestamp": datetime.now().isoformat(),
115
+ "timestamp": tz.now().isoformat(),
115
116
  "user_id_hash": msg.user_id_hash,
116
117
  "nickname": msg.nickname,
117
118
  "content": msg.content,
@@ -124,7 +125,7 @@ class JsonlWriter(ChatWriter):
124
125
  """Write a sent message in JSONL format."""
125
126
  data = {
126
127
  "type": "sent",
127
- "timestamp": datetime.now().isoformat(),
128
+ "timestamp": tz.now().isoformat(),
128
129
  "content": content,
129
130
  }
130
131
  self._file.write(json.dumps(data, ensure_ascii=False) + "\n")
@@ -150,7 +151,7 @@ class TextWriter(ChatWriter):
150
151
 
151
152
  def write_chat(self, msg: ChatMessage) -> None:
152
153
  """Write a chat message in text format."""
153
- timestamp = datetime.now().strftime("%H:%M:%S")
154
+ timestamp = tz.now().strftime("%H:%M:%S")
154
155
  badge_name = None
155
156
  if msg.profile:
156
157
  # 1. Check profile.badge dict
@@ -175,14 +176,14 @@ class TextWriter(ChatWriter):
175
176
 
176
177
  def write_donation(self, msg: DonationMessage) -> None:
177
178
  """Write a donation message in text format."""
178
- timestamp = datetime.now().strftime("%H:%M:%S")
179
+ timestamp = tz.now().strftime("%H:%M:%S")
179
180
  content = msg.content or ""
180
181
  self._file.write(f"[{timestamp}] {msg.pay_amount}원 {msg.nickname}: {content}\n")
181
182
  self._file.flush()
182
183
 
183
184
  def write_sent(self, content: str) -> None:
184
185
  """Write a sent message in text format."""
185
- timestamp = datetime.now().strftime("%H:%M:%S")
186
+ timestamp = tz.now().strftime("%H:%M:%S")
186
187
  self._file.write(f"[{timestamp}] > {content}\n")
187
188
  self._file.flush()
188
189
 
@@ -219,9 +220,9 @@ def generate_chat_log_filename(
219
220
  # Parse date from open_date string (format: "YYYY-MM-DD HH:MM:SS")
220
221
  date_str = open_date.split()[0].replace("-", "")
221
222
  except (IndexError, ValueError):
222
- date_str = datetime.now().strftime("%Y%m%d")
223
+ date_str = tz.now().strftime("%Y%m%d")
223
224
  else:
224
- date_str = datetime.now().strftime("%Y%m%d")
225
+ date_str = tz.now().strftime("%Y%m%d")
225
226
 
226
227
  # Use live_id if available, otherwise use "offline"
227
228
  live_id_str = str(live_id) if live_id else "offline"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes