# img_bot.py import discord, os, io, re, random, asyncio, logging, requests, replicate, subprocess, base64 from urllib.parse import urljoin, quote_plus from transformers import pipeline as transformers_pipeline from gradio_client import Client # ── 환경 변수 ──────────────────────────────────────────────── TOKEN = os.getenv("DISCORD_TOKEN") CHANNEL_ID = int(os.getenv("DISCORD_CHANNEL_ID")) REPL_TOKEN = (os.getenv("OPENAI_API_KEY") or "").strip() HF_TOKEN = (os.getenv("HF_TOKEN") or "").strip() if not TOKEN or not CHANNEL_ID: raise RuntimeError("DISCORD_TOKEN 과 DISCORD_CHANNEL_ID 환경 변수를 모두 지정하세요.") if not REPL_TOKEN: raise RuntimeError("OPENAI_API_KEY 에 Replicate Personal Access Token 값을 넣어주세요.") os.environ["REPLICATE_API_TOKEN"] = REPL_TOKEN # 구조 유지 (Replicate 미사용) # ── Gradio 서버 ───────────────────────────────────────────── GRADIO_URL = "http://211.233.58.201:7971" GRADIO_API = "/process_and_save_image" # ── 번역 파이프라인 (CPU) ─────────────────────────────────── translator = transformers_pipeline( "translation", model="Helsinki-NLP/opus-mt-ko-en", device=-1, **({"token": HF_TOKEN} if HF_TOKEN else {}) ) async def ko2en_async(text: str) -> str: """한글 포함 시 비동기 영어 번역.""" if not re.search(r"[가-힣]", text): return text loop = asyncio.get_running_loop() try: return await loop.run_in_executor( None, lambda: translator(text, max_length=256, num_beams=1)[0]["translation_text"].strip() ) except Exception as e: logging.warning(f"번역 실패, 원문 사용: {e}") return text # ── 로깅 ──────────────────────────────────────────────────── logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler()]) # ── Discord 인텐트 ────────────────────────────────────────── intents = discord.Intents.default() intents.message_content = True class ImageBot(discord.Client): async def on_ready(self): logging.info(f"Logged in as {self.user} (id={self.user.id})") try: subprocess.Popen(["python", "web.py"]) logging.info("web.py server has been started.") except Exception as e: logging.warning(f"web.py 실행 실패: {e}") async def on_message(self, message: discord.Message): if message.author.id == self.user.id or message.channel.id != CHANNEL_ID: return prompt_raw = message.content.strip() if not prompt_raw: return prompt_en = await ko2en_async(prompt_raw) await message.channel.typing() # ── Gradio 호출 ──────────────────────────────────── def generate_image(): client = Client(GRADIO_URL) return client.predict( height=768, width=768, steps=30, scales=3.5, prompt=prompt_en, seed=random.randint(0, 2**32 - 1), api_name=GRADIO_API ) # ❗ 리스트나 dict·str 그대로 반환 try: img_info = await asyncio.get_running_loop().run_in_executor(None, generate_image) logging.info(f"Gradio result type={type(img_info)} → {img_info}") except Exception as e: logging.error(f"Gradio API error: {e}") await message.reply("⚠️ 이미지 생성 실패!") return # ── Discord 파일 준비 ────────────────────────────── data = None filename = "generated.webp" def download(url: str) -> bytes | None: try: return requests.get(url, timeout=10).content except Exception as err: logging.warning(f"URL 다운로드 실패({url}): {err}") return None # ① `img_info` 가 리스트면 첫 요소 사용 if isinstance(img_info, list) and img_info: img_info = img_info[0] # ② dict if isinstance(img_info, dict): # url 우선 url = img_info.get("url") if url: if url.startswith("data:"): try: data = base64.b64decode(re.sub(r"^data:image/[^;]+;base64,", "", url)) except Exception as e: logging.warning(f"base64 디코딩 실패: {e}") else: if url.startswith("/"): url = urljoin(GRADIO_URL, url) data = download(url) # path 보조 if data is None: path = img_info.get("path") if path: # 원격 Gradio 파일 end-point remote_url = urljoin(GRADIO_URL, f"/gradio_api/file={quote_plus(path)}") data = download(remote_url) or (open(path, "rb").read() if os.path.isfile(path) else None) # ③ str (로컬 경로/URL/data URI) elif isinstance(img_info, str): s = img_info.strip() if s.startswith("data:"): try: data = base64.b64decode(re.sub(r"^data:image/[^;]+;base64,", "", s)) except Exception as e: logging.warning(f"base64 디코딩 실패: {e}") elif s.startswith("http"): data = download(s) else: # 원격 파일로 간주 remote_url = urljoin(GRADIO_URL, f"/gradio_api/file={quote_plus(s)}") data = download(remote_url) or (open(s, "rb").read() if os.path.isfile(s) else None) # ── Discord 전송 ────────────────────────────────── if data: await message.reply(files=[discord.File(io.BytesIO(data), filename=filename)]) else: await message.reply("⚠️ 이미지를 전송할 수 없습니다.") # ── 실행 ──────────────────────────────────────────────────── if __name__ == "__main__": replicate.Client(api_token=REPL_TOKEN) # 구조 유지 ImageBot(intents=intents).run(TOKEN)