通过 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,有空我就会改改代码发个新方案


啥也不会 啥也不是