fantos's picture
Update app.py
c48780b verified
# img_bot.py
# β€œμ΄λ―Έμ§€ 첨뢀 β†’ μ‹€νŒ¨ μ‹œ URL 좜λ ₯” μ™„κ²°νŒ
import os, io, re, random, asyncio, logging, subprocess, base64
from urllib.parse import urljoin, quote_plus
import discord
import requests
import replicate
from transformers import pipeline as transformers_pipeline
from gradio_client import Client
try:
from PIL import Image # WEBP β†’ PNG λ³€ν™˜
PIL_OK = True
except Exception:
PIL_OK = False # Pillow λ―Έμ„€μΉ˜ μ‹œ λ³€ν™˜ μƒλž΅
# ────────────────── ν™˜κ²½ λ³€μˆ˜ ──────────────────
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"
# ────────────────── λ²ˆμ—­ νŒŒμ΄ν”„λΌμΈ ─────────────
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
)
try:
result = await asyncio.get_running_loop().run_in_executor(None, generate_image)
except Exception as e:
logging.error(f"Gradio API error: {e}")
await message.reply("⚠️ 이미지 생성 μ‹€νŒ¨!")
return
# 리슀트 κ²°κ³Ό β†’ 첫 μš”μ†Œ
if isinstance(result, list):
result = result[0]
# ───────────── 이미지 데이터 확보 ─────────────
data = None
remote_path = None # μ‹€νŒ¨ μ‹œ μ‚¬μš©μžμ—κ²Œ 보여쀄 URL
candidate_urls: list[str] = []
def add_remote(p: str):
url = urljoin(GRADIO_URL, f"/gradio_api/file={quote_plus(p)}")
candidate_urls.append(url)
return url
# dict κ²°κ³Ό
if isinstance(result, dict):
u = result.get("url")
if u:
if u.startswith("/"):
u = urljoin(GRADIO_URL, u)
candidate_urls.append(u)
remote_path = u
p = result.get("path")
if p:
remote_path = add_remote(p)
if os.path.isfile(p):
try:
with open(p, "rb") as f:
data = f.read()
except Exception:
pass
# str κ²°κ³Ό
elif isinstance(result, str):
s = result.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"):
candidate_urls.append(s)
remote_path = s
else:
remote_path = add_remote(s)
if s.startswith("/"):
candidate_urls.append(urljoin(GRADIO_URL, s))
if os.path.isfile(s):
try:
with open(s, "rb") as f:
data = f.read()
except Exception:
pass
# URL λ‹€μš΄λ‘œλ“œ (헀더 λ¬΄μ‹œ, 200 OK 만 확인)
for u in candidate_urls:
if data:
break
try:
r = requests.get(u, timeout=10)
if r.ok and r.content:
data = r.content
break
except Exception as e:
logging.warning(f"URL λ‹€μš΄λ‘œλ“œ μ‹€νŒ¨({u}): {e}")
# ───────────── Discord 전솑 ─────────────
if data:
if PIL_OK: # WEBP β†’ PNG λ³€ν™˜
try:
buf = io.BytesIO()
Image.open(io.BytesIO(data)).convert("RGB").save(buf, format="PNG")
buf.seek(0)
await message.reply(files=[discord.File(buf, filename="image.png")])
return
except Exception as e:
logging.warning(f"PNG λ³€ν™˜ μ‹€νŒ¨: {e}")
# λ³€ν™˜ μ‹€νŒ¨ λ˜λŠ” Pillow μ—†μŒ β†’ 원본 전솑
await message.reply(files=[discord.File(io.BytesIO(data), filename="image.webp")])
return
# ───────────── μ‹€νŒ¨ μ‹œ URL 좜λ ₯ ─────────────
if remote_path:
await message.reply(content=remote_path)
else:
await message.reply("⚠️ 이미지λ₯Ό 전솑할 수 μ—†μŠ΅λ‹ˆλ‹€.")
# ────────────────── μ‹€ν–‰ ────────────────────────
if __name__ == "__main__":
replicate.Client(api_token=REPL_TOKEN) # ꡬ쑰 μœ μ§€
ImageBot(intents=intents).run(TOKEN)