+ hot_info_1 = page.locator('div.hot-info').nth(0)
+ await hot_info_1.screenshot(path=data_dir / 'hot_info_1.png')
+
+ # 截取
+ container_hidden_xs = page.locator('div.container.hidden-xs')
+ await container_hidden_xs.screenshot(path=data_dir / 'container_hidden_xs.png')
+ # 截取第二个
+ hot_info_2 = page.locator('div.hot-info').nth(1)
+ await hot_info_2.screenshot(path=data_dir / 'hot_info_2.png')
+ combine_imgs()
+ finally:
+ if browser:
+ await browser.close()
+
+ return vb_file
+
+
+
+def combine_imgs():
+ try:
+ # 打开三个截图
+ hot_info_1 = Image.open(data_dir / 'hot_info_1.png')
+ container_hidden_xs = Image.open(data_dir / 'container_hidden_xs.png')
+ hot_info_2 = Image.open(data_dir / 'hot_info_2.png')
+
+ # 获取每个截图的尺寸
+ width1, height1 = hot_info_1.size
+ width2, height2 = container_hidden_xs.size
+ width3, height3 = hot_info_2.size
+
+ # 创建一个新的图像,宽度为最宽的截图宽度,高度为三个截图高度之和
+ total_width = max(width1, width2, width3)
+ total_height = height1 + height2 + height3
+ combined_image = Image.new('RGB', (total_width, total_height))
+
+ # 将每个截图粘贴到新的图像中
+ combined_image.paste(hot_info_1, (0, 0))
+ combined_image.paste(container_hidden_xs, (0, height1))
+ combined_image.paste(hot_info_2, (0, height1 + height2))
+
+ # 保存合并后的图像
+ combined_image.save(vb_file)
+ finally:
+ # 关闭所有打开的图像资源
+ hot_info_1.close()
+ container_hidden_xs.close()
+ hot_info_2.close()
+ combined_image.close()
diff --git a/nonebot_plugin_fortnite/shop.py b/nonebot_plugin_fortnite/shop.py
new file mode 100644
index 0000000..86ec5fa
--- /dev/null
+++ b/nonebot_plugin_fortnite/shop.py
@@ -0,0 +1,56 @@
+import httpx
+import asyncio
+
+from PIL import Image
+from pathlib import Path
+from nonebot.log import logger
+from playwright.async_api import async_playwright
+
+from .config import data_dir
+
+shop_file = data_dir / "shop.png"
+
+async def screenshot_shop_img() -> Path:
+ # url = "https://www.fortnite.com/item-shop?lang=zh-Hans"
+ url = "https://fortnite.gg/shop"
+
+ headers = {
+ 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36",
+ 'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
+ 'Accept-Encoding': "gzip, deflate",
+ 'upgrade-insecure-requests': "1",
+ 'dnt': "1",
+ 'x-requested-with': "mark.via",
+ 'sec-fetch-site': "none",
+ 'sec-fetch-mode': "navigate",
+ 'sec-fetch-user': "?1",
+ 'sec-fetch-dest': "document",
+ 'accept-language': "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7",
+ 'Cookie': "_sharedid=f02028dd-dce2-4b07-bba9-301d54e68dbd; _sharedid_cst=zix7LPQsHA%3D%3D; _lr_env_src_ats=false; hb_insticator_uid=799b5897-b5a3-48c4-a46f-8bb8bf9082ac"
+ }
+
+ # token = await cf_token()
+ # logger.info(token)
+ browser = None
+ try:
+ async with async_playwright() as p:
+ browser = await p.chromium.launch(headless=True)
+ context = await browser.new_context(extra_http_headers = headers)
+ page = await context.new_page()
+ # page.on('requestfailed', lambda request: logger.warning(f'Request failed: {request.url}'))
+ await page.add_style_tag(content='* { transition: none !important; animation: none !important; }')
+ await page.goto(url)
+ # 模拟滚动到页面底部
+ for _ in range(10):
+ await page.evaluate("""() => {
+ window.scrollBy(0, document.body.scrollHeight);
+ }""")
+ await asyncio.sleep(2) # 等待2秒以加载内容
+
+ await page.wait_for_load_state('networkidle', timeout=100000)
+ # await page.wait_for_load_state('load') # 等待页面加载完毕
+ await page.screenshot(path=shop_file, full_page=True)
+ return shop_file
+ finally:
+ await browser.close()
+
diff --git a/nonebot_plugin_fortnite/stats.py b/nonebot_plugin_fortnite/stats.py
index bd99034..10386a0 100644
--- a/nonebot_plugin_fortnite/stats.py
+++ b/nonebot_plugin_fortnite/stats.py
@@ -1,9 +1,23 @@
import httpx
import asyncio
-# from pathlib import Path
-from .config import fconfig
-from .other import exception_handler
+from io import BytesIO
+from pathlib import Path
+from nonebot import get_driver
+from nonebot.log import logger
+
+from PIL import (
+ Image,
+ ImageFont,
+ ImageDraw
+)
+
+from .config import (
+ fconfig,
+ cache_dir,
+ data_dir
+)
+
from fortnite_api import (
Client,
BrPlayerStats,
@@ -11,65 +25,97 @@
TimeWindow
)
+from .other import exception_handler
+
api_key = fconfig.fortnite_api_key
-@exception_handler
async def get_stats(
name: str,
time_window: TimeWindow = TimeWindow.SEASON,
- image: StatsImageType = None
+ image_type: StatsImageType = None
) -> BrPlayerStats:
+ params = {
+ 'name': name,
+ 'time_window': time_window
+ }
+ if image_type:
+ params['image'] = image_type
async with Client(api_key=api_key) as client:
- return await client.fetch_br_stats(
- name=name,
- time_window=time_window,
- image=image
- )
-
+ return await client.fetch_br_stats(**params)
+
+@exception_handler()
async def get_level(name: str, time_window: str) -> int:
time_window = TimeWindow.LIFETIME if time_window.startswith("生涯") else TimeWindow.SEASON
stats = await get_stats(name, time_window)
bp = stats.battle_pass
return f'等级: {bp.level} 下一级进度: {bp.progress}%'
-async def get_stats_image(name: str, time_window: str) -> str:
+@exception_handler()
+async def get_stats_image(name: str, time_window: str) -> Path:
time_window = TimeWindow.LIFETIME if time_window.startswith("生涯") else TimeWindow.SEASON
stats = await get_stats(name, time_window, StatsImageType.ALL)
- return stats.image.url
-
-
-# async def write_cn_name(url: str, nickname: str):
-# # 打开原始图像
-# image = Image.open(IMG_PATH / "zhanji.png")
-# async with httpx.AsyncClient() as client:
-# resp = await client.get(url)
-# im = Image.open(BytesIO(resp.content))
-# draw = ImageDraw.Draw(im)
+ return await get_stats_img_by_url(stats.image.url, name)
-# # 矩形区域的坐标
-# left, top, right, bottom = 26, 90, 423, 230
-# # 获取渐变色的起始和结束颜色
-# start_color = image.getpixel((left, top))
-# end_color = image.getpixel((right, bottom))
+
+font_path: Path | None = None
+
+@get_driver().on_startup
+async def _():
+ hans = data_dir / "SourceHanSansSC-Bold-2.otf"
+ global font_path
+ if hans.exists():
+ font_path = hans
+ logger.info(f'战绩绘图将使用字体: {font_path.name}')
+ else:
+ logger.warning(f"请前往仓库下载字体到 {data_dir}/,否则战绩查询可能无法显示中文名称")
-# # 创建渐变色并填充矩形区域
-# width = right - left
-# height = bottom - top
+async def get_stats_img_by_url(url: str, name: str) -> Path:
+ file = cache_dir / f"{name}.png"
+ async with httpx.AsyncClient() as client:
+ resp = await client.get(url)
+ resp.raise_for_status()
+
+ with open(file, "wb") as f:
+ f.write(resp.content)
+ # 如果不包含中文名,返回
+ if not font_path or not contains_chinese(name):
+ return file
-# for i in range(width):
-# for j in range(height):
-# r = int(start_color[0] + (end_color[0] - start_color[0]) * (i + j) / (width + height))
-# g = int(start_color[1] + (end_color[1] - start_color[1]) * (i + j) / (width + height))
-# b = int(start_color[2] + (end_color[2] - start_color[2]) * (i + j) / (width + height))
-# draw.point((left + i, top + j), fill=(r, g, b))
+ with Image.open(file) as img:
+ draw = ImageDraw.Draw(img)
+
+ # 矩形区域的坐标
+ left, top, right, bottom = 26, 90, 423, 230
-# font_size = 36
-# hansans = (FONT_PATH / "SourceHanSansSC-Bold-2.otf").absolute()
-# font = ImageFont.truetype(hansans, font_size)
-# length = draw.textlength(nickname, font=font)
-# x = left + (right - left - length) / 2
-# y = top + (bottom - top - font_size) / 2
-# draw.text((x, y), nickname, fill = "#fafafa", font = font)
-# buffered = BytesIO()
-# im.save(buffered, format="PNG")
-# return base64.b64encode(buffered.getvalue()).decode()
+ # 创建渐变色并填充矩形区域
+ width = right - left
+ height = bottom - top
+
+ start_color = (0, 33, 69, 255)
+ end_color = (0, 82, 106, 255)
+ for i in range(width):
+ for j in range(height):
+ r = int(start_color[0] + (end_color[0] - start_color[0]) * (i + j) / (width + height))
+ g = int(start_color[1] + (end_color[1] - start_color[1]) * (i + j) / (width + height))
+ b = int(start_color[2] + (end_color[2] - start_color[2]) * (i + j) / (width + height))
+ draw.point((left + i, top + j), fill=(r, g, b))
+
+ # 指定字体
+ font_size = 36
+ # hansans = data_dir / "SourceHanSansSC-Bold-2.otf"
+ font = ImageFont.truetype(font_path, font_size)
+
+ # 计算字体坐标
+ length = draw.textlength(name, font=font)
+ x = left + (right - left - length) / 2
+ y = top + (bottom - top - font_size) / 2
+ draw.text((x, y), name, fill = "#fafafa", font = font)
+
+ # 保存
+ img.save(file)
+ return file
+
+def contains_chinese(text):
+ import re
+ pattern = re.compile(r'[\u4e00-\u9fff]')
+ return bool(pattern.search(text))
diff --git a/pyproject.toml b/pyproject.toml
index e6a4478..1c11681 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "nonebot-plugin-fortnite"
-version = "0.0.6"
+version = "0.1.0"
description = "堡垒之夜插件"
authors = ["fllesser "]
readme = "README.md"
@@ -12,8 +12,9 @@ keywords = ["nonebot", "nonebot2"]
[tool.poetry.dependencies]
python = "^3.10"
fortnite-api = "^3.2.1"
+pillow = "^10.4.0"
+playwright = "^1.4.9.1"
nonebot2 = "^2.4.0"
-nonebot-adapter-onebot = "^2.4.6"
nonebot-plugin-uninfo = "^0.6.5"
nonebot-plugin-alconna = "^0.54.2"
nonebot-plugin-localstore = "^0.7.3"
@@ -24,7 +25,7 @@ nb-cli = "^1.4.2"
nonebot2 = {version = "^2.4.0", extras = ["fastapi"]}
[tool.nonebot]
-adapters = [{name="Onebot", module_name="nonebot.adapters.onebot.v11"}]
+adapters = []
plugins = ["nonebot_plugin_fortnite"]
[build-system]