discordApiUse / app.py
dedlepexa's picture
Update app.py
03080d7 verified
Raw
History Blame Contribute Delete
7.91 kB
from fastapi import FastAPI
from fastapi.responses import StreamingResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from PIL import Image
import requests
from io import BytesIO
import urllib3
import json
import base64
import os
import uuid
import time
from pydantic import BaseModel
from typing import Optional
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
app = FastAPI()
# =========================
# CONFIG
# =========================
BOT_TOKEN = "MTUwMTk1OTI1Njk4MTc2NjE4NA.Gcshyg.YHzk_9k39AVEgLq2IHXtKeLHYMl6AT7GbnUpwE"
CHANNEL_ID = "1470082028518113342"
MINESKIN_TOKEN = "msk_SBLac6yF_ApAeLAIAUGjlX9FQkiZ2itwwSmuzQ3sBM8KryyVHcD1HrW4KqPCcRGgEfIFPAVSY"
# Создаем папку для сохранения скинов
os.makedirs("skins", exist_ok=True)
# Монтируем статические файлы
app.mount("/skins", StaticFiles(directory="skins"), name="skins")
# =========================
# MODEL
# =========================
class SkinRequest(BaseModel):
Playername: str
HeadUrl: str
BodyUrl: str
ArmLeft: Optional[str] = None
ArmRight: Optional[str] = None
LegLeft: Optional[str] = None
LegRight: Optional[str] = None
# =========================
# DOWNLOAD SKIN
# =========================
def download_skin(url: str):
headers = {"User-Agent": "Mozilla/5.0"}
r = requests.get(url, headers=headers, timeout=30, verify=False)
r.raise_for_status()
return Image.open(BytesIO(r.content)).convert("RGBA")
# =========================
# PARSE SKIN INPUT
# =========================
def parse_skin_input(value: str):
if not value:
return None
if value.startswith("http"):
return value
try:
decoded = base64.b64decode(value).decode("utf-8")
data = json.loads(decoded)
return data["textures"]["SKIN"]["url"]
except:
return value
# =========================
# MINECRAFT SKIN PARTS (ИЗ ПЕРВОГО КОДА)
# =========================
HEAD = [
(0, 0, 32, 16),
(32, 0, 64, 16)
]
BODY = [
(16, 16, 40, 32),
(16, 32, 40, 48)
]
RIGHT_ARM = [
(40, 16, 56, 32),
(40, 32, 56, 48)
]
LEFT_ARM = [
(32, 48, 48, 64),
(48, 48, 64, 64)
]
RIGHT_LEG = [
(0, 16, 16, 32),
(0, 32, 16, 48)
]
LEFT_LEG = [
(16, 48, 32, 64),
(0, 48, 16, 64)
]
# =========================
# CLEAR AREA (ИЗ ПЕРВОГО КОДА)
# =========================
def clear_area(img, box):
x1, y1, x2, y2 = box
empty = Image.new("RGBA", (x2 - x1, y2 - y1), (0, 0, 0, 0))
img.paste(empty, (x1, y1))
# =========================
# COPY PART (ИЗ ПЕРВОГО КОДА)
# =========================
def copy(img_from, img_to, box):
x1, y1, x2, y2 = box
part = img_from.crop((x1, y1, x2, y2))
img_to.paste(part, (x1, y1), part)
# =========================
# BUILD SKIN (ИЗ ПЕРВОГО КОДА)
# =========================
def create_merged_skin(req: SkinRequest):
body = download_skin(parse_skin_input(req.BodyUrl))
head = download_skin(parse_skin_input(req.HeadUrl))
arm_l = download_skin(parse_skin_input(req.ArmLeft)) if req.ArmLeft else None
arm_r = download_skin(parse_skin_input(req.ArmRight)) if req.ArmRight else None
leg_l = download_skin(parse_skin_input(req.LegLeft)) if req.LegLeft else None
leg_r = download_skin(parse_skin_input(req.LegRight)) if req.LegRight else None
# CLEAN BASE
result = Image.new("RGBA", (64, 64), (0, 0, 0, 0))
# BODY FIRST
for b in BODY:
copy(body, result, b)
# HEAD
for b in HEAD:
copy(head, result, b)
# ARMS (optional override)
if arm_r:
for b in RIGHT_ARM:
clear_area(result, b)
copy(arm_r, result, b)
if arm_l:
for b in LEFT_ARM:
clear_area(result, b)
copy(arm_l, result, b)
# LEGS
if leg_r:
for b in RIGHT_LEG:
clear_area(result, b)
copy(leg_r, result, b)
if leg_l:
for b in LEFT_LEG:
clear_area(result, b)
copy(leg_l, result, b)
return result
# =========================
# SAVE SKIN
# =========================
def save_skin(image, playername: str):
safe_name = "".join(c for c in playername if c.isalnum() or c in ('_', '-'))
filename = f"{safe_name}_{uuid.uuid4().hex[:8]}_{int(time.time())}.png"
filepath = os.path.join("skins", filename)
image.save(filepath, format="PNG")
space_url = os.getenv("SPACE_HOST", "dedlepexa-discordapiuse.hf.space")
public_url = f"https://{space_url}/skins/{filename}"
return public_url, filename
# =========================
# ENDPOINTS (ИЗ ВТОРОГО КОДА)
# =========================
@app.post("/merge-to-link")
def merge_to_link(request: SkinRequest):
try:
print(f"🎮 Игрок: {request.Playername}")
merged_skin = create_merged_skin(request)
public_url, filename = save_skin(merged_skin, request.Playername)
print(f"🔗 URL: {public_url}")
return {
"success": True,
"Playername": request.Playername,
"url": public_url,
"filename": filename
}
except Exception as e:
print(f"❌ Ошибка: {str(e)}")
return {
"success": False,
"error": str(e)
}
@app.get("/merge.png")
def merge_png(head_skin: str, body_skin: str):
req = SkinRequest(Playername="test", HeadUrl=head_skin, BodyUrl=body_skin)
img = create_merged_skin(req)
buf = BytesIO()
img.save(buf, "PNG")
buf.seek(0)
return StreamingResponse(buf, media_type="image/png")
@app.api_route("/skins/{file}", methods=["GET", "HEAD"])
def skin_file(file: str):
return FileResponse(os.path.join("skins", file))
# =========================
# GET/HEAD для Mineskin
# =========================
@app.post("/merge-to-mineskin")
def merge_to_mineskin(request: SkinRequest):
try:
print(f"🎮 Игрок: {request.Playername}")
merged_skin = create_merged_skin(request)
public_url, filename = save_skin(
merged_skin,
request.Playername
)
print(f"🔗 Skin URL: {public_url}")
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "SkinMerger/1.0",
"Authorization": f"Bearer {MINESKIN_TOKEN}"
}
payload = {
"variant": "classic",
"name": request.Playername,
"visibility": "public",
"url": public_url
}
response = requests.post(
"https://api.mineskin.org/v2/generate",
headers=headers,
json=payload,
timeout=60,
verify=False
)
mineskin_response = response.json()
return {
"success": True,
"Playername": request.Playername,
"skin_url": public_url,
"filename": filename,
"mineskin_response": mineskin_response
}
except Exception as e:
return {
"success": False,
"error": str(e)
}
@app.get("/")
def root():
return {
"status": "ok",
"service": "Skin Merger - Fixed",
"endpoints": {
"POST /merge-to-link": "Создать скин и получить ссылку",
"POST /merge-to-mineskin": "Создать скин и загрузить в MineSkin",
"GET /merge.png": "Быстрое тестирование",
"GET /skins/{filename}": "Доступ к сохраненным скинам"
}
}
# =========================
# RUN
# =========================
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)