mirror of
https://github.com/direct-img/direct-img.link.git
synced 2026-10-17 18:27:52 +00:00
Feat: Ask Brave for 100 results and allow ?i= up to 40 on the main site
free.* and ai.* stay at 20, since Openverse and Wikimedia return ~50 results together. Also note that production's Openverse requests are authenticated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,7 @@ Add `?i=` to serve a later search result instead of the first, e.g. when the fir
|
||||
https://direct-img.link/orange+cat?i=2
|
||||
```
|
||||
|
||||
- `i` is `1`–`20`, default `1`. Invalid values serve `bad.webp`
|
||||
- `i` is `1`–`40`, default `1`. Invalid values serve `bad.webp`
|
||||
- Leading zeros are ignored (`?i=02` = `?i=2`)
|
||||
- Only images that download count, so broken links and deleted photos are skipped and every `i` is a different image. `?i=2` is the 2nd working image
|
||||
- Higher `i` values take a little longer the first time, since the working images before it are downloaded to count them
|
||||
|
||||
@@ -6,7 +6,8 @@ import { aiImage, parseAiSource, DEFAULT_AI_SOURCE } from "./_utils/ai.js";
|
||||
import * as LIMITS from "../assets/rate-limit.js";
|
||||
|
||||
const TTL_SECONDS = 90 * 24 * 60 * 60;
|
||||
const MAX_INDEX = 20;
|
||||
// Highest ?i= per site: 40 on the main site (100 Brave results), 20 on free.* (~50 Openverse + Wikimedia results) and ai.*
|
||||
const MAX_INDEX = { "": 40, free: 20, ai: 20 };
|
||||
// How long a search may hold its query's lock; longer than the 20s image deadline plus the search itself
|
||||
const LOCK_SECONDS = 45;
|
||||
// How long a search's results are kept for the query's other ?i=, which then don't search again or count toward limits
|
||||
@@ -43,7 +44,7 @@ export async function onRequest(context) {
|
||||
const site = ["free", "ai"].find(s => url.hostname.startsWith(`${s}.`)) || "";
|
||||
const free = site === "free", ai = site === "ai";
|
||||
const label = { free: "Free ", ai: "AI " }[site] || "";
|
||||
const i = parseIndex(url.searchParams);
|
||||
const i = parseIndex(url.searchParams, MAX_INDEX[site]);
|
||||
const src = free ? parseFreeSource(url.searchParams) : ai ? parseAiSource(url.searchParams) : null;
|
||||
const extra = [...url.searchParams.keys()].some(k => k !== "i" && !(site && k === "src"));
|
||||
if (!i || (site && !src) || extra) return asset("bad.webp");
|
||||
@@ -278,10 +279,10 @@ COMMIT TRANSACTION;`;
|
||||
return job;
|
||||
}
|
||||
|
||||
// ?i=1..MAX_INDEX (default 1). Returns null if invalid.
|
||||
function parseIndex(searchParams) {
|
||||
// ?i=1..max (default 1). Returns null if invalid.
|
||||
function parseIndex(searchParams, max) {
|
||||
const i = searchParams.get("i") || "1";
|
||||
return /^\d+$/.test(i) && +i >= 1 && +i <= MAX_INDEX ? +i : null;
|
||||
return /^\d+$/.test(i) && +i >= 1 && +i <= max ? +i : null;
|
||||
}
|
||||
|
||||
// Brave results, or Bing's if Brave has none or refuses (e.g. once its spending limit is hit). null if Bing fails too,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// { urls } ([] if none found), or { error } if Brave refused or failed, e.g. once our rate or spending limit is hit
|
||||
export async function braveImageSearch(query, apiKey) {
|
||||
try {
|
||||
const res = await fetch(`https://api.search.brave.com/res/v1/images/search?q=${encodeURIComponent(query)}&count=50&safesearch=off`, {
|
||||
const res = await fetch(`https://api.search.brave.com/res/v1/images/search?q=${encodeURIComponent(query)}&count=100&safesearch=off`, {
|
||||
headers: { "Accept": "application/json", "X-Subscription-Token": apiKey },
|
||||
});
|
||||
if (!res.ok) return { error: `HTTP ${res.status}: ${(await res.text()).slice(0, 300)}` };
|
||||
|
||||
@@ -2,7 +2,8 @@ const API = "https://api.openverse.org/v1";
|
||||
// Uppercase so it can't collide with query cache keys, which are always lowercased
|
||||
const TOKEN_KEY = "OPENVERSE_TOKEN";
|
||||
|
||||
// Tokens expire (~12h); client ID/secret are permanent, so fetch a new token whenever the cached one is gone
|
||||
// Tokens expire (~12h); client ID/secret are permanent, so fetch a new token whenever the cached one is gone.
|
||||
// Production has OPENVERSE_CLIENT_ID/SECRET set, so its requests are authenticated.
|
||||
async function getToken(env) {
|
||||
if (!env.OPENVERSE_CLIENT_ID || !env.OPENVERSE_CLIENT_SECRET) return null;
|
||||
const cached = await env.DIRECT_IMG_CACHE.get(TOKEN_KEY);
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ Use images to complement your responses, powered by Brave.</code></pre>
|
||||
<h2>Usage</h2>
|
||||
<p>Embed directly in your markdown files:</p>
|
||||
<pre><code></code></pre>
|
||||
<p>Not the image you wanted? Add <code>?i=2</code> (up to <code>20</code>) to serve the next one:</p>
|
||||
<p>Not the image you wanted? Add <code>?i=2</code> (up to <code>40</code>) to serve the next one:</p>
|
||||
<pre><code></code></pre>
|
||||
<p style="font-size:0.85rem;margin-top:12px;color:var(--text-muted);">* Note: Literal dots (.) and slashes (/) are rejected to prevent bot abuse. Use <code>%2E</code> or <code>%2F</code> if your query requires them.</p>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user