# -*- coding: utf-8 -*- """ gnu_autoupload.py 기능: 1. Selenium을 이용해 날씨 정보를 캡처 (weather_capture.py 호출) 2. FTP를 이용해 이미지 업로드 3. 그누보드 DB에 게시글 및 첨부파일 정보 자동 등록 """ import os import sys import time import subprocess import tempfile import hashlib from datetime import datetime from PIL import Image import pymysql import ftputil from config import DB_CONFIG, FTP_CONFIG, MAIN # --------------------------- # 이미지 캡처 관련 함수 # --------------------------- def capture_image(script_path, output_path, max_attempts=5): """ weather_capture.py를 실행해 이미지 파일을 생성 :param script_path: 캡처 스크립트 경로 :param output_path: 기대되는 이미지 파일 경로 :param max_attempts: 최대 재시도 횟수 :return: 성공 여부 (True/False) """ for attempt in range(max_attempts): print(f"[{datetime.now().strftime('%H:%M:%S')}] 캡처 시도 {attempt + 1}/{max_attempts}") try: subprocess.run(['python3', script_path], check=True) except subprocess.CalledProcessError as e: print(f"[오류] weather_capture.py 실행 실패: {e}") if os.path.isfile(output_path): print(f"[성공] 이미지 캡처 완료: {output_path}") return True time.sleep(2) print(f"[실패] {max_attempts}회 시도 후에도 이미지가 생성되지 않았습니다.") return False # --------------------------- # 파일 업로드 및 처리 관련 # --------------------------- def file_type(ext): """ 파일 확장자를 기반으로 그누보드용 파일 유형 코드 반환 """ return { 'gif': '1', 'jpeg': '2', 'jpg': '2', 'png': '3', 'swf': '4', 'psd': '5', 'bmp': '6', 'tif': '7', 'tiff': '7', 'jpc': '9', 'jp2': '10', 'jpx': '11', 'jb2': '12', 'swc': '13', 'iff': '14', 'wbmp': '15', 'xbm': '16' }.get(ext.lower(), '0') def get_filename(filename): """ 원본 파일명을 기반으로 해시된 파일명을 생성 """ ms = datetime.now().microsecond encoded_name = filename.encode('utf-8') return f'{ms}_{hashlib.sha1(encoded_name).hexdigest()}' def file_upload(filename, bf_file): """ FTP 서버에 파일 업로드 :param filename: 로컬 경로 :param bf_file: 서버에 저장될 파일명 """ try: with ftputil.FTPHost(FTP_CONFIG['HOST'], FTP_CONFIG['USER'], FTP_CONFIG['PASS']) as fh: fh.chdir(FTP_CONFIG['UPLOAD_DIR']) fh.upload(filename, bf_file) print(f"[업로드 완료] {filename} → {bf_file}") return True except Exception as e: print(f"[FTP 오류] {type(e).__name__}: {e}") return False # --------------------------- # 게시글 작성 함수 # --------------------------- def write_board(board, subject, content, mb_id, nickname, ca_name=None, file_list=None): """ 그누보드 DB에 게시글과 파일을 한 트랜잭션으로 삽입 :param board: 게시판 ID :param subject: 제목 :param content: 내용 :param mb_id: 회원 ID :param nickname: 작성자 이름 :param ca_name: 카테고리명 :param file_list: 첨부파일 경로 리스트 """ try: # 데이터베이스 연결 conn = pymysql.connect( host=DB_CONFIG['HOST'], user=DB_CONFIG['USER'], db=DB_CONFIG['DBNAME'], password=DB_CONFIG['PASS'], charset='utf8' ) curs = conn.cursor() now = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # 게시글 번호(wr_num) 계산 curs.execute(f"SELECT wr_num FROM g5_write_{board}") wr_num = str(int(curs.fetchone()[0]) - 1) # 게시글 삽입 curs.execute(f"""INSERT INTO g5_write_{board} SET wr_num = {wr_num}, wr_reply = '', wr_comment = 0, ca_name = %s, wr_option = 'html1', wr_subject = %s, wr_content = %s, wr_link1 = '', wr_link2 = '', wr_link1_hit = 0, wr_link2_hit = 0, wr_hit = 1, wr_good = 0, wr_nogood = 0, mb_id = %s, wr_password = '', wr_name = %s, wr_email = '', wr_homepage = '', wr_datetime = %s, wr_last = %s, wr_ip = '127.0.0.1'""", (ca_name, subject, content, mb_id, nickname, now, now)) # wr_id 조회 curs.execute(f"SELECT wr_id FROM g5_write_{board} ORDER BY wr_id DESC LIMIT 1") wr_id = str(curs.fetchone()[0]) # wr_parent 업데이트 curs.execute(f"UPDATE g5_write_{board} SET wr_parent = {wr_id} WHERE wr_id = {wr_id}") # 새글 테이블 등록 curs.execute(f"""INSERT INTO g5_board_new (bo_table, wr_id, wr_parent, bn_datetime, mb_id) VALUES (%s, %s, %s, %s, %s)""", (board, wr_id, wr_id, now, mb_id)) # 게시글 수 증가 curs.execute(f"SELECT bo_count_write FROM g5_board WHERE bo_table = %s", (board,)) bo_count_write = int(curs.fetchone()[0]) curs.execute(f"UPDATE g5_board SET bo_count_write = %s WHERE bo_table = %s", (bo_count_write + 1, board)) # 파일 업로드 처리 file_count = 0 if file_list: for idx, file in enumerate(file_list): ext = os.path.splitext(file)[1].lstrip('.') bf_file = f"{get_filename(file)}.{ext}" if file_upload(file, bf_file): img_type = file_type(ext) width, height = (0, 0) if img_type != '0': with Image.open(file) as img: width, height = img.size size = os.path.getsize(file) curs.execute(f"""INSERT INTO g5_board_file SET bo_table = %s, wr_id = %s, bf_no = %s, bf_source = %s, bf_file = %s, bf_content = '', bf_download = 0, bf_filesize = %s, bf_width = %s, bf_height = %s, bf_type = %s, bf_datetime = %s""", (board, wr_id, idx, os.path.basename(file), bf_file, size, width, height, img_type, now)) file_count += 1 else: print(f"[경고] 파일 업로드 실패: {file}") # 첨부파일 수 업데이트 curs.execute(f"UPDATE g5_write_{board} SET wr_file = %s WHERE wr_id = %s", (file_count, wr_id)) # 트랜잭션 커밋 (단 1회) conn.commit() print("[완료] 게시글 작성 및 파일 업로드 완료") except Exception as e: # 예외 발생 시 롤백 conn.rollback() print(f"[DB 오류] {type(e).__name__}: {e}") finally: # 연결 종료 conn.close() # --------------------------- # 메인 실행 함수 # --------------------------- def main(): # 파일 경로 및 이름 설정 today = datetime.today().strftime('%Y%m%d') script_dir = os.path.dirname(os.path.abspath(__file__)) capture_script = os.path.join(script_dir, 'weather_capture.py') weather_filename = f'weather_capture_{today}.png' weather_file = os.path.join(script_dir, weather_filename) # 이미지 생성 시도 if not capture_image(capture_script, weather_file): return # 설정값 동적 갱신 FTP_CONFIG['UPLOAD_DIR'] = f"/www/data/file/{MAIN['board']}/" MAIN['subject'] = f"[{datetime.now().strftime('%Y-%m-%d')}] 날씨 정보" MAIN['file1'] = MAIN['file2'] = weather_file file_list = [MAIN['file1']] # 게시글 작성 write_board( board=MAIN['board'], subject=MAIN['subject'], content=MAIN['content'], mb_id=MAIN['mb_id'], nickname=MAIN['nickname'], ca_name=MAIN['ca_name'], file_list=file_list ) # 캡처 파일 삭제 try: if os.path.isfile(weather_file): os.remove(weather_file) print(f"[삭제 완료] {weather_file}") except Exception as e: print(f"[삭제 오류] {type(e).__name__}: {e}") # --------------------------- # 스크립트 실행 진입점 # --------------------------- if __name__ == "__main__": main()