chzzk-python 0.11.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.13.0/.dockerignore +26 -0
  2. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/.env.example +18 -0
  3. chzzk_python-0.13.0/.github/workflows/docker.yml +160 -0
  4. chzzk_python-0.13.0/Dockerfile +54 -0
  5. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/PKG-INFO +53 -1
  6. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/README.md +52 -0
  7. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/README_KO.md +53 -1
  8. chzzk_python-0.13.0/docker-compose.yml +140 -0
  9. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/_version.py +2 -2
  10. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/chat.py +4 -4
  11. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/formatter.py +4 -3
  12. chzzk_python-0.13.0/src/chzzk/cli/timezone.py +49 -0
  13. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/writers.py +10 -9
  14. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/.github/workflows/build.yml +0 -0
  15. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/.github/workflows/ci.yml +0 -0
  16. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/.github/workflows/publish.yml +0 -0
  17. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/.gitignore +0 -0
  18. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/.python-version +0 -0
  19. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/LICENSE +0 -0
  20. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/chzzk.spec +0 -0
  21. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/docs/unofficial-chat-websocket-protocol.md +0 -0
  22. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/examples/.env.example +0 -0
  23. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/examples/oauth_server.py +0 -0
  24. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/examples/realtime_chat.py +0 -0
  25. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/examples/realtime_chat_async.py +0 -0
  26. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/examples/session_management.py +0 -0
  27. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/examples/unofficial_chat.py +0 -0
  28. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/examples/unofficial_chat_async.py +0 -0
  29. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/main.py +0 -0
  30. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/pyproject.toml +0 -0
  31. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/scripts/build.py +0 -0
  32. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/__init__.py +0 -0
  33. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/__init__.py +0 -0
  34. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/base.py +0 -0
  35. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/category.py +0 -0
  36. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/channel.py +0 -0
  37. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/chat.py +0 -0
  38. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/live.py +0 -0
  39. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/restriction.py +0 -0
  40. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/session.py +0 -0
  41. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/api/user.py +0 -0
  42. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/auth/__init__.py +0 -0
  43. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/auth/models.py +0 -0
  44. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/auth/oauth.py +0 -0
  45. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/auth/token.py +0 -0
  46. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/__init__.py +0 -0
  47. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/__init__.py +0 -0
  48. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/auth.py +0 -0
  49. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/commands/live.py +0 -0
  50. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/config.py +0 -0
  51. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/logging.py +0 -0
  52. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/cli/main.py +0 -0
  53. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/client.py +0 -0
  54. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/constants.py +0 -0
  55. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/exceptions/__init__.py +0 -0
  56. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/exceptions/errors.py +0 -0
  57. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/http/__init__.py +0 -0
  58. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/http/_base.py +0 -0
  59. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/http/client.py +0 -0
  60. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/http/endpoints.py +0 -0
  61. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/logging.py +0 -0
  62. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/__init__.py +0 -0
  63. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/category.py +0 -0
  64. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/channel.py +0 -0
  65. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/chat.py +0 -0
  66. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/common.py +0 -0
  67. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/live.py +0 -0
  68. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/restriction.py +0 -0
  69. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/session.py +0 -0
  70. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/models/user.py +0 -0
  71. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/py.typed +0 -0
  72. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/realtime/__init__.py +0 -0
  73. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/realtime/client.py +0 -0
  74. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/__init__.py +0 -0
  75. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/__init__.py +0 -0
  76. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/base.py +0 -0
  77. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/chat.py +0 -0
  78. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/live.py +0 -0
  79. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/api/user.py +0 -0
  80. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/auth/__init__.py +0 -0
  81. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/auth/cookie.py +0 -0
  82. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/__init__.py +0 -0
  83. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/client.py +0 -0
  84. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/connection.py +0 -0
  85. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/handler.py +0 -0
  86. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/chat/monitor.py +0 -0
  87. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/client.py +0 -0
  88. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/__init__.py +0 -0
  89. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/_base.py +0 -0
  90. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/client.py +0 -0
  91. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/http/endpoints.py +0 -0
  92. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/__init__.py +0 -0
  93. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/chat.py +0 -0
  94. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/live.py +0 -0
  95. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/reconnect.py +0 -0
  96. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/src/chzzk/unofficial/models/user.py +0 -0
  97. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/__init__.py +0 -0
  98. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/__init__.py +0 -0
  99. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/test_category.py +0 -0
  100. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/test_channel.py +0 -0
  101. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/test_chat.py +0 -0
  102. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/test_live.py +0 -0
  103. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/test_restriction.py +0 -0
  104. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/test_session.py +0 -0
  105. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/api/test_user.py +0 -0
  106. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/auth/__init__.py +0 -0
  107. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/auth/test_oauth.py +0 -0
  108. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/cli/__init__.py +0 -0
  109. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/cli/test_formatter.py +0 -0
  110. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/cli/test_writers.py +0 -0
  111. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/realtime/__init__.py +0 -0
  112. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/realtime/test_client.py +0 -0
  113. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/test_client.py +0 -0
  114. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/unofficial/__init__.py +0 -0
  115. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/unofficial/test_client.py +0 -0
  116. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/tests/unofficial/test_monitor.py +0 -0
  117. {chzzk_python-0.11.0 → chzzk_python-0.13.0}/uv.lock +0 -0
