diff --git a/.gitignore b/.gitignore
index 013b8d8..7f6a831 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
node_modules/
+.wrangler/
coverage/
.env
.env.*
diff --git a/README.en.md b/README.en.md
index afb83e7..5550025 100644
--- a/README.en.md
+++ b/README.en.md
@@ -8,7 +8,7 @@
-
+
简体中文 · English
diff --git a/README.md b/README.md
index f28d2a6..2e3497e 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-
+
简体中文 · English
diff --git a/test/random-badge-worker.test.mjs b/test/random-badge-worker.test.mjs
new file mode 100644
index 0000000..b3cb44b
--- /dev/null
+++ b/test/random-badge-worker.test.mjs
@@ -0,0 +1,55 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+
+import worker, {
+ createBadgeResponse,
+ pickBadgeValue,
+ renderBadge,
+} from '../worker/random-badge.mjs';
+
+test('random badge selects both supported values at the sample boundary', () => {
+ assert.equal(pickBadgeValue(() => 0), '今天是梁子');
+ assert.equal(pickBadgeValue(() => 0.499999), '今天是梁子');
+ assert.equal(pickBadgeValue(() => 0.5), '今天是梁圣');
+ assert.equal(pickBadgeValue(() => 0.999999), '今天是梁圣');
+});
+
+test('random badge rejects invalid random samples and badge values', () => {
+ assert.throws(() => pickBadgeValue(() => -0.1), RangeError);
+ assert.throws(() => pickBadgeValue(() => 1), RangeError);
+ assert.throws(() => renderBadge('今天是别人'), RangeError);
+});
+
+test('random badge reproduces the Shields layout for either value', () => {
+ for (const value of ['今天是梁子', '今天是梁圣']) {
+ const svg = renderBadge(value);
+
+ assert.match(svg, /width="130" height="20"/);
+ assert.match(svg, //);
+ assert.match(svg, //);
+ assert.match(svg, new RegExp(`滑动变祖器: ${value}`));
+ }
+});
+
+test('worker returns a fresh SVG and explicitly disables every cache layer', async () => {
+ const response = await worker.fetch(new Request('https://badge.example/'));
+ const svg = await response.text();
+
+ assert.equal(response.status, 200);
+ assert.equal(response.headers.get('content-type'), 'image/svg+xml; charset=utf-8');
+ assert.equal(response.headers.get('cache-control'), 'no-store, no-cache, must-revalidate, max-age=0');
+ assert.equal(response.headers.get('cdn-cache-control'), 'no-store');
+ assert.equal(response.headers.get('cloudflare-cdn-cache-control'), 'no-store');
+ assert.equal(response.headers.get('surrogate-control'), 'no-store');
+ assert.match(svg, /今天是梁(?:子|圣)/);
+});
+
+test('worker supports HEAD and rejects state-changing methods', async () => {
+ const headResponse = createBadgeResponse('HEAD');
+ const postResponse = createBadgeResponse('POST');
+
+ assert.equal(headResponse.status, 200);
+ assert.equal(await headResponse.text(), '');
+ assert.equal(postResponse.status, 405);
+ assert.equal(postResponse.headers.get('allow'), 'GET, HEAD');
+});
diff --git a/worker/random-badge.mjs b/worker/random-badge.mjs
new file mode 100644
index 0000000..aa0013b
--- /dev/null
+++ b/worker/random-badge.mjs
@@ -0,0 +1,61 @@
+const BADGE_LABEL = '滑动变祖器';
+const BADGE_VALUES = Object.freeze(['今天是梁子', '今天是梁圣']);
+
+function secureRandom() {
+ const sample = new Uint32Array(1);
+ crypto.getRandomValues(sample);
+ return sample[0] / 0x1_0000_0000;
+}
+
+export function pickBadgeValue(random = secureRandom) {
+ const sample = random();
+
+ if (!Number.isFinite(sample) || sample < 0 || sample >= 1) {
+ throw new RangeError('Random samples must be finite numbers from 0 (inclusive) to 1 (exclusive).');
+ }
+
+ return BADGE_VALUES[Math.floor(sample * BADGE_VALUES.length)];
+}
+
+export function renderBadge(value) {
+ if (!BADGE_VALUES.includes(value)) {
+ throw new RangeError(`Unsupported badge value: ${value}`);
+ }
+
+ const accessibleLabel = `${BADGE_LABEL}: ${value}`;
+
+ return ``;
+}
+
+const NO_CACHE_HEADERS = {
+ 'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
+ 'CDN-Cache-Control': 'no-store',
+ 'Cloudflare-CDN-Cache-Control': 'no-store',
+ 'Content-Type': 'image/svg+xml; charset=utf-8',
+ Expires: '0',
+ Pragma: 'no-cache',
+ 'Surrogate-Control': 'no-store',
+ 'X-Content-Type-Options': 'nosniff',
+};
+
+export function createBadgeResponse(method = 'GET') {
+ if (method !== 'GET' && method !== 'HEAD') {
+ return new Response('Method Not Allowed', {
+ status: 405,
+ headers: {
+ ...NO_CACHE_HEADERS,
+ Allow: 'GET, HEAD',
+ 'Content-Type': 'text/plain; charset=utf-8',
+ },
+ });
+ }
+
+ const body = method === 'HEAD' ? null : renderBadge(pickBadgeValue());
+ return new Response(body, { headers: NO_CACHE_HEADERS });
+}
+
+export default {
+ fetch(request) {
+ return createBadgeResponse(request.method);
+ },
+};
diff --git a/wrangler.jsonc b/wrangler.jsonc
new file mode 100644
index 0000000..b5bcd1d
--- /dev/null
+++ b/wrangler.jsonc
@@ -0,0 +1,8 @@
+{
+ "$schema": "https://raw.githubusercontent.com/cloudflare/workers-sdk/main/packages/wrangler/config-schema.json",
+ "name": "dsh-im-random-badge",
+ "main": "worker/random-badge.mjs",
+ "compatibility_date": "2026-08-18",
+ "workers_dev": true,
+ "preview_urls": false
+}