카카오 webhook 응답을 위한 새 이미지 추가
This commit is contained in:
BIN
autouploader/__pycache__/config.cpython-310.pyc
Normal file
BIN
autouploader/__pycache__/config.cpython-310.pyc
Normal file
Binary file not shown.
BIN
autouploader/__pycache__/config.cpython-311.pyc
Normal file
BIN
autouploader/__pycache__/config.cpython-311.pyc
Normal file
Binary file not shown.
BIN
autouploader/__pycache__/weather.cpython-310.pyc
Normal file
BIN
autouploader/__pycache__/weather.cpython-310.pyc
Normal file
Binary file not shown.
@ -1,7 +1,9 @@
|
|||||||
# weather.py
|
|
||||||
import requests
|
import requests
|
||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
|
import sqlite3
|
||||||
|
import os
|
||||||
|
import time
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from config import serviceKey, TODAY
|
from config import serviceKey, TODAY
|
||||||
|
|
||||||
@ -36,6 +38,7 @@ def get_precipitation_summary(retry=True):
|
|||||||
try:
|
try:
|
||||||
data = response.json()
|
data = response.json()
|
||||||
total_rainfall = 0.0
|
total_rainfall = 0.0
|
||||||
|
time_precip_list = []
|
||||||
|
|
||||||
lines = [
|
lines = [
|
||||||
'<div class="weatherinfo" style="max-width: 100%; overflow-x: auto; padding: 10px; box-sizing: border-box;">',
|
'<div class="weatherinfo" style="max-width: 100%; overflow-x: auto; padding: 10px; box-sizing: border-box;">',
|
||||||
@ -58,11 +61,17 @@ def get_precipitation_summary(retry=True):
|
|||||||
time_str = f"{time[:2]}:{time[2:]}"
|
time_str = f"{time[:2]}:{time[2:]}"
|
||||||
lines.append(f'<tr><td style="border: 1px solid #333; padding: 2px;text-align: center;">{time_str}</td><td style="border: 1px solid #333; padding: 2px;text-align: center;">{mm}mm</td></tr>')
|
lines.append(f'<tr><td style="border: 1px solid #333; padding: 2px;text-align: center;">{time_str}</td><td style="border: 1px solid #333; padding: 2px;text-align: center;">{mm}mm</td></tr>')
|
||||||
total_rainfall += mm
|
total_rainfall += mm
|
||||||
|
time_precip_list.append((time_str, mm))
|
||||||
|
|
||||||
lines.append(f'<tr><td colspan="2" style="border: 1px solid #333; padding: 2px;text-align: center; font-weight: bold;">영업시간 중 총 예상 강수량: {total_rainfall:.1f}mm</td></tr>')
|
lines.append(f'<tr><td colspan="2" style="border: 1px solid #333; padding: 2px;text-align: center; font-weight: bold;">영업시간 중 총 예상 강수량: {total_rainfall:.1f}mm</td></tr>')
|
||||||
lines.append('</tbody></table><p style="text-align:right; font-size: 0.8em;">08:00 파주 조리읍 기상청 단기예보 기준</div>')
|
lines.append('</tbody></table><p style="text-align:right; font-size: 0.8em;">08:00 파주 조리읍 기상청 단기예보 기준</div>')
|
||||||
|
|
||||||
return ''.join(lines)
|
html_summary = ''.join(lines)
|
||||||
|
|
||||||
|
# SQLite 저장 함수 호출
|
||||||
|
save_weather_to_sqlite(date=TODAY, time_precip_list=time_precip_list, total_rainfall=total_rainfall)
|
||||||
|
|
||||||
|
return html_summary
|
||||||
|
|
||||||
except json.decoder.JSONDecodeError:
|
except json.decoder.JSONDecodeError:
|
||||||
if retry:
|
if retry:
|
||||||
@ -73,6 +82,45 @@ def get_precipitation_summary(retry=True):
|
|||||||
print("응답이 JSON 형식이 아닙니다.")
|
print("응답이 JSON 형식이 아닙니다.")
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
def save_weather_to_sqlite(date, time_precip_list, total_rainfall):
|
||||||
|
db_path = '/data/weather.sqlite'
|
||||||
|
os.makedirs(os.path.dirname(db_path), exist_ok=True)
|
||||||
|
|
||||||
|
conn = sqlite3.connect(db_path)
|
||||||
|
curs = conn.cursor()
|
||||||
|
|
||||||
|
# 테이블 생성 (없으면)
|
||||||
|
curs.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS precipitation (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT NOT NULL,
|
||||||
|
time TEXT NOT NULL,
|
||||||
|
rainfall REAL NOT NULL
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
curs.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS precipitation_summary (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
date TEXT NOT NULL UNIQUE,
|
||||||
|
total_rainfall REAL NOT NULL
|
||||||
|
)
|
||||||
|
''')
|
||||||
|
|
||||||
|
# 기존 데이터 삭제 (동일 날짜)
|
||||||
|
curs.execute('DELETE FROM precipitation WHERE date = ?', (date,))
|
||||||
|
curs.execute('DELETE FROM precipitation_summary WHERE date = ?', (date,))
|
||||||
|
|
||||||
|
# 데이터 삽입
|
||||||
|
curs.executemany('INSERT INTO precipitation (date, time, rainfall) VALUES (?, ?, ?)',
|
||||||
|
[(date, t, r) for t, r in time_precip_list])
|
||||||
|
|
||||||
|
curs.execute('INSERT INTO precipitation_summary (date, total_rainfall) VALUES (?, ?)', (date, total_rainfall))
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
print(f"[DB 저장 완료] {date} 강수량 데이터 저장됨")
|
||||||
|
|
||||||
# 테스트용
|
# 테스트용
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(get_precipitation_summary())
|
print(get_precipitation_summary())
|
||||||
24
build.yml
24
build.yml
@ -1,10 +1,26 @@
|
|||||||
services:
|
services:
|
||||||
fg-auto:
|
gnu-autouploader:
|
||||||
build:
|
build:
|
||||||
context: ./build
|
context: ./build/autouploader
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
image: reg.firstgarden.co.kr/fg-auto:latest
|
image: reg.firstgarden.co.kr/gnu-autouploader:latest
|
||||||
container_name: fg-auto
|
container_name: gnu-autouploader
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
|
- ./autouploader:/app
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
fg-webhook:
|
||||||
|
build:
|
||||||
|
context: ./build/webhook
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: reg.firstgarden.co.kr/fg-webhook:latest
|
||||||
|
container_name: fg-webhook
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./webhook:/app
|
||||||
|
ports:
|
||||||
|
- 5000:5000
|
||||||
|
#environment:
|
||||||
|
# - DOMAIN=https://webhook.firstgarden.co.kr
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@ -1,54 +0,0 @@
|
|||||||
FROM python:3.10-slim
|
|
||||||
|
|
||||||
ENV LANG=ko_KR.UTF-8 \
|
|
||||||
LANGUAGE=ko_KR:ko \
|
|
||||||
LC_ALL=ko_KR.UTF-8 \
|
|
||||||
TZ=Asia/Seoul \
|
|
||||||
DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
# 필수 패키지 설치
|
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends \
|
|
||||||
locales tzdata bash cron curl unzip wget gnupg ca-certificates \
|
|
||||||
xvfb x11-utils libglib2.0-0 libnss3 libgconf-2-4 libfontconfig1 \
|
|
||||||
libxss1 libxshmfence1 libasound2 libxtst6 libappindicator3-1 \
|
|
||||||
fonts-nanum libu2f-udev \
|
|
||||||
libjpeg-dev zlib1g-dev && \
|
|
||||||
sed -i '/ko_KR.UTF-8/s/^# //g' /etc/locale.gen && \
|
|
||||||
locale-gen && \
|
|
||||||
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
|
|
||||||
ln -sf /usr/bin/python3 /usr/bin/python && \
|
|
||||||
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
# Chrome 설치
|
|
||||||
RUN curl -fsSL https://dl.google.com/linux/linux_signing_key.pub | gpg --dearmor -o /usr/share/keyrings/google-linux-keyring.gpg && \
|
|
||||||
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/google-linux-keyring.gpg] http://dl.google.com/linux/chrome/deb/ stable main" \
|
|
||||||
> /etc/apt/sources.list.d/google-chrome.list && \
|
|
||||||
apt-get update && \
|
|
||||||
apt-get install -y google-chrome-stable && \
|
|
||||||
rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Python 패키지 설치
|
|
||||||
RUN pip install --no-cache-dir \
|
|
||||||
selenium>=4.10 \
|
|
||||||
pymysql \
|
|
||||||
ftputil \
|
|
||||||
pillow \
|
|
||||||
pyvirtualdisplay \
|
|
||||||
requests
|
|
||||||
|
|
||||||
# 작업 디렉토리 설정
|
|
||||||
WORKDIR /data
|
|
||||||
|
|
||||||
# 글꼴 캐시
|
|
||||||
RUN fc-cache -f -v
|
|
||||||
|
|
||||||
# 실행 스크립트 복사 및 실행 권한 부여
|
|
||||||
COPY run.sh /data/run.sh
|
|
||||||
RUN chmod +x /data/run.sh
|
|
||||||
|
|
||||||
# crontab 직접 등록
|
|
||||||
RUN echo "* * * * * /data/run.sh >> /proc/1/fd/1 2>&1" | crontab -
|
|
||||||
|
|
||||||
# 크론 실행
|
|
||||||
CMD ["bash", "-c", "cron -f"]
|
|
||||||
@ -29,14 +29,14 @@ RUN pip install --no-cache-dir \
|
|||||||
requests
|
requests
|
||||||
|
|
||||||
|
|
||||||
WORKDIR /data
|
WORKDIR /app
|
||||||
|
|
||||||
RUN fc-cache -f -v
|
RUN fc-cache -f -v
|
||||||
|
|
||||||
COPY run.sh /data/run.sh
|
COPY run.sh /app/run.sh
|
||||||
RUN chmod +x /data/run.sh
|
RUN chmod +x /app/run.sh
|
||||||
|
|
||||||
# crontab 등록
|
# crontab 등록
|
||||||
RUN echo "0 9 * * * /data/run.sh >> /proc/1/fd/1 2>&1" | crontab -
|
RUN echo "0 9 * * * /app/run.sh >> /proc/1/fd/1 2>&1" | crontab -
|
||||||
|
|
||||||
CMD ["/bin/bash", "-c", "cron -f"]
|
CMD ["/bin/bash", "-c", "cron -f"]
|
||||||
30
build/webhook/Dockerfile
Normal file
30
build/webhook/Dockerfile
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Dockerfile for webhook server (Ubuntu 22.04 + Python Flask)
|
||||||
|
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
|
LANG=ko_KR.UTF-8 \
|
||||||
|
LANGUAGE=ko_KR:ko \
|
||||||
|
LC_ALL=ko_KR.UTF-8
|
||||||
|
|
||||||
|
# 기본 패키지 설치 및 로케일 설정
|
||||||
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
locales tzdata python3 python3-pip curl ca-certificates && \
|
||||||
|
locale-gen ko_KR.UTF-8 && \
|
||||||
|
ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
|
||||||
|
dpkg-reconfigure --frontend noninteractive tzdata && \
|
||||||
|
ln -sf /usr/bin/python3 /usr/bin/python && \
|
||||||
|
apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Flask 설치
|
||||||
|
RUN pip3 install --no-cache-dir flask requests
|
||||||
|
|
||||||
|
# 작업 디렉토리
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 외부 접속 허용 포트
|
||||||
|
EXPOSE 5000
|
||||||
|
|
||||||
|
# Flask 앱 실행
|
||||||
|
CMD ["python3", "webhook.py"]
|
||||||
BIN
data/weather.sqlite
Normal file
BIN
data/weather.sqlite
Normal file
Binary file not shown.
BIN
data/weather_capture_20250630.png
Normal file
BIN
data/weather_capture_20250630.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 128 KiB |
@ -1,7 +1,16 @@
|
|||||||
services:
|
services:
|
||||||
fg-auto:
|
gnu-autouploader:
|
||||||
image: reg.firstgarden.co.kr/fg-auto:latest
|
image: reg.firstgarden.co.kr/gnu-autouploader:latest
|
||||||
container_name: fg-auto
|
container_name: gnu-autouploader
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
|
- ./autouploader:/app
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
fg-webhook:
|
||||||
|
image: reg.firstgarden.co.kr/fg-webhook:latest
|
||||||
|
container_name: fg-webhook
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./webhook:/app
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@ -2,9 +2,9 @@ services:
|
|||||||
fg-auto:
|
fg-auto:
|
||||||
build:
|
build:
|
||||||
context: ./build
|
context: ./build
|
||||||
dockerfile: Dockerfile-slim
|
dockerfile: Dockerfile
|
||||||
image: reg.firstgarden.co.kr/fg-auto:slim
|
image: reg.firstgarden.co.kr/fg-webhook:latest
|
||||||
container_name: fg-auto
|
container_name: fg-webhook
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/data
|
- ./data:/data
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
75
webhook/webhook.py
Normal file
75
webhook/webhook.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import os
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
import sqlite3
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
DB_PATH = '/data/weather.sqlite'
|
||||||
|
|
||||||
|
# 환경변수에서 DOMAIN 읽기, 없으면 기본값 지정 (로컬 테스트용)
|
||||||
|
DOMAIN = os.getenv('DOMAIN', 'http://localhost:5000')
|
||||||
|
|
||||||
|
def get_rain_data(date):
|
||||||
|
conn = sqlite3.connect(DB_PATH)
|
||||||
|
curs = conn.cursor()
|
||||||
|
|
||||||
|
curs.execute('SELECT time, rainfall FROM precipitation WHERE date = ? ORDER BY time', (date,))
|
||||||
|
time_rain_list = curs.fetchall()
|
||||||
|
|
||||||
|
curs.execute('SELECT total_rainfall FROM precipitation_summary WHERE date = ?', (date,))
|
||||||
|
row = curs.fetchone()
|
||||||
|
total_rainfall = row[0] if row else 0.0
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
return time_rain_list, total_rainfall
|
||||||
|
|
||||||
|
@app.route('/webhook', methods=['POST'])
|
||||||
|
def webhook():
|
||||||
|
today = datetime.today().strftime('%Y%m%d')
|
||||||
|
response_text = ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
time_rain_list, total_rainfall = get_rain_data(today)
|
||||||
|
|
||||||
|
if not time_rain_list:
|
||||||
|
response_text = f"{today} 날짜의 강수량 데이터가 없습니다."
|
||||||
|
else:
|
||||||
|
lines = []
|
||||||
|
for time_str, rain in time_rain_list:
|
||||||
|
rain_display = f"{rain}mm" if rain > 0 else "강수 없음"
|
||||||
|
lines.append(f"{time_str} → {rain_display}")
|
||||||
|
|
||||||
|
lines.append(f"\n영업시간 내 총 강수량은 {total_rainfall:.1f}mm 입니다.")
|
||||||
|
response_text = '\n'.join(lines)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
response_text = f"데이터 조회 중 오류가 발생했습니다: {e}"
|
||||||
|
|
||||||
|
image_filename = f"weather_capture_{today}.png"
|
||||||
|
image_path = f"/data/{image_filename}"
|
||||||
|
|
||||||
|
outputs = [{
|
||||||
|
"simpleText": {
|
||||||
|
"text": response_text
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
|
||||||
|
if os.path.isfile(image_path):
|
||||||
|
image_url = f"{DOMAIN}/data/{image_filename}"
|
||||||
|
outputs.append({
|
||||||
|
"image": {
|
||||||
|
"imageUrl": image_url,
|
||||||
|
"altText": "오늘의 날씨 캡처 이미지"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"version": "2.0",
|
||||||
|
"template": {
|
||||||
|
"outputs": outputs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(host='0.0.0.0', port=5000)
|
||||||
Reference in New Issue
Block a user