@@ -0,0 +1,26 @@
1
+ .git
2
+ .gitignore
3
+ __pycache__
4
+ *.py[cod]
5
+ .venv
6
+ venv/
7
+ .pytest_cache/
8
+ .ruff_cache/
9
+ .mypy_cache/
10
+ dist/
11
+ build/
12
+ *.egg-info/
13
+ .env
14
+ .env.*
15
+ *.token.json
16
+ .chzzk_token.json
17
+ *.log
18
+ *.jsonl
19
+ docs/
20
+ .github/
21
+ .mcp.json
22
+ CLAUDE.md
23
+ .claude/
24
+ chzzk.spec
25
+ tests/
26
+ examples/
@@ -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
@@ -0,0 +1,160 @@
1
+ name: Docker Build and Push
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+ workflow_dispatch:
7
+ inputs:
8
+ push:
9
+ description: "Push image to registry"
10
+ required: false
11
+ default: false
12
+ type: boolean
13
+
14
+ env:
15
+ REGISTRY: ghcr.io
16
+ IMAGE_NAME: ${{ github.repository }}
17
+
18
+ jobs:
19
+ ci:
20
+ uses: ./.github/workflows/ci.yml
21
+
22
+ # Stage 1: 각 아키텍처를 네이티브 러너에서 병렬 빌드
23
+ build:
24
+ needs: ci
25
+ strategy:
26
+ fail-fast: false
27
+ matrix:
28
+ include:
29
+ - platform: linux/amd64
30
+ runner: ubuntu-latest
31
+ - platform: linux/arm64
32
+ runner: ubuntu-24.04-arm # Native ARM runner (QEMU 없음!)
33
+ runs-on: ${{ matrix.runner }}
34
+ permissions:
35
+ contents: read
36
+ packages: write
37
+
38
+ steps:
39
+ - name: Checkout repository
40
+ uses: actions/checkout@v4
41
+ with:
42
+ fetch-depth: 0
43
+
44
+ - name: Set up Docker Buildx
45
+ uses: docker/setup-buildx-action@v3
46
+
47
+ - name: Log in to GitHub Container Registry
48
+ uses: docker/login-action@v3
49
+ with:
50
+ registry: ${{ env.REGISTRY }}
51
+ username: ${{ github.actor }}
52
+ password: ${{ secrets.GITHUB_TOKEN }}
53
+
54
+ - name: Extract metadata
55
+ id: meta
56
+ uses: docker/metadata-action@v5
57
+ with:
58
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
59
+
60
+ - name: Get version from tag
61
+ id: version
62
+ run: |
63
+ if [[ "${{ github.ref_type }}" == "tag" ]]; then
64
+ VERSION="${{ github.ref_name }}"
65
+ VERSION="${VERSION#v}" # Remove 'v' prefix
66
+ else
67
+ VERSION="0.0.0+$(git rev-parse --short HEAD)"
68
+ fi
69
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
70
+
71
+ - name: Build and push by digest
72
+ id: build
73
+ uses: docker/build-push-action@v6
74
+ with:
75
+ context: .
76
+ platforms: ${{ matrix.platform }}
77
+ labels: ${{ steps.meta.outputs.labels }}
78
+ build-args: |
79
+ VERSION=${{ steps.version.outputs.version }}
80
+ outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
81
+ cache-from: type=gha,scope=${{ matrix.platform }}
82
+ cache-to: type=gha,scope=${{ matrix.platform }},mode=max
83
+
84
+ - name: Export digest
85
+ run: |
86
+ mkdir -p /tmp/digests
87
+ digest="${{ steps.build.outputs.digest }}"
88
+ touch "/tmp/digests/${digest#sha256:}"
89
+
90
+ - name: Upload digest
91
+ uses: actions/upload-artifact@v4
92
+ with:
93
+ name: digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
94
+ path: /tmp/digests/*
95
+ if-no-files-found: error
96
+ retention-days: 1
97
+
98
+ # Stage 2: Multi-arch manifest 병합
99
+ merge:
100
+ needs: build
101
+ runs-on: ubuntu-latest
102
+ permissions:
103
+ contents: read
104
+ packages: write
105
+ attestations: write
106
+ id-token: write
107
+
108
+ steps:
109
+ - name: Download digests
110
+ uses: actions/download-artifact@v4
111
+ with:
112
+ path: /tmp/digests
113
+ pattern: digests-*
114
+ merge-multiple: true
115
+
116
+ - name: Set up Docker Buildx
117
+ uses: docker/setup-buildx-action@v3
118
+
119
+ - name: Log in to GitHub Container Registry
120
+ uses: docker/login-action@v3
121
+ with:
122
+ registry: ${{ env.REGISTRY }}
123
+ username: ${{ github.actor }}
124
+ password: ${{ secrets.GITHUB_TOKEN }}
125
+
126
+ - name: Extract metadata
127
+ id: meta
128
+ uses: docker/metadata-action@v5
129
+ with:
130
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
131
+ tags: |
132
+ type=semver,pattern={{version}}
133
+ type=semver,pattern={{major}}.{{minor}}
134
+ type=semver,pattern={{major}}
135
+ type=raw,value=latest,enable={{is_default_branch}}
136
+ type=sha,prefix=sha-
137
+ labels: |
138
+ org.opencontainers.image.title=chzzk-python
139
+ org.opencontainers.image.description=Unofficial Python SDK and CLI for Chzzk
140
+ org.opencontainers.image.vendor=hypn4
141
+
142
+ - name: Create manifest list and push
143
+ working-directory: /tmp/digests
144
+ run: |
145
+ docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
146
+ $(printf '${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@sha256:%s ' *)
147
+
148
+ - name: Get image digest
149
+ id: digest
150
+ run: |
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"
154
+
155
+ - name: Generate artifact attestation
156
+ uses: actions/attest-build-provenance@v2
157
+ with:
158
+ subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
159
+ subject-digest: ${{ steps.digest.outputs.digest }}
160
+ push-to-registry: true
@@ -0,0 +1,54 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ # =============================================================================
4
+ # Stage 1: Builder - uv로 의존성 설치
5
+ # =============================================================================
6
+ FROM ghcr.io/astral-sh/uv:python3.14-bookworm-slim AS builder
7
+
8
+ # 버전 정보 (hatch-vcs용 - .git 없이 빌드 지원)
9
+ ARG VERSION=0.0.0
10
+ ENV SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION}
11
+
12
+ ENV UV_COMPILE_BYTECODE=1 \
13
+ UV_LINK_MODE=copy \
14
+ UV_PYTHON_DOWNLOADS=0
15
+
16
+ WORKDIR /app
17
+
18
+ # 의존성 먼저 설치 (레이어 캐싱 최적화)
19
+ RUN --mount=type=cache,target=/root/.cache/uv \
20
+ --mount=type=bind,source=uv.lock,target=uv.lock \
21
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
22
+ uv sync --locked --no-install-project --no-dev --all-extras
23
+
24
+ # 소스 코드 복사 및 프로젝트 설치
25
+ COPY src/ /app/src/
26
+ COPY pyproject.toml uv.lock README.md LICENSE /app/
27
+
28
+ RUN --mount=type=cache,target=/root/.cache/uv \
29
+ uv sync --locked --no-dev --all-extras --no-editable
30
+
31
+ # =============================================================================
32
+ # Stage 2: Runtime - 최소 프로덕션 이미지
33
+ # =============================================================================
34
+ FROM python:3.14-slim-bookworm AS runtime
35
+
36
+ # 보안: non-root 사용자
37
+ RUN groupadd --gid 1000 chzzk && \
38
+ useradd --uid 1000 --gid 1000 --shell /bin/bash --create-home chzzk
39
+
40
+ # 가상환경만 복사
41
+ COPY --from=builder --chown=chzzk:chzzk /app/.venv /app/.venv
42
+
43
+ ENV PATH="/app/.venv/bin:$PATH" \
44
+ PYTHONUNBUFFERED=1 \
45
+ PYTHONDONTWRITEBYTECODE=1
46
+
47
+ USER chzzk
48
+ WORKDIR /home/chzzk
49
+
50
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
51
+ CMD chzzk --help || exit 1
52
+
53
+ ENTRYPOINT ["chzzk"]
54
+ CMD ["--help"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: chzzk-python
3
- Version: 0.11.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
@@ -39,6 +39,7 @@ Description-Content-Type: text/markdown
39
39
 
40
40
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
41
41
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
42
+ [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue.svg)](https://ghcr.io/hypn4/chzzk-python)
42
43
 
43
44
  Unofficial Python SDK for Chzzk (NAVER Live Streaming Platform) API
44
45
 
@@ -64,6 +65,55 @@ uv add "chzzk-python[cli]"
64
65
  pip install "chzzk-python[cli]"
65
66
  ```
66
67
 
68
+ ### Docker
69
+
70
+ ```bash
71
+ # Pull from GitHub Container Registry
72
+ docker pull ghcr.io/hypn4/chzzk-python:latest
73
+
74
+ # Run CLI commands
75
+ docker run --rm ghcr.io/hypn4/chzzk-python --help
76
+ docker run --rm ghcr.io/hypn4/chzzk-python auth --help
77
+
78
+ # Watch chat (with environment variables)
79
+ docker run --rm -it \
80
+ -e CHZZK_NID_AUT="your-nid-aut" \
81
+ -e CHZZK_NID_SES="your-nid-ses" \
82
+ ghcr.io/hypn4/chzzk-python chat watch CHANNEL_ID
83
+
84
+ # Interactive chat (requires -it flags)
85
+ docker run --rm -it \
86
+ -e CHZZK_NID_AUT="your-nid-aut" \
87
+ -e CHZZK_NID_SES="your-nid-ses" \
88
+ ghcr.io/hypn4/chzzk-python chat send CHANNEL_ID -i
89
+ ```
90
+
91
+ Available tags: `latest`, `X.Y.Z`, `X.Y`, `X`
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
+
67
117
  ## Quick Start
68
118
 
69
119
  ```python
@@ -526,6 +576,8 @@ chzzk chat send CHANNEL_ID -i --offline
526
576
  | `CHZZK_CHAT_OUTPUT_FORMAT` | Default chat output format (jsonl, txt) |
527
577
  | `CHZZK_POLL_INTERVAL` | Live status polling interval in seconds (default: 10) |
528
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) |
529
581
 
530
582
  ## Examples
531
583
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue.svg)](https://ghcr.io/hypn4/chzzk-python)
5
6
 
6
7
  Unofficial Python SDK for Chzzk (NAVER Live Streaming Platform) API
7
8
 
@@ -27,6 +28,55 @@ uv add "chzzk-python[cli]"
27
28
  pip install "chzzk-python[cli]"
28
29
  ```
29
30
 
31
+ ### Docker
32
+
33
+ ```bash
34
+ # Pull from GitHub Container Registry
35
+ docker pull ghcr.io/hypn4/chzzk-python:latest
36
+
37
+ # Run CLI commands
38
+ docker run --rm ghcr.io/hypn4/chzzk-python --help
39
+ docker run --rm ghcr.io/hypn4/chzzk-python auth --help
40
+
41
+ # Watch chat (with environment variables)
42
+ docker run --rm -it \
43
+ -e CHZZK_NID_AUT="your-nid-aut" \
44
+ -e CHZZK_NID_SES="your-nid-ses" \
45
+ ghcr.io/hypn4/chzzk-python chat watch CHANNEL_ID
46
+
47
+ # Interactive chat (requires -it flags)
48
+ docker run --rm -it \
49
+ -e CHZZK_NID_AUT="your-nid-aut" \
50
+ -e CHZZK_NID_SES="your-nid-ses" \
51
+ ghcr.io/hypn4/chzzk-python chat send CHANNEL_ID -i
52
+ ```
53
+
54
+ Available tags: `latest`, `X.Y.Z`, `X.Y`, `X`
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
+
30
80
  ## Quick Start
31
81
 
32
82
  ```python
@@ -489,6 +539,8 @@ chzzk chat send CHANNEL_ID -i --offline
489
539
  | `CHZZK_CHAT_OUTPUT_FORMAT` | Default chat output format (jsonl, txt) |
490
540
  | `CHZZK_POLL_INTERVAL` | Live status polling interval in seconds (default: 10) |
491
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) |
492
544
 
493
545
  ## Examples
494
546
 
@@ -1,7 +1,8 @@
1
1
  # chzzk-python
2
2
 
3
- [![Python 3.14+](https://img.shields.io/badge/python-3.14+-blue.svg)](https://www.python.org/downloads/)
3
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
4
4
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Docker](https://img.shields.io/badge/docker-ghcr.io-blue.svg)](https://ghcr.io/hypn4/chzzk-python)
5
6
 
6
7
  치지직(CHZZK) 스트리밍 플랫폼을 위한 비공식 Python SDK
7
8
 
@@ -27,6 +28,55 @@ uv add "chzzk-python[cli]"
27
28
  pip install "chzzk-python[cli]"
28
29
  ```
29
30
 
31
+ ### Docker
32
+
33
+ ```bash
34
+ # GitHub Container Registry에서 Pull
35
+ docker pull ghcr.io/hypn4/chzzk-python:latest
36
+
37
+ # CLI 명령어 실행
38
+ docker run --rm ghcr.io/hypn4/chzzk-python --help
39
+ docker run --rm ghcr.io/hypn4/chzzk-python auth --help
40
+
41
+ # 채팅 보기 (환경 변수 사용)
42
+ docker run --rm -it \
43
+ -e CHZZK_NID_AUT="your-nid-aut" \
44
+ -e CHZZK_NID_SES="your-nid-ses" \
45
+ ghcr.io/hypn4/chzzk-python chat watch CHANNEL_ID
46
+
47
+ # 대화형 채팅 (-it 플래그 필요)
48
+ docker run --rm -it \
49
+ -e CHZZK_NID_AUT="your-nid-aut" \
50
+ -e CHZZK_NID_SES="your-nid-ses" \
51
+ ghcr.io/hypn4/chzzk-python chat send CHANNEL_ID -i
52
+ ```
53
+
54
+ 사용 가능한 태그: `latest`, `X.Y.Z`, `X.Y`, `X`
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
+
30
80
  ## 빠른 시작
31
81
 
32
82
  ```python
@@ -489,6 +539,8 @@ chzzk chat send CHANNEL_ID -i --offline
489
539
  | `CHZZK_CHAT_OUTPUT_FORMAT` | 기본 채팅 출력 형식 (jsonl, txt) |
490
540
  | `CHZZK_POLL_INTERVAL` | 라이브 상태 폴링 간격 (초, 기본값: 10) |
491
541
  | `CHZZK_AUTO_RECONNECT` | 자동 재연결 활성화 (기본값: true, 비활성화: "false") |
542
+ | `CHZZK_TIMEZONE` | 채팅 타임스탬프 및 로그 파일명 시간대 (기본값: Asia/Seoul) |
543
+ | `TZ` | Docker 컨테이너 시스템 시간대 (기본값: Asia/Seoul) |
492
544
 
493
545
  ## 예제 코드
494
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.11.0'
32
- __version_tuple__ = version_tuple = (0, 11, 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