diff --git a/PRIVACY.md b/PRIVACY.md new file mode 100644 index 0000000..346c2cb --- /dev/null +++ b/PRIVACY.md @@ -0,0 +1,43 @@ +# Privacy Policy + +_Last updated: September 26, 2026_ + +direct-img.link has no accounts and doesn't sell data. This page explains what is collected when you use it. + +## IP addresses + +- Your IP address is used to enforce rate limits. +- For the daily search limit, it's stored only as a hash combined with the current date, along with a counter. These records are periodically deleted once they are older than about a day. +- Cloudflare also uses your IP address for burst rate limiting and security. + +## Search queries and images + +- When a query isn't already cached, it's sent to Brave Search, and to Bing if Brave returns nothing. Searches and image downloads are made from our servers, so your IP address isn't shared with Brave, Bing, or the sites images come from. +- Each query and the image it returned are cached for up to 90 days. Failed searches are remembered for 24 hours. +- Cached images are shared: anyone requesting the same query gets the same image. + +## Operational alerts + +Queries and IP addresses may be included in alerts sent to the maintainer through a push notification service (ntfy), used to monitor usage and abuse. + +## Analytics + +We use [GoatCounter](https://www.goatcounter.com), a privacy-focused analytics service that doesn't use cookies. + +- The homepage loads GoatCounter's script, which records the visit, the referring page, and your screen width. +- Each image request is reported to GoatCounter from our servers, including the query, the referring page (for example, the site embedding the image), your user agent, and your IP address. +- GoatCounter uses your IP address and user agent to work out your country, browser, and operating system, and to count unique visitors. It [doesn't store the IP address or user agent itself](https://www.goatcounter.com/help/sessions). + +## Hosting and third parties + +- The service runs on Cloudflare, which processes all requests under its own [privacy policy](https://www.cloudflare.com/privacypolicy/). +- The homepage embeds GitHub's Sponsor button, which is loaded from GitHub under GitHub's [privacy statement](https://docs.github.com/en/site-policy/privacy-policies/github-general-privacy-statement). +- direct-img.link doesn't set any cookies. + +## Changes + +We may update this policy at any time. The date above shows the latest revision. + +## Contact + +Privacy questions: [contact@direct-img.link](mailto:contact@direct-img.link) diff --git a/README.md b/README.md index c7e5989..40bb649 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,9 @@ Literal slashes (`/`) and dots (`.`) in the URL path are **rejected** to prevent Add this to your system instructions: ``` -When writing markdown, embed relevant images using direct-img.link — a free image search proxy that returns images directly from a URL. But keep it entertaining, for example use it to show reaction faces, anime girl, or fun, not technical. +When writing markdown, embed relevant images/gifs using direct-img.link — a free image search proxy that returns images directly from a URL. But keep it entertaining, for example use it to show reaction faces, girl, or fun, not technical. Example structure: -![Happy Girl](https://direct-img.link/happy+girl) +![Cat Gif](https://direct-img.link/cat+gif) Use images to complement your responses, powered by Brave. ``` @@ -155,6 +155,8 @@ Create in your Cloudflare dashboard: | `SURREAL_USER` | SurrealDB username | Yes | | `SURREAL_PASS` | SurrealDB password | Yes | | `NTFY_URL` | ntfy.sh topic URL for alerts | Optional | +| `GOATCOUNTER_URL` | GoatCounter site URL for image hit analytics (e.g. `https://direct-img.goatcounter.com`) | Optional | +| `GOATCOUNTER_TOKEN` | GoatCounter API token with "Record pageviews" permission | Optional | ### 5. WAF Rules diff --git a/TERMS.md b/TERMS.md new file mode 100644 index 0000000..d18b07a --- /dev/null +++ b/TERMS.md @@ -0,0 +1,50 @@ +# Terms of Use + +_Last updated: September 26, 2026_ + +By using direct-img.link ("the service"), you agree to these terms. If you don't agree, don't use the service. + +## Rights + +- Images come from third-party websites and belong to their owners. We don't own, license, or endorse them. +- You are responsible for making sure you have the rights to any image you display or publish. + +## Results can be wrong, change, or be inappropriate + +- Results are picked automatically and may be inaccurate, misleading, offensive, or explicit. +- The image for a query can change without notice, for example when the cache expires. + +## Acceptable use + +Don't use the service to: + +- Intentionally get around rate limits (for example by rotating IPs or using proxies) +- Bulk-download, scrape, or build datasets from results +- Overload, attack, or disrupt the service +- Search for or distribute illegal content, or break any law + +We may block queries, IPs, or traffic that we believe are abusive. + +## Availability + +We may change, limit, or shut down the service at any time without notice. + +## Copyright and takedown requests + +If an image served by direct-img.link belongs to you and you want it removed, email **[contact@direct-img.link](mailto:contact@direct-img.link)** with: + +- The direct-img.link URL(s) serving the image +- A description of the work and proof that you own it or are authorized to act for the owner +- Your contact information + +## No warranty and limitation of liability + +The service is provided "as is" and "as available", without warranties of any kind. To the maximum extent permitted by law, the maintainers are not liable for any damages arising from your use of the service or of any image it returns. + +## Changes + +We may update these terms at any time. Continued use of the service after changes means you accept the updated terms. + +## Contact + +[contact@direct-img.link](mailto:contact@direct-img.link) diff --git a/functions/[[path]].js b/functions/[[path]].js index a3432bb..adacc1d 100644 --- a/functions/[[path]].js +++ b/functions/[[path]].js @@ -23,6 +23,8 @@ export async function onRequest(context) { if (!query) return jsonResponse(400, { error: "Empty query" }); if (query.length > 200) return jsonResponse(400, { error: "Query too long (max 200 characters)" }); + context.waitUntil(countHit(env, request, query)); + const cacheKey = query; const r2Key = await sha256(query); @@ -209,6 +211,19 @@ async function notify(env,{ title, message, tags, priority }) { } catch {} } +async function countHit(env, request, query) { + if (!env.GOATCOUNTER_URL || !env.GOATCOUNTER_TOKEN) return; + const h = k => request.headers.get(k) || ""; + try { + await fetch(`${env.GOATCOUNTER_URL}/api/v0/count`, { + method: "POST", + headers: { "Content-Type": "application/json", "Authorization": `Bearer ${env.GOATCOUNTER_TOKEN}` }, + body: JSON.stringify({ no_sessions: true, hits: [{ path: `/${query.replace(/ /g, "+")}`, title: query, ref: h("referer"), user_agent: h("user-agent"), ip: h("cf-connecting-ip") }] }), + signal: AbortSignal.timeout(5000) + }); + } catch {} +} + function normalizeQuery(path) { try { return decodeURIComponent(path.replace(/\+/g, " ")).toLowerCase().trim().replace(/[\x00-\x1f]/g, "").replace(/\/+$/, "").replace(/\s+/g, " "); diff --git a/index.html b/index.html index 5b3b568..2372d9a 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,8 @@ direct-img.link | Live images in markdown - +