| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252 |
- import os
- import sys
- import boto3
- import requests
- from bs4 import BeautifulSoup
- from botocore.config import Config
- from io import BytesIO
- from PIL import Image
- from minecraft_version import MCVM, MinecraftVersion
- # ============================================================
- # 配置
- # ============================================================
- USER_AGENT = (
- "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
- "AppleWebKit/537.36 (KHTML, like Gecko) "
- "Chrome/139.0 Safari/537.36"
- )
- # ============================================================
- # R2
- # ============================================================
- def create_r2_client():
- account_id = os.environ["R2_ACCOUNT_ID"]
- access_key = os.environ["R2_ACCESS_KEY_ID"]
- secret_key = os.environ["R2_SECRET_ACCESS_KEY"]
- return boto3.client(
- "s3",
- endpoint_url=f"https://{account_id}.r2.cloudflarestorage.com",
- aws_access_key_id=access_key,
- aws_secret_access_key=secret_key,
- region_name="auto",
- config=Config(
- signature_version="s3v4"
- ),
- )
- # ============================================================
- # 获取更新日志 URL
- # ============================================================
- def get_change_log_url(version_id: str) -> str:
- version = MCVM.get(version_id)
- if version is None:
- raise ValueError(
- f"MC 版本 {version_id} 不存在"
- )
- url = version.change_log_url
- if not url:
- raise ValueError(
- f"MC 版本 {version_id} 没有更新日志链接"
- )
- #print(f"版本: {version.id}")
- #print(f"更新日志: {url}")
- return url
- # ============================================================
- # 下载 HTML
- # ============================================================
- def download_html(url: str) -> str:
- #print("[1/4] 下载 HTML...")
- headers = {
- "User-Agent": USER_AGENT,
- "Referer": "https://www.minecraft.net/",
- "Accept": (
- "text/html,application/xhtml+xml,"
- "application/xml;q=0.9,*/*;q=0.8"
- ),
- }
- response = requests.get(
- url,
- headers=headers,
- timeout=30,
- )
- response.raise_for_status()
- response.encoding = response.apparent_encoding
- return response.text
- def find_hero_image(html: str, page_url: str) -> str:
- #print("[2/4] 获取图片路径...")
- soup = BeautifulSoup(
- html,
- "html.parser",
- )
- poster = soup.select_one(
- "div.MC_articleHeroA_poster"
- )
- if poster is None:
- raise RuntimeError(
- "找不到 div.MC_articleHeroA_poster"
- )
- img = poster.select_one(
- "picture img"
- )
- if img is None:
- raise RuntimeError(
- "找不到 picture img"
- )
- src = img.get("src")
- if not src:
- raise RuntimeError(
- "img 没有 src 属性"
- )
- from urllib.parse import urljoin
- image_url = urljoin(
- page_url,
- str(src),
- )
- #print(f"图片: {image_url}")
- return image_url
- def download_image(
- image_url: str,
- page_url: str,
- ) -> tuple[bytes, str]:
- #print("[3/4] 下载图片...")
- headers = {
- "User-Agent": USER_AGENT,
- "Referer": page_url,
- "Accept": (
- "image/avif,image/webp,image/apng,"
- "image/svg+xml,image/*,*/*;q=0.8"
- ),
- }
- response = requests.get(
- image_url,
- headers=headers,
- timeout=60,
- )
- response.raise_for_status()
- content_type = response.headers.get(
- "Content-Type",
- "application/octet-stream",
- )
- image = Image.open(BytesIO(response.content))
- if image.mode != "RGB":
- image = image.convert("RGB")
- output = BytesIO()
- image.save(
- output,
- format="JPEG",
- quality=95,
- optimize=True,
- )
- jpeg_data = output.getvalue()
- return jpeg_data, "image/jpeg"
- def upload_to_r2(
- data: bytes,
- bucket: str,
- object_key: str,
- content_type: str,
- ):
- #print("[4/4] 上传 Cloudflare R2...")
- s3 = create_r2_client()
- s3.put_object(
- Bucket=bucket,
- Key=object_key,
- Body=data,
- ContentType=content_type,
- )
- def upload_header_image(version):
- if isinstance(version, str):
- version = MCVM.get(version)
- if not isinstance(version, MinecraftVersion):
- raise TypeError()
- change_log_url = version.change_log_url
- html = download_html(
- change_log_url
- )
- image_url = find_hero_image(
- html,
- change_log_url,
- )
- image_data, content_type = download_image(
- image_url,
- change_log_url,
- )
- bucket = "images"
- object_key = f"news/version/{version.major_version}/{version.id}.jpg"
- upload_to_r2(
- image_data,
- bucket,
- object_key,
- content_type,
- )
- return f"https://image.bugjump.net/{object_key}"
-
- def main():
- if len(sys.argv) != 2:
- print(
- f"用法: {sys.argv[0]} <Minecraft版本号>"
- )
- print()
- print("例如:")
- print(f" {sys.argv[0]} 26.3")
- print(f" {sys.argv[0]} 26.3-rc-1")
- sys.exit(1)
- version_id = sys.argv[1]
- version = MCVM.get(version_id)
- if not version:
- raise ValueError('错误:未找到版本')
- url = upload_header_image(version)
- print(f"已上传至{url}")
- if __name__ == "__main__":
- main()
|