Feat: Add GoatCounter analytics, Terms & Privacy pages, prompt copy button

- Update AI system prompt example (README + index)
- Add copy button for the prompt on the index page
- Add GoatCounter to homepage; report image hits server-side via
  waitUntil (optional GOATCOUNTER_URL/GOATCOUNTER_TOKEN)
- Add TERMS.md and PRIVACY.md, linked in the index footer
- Remove dead c.planetrenox.com tracker

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-26 08:51:48 -07:00
co-authored by Claude Opus 5.5
parent 2976236c00
commit 023f4d132c
5 changed files with 133 additions and 6 deletions
+43
View File
@@ -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)
+4 -2
View File
@@ -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
+50
View File
@@ -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)
+15
View File
@@ -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, " ");
+21 -4
View File
@@ -5,7 +5,8 @@
<title>direct-img.link | Live images in markdown</title>
<meta name="description" content="Embed live images in markdown powered by search. No APIs, no tokens, just search.">
<link rel="icon" href="/assets/favicon.ico" sizes="16x16 32x32 48x48"><link rel="icon" href="/assets/logo.svg" type="image/svg+xml">
<script defer src="https://c.planetrenox.com/tracker.js"></script>
<script data-goatcounter="https://direct-img.goatcounter.com/count"
async src="//gc.zgo.at/count.js"></script>
<style>
:root{--bg:#fff;--text:#1a1a1a;--text-muted:#666;--accent:#0070f3;--warning:#eb5757;--code-bg:#f5f5f5;--border:#eaeaea;--radius:8px;}
*{box-sizing:border-box;}
@@ -21,6 +22,10 @@
section{margin-bottom:48px;}
h2{font-size:1.5rem;font-weight:700;margin-bottom:16px;border-bottom:1px solid var(--border);padding-bottom:8px;}
pre{background:var(--code-bg);padding:16px;border-radius:var(--radius);overflow-x:auto;font-size:0.9rem;border:1px solid var(--border);}
.copy-wrap{position:relative;}
.copy-wrap pre{white-space:pre-wrap;padding-right:72px;}
.copy{position:absolute;top:8px;right:8px;font:inherit;font-size:0.75rem;font-weight:600;padding:4px 10px;border:1px solid var(--border);border-radius:6px;background:var(--bg);color:var(--text-muted);cursor:pointer;}
.copy:hover{color:var(--accent);border-color:var(--accent);}
code{font-family:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;}
table{width:100%;border-collapse:collapse;margin:16px 0;font-size:0.9rem;}
th,td{text-align:left;padding:12px;border-bottom:1px solid var(--border);}
@@ -50,10 +55,13 @@
<section>
<h2>For AI System Prompts</h2>
<p>Add this to your system instructions:</p>
<pre><code>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.
<div class="copy-wrap">
<button class="copy" onclick="copyPrompt(this)">Copy</button>
<pre><code id="prompt">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.</code></pre>
</div>
</section>
<section>
@@ -87,7 +95,16 @@ Use images to complement your responses, powered by Brave.</code></pre>
<footer>
<div>Built for the AI era.</div>
<div><a href="https://github.com/direct-img/direct-img.link">GitHub</a></div>
<div><a href="https://github.com/direct-img/direct-img.link/blob/main/TERMS.md">Terms</a> · <a href="https://github.com/direct-img/direct-img.link/blob/main/PRIVACY.md">Privacy</a> · <a href="https://github.com/direct-img/direct-img.link">GitHub</a></div>
</footer>
<script>
const copyPrompt=b=>{
const p=document.getElementById('prompt');
(navigator.clipboard?.writeText(p.textContent)??Promise.reject()).then(()=>{
b.textContent='Copied!';
setTimeout(()=>b.textContent='Copy',1500);
},()=>getSelection().selectAllChildren(p));
};
</script>
</body>
</html>