diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..17d6fdd
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,40 @@
+---
+name: Bug 报告
+about: 报告一个 Bug 或异常行为
+title: "[Bug] "
+labels: bug
+---
+
+## 问题描述
+
+清晰简洁地描述你遇到的 Bug。
+
+## 复现步骤
+
+1. 步骤一
+2. 步骤二
+3. 步骤三
+
+## 预期行为
+
+描述你期望发生的结果。
+
+## 实际行为
+
+描述实际发生的结果。
+
+## 环境信息
+
+- **操作系统**: [例如 Windows 11 / macOS / Ubuntu]
+- **Node.js 版本**: [例如 20.0.0]
+- **Python 版本**: [例如 3.12]
+- **Wrangler 版本**: [例如 4.x]
+- **浏览器**: [如适用]
+
+## 截图或日志
+
+如有截图或错误日志,请贴在这里。
+
+## 补充说明
+
+任何其他上下文信息。
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..e455e8b
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,26 @@
+---
+name: 功能建议
+about: 为项目提出新功能或改进建议
+title: "[Feature] "
+labels: enhancement
+---
+
+## 功能描述
+
+清晰简洁地描述你想要的功能或改进。
+
+## 使用场景
+
+描述这个功能会在什么场景下使用,解决什么问题。
+
+## 可能的实现方案
+
+如果你有实现思路,可以简要描述。
+
+## 替代方案
+
+是否考虑过其他替代方案?
+
+## 补充说明
+
+任何其他上下文信息。
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000..17e1196
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,26 @@
+## 描述
+
+请简要描述这个 PR 的内容和目的。
+
+## 变更类型
+
+- [ ] Bug 修复
+- [ ] 新功能
+- [ ] 文档更新
+- [ ] 代码重构
+- [ ] 性能优化
+- [ ] 其他
+
+## 检查清单
+
+- [ ] 代码已测试,本地 `npm run dev` 可正常运行
+- [ ] 没有提交敏感信息(API Key、账号等)
+- [ ] 没有提交原始图片到根目录 `mobile-image/` / `desketop-image/`
+- [ ] 相关文档已更新(README / CONTRIBUTING)
+- [ ] 提交信息符合 Conventional Commits 规范
+
+## 关联 Issue
+
+如有,请填写:
+
+Fixes #(issue 编号)
diff --git a/.gitignore b/.gitignore
index 9a1cd7a..2b03e70 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,68 @@
+# Dependencies
+node_modules/
+package-lock.json
+
+# Build artifacts
.build-venv/
.wrangler/
__pycache__/
*.pyc
+
+# Logs
+*.log
+npm-debug.log*
+
+# Source images (only numbered .webp should be tracked; original images stay local)
+# Ignore everything in source dirs
+/mobile-image/*
+/desketop-image/*
+
+# Keep .gitkeep to preserve empty directories
+!/mobile-image/.gitkeep
+!/desketop-image/.gitkeep
+
+# Keep only purely numbered .webp in source dirs (generated build artifacts)
+# e.g. 1.webp, 10.webp, 123.webp — but NOT 3yfvfgrD.webp
+!/mobile-image/[0-9].webp
+!/mobile-image/[0-9][0-9].webp
+!/mobile-image/[0-9][0-9][0-9].webp
+!/mobile-image/[0-9][0-9][0-9][0-9].webp
+
+!/desketop-image/[0-9].webp
+!/desketop-image/[0-9][0-9].webp
+!/desketop-image/[0-9][0-9][0-9].webp
+!/desketop-image/[0-9][0-9][0-9][0-9].webp
+
+# Ignore all original image formats (redundant safety net)
+/mobile-image/*.jpg
+/mobile-image/*.jpeg
+/mobile-image/*.png
+/mobile-image/*.gif
+/mobile-image/*.bmp
+/mobile-image/*.tif
+/mobile-image/*.tiff
+
+/desketop-image/*.jpg
+/desketop-image/*.jpeg
+/desketop-image/*.png
+/desketop-image/*.gif
+/desketop-image/*.bmp
+/desketop-image/*.tif
+/desketop-image/*.tiff
+
+# OS files
+.DS_Store
+Thumbs.db
+desktop.ini
+
+# Editor / IDE
+.vscode/
+.idea/
+*.swp
+*.swo
+*~
+
+# Wrangler local state
+.dev.vars
+.env
+.env.local
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..394692c
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,67 @@
+# 贡献指南
+
+感谢你对 RandBG API 项目的关注!我们欢迎任何形式的贡献,包括但不限于提交 Issue、修复 Bug、改进文档、新增功能等。
+
+## 如何贡献
+
+### 提交 Issue
+
+如果你发现了 Bug 或有新功能建议,请通过 [GitHub Issues](https://github.com/shumengya/randbg-api/issues) 提交。
+
+提交 Issue 时,请尽可能包含以下信息:
+
+- **问题描述**:清晰描述你遇到的问题或建议
+- **复现步骤**:如果是 Bug,请提供详细的复现步骤
+- **环境信息**:操作系统、Node.js 版本、Python 版本等
+- **预期结果**与**实际结果**
+- **截图或日志**(如有)
+
+### 提交 Pull Request
+
+1. **Fork 本仓库** 并克隆到本地
+2. **创建分支**:从 `main` 分支创建一个新的功能分支
+ ```bash
+ git checkout -b feature/your-feature-name
+ ```
+3. **进行修改**:确保代码风格与现有项目保持一致
+4. **测试**:在本地运行 `npm run dev` 确保修改不会破坏现有功能
+5. **提交**:使用清晰的提交信息
+ ```bash
+ git commit -m "feat: 添加 xxx 功能"
+ ```
+6. **推送并创建 PR**:推送到你的 Fork 仓库,然后向本仓库提交 Pull Request
+
+## 代码规范
+
+- **JavaScript**:使用 ES Module,保持简洁,添加必要的 JSDoc 注释
+- **Python**:遵循 PEP 8 规范,使用类型注解(如有)
+- **Git 提交信息**:参考 [Conventional Commits](https://www.conventionalcommits.org/)
+ - `feat:` 新功能
+ - `fix:` 修复 Bug
+ - `docs:` 文档更新
+ - `refactor:` 代码重构
+ - `chore:` 构建/工具链更新
+
+## 项目结构说明
+
+- `src/`:Worker 运行时代码(API 逻辑)
+- `public/`:静态资源(会被部署到 Cloudflare Assets)
+ - `index.html`:演示页面
+ - `manifest.json`:图片清单(由 `build.py` 自动生成)
+ - `mobile-image/` / `desketop-image/`:构建后的 WebP 图片
+- `mobile-image/` / `desketop-image/`(根目录):**原始图片存放目录,不要提交到 Git**
+- `build.py`:构建脚本,将原图转换为 WebP 并生成清单
+
+## 注意事项
+
+- **不要提交原始图片**:根目录下的 `mobile-image/` 和 `desketop-image/` 仅用于本地构建,已配置 `.gitignore` 忽略非编号文件。
+- **不要提交敏感信息**:`wrangler.toml` 中不应包含 `account_id` 或 API Token。
+- **保持 API 兼容性**:如果修改 API 接口,请确保向后兼容,或在文档中明确标注 Breaking Changes。
+
+## 行为准则
+
+- 尊重他人,保持友善和建设性的沟通
+- 接受不同的观点和经验
+- 关注对社区最有利的事情
+
+再次感谢你的贡献!
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d17fa7b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 shumengya
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.md b/README.md
index d27d357..58b236b 100644
--- a/README.md
+++ b/README.md
@@ -1,54 +1,236 @@
-# 随机背景API
+# RandBG API - 随机背景图片服务
-一个可直接部署到 Cloudflare Pages 的静态站点 + Pages Functions API,用来按设备方向随机返回背景图。
+[](https://workers.cloudflare.com/)
+[](LICENSE)
+[](https://randbg.smyhub.com)
+
+> 一个基于 **Cloudflare Workers** 部署的轻量级随机背景图片 API,支持自动识别设备类型(移动端/桌面端),并提供优雅的 Web 演示界面。
+
+---
+
+## 在线体验
+
+- **在线演示**: [https://randbg.smyhub.com](https://randbg.smyhub.com)
+- **GitHub 仓库**: [https://github.com/shumengya/randbg-api](https://github.com/shumengya/randbg-api)
+
+---
+
+## 特性
+
+- **自动设备识别**:根据 User-Agent 和 Client Hints 自动判断移动端/桌面端
+- **双重响应格式**:支持 `302 Redirect` 直接跳转图片,或 `JSON` 返回图片元数据
+- **静态资源优化**:使用 WebP 格式,配合 Cloudflare 静态资源缓存,加载极速
+- **智能构建流程**:`build.py` 自动将原图转换为编号 WebP 并生成 `manifest.json`
+- **一体化部署**:Worker 负责 API 逻辑,静态页面与图片通过 Assets 提供,单次部署即发布前后端
+- **零成本部署**:基于 Cloudflare Workers Free Tier,完全免费
+
+---
+
+## API 文档
+
+### `GET /api/random`
+
+随机返回一张背景图片。
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `mode` | string | `auto` | 强制指定类型:`auto` / `mobile` / `desktop` |
+| `orientation` | string | `auto` | 屏幕方向:`portrait` / `landscape` |
+| `device` | string | `auto` | 设备类型:`mobile` / `desktop` |
+| `format` | string | `redirect` | 响应格式:`redirect`(302 跳转)/ `json` |
+
+**示例请求:**
+
+```bash
+# 默认 302 跳转到随机图片
+curl https://randbg.smyhub.com/api/random
+
+# 返回 JSON
+curl https://randbg.smyhub.com/api/random?format=json
+# 响应:
+# {
+# "variant": "desktop",
+# "folder": "desketop-image",
+# "filename": "7.webp",
+# "url": "https://randbg.smyhub.com/desketop-image/7.webp",
+# "count": 19
+# }
+
+# 强制使用移动端图片
+curl https://randbg.smyhub.com/api/random?mode=mobile
+
+# 强制桌面端并返回 JSON
+curl https://randbg.smyhub.com/api/random?mode=desktop&format=json
+```
+
+### `GET /api/list`
+
+返回各变体的图片清单与统计信息。
+
+```bash
+curl https://randbg.smyhub.com/api/list
+```
+
+**响应示例:**
+
+```json
+{
+ "generated_at": "2026-05-18T11:45:45.514521+00:00",
+ "variants": {
+ "mobile": {
+ "folder": "mobile-image",
+ "count": 6,
+ "images": ["1.webp", "2.webp", "3.webp", "4.webp", "5.webp", "6.webp"]
+ },
+ "desktop": {
+ "folder": "desketop-image",
+ "count": 19,
+ "images": ["1.webp", "2.webp", ...]
+ }
+ }
+}
+```
+
+### `GET /manifest.json`
+
+静态清单文件,由 `build.py` 自动生成,记录各变体的目录与文件名列表。
+
+---
+
+## 技术栈
+
+| 层级 | 技术 |
+|------|------|
+| Runtime | [Cloudflare Workers](https://workers.cloudflare.com/) |
+| 静态托管 | Cloudflare Workers Static Assets |
+| 构建脚本 | Python 3 + [Pillow](https://pillow.readthedocs.io/) |
+| 前端 | Vanilla HTML / CSS / JS |
+
+---
+
+## 项目结构
+
+```
+randbg-api/
+├── src/
+│ ├── worker.js # Workers 入口路由
+│ ├── api/
+│ │ ├── random.js # /api/random 接口逻辑
+│ │ └── list.js # /api/list 接口逻辑
+│ └── lib/
+│ └── background.js # 通用工具(清单加载、设备识别、URL 构建)
+├── public/
+│ ├── index.html # 演示与文档页面
+│ ├── manifest.json # 图片清单(build.py 生成)
+│ ├── _headers # 静态资源 HTTP 响应头
+│ ├── mobile-image/ # 移动端 WebP 图片(构建产物)
+│ └── desketop-image/ # 桌面端 WebP 图片(构建产物)
+├── mobile-image/ # 原始图片(本地工作目录,不提交到 Git)
+├── desketop-image/ # 原始图片(本地工作目录,不提交到 Git)
+├── build.py # 构建脚本:原图 → WebP + manifest.json
+├── wrangler.toml # Workers 部署配置
+├── package.json # 依赖与脚本
+└── README.md # 本文档
+```
+
+> **注意**:根目录下的 `mobile-image/` 和 `desketop-image/` 仅用于本地存放原始素材,不应提交到 GitHub。`build.py` 会将转换后的编号 WebP 输出到 `public/` 目录,只有 `public/` 下的内容会被部署。
+
+---
+
+## 部署指南
+
+### 前置条件
+
+- [Cloudflare 账号](https://dash.cloudflare.com/)
+- [Node.js](https://nodejs.org/) 18+
+- [Python 3](https://python.org/) 3.9+
+- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/)
+
+### 1. 克隆项目
+
+```bash
+git clone https://github.com/shumengya/randbg-api.git
+cd randbg-api
+```
+
+### 2. 安装依赖
+
+```bash
+npm install
+```
+
+### 3. 准备图片
+
+将原始图片放入对应目录:
+
+```
+mobile-image/ # 竖屏图片(移动端)
+desketop-image/ # 横屏图片(桌面端)
+```
+
+支持格式:`.jpg` `.jpeg` `.png` `.bmp` `.gif` `.tif` `.tiff` `.webp`
+
+### 4. 构建 WebP 与清单
+
+```bash
+python3 build.py
+```
+
+脚本会自动完成以下工作:
+1. 将原图转换为高质量编号 WebP(`1.webp` `2.webp` ...)
+2. 生成 `public/manifest.json`
+3. 将转换后的文件输出到 `public/` 部署目录
+
+如果 `public/` 下已有序号 WebP 且没有原图,脚本会**保留现有文件**,不会误删。
+
+### 5. 本地预览
+
+```bash
+npm run dev
+```
+
+默认在 `http://localhost:8787` 启动预览。
+
+### 6. 部署到 Cloudflare
+
+```bash
+npm run deploy
+```
+
+Wrangler 会将 `src/worker.js` 打包为 Worker,同时将 `public/` 作为静态 Assets 一起发布。
+
+---
## 目录约定
-- `mobile-image/`:竖屏图片原图上传目录
-- `desketop-image/`:横屏图片原图上传目录
+- **`public/mobile-image/`**:竖屏图片(移动端)
+- **`public/desketop-image/`**:横屏图片(桌面端)
+ - 注意拼写沿用历史目录名,API 内部已兼容
+- **`public/manifest.json`**:构建产物,API 运行时通过内部请求读取该清单
-## 使用流程
+---
-1. 把图片上传到对应目录。
-2. 运行构建脚本:
- ```bash
- python3 build.py
- ```
-3. 把项目根目录作为 Cloudflare Pages 的发布目录部署。
-4. 打开:
- - 页面:`/`
- - API:`/api/random`
+## 注意事项
-## API
+1. **原图不上传**:根目录下的 `mobile-image/` 和 `desketop-image/` 仅用于本地构建,已配置 `.gitignore` 避免误提交。GitHub 仓库中只保留构建后的 `public/` 资源。
+2. **图片版权**:请确保上传的图片拥有合法使用权或来自版权友好的来源。
+3. **Worker 体积限制**:静态资源通过 Cloudflare Assets 提供,不占用 Worker 脚本 4 MiB 限制,可放心使用大量图片。
+4. **缓存策略**:`public/_headers` 已为图片配置了 `immutable` 长期缓存,为 `manifest.json` 配置了 5 分钟缓存。如需更新图片,请重新运行 `build.py` 并部署。
-- `GET /api/random`
- - 默认按 `mode=auto` 随机返回图片重定向
-- `GET /api/random?format=json`
- - 返回 JSON
-- `GET /api/list`
- - 返回图片清单
+---
-## 组件嵌入
+## 开源协议
-```html
-
-
-
-```
+本项目基于 [MIT License](LICENSE) 开源。
-`widget.js` 只读取静态的 `manifest.json` 和图片文件,不会调用 `api/random`。
+---
-## 构建说明
+## 贡献
-`build.py` 会把目录里的原图转换成同目录下的编号 `webp` 文件,例如:
+欢迎提交 Issue 和 Pull Request!如果你有任何建议或发现问题,请随时反馈。
-- `1.webp`
-- `2.webp`
-- `55.webp`
+---
-同时生成 `manifest.json`,供 Pages Functions 读取。
+
+ Made with ❤️ by shumengya
+
diff --git a/build.py b/build.py
index 6b0859f..b680aad 100644
--- a/build.py
+++ b/build.py
@@ -4,6 +4,7 @@ from __future__ import annotations
import json
import os
import re
+import shutil
import subprocess
import sys
import venv
@@ -11,11 +12,24 @@ from datetime import datetime, timezone
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent
-MANIFEST_PATH = BASE_DIR / "manifest.json"
+PUBLIC_DIR = BASE_DIR / "public"
+MANIFEST_PATH = PUBLIC_DIR / "manifest.json"
VARIANTS = {
- "mobile": [BASE_DIR / "mobile-image"],
- "desktop": [BASE_DIR / "desketop-image"],
+ "mobile": {
+ "out": PUBLIC_DIR / "mobile-image",
+ "scan": [
+ PUBLIC_DIR / "mobile-image",
+ BASE_DIR / "mobile-image",
+ ],
+ },
+ "desktop": {
+ "out": PUBLIC_DIR / "desketop-image",
+ "scan": [
+ PUBLIC_DIR / "desketop-image",
+ BASE_DIR / "desketop-image",
+ ],
+ },
}
INPUT_EXTENSIONS = {
@@ -81,14 +95,15 @@ def iter_source_files(directory: Path) -> list[Path]:
return files
-def select_source_directory(candidates: list[Path]) -> Path:
- for directory in candidates:
+def select_scan_directory(scan_candidates: list[Path]) -> Path:
+ """优先有原图的目录,其次有序号 webp;否则退回 scan 第一项(通常为 public下的输出目录)。"""
+ for directory in scan_candidates:
if iter_source_files(directory):
return directory
- for directory in candidates:
- if directory.exists():
+ for directory in scan_candidates:
+ if sorted_generated_webp_names(directory):
return directory
- return candidates[0]
+ return scan_candidates[0]
def clear_generated_files(directory: Path) -> None:
@@ -100,6 +115,23 @@ def clear_generated_files(directory: Path) -> None:
path.unlink()
+def sorted_generated_webp_names(directory: Path) -> list[str]:
+ """已存在的序号 webp(无原图时再跑脚本不会清空)。"""
+ if not directory.exists():
+ return []
+ names = [
+ path.name
+ for path in directory.iterdir()
+ if path.is_file() and GENERATED_NAME_RE.match(path.name)
+ ]
+
+ def sort_key(entry: str) -> int:
+ return int(Path(entry).stem)
+
+ names.sort(key=sort_key)
+ return names
+
+
def convert_to_webp(source: Path, target: Path, image_mod, image_ops) -> None:
with image_mod.open(source) as image:
image = image_ops.exif_transpose(image)
@@ -114,24 +146,47 @@ def convert_to_webp(source: Path, target: Path, image_mod, image_ops) -> None:
image.save(target, format="WEBP", quality=92, method=6)
-def build_variant(name: str, directories: list[Path], image_mod, image_ops) -> dict:
- directory = select_source_directory(directories)
- directory.mkdir(parents=True, exist_ok=True)
- clear_generated_files(directory)
+def copy_generated_webps(source_dir: Path, out_dir: Path, names: list[str]) -> None:
+ """将已有序号 webp 同步到 public 下的部署目录。"""
+ out_dir.mkdir(parents=True, exist_ok=True)
+ for entry in names:
+ shutil.copy2(source_dir / entry, out_dir / entry)
+
+
+def build_variant(name: str, spec: dict[str, Path | list[Path]], image_mod, image_ops) -> dict:
+ scan_dir = select_scan_directory(list(spec["scan"]))
+ out_dir = spec["out"]
+ out_dir.mkdir(parents=True, exist_ok=True)
+ sources = iter_source_files(scan_dir)
- sources = iter_source_files(directory)
images: list[str] = []
- for index, source in enumerate(sources, start=1):
- target = directory / f"{index}.webp"
- try:
- convert_to_webp(source, target, image_mod, image_ops)
- images.append(target.name)
- print(f"[{name}] {source.name} -> {target.name}")
- except Exception as exc:
- print(f"[{name}] skip {source.name}: {exc}", file=sys.stderr)
+ if sources:
+ clear_generated_files(out_dir)
+ for index, source in enumerate(sources, start=1):
+ target = out_dir / f"{index}.webp"
+ try:
+ convert_to_webp(source, target, image_mod, image_ops)
+ images.append(target.name)
+ rel_src = os.path.relpath(source.resolve(), BASE_DIR.resolve())
+ print(f"[{name}] {rel_src} -> {target.name}")
+ except Exception as exc:
+ print(f"[{name}] skip {source.name}: {exc}", file=sys.stderr)
+ else:
+ images = sorted_generated_webp_names(out_dir)
+ if not images and scan_dir.resolve() != out_dir.resolve():
+ legacy = sorted_generated_webp_names(scan_dir)
+ if legacy:
+ copy_generated_webps(scan_dir, out_dir, legacy)
+ images = legacy
+ print(f"[{name}] 已从 {scan_dir.relative_to(BASE_DIR)} 复制序号 webp ×{len(images)} -> public/")
+ if images:
+ if scan_dir.resolve() == out_dir.resolve():
+ print(f"[{name}] 未检测到原图,保留 public 下序号 webp ×{len(images)}")
+ else:
+ print(f"[{name}] 跳过:未发现原图,且 public/ 与归档目录均无 1.webp 等序号文件")
return {
- "folder": directory.name,
+ "folder": out_dir.name,
"count": len(images),
"images": images,
}
@@ -149,8 +204,8 @@ def main() -> int:
"variants": {},
}
- for name, directories in VARIANTS.items():
- manifest["variants"][name] = build_variant(name, directories, image_mod, image_ops)
+ for name, variant_spec in VARIANTS.items():
+ manifest["variants"][name] = build_variant(name, variant_spec, image_mod, image_ops)
write_manifest(manifest)
print(f"Wrote manifest: {MANIFEST_PATH}")
diff --git a/desketop-image/3yfvfgrD.webp b/desketop-image/3yfvfgrD.webp
deleted file mode 100644
index e8af8ec..0000000
Binary files a/desketop-image/3yfvfgrD.webp and /dev/null differ
diff --git a/desketop-image/5CrdoShv.webp b/desketop-image/5CrdoShv.webp
deleted file mode 100644
index 908add8..0000000
Binary files a/desketop-image/5CrdoShv.webp and /dev/null differ
diff --git a/desketop-image/5E2wsNlc.webp b/desketop-image/5E2wsNlc.webp
deleted file mode 100644
index e9adaea..0000000
Binary files a/desketop-image/5E2wsNlc.webp and /dev/null differ
diff --git a/desketop-image/AGWXMclV.webp b/desketop-image/AGWXMclV.webp
deleted file mode 100644
index c9463a9..0000000
Binary files a/desketop-image/AGWXMclV.webp and /dev/null differ
diff --git a/desketop-image/IIbmur0t.webp b/desketop-image/IIbmur0t.webp
deleted file mode 100644
index 0256344..0000000
Binary files a/desketop-image/IIbmur0t.webp and /dev/null differ
diff --git a/desketop-image/ItOJOHST.webp b/desketop-image/ItOJOHST.webp
deleted file mode 100644
index ec5ff63..0000000
Binary files a/desketop-image/ItOJOHST.webp and /dev/null differ
diff --git a/desketop-image/L0nQHehz.webp b/desketop-image/L0nQHehz.webp
deleted file mode 100644
index 1139765..0000000
Binary files a/desketop-image/L0nQHehz.webp and /dev/null differ
diff --git a/desketop-image/RK0sjvKj.webp b/desketop-image/RK0sjvKj.webp
deleted file mode 100644
index dfb950f..0000000
Binary files a/desketop-image/RK0sjvKj.webp and /dev/null differ
diff --git a/desketop-image/XI78fkzD.webp b/desketop-image/XI78fkzD.webp
deleted file mode 100644
index c70ad70..0000000
Binary files a/desketop-image/XI78fkzD.webp and /dev/null differ
diff --git a/desketop-image/atm7rt6D.webp b/desketop-image/atm7rt6D.webp
deleted file mode 100644
index bc3eaf7..0000000
Binary files a/desketop-image/atm7rt6D.webp and /dev/null differ
diff --git a/desketop-image/c2HxMuGK.webp b/desketop-image/c2HxMuGK.webp
deleted file mode 100644
index 32dae92..0000000
Binary files a/desketop-image/c2HxMuGK.webp and /dev/null differ
diff --git a/desketop-image/cUDkKiOf.webp b/desketop-image/cUDkKiOf.webp
deleted file mode 100644
index 90846fb..0000000
Binary files a/desketop-image/cUDkKiOf.webp and /dev/null differ
diff --git a/desketop-image/cuSpSkq4.webp b/desketop-image/cuSpSkq4.webp
deleted file mode 100644
index 4a6e914..0000000
Binary files a/desketop-image/cuSpSkq4.webp and /dev/null differ
diff --git a/desketop-image/hj64Cqxn.webp b/desketop-image/hj64Cqxn.webp
deleted file mode 100644
index d09984a..0000000
Binary files a/desketop-image/hj64Cqxn.webp and /dev/null differ
diff --git a/desketop-image/mrSQGVzt.webp b/desketop-image/mrSQGVzt.webp
deleted file mode 100644
index dcb0c23..0000000
Binary files a/desketop-image/mrSQGVzt.webp and /dev/null differ
diff --git a/desketop-image/mtQKTMpU.webp b/desketop-image/mtQKTMpU.webp
deleted file mode 100644
index b43d8dd..0000000
Binary files a/desketop-image/mtQKTMpU.webp and /dev/null differ
diff --git a/desketop-image/nhS9Wnnh.webp b/desketop-image/nhS9Wnnh.webp
deleted file mode 100644
index 9d9815c..0000000
Binary files a/desketop-image/nhS9Wnnh.webp and /dev/null differ
diff --git a/desketop-image/xTsVkCli.webp b/desketop-image/xTsVkCli.webp
deleted file mode 100644
index 2c6433f..0000000
Binary files a/desketop-image/xTsVkCli.webp and /dev/null differ
diff --git a/desketop-image/zr6atT6m.webp b/desketop-image/zr6atT6m.webp
deleted file mode 100644
index 028b9ea..0000000
Binary files a/desketop-image/zr6atT6m.webp and /dev/null differ
diff --git a/index.html b/index.html
deleted file mode 100644
index 93b95ed..0000000
--- a/index.html
+++ /dev/null
@@ -1,390 +0,0 @@
-
-
-
-
-
- 随机背景 API
-
-
-
-
-
-
-
-
随机背景 API
-
Cloudflare Pages 随机背景接口。上传图片后先用 `build.py` 生成序号 `.webp`,然后可通过 `/api/random` 和 `/api/list` 调用。
-
- 竖屏:mobile-image
- 横屏:desketop-image
- 返回:302 / JSON
- 组件:widget.js
-
-
-
-
-
-
-
-
-
API 调用
-
-
-
随机图片
-
默认 302 跳转到随机图片。
-
GET /api/random
-
-
-
返回 JSON
-
适合前端自己处理图片地址。
-
GET /api/random?format=json
-
-
-
-
-
-
强制竖屏
-
GET /api/random?mode=mobile
-
-
-
强制横屏
-
GET /api/random?mode=desktop
-
-
-
-
支持参数:`mode=auto|mobile|desktop`、`orientation=portrait|landscape`、`device=mobile|desktop`、`format=redirect|json`。
-
-
-
-
在线预览
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
![随机背景预览]()
-
-
-
-
-
-
-
-
- 嵌入组件
-
-
-
直接引用
-
<script src="https://random-background-api.pages.dev/widget.js"></script>
-<random-background-widget
- src="https://random-background-api.pages.dev"
- blur="12"
- radius="16px"
- height="260px">
-</random-background-widget>
-
-
-
说明
-
组件只读静态 `manifest.json` 和图片,不消耗 Functions 额度;`blur` 控制高斯模糊像素值。
-
-
-
-
-
-
-
-
-
-
diff --git a/mobile-image/1772108021881_44374ab6c1daa54e0204bca48ac382f2.png b/mobile-image/1772108021881_44374ab6c1daa54e0204bca48ac382f2.png
deleted file mode 100644
index c2bdc79..0000000
Binary files a/mobile-image/1772108021881_44374ab6c1daa54e0204bca48ac382f2.png and /dev/null differ
diff --git a/mobile-image/1772108021977_8020902a0c8788538eee1cd06e784c6a.png b/mobile-image/1772108021977_8020902a0c8788538eee1cd06e784c6a.png
deleted file mode 100644
index 8ba092f..0000000
Binary files a/mobile-image/1772108021977_8020902a0c8788538eee1cd06e784c6a.png and /dev/null differ
diff --git a/mobile-image/1772108022800_f945774e0a45f7a4afdc3da2b112025f.png b/mobile-image/1772108022800_f945774e0a45f7a4afdc3da2b112025f.png
deleted file mode 100644
index fd16663..0000000
Binary files a/mobile-image/1772108022800_f945774e0a45f7a4afdc3da2b112025f.png and /dev/null differ
diff --git a/mobile-image/1772108024006_3f9030ba77e355869115bc90fe019d53.png b/mobile-image/1772108024006_3f9030ba77e355869115bc90fe019d53.png
deleted file mode 100644
index a514b02..0000000
Binary files a/mobile-image/1772108024006_3f9030ba77e355869115bc90fe019d53.png and /dev/null differ
diff --git a/mobile-image/1772108030393_159bbf61f88b38475ee9144a2e8e4956.png b/mobile-image/1772108030393_159bbf61f88b38475ee9144a2e8e4956.png
deleted file mode 100644
index 228aa28..0000000
Binary files a/mobile-image/1772108030393_159bbf61f88b38475ee9144a2e8e4956.png and /dev/null differ
diff --git a/mobile-image/1772108123232_VJ86r.jpg b/mobile-image/1772108123232_VJ86r.jpg
deleted file mode 100644
index 2814cd3..0000000
Binary files a/mobile-image/1772108123232_VJ86r.jpg and /dev/null differ
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0ad85da
--- /dev/null
+++ b/package.json
@@ -0,0 +1,11 @@
+{
+ "name": "randbg-api",
+ "private": true,
+ "scripts": {
+ "dev": "wrangler dev",
+ "deploy": "wrangler deploy"
+ },
+ "devDependencies": {
+ "wrangler": "^4"
+ }
+}
diff --git a/_headers b/public/_headers
similarity index 73%
rename from _headers
rename to public/_headers
index 844f195..cb53805 100644
--- a/_headers
+++ b/public/_headers
@@ -2,10 +2,6 @@
Access-Control-Allow-Origin: *
Cache-Control: public, max-age=300
-/widget.js
- Access-Control-Allow-Origin: *
- Cache-Control: public, max-age=300
-
/mobile-image/*
Cache-Control: public, max-age=31536000, immutable
diff --git a/public/desketop-image/1.webp b/public/desketop-image/1.webp
new file mode 100644
index 0000000..2c22953
Binary files /dev/null and b/public/desketop-image/1.webp differ
diff --git a/public/desketop-image/10.webp b/public/desketop-image/10.webp
new file mode 100644
index 0000000..6c8eb80
Binary files /dev/null and b/public/desketop-image/10.webp differ
diff --git a/public/desketop-image/11.webp b/public/desketop-image/11.webp
new file mode 100644
index 0000000..07a4d25
Binary files /dev/null and b/public/desketop-image/11.webp differ
diff --git a/public/desketop-image/12.webp b/public/desketop-image/12.webp
new file mode 100644
index 0000000..7ed3417
Binary files /dev/null and b/public/desketop-image/12.webp differ
diff --git a/public/desketop-image/13.webp b/public/desketop-image/13.webp
new file mode 100644
index 0000000..b587ec4
Binary files /dev/null and b/public/desketop-image/13.webp differ
diff --git a/public/desketop-image/14.webp b/public/desketop-image/14.webp
new file mode 100644
index 0000000..5afd06e
Binary files /dev/null and b/public/desketop-image/14.webp differ
diff --git a/public/desketop-image/15.webp b/public/desketop-image/15.webp
new file mode 100644
index 0000000..7ee04a9
Binary files /dev/null and b/public/desketop-image/15.webp differ
diff --git a/public/desketop-image/16.webp b/public/desketop-image/16.webp
new file mode 100644
index 0000000..d239c20
Binary files /dev/null and b/public/desketop-image/16.webp differ
diff --git a/public/desketop-image/17.webp b/public/desketop-image/17.webp
new file mode 100644
index 0000000..0f27713
Binary files /dev/null and b/public/desketop-image/17.webp differ
diff --git a/public/desketop-image/18.webp b/public/desketop-image/18.webp
new file mode 100644
index 0000000..d281c39
Binary files /dev/null and b/public/desketop-image/18.webp differ
diff --git a/public/desketop-image/19.webp b/public/desketop-image/19.webp
new file mode 100644
index 0000000..1997f9f
Binary files /dev/null and b/public/desketop-image/19.webp differ
diff --git a/public/desketop-image/2.webp b/public/desketop-image/2.webp
new file mode 100644
index 0000000..6825a63
Binary files /dev/null and b/public/desketop-image/2.webp differ
diff --git a/public/desketop-image/3.webp b/public/desketop-image/3.webp
new file mode 100644
index 0000000..a699097
Binary files /dev/null and b/public/desketop-image/3.webp differ
diff --git a/public/desketop-image/4.webp b/public/desketop-image/4.webp
new file mode 100644
index 0000000..21af0f9
Binary files /dev/null and b/public/desketop-image/4.webp differ
diff --git a/public/desketop-image/5.webp b/public/desketop-image/5.webp
new file mode 100644
index 0000000..d8ecf88
Binary files /dev/null and b/public/desketop-image/5.webp differ
diff --git a/public/desketop-image/6.webp b/public/desketop-image/6.webp
new file mode 100644
index 0000000..e1ef0d5
Binary files /dev/null and b/public/desketop-image/6.webp differ
diff --git a/public/desketop-image/7.webp b/public/desketop-image/7.webp
new file mode 100644
index 0000000..5d79439
Binary files /dev/null and b/public/desketop-image/7.webp differ
diff --git a/public/desketop-image/8.webp b/public/desketop-image/8.webp
new file mode 100644
index 0000000..daef5ea
Binary files /dev/null and b/public/desketop-image/8.webp differ
diff --git a/public/desketop-image/9.webp b/public/desketop-image/9.webp
new file mode 100644
index 0000000..889147d
Binary files /dev/null and b/public/desketop-image/9.webp differ
diff --git a/public/index.html b/public/index.html
new file mode 100644
index 0000000..90a034e
--- /dev/null
+++ b/public/index.html
@@ -0,0 +1,258 @@
+
+
+
+
+
+ 随机背景API
+
+
+
+
+
+ 随机背景 API
+ Cloudflare Workers 接口。先用 build.py 在 public/ 下生成序号 .webp 与 manifest.json,再通过 /api/random、/api/list 调用。
+ 约定:mobile-image 竖屏,desketop-image 横屏。默认响应为 302;加 format=json 时返回 JSON。
+
+ 快捷链接
+
+
+ 接口说明
+
+
+
GET /api/random
+
按设备/方向选一个文件名,302 跳到对应图片。
+
GET /api/random
+
+
+
+
GET /api/random?format=json
+
返回包含 url 等字段的 JSON。
+
GET /api/random?format=json
+
+
+
+
GET /api/random?mode=mobile / ?mode=desktop
+
强制使用竖屏或横屏素材目录。
+
GET /api/random?mode=mobile
+GET /api/random?mode=desktop
+
+
+ 查询参数:mode=auto|mobile|desktop,orientation=portrait|landscape,device=mobile|desktop,format=redirect|json。
+
+ 在线预览
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 响应体
+ {}
+
+
+
+
+
+
+
diff --git a/manifest.json b/public/manifest.json
similarity index 92%
rename from manifest.json
rename to public/manifest.json
index fffe585..69c75a9 100644
--- a/manifest.json
+++ b/public/manifest.json
@@ -1,5 +1,5 @@
{
- "generated_at": "2026-03-28T07:14:07.314011+00:00",
+ "generated_at": "2026-05-18T11:45:45.514521+00:00",
"variants": {
"mobile": {
"folder": "mobile-image",
diff --git a/public/mobile-image/1.webp b/public/mobile-image/1.webp
new file mode 100644
index 0000000..35d69e3
Binary files /dev/null and b/public/mobile-image/1.webp differ
diff --git a/public/mobile-image/2.webp b/public/mobile-image/2.webp
new file mode 100644
index 0000000..689e07f
Binary files /dev/null and b/public/mobile-image/2.webp differ
diff --git a/public/mobile-image/3.webp b/public/mobile-image/3.webp
new file mode 100644
index 0000000..74f13f7
Binary files /dev/null and b/public/mobile-image/3.webp differ
diff --git a/public/mobile-image/4.webp b/public/mobile-image/4.webp
new file mode 100644
index 0000000..95dd2e1
Binary files /dev/null and b/public/mobile-image/4.webp differ
diff --git a/public/mobile-image/5.webp b/public/mobile-image/5.webp
new file mode 100644
index 0000000..f10b9a2
Binary files /dev/null and b/public/mobile-image/5.webp differ
diff --git a/public/mobile-image/6.webp b/public/mobile-image/6.webp
new file mode 100644
index 0000000..a37bf8a
Binary files /dev/null and b/public/mobile-image/6.webp differ
diff --git a/functions/api/list.js b/src/api/list.js
similarity index 65%
rename from functions/api/list.js
rename to src/api/list.js
index f3c34bb..35da14e 100644
--- a/functions/api/list.js
+++ b/src/api/list.js
@@ -1,8 +1,8 @@
import { getVariantPayload, loadManifest } from '../lib/background.js';
-export async function onRequestGet(context) {
- const { request } = context;
- const manifest = await loadManifest(request);
+/** @param {Request} request @param {{ ASSETS?: { fetch(input: RequestInfo, init?: RequestInit): Promise } }} env */
+export async function handleListGet(request, env) {
+ const manifest = await loadManifest(request, env);
return Response.json(
{
@@ -16,6 +16,7 @@ export async function onRequestGet(context) {
);
}
+/** @param {unknown} manifest @param {'mobile' | 'desktop'} variant */
function summarizeVariant(manifest, variant) {
const { folder, images } = getVariantPayload(manifest, variant);
return { folder, count: images.length, images };
diff --git a/functions/api/random.js b/src/api/random.js
similarity index 82%
rename from functions/api/random.js
rename to src/api/random.js
index cff8383..a2dddd8 100644
--- a/functions/api/random.js
+++ b/src/api/random.js
@@ -1,10 +1,10 @@
import { buildAssetUrl, detectVariant, getVariantPayload, loadManifest, pickRandom } from '../lib/background.js';
-export async function onRequestGet(context) {
- const { request } = context;
+/** @param {Request} request @param {{ ASSETS?: { fetch(input: RequestInfo, init?: RequestInit): Promise } }} env */
+export async function handleRandomGet(request, env) {
const url = new URL(request.url);
const format = (url.searchParams.get('format') || 'redirect').toLowerCase();
- const manifest = await loadManifest(request);
+ const manifest = await loadManifest(request, env);
const variant = detectVariant(request);
const { folder, images } = getVariantPayload(manifest, variant);
diff --git a/functions/lib/background.js b/src/lib/background.js
similarity index 89%
rename from functions/lib/background.js
rename to src/lib/background.js
index f254805..f6a345b 100644
--- a/functions/lib/background.js
+++ b/src/lib/background.js
@@ -5,9 +5,11 @@ const FALLBACK_MANIFEST = {
},
};
-export async function loadManifest(request) {
+export async function loadManifest(request, env) {
try {
- const response = await fetch(new URL('/manifest.json', request.url));
+ const url = new URL('/manifest.json', request.url);
+ const response =
+ env && env.ASSETS ? await env.ASSETS.fetch(new Request(url.toString())) : await fetch(url);
if (!response.ok) return FALLBACK_MANIFEST;
return await response.json();
} catch {
diff --git a/src/worker.js b/src/worker.js
new file mode 100644
index 0000000..d97b5c6
--- /dev/null
+++ b/src/worker.js
@@ -0,0 +1,38 @@
+import { handleListGet } from './api/list.js';
+import { handleRandomGet } from './api/random.js';
+
+export default {
+ /**
+ * @param {Request} request
+ * @param {{ ASSETS: { fetch(input: Request | string, init?: RequestInit): Promise } }} env
+ */
+ async fetch(request, env) {
+ const url = new URL(request.url);
+ const pathname = normalizePath(url.pathname);
+
+ if (pathname === '/api/random') return routeApiGet(request, env, handleRandomGet);
+ if (pathname === '/api/list') return routeApiGet(request, env, handleListGet);
+
+ return env.ASSETS.fetch(request);
+ },
+};
+
+/**
+ * @param {string} pathname
+ */
+function normalizePath(pathname) {
+ if (pathname.length > 1 && pathname.endsWith('/')) return pathname.replace(/\/+$/, '');
+ return pathname;
+}
+
+/**
+ * @param {Request} request
+ * @param {{ ASSETS?: { fetch(input: RequestInfo, init?: RequestInit): Promise } }} env
+ * @param {(request: Request, env: object) => Promise | Response} handler
+ */
+async function routeApiGet(request, env, handler) {
+ if (request.method !== 'GET') {
+ return new Response('Method Not Allowed', { status: 405 });
+ }
+ return handler(request, env);
+}
diff --git a/widget.js b/widget.js
deleted file mode 100644
index 8f088ce..0000000
--- a/widget.js
+++ /dev/null
@@ -1,209 +0,0 @@
-(() => {
- const DEFAULT_BASE_URL = (() => {
- try {
- return new URL(document.currentScript?.src || window.location.href).origin;
- } catch {
- return window.location.origin;
- }
- })();
-
- const STYLE = `
- :host {
- display: block;
- width: var(--rbw-width, 100%);
- height: var(--rbw-height, 260px);
- }
- .frame {
- position: relative;
- width: 100%;
- height: 100%;
- overflow: hidden;
- border-radius: var(--rbw-radius, 14px);
- background: #eef2f7;
- border: 1px solid rgba(15, 23, 42, 0.08);
- box-shadow: 0 10px 28px rgba(15, 23, 42, 0.06);
- }
- img {
- position: absolute;
- inset: 0;
- width: 100%;
- height: 100%;
- object-fit: var(--rbw-fit, cover);
- filter: blur(var(--rbw-blur, 0px));
- transform: scale(1.04);
- opacity: 0;
- transition: opacity 180ms ease;
- will-change: filter, opacity, transform;
- }
- .state {
- position: absolute;
- inset: 0;
- display: grid;
- place-items: center;
- padding: 16px;
- text-align: center;
- color: #5b657a;
- font: 13px/1.5 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
- background: linear-gradient(180deg, rgba(255,255,255,.45), rgba(255,255,255,.2));
- backdrop-filter: blur(4px);
- }
- :host([loaded]) .state,
- :host([error]) .state {
- opacity: 0;
- pointer-events: none;
- }
- :host([loaded]) img {
- opacity: 1;
- }
- `;
-
- const DEFAULTS = {
- mode: 'auto',
- blur: '0',
- radius: '14px',
- fit: 'cover',
- height: '260px',
- width: '100%',
- };
-
- class RandomBackgroundWidget extends HTMLElement {
- static observedAttributes = ['src', 'mode', 'blur', 'radius', 'fit', 'height', 'width'];
-
- constructor() {
- super();
- this.attachShadow({ mode: 'open' });
- this._manifest = null;
- this._requestSeq = 0;
- this._boundRefresh = this.refresh.bind(this);
- }
-
- connectedCallback() {
- this.render();
- this.refresh();
- window.addEventListener('resize', this._boundRefresh, { passive: true });
- }
-
- disconnectedCallback() {
- window.removeEventListener('resize', this._boundRefresh);
- }
-
- attributeChangedCallback() {
- if (this.isConnected) {
- this.render();
- this.refresh();
- }
- }
-
- get baseUrl() {
- return (this.getAttribute('src') || DEFAULT_BASE_URL).replace(/\/+$/, '');
- }
-
- get mode() {
- return (this.getAttribute('mode') || DEFAULTS.mode).toLowerCase();
- }
-
- get blur() {
- const raw = Number.parseFloat(this.getAttribute('blur') || DEFAULTS.blur);
- return Number.isFinite(raw) && raw > 0 ? raw : 0;
- }
-
- get radius() {
- return this.getAttribute('radius') || DEFAULTS.radius;
- }
-
- get fit() {
- return this.getAttribute('fit') || DEFAULTS.fit;
- }
-
- get height() {
- return this.getAttribute('height') || DEFAULTS.height;
- }
-
- get width() {
- return this.getAttribute('width') || DEFAULTS.width;
- }
-
- render() {
- if (!this.shadowRoot) return;
- this.style.setProperty('--rbw-width', this.width);
- this.style.setProperty('--rbw-height', this.height);
- this.style.setProperty('--rbw-radius', this.radius);
- this.style.setProperty('--rbw-fit', this.fit);
- this.style.setProperty('--rbw-blur', `${this.blur}px`);
-
- this.shadowRoot.innerHTML = `
-
-
-
![random background]()
-
加载中
-
- `;
- }
-
- async refresh() {
- const seq = ++this._requestSeq;
- const img = this.shadowRoot.querySelector('img');
- const state = this.shadowRoot.querySelector('.state');
-
- try {
- if (!this._manifest) {
- this._manifest = await this.fetchManifest();
- }
- if (seq !== this._requestSeq) return;
-
- const variant = this.resolveVariant();
- const data = this._manifest?.variants?.[variant] || {};
- const folder = data.folder || (variant === 'mobile' ? 'mobile-image' : 'desketop-image');
- const images = Array.isArray(data.images) ? data.images.filter((item) => typeof item === 'string') : [];
-
- if (!images.length) {
- throw new Error(`no images in ${folder}`);
- }
-
- const filename = images[Math.floor(Math.random() * images.length)];
- img.src = `${this.baseUrl}/${folder}/${filename}`;
- img.alt = `random background ${variant}`;
- this.setAttribute('loaded', '');
- this.removeAttribute('error');
- state.textContent = '';
- } catch (error) {
- this.removeAttribute('loaded');
- this.setAttribute('error', '');
- state.textContent = error instanceof Error ? error.message : 'load failed';
- }
- }
-
- resolveVariant() {
- if (this.mode === 'mobile' || this.mode === 'desktop') return this.mode;
-
- const orientation = window.matchMedia('(orientation: portrait)').matches ? 'portrait' : 'landscape';
- const device = this.detectDevice();
-
- if (orientation === 'portrait') return 'mobile';
- if (orientation === 'landscape') return 'desktop';
- return device;
- }
-
- detectDevice() {
- if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') {
- return navigator.userAgentData.mobile ? 'mobile' : 'desktop';
- }
- return /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent) ? 'mobile' : 'desktop';
- }
-
- async fetchManifest() {
- const url = new URL('manifest.json', `${this.baseUrl}/`);
- const response = await fetch(url.toString(), { mode: 'cors', cache: 'no-store' });
- if (!response.ok) {
- throw new Error(`manifest ${response.status}`);
- }
- return await response.json();
- }
- }
-
- if (!customElements.get('random-background-widget')) {
- customElements.define('random-background-widget', RandomBackgroundWidget);
- }
-
- window.RandomBackgroundWidget = RandomBackgroundWidget;
-})();
diff --git a/wrangler.toml b/wrangler.toml
new file mode 100644
index 0000000..f24c20c
--- /dev/null
+++ b/wrangler.toml
@@ -0,0 +1,9 @@
+name = "randbg-api"
+main = "src/worker.js"
+compatibility_date = "2026-03-01"
+
+# 静态前端与 manifest / 图片均放在 public;仅 Worker 脚本被打包(API 体量极小)
+[assets]
+directory = "public"
+binding = "ASSETS"
+run_worker_first = true