通过 CloudFlare Workers 和 Github 实现一个完全白嫖的随机图片 API!
根据一些需求的差别有不同的代码实现,受限于篇幅文中只提一种,如果有兴趣的话可以前往查看我的Github仓库,还有很多不同需求的实现
上传图片
新建一个Github仓库,将你的所有图片按需要的分类分好文件夹存放进去,如果不需要分类抽取则不用分类,放仓库根目录或是统一存放在一个文件夹中都行。但需要注意的是,一个文件夹中的图片应该按1.jpg,2.jpg,3.jpg...这样来命名
因为本方案是通过生成给定范围内随机整数的方式来进行抽取,所以图片名称必须是正整数按顺序标号
Cloudflare Workers代码
var urlIndex = "https://raw.githubusercontent.com/Cheshire-Nya/easy-random-image-api/main/html-template/index.html";
//主页模板地址
var url404 = "https://raw.githubusercontent.com/Cheshire-Nya/easy-random-image-api/main/html-template/404.html";
//404模板地址
var imgHost = "https://raw.githubusercontent.com/Cheshire-Nya/easy-random-image-api/main";
//图片地址前部不会发生改变的部分
//用github作为图库应按照此格式"https://raw.githubusercontent.com/<github用户名>/<仓库名>/<分支名>"
var defaultPath = '/'; //现在是仓库根目录(换其他文件夹格式`'/<文件夹名>'`)
//访问的url路径为`/api`或`/api/`时抽图的文件夹
var redirectProxy = 2;
//type=302时返回的链接是否是经过代理的,0 不代理(返回github原链接),1 worker代理,2 ghproxy代理
var maxValues = {
"/": 2, //(defaultPath和对应图片数)现在是仓库根目录,对应2张图,前部需要有`/`
"示例图": 10, //示例图
//中文路径
"demoimg": 5, //demoimg
//英文路径
//其他要抽图的文件夹和对应图片数,不用在名称前加`/`
//其他路径下同理,只需要这样相同格式多写一条键值对即可`'<文件夹名>': <数值>,`
}
//存储键值对:仓库下图片文件夹名称及对应的图片数
var ghproxyUrl = "https://ghproxy.com/";
var min = 1;
var max;
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
function handleRequest(request) {
let nowUrl = new URL(request.url);
let wholePath = nowUrl.pathname;
let urlSearch = nowUrl.search;
if (nowUrl.pathname === '/api' || nowUrl.pathname === '/api/') {
if (nowUrl.search) {
return extractSearch(urlSearch, request);
}
else {
return random(defaultPath);
};
}
else if (nowUrl.pathname === '/') {
return index();
}
else {
return error();
}
}
function extractSearch(urlSearch, request) {
let searchParams = new URLSearchParams(urlSearch);
let id = searchParams.get('id');
let cats = searchParams.getAll('cat');
let type = searchParams.get('type');
if (id && !searchParams.has('cat')) {
let imgName = id;
if (type === 'json') {
return typejson(defaultPath, imgName, request);
}
else if (!searchParams.has('type')) {
return prescriptive(defaultPath, imgName);
}
else {
return error();
}
}
else if (type && !searchParams.has('id') && !searchParams.has('cat')) {
if (type === '302') {
return redirect(defaultPath, request);
}
else if (type === 'json') {
let max = maxValues[defaultPath];
let imgName = Math.floor(Math.random()*(max-min+1)+min);
return typejson(defaultPath, imgName, request);
}
else return error();
}
else if (cats) {
let allCatsValid = true;
for (let i = 0; i < cats.length; i++) {
if (!(cats[i] in maxValues) || maxValues[cats[i]] < 1) {
allCatsValid = false;
break;
} else {
maxValues[cats[i]]--;
}
}
if (allCatsValid) {
if (id) {
let imgName = id;
let imgPath = cats[Math.floor(Math.random() * cats.length)];
if (type === 'json') {
return typejson(imgPath, imgName, request);
}
else if (!searchParams.has('type')) {
return prescriptive(imgPath, imgName);
}
else {
return error();
}
}
else if (!searchParams.has('id')) {
let imgPath = cats[Math.floor(Math.random() * cats.length)];
if (type === '302') {
return redirect(imgPath, request);
}
else if (type === 'json') {
let max = maxValues[imgPath];
let imgName = Math.floor(Math.random()*(max-min+1)+min);
return typejson(imgPath, imgName, request);
}
else if (!searchParams.has('type')) {
return random(imgPath);
}
else return error();
}
else return error();
}
else return error();//不支持的分类
}
else return error();
}
function random(imgPath) {
let max = maxValues[imgPath];
var encodedPath = encodeURIComponent(imgPath);
let imgUrl = imgHost + "/" + encodedPath + "/" + Math.floor(Math.random()*(max-min+1)+min) + ".jpg";
let getimg = new Request(imgUrl);
return fetch(getimg, {
headers: {
'cache-control': 'max-age=0, s-maxage=0',
'content-type': 'image/jpeg',
'Cloudflare-CDN-Cache-Control': 'max-age=0',
'CDN-Cache-Control': 'max-age=0'
},
});
}
function prescriptive(imgPath, imgName) {
if (imgPath in maxValues) {
if (imgName >= 1 && imgName <= maxValues[imgPath]) {
if (imgPath !== defaultPath) {
imgPath = "/" + imgPath; //为非defaultPath路径头部添加'/'
}
let imgUrl = imgHost + imgPath + "/" + imgName + ".jpg";
return fetch(new Request(imgUrl), {
headers: {
'cache-control': 'max-age=0, s-maxage=0',
'content-type': 'image/jpeg',
'Cloudflare-CDN-Cache-Control': 'max-age=0',
'CDN-Cache-Control': 'max-age=0'
},
});
}
else return error();
}
else return error();
}
function redirect(imgPath, request) {
let max = maxValues[imgPath];
let encodedPath = encodeURIComponent(imgPath);
if (redirectProxy === 0) {
const redirectUrl = imgHost + "/" + encodedPath + "/" + Math.floor(Math.random()*(max-min+1)+min) + ".jpg";
return type302(redirectUrl);
}
else if (redirectProxy === 1) {
const nowUrl = new URL(request.url);
const myHost = nowUrl.hostname;
if (imgPath === defaultPath) {
const redirectUrl = "https://" + myHost + "/api" + "?id=" + Math.floor(Math.random()*(max-min+1)+min);
return type302(redirectUrl);
}
else if (maxValues.hasOwnProperty(imgPath) && imgPath !== defaultPath) {
const redirectUrl = "https://" + myHost + "/api" + "?id=" + Math.floor(Math.random()*(max-min+1)+min) + "&cat=" + encodedPath;
return type302(redirectUrl);
}
else return error();
}
else if (redirectProxy === 2) {
const redirectUrl = ghproxyUrl + imgHost + "/" + encodedPath + "/" + Math.floor(Math.random()*(max-min+1)+min) + ".jpg";
return type302(redirectUrl);
}
else return error();
}
function type302(redirectUrl) {
return new Response("", {
status: 302,
headers: {
Location: redirectUrl
}
});
}
function typejson(imgPath, imgName, request) {
if (imgName >= 1 && imgName <= maxValues[imgPath]) {
let nowUrl = new URL(request.url);
let myHost = nowUrl.hostname;
let githubUrl = null;
let workerUrl = null;
let proxyUrl = null;
if (imgPath === defaultPath) {
githubUrl = imgHost + defaultPath + imgName + ".jpg";
workerUrl = "https://" + myHost + "/api" + "?id=" + imgName;
proxyUrl = ghproxyUrl + imgHost + defaultPath + imgName + ".jpg";
}
else {
githubUrl = imgHost + "/" + imgPath + "/" + imgName + ".jpg";
workerUrl = "https://" + myHost + "/api" + "?id=" + imgName + "&cat=" + imgPath;
proxyUrl = ghproxyUrl + imgHost + "/" + imgPath + "/" + imgName + ".jpg";
}
return new Response(
JSON.stringify({
"category": imgPath,
"id": imgName,
"githubUrl": githubUrl,
"workerUrl": workerUrl,
"proxyUrl": proxyUrl
}, null, 2), {
headers: {
'Content-Type': 'application/json'
}
});
}
else return error();
}
async function error() {
let response = await fetch(url404);
response = new Response(response.body, {
status: 404,
statusText: 'Not Found',
headers: { 'Content-Type': 'text/html' }
});
return response
}
async function index() {
let response = await fetch(urlIndex);
response = new Response(response.body, {
status: 200,
statusText: 'OK',
headers: { 'Content-Type': 'text/html' }
});
return response
}
urlIndex
:主页模板的地址
url404
:404页模板的地址
imgHost
:图片仓库的地址,通常为此格式`https://raw.githubusercontent.com/<github用户名>/<仓库名>/<分支名>`
defaultPath
:当访问的url为`https://example.com/api`时抽取图片的文件夹,为/<文件夹名>
maxValues
:用来抽图的文件夹名称和对应的图片数,以键值对形式存储,格式为'<名称>': <数量>
,defaultPath
以及对应数量应写为/<名称>: <数量>
cloudflare免费提供的worker.dev域名在国内无法正常解析,所以通常需要绑自定义域名
写在最后
刚开始学,逻辑写的可能不太好
更详细的说明、演示站点和很多不同的代码实现都在我的仓库里,比如区分图片适合的设备,返回json,302跳转,或是不用重命名图片自行编写图片信息json文件。
如果你有什么疑问或者好的建议,可以在仓库里提issue,有空我就会改改代码发个新方案
Comments 1 条评论
博客作者 Bensz
感觉还不错 (ฅ´ω`ฅ) 学习一下