chore: drop asuracomic.net from the userscript, CORS allowlist and docs (#96)
The old domain's deep links 301 to the asurascans.com root, discarding the path, so a page on it never yields a series document and a stored address on it never yields a series page. #94 pinned each Site to one hostname, which already rejects such an address server-side; this removes the remaining references so nothing invites a Reader onto the dead host. Live deploys still carry the origin in their own .env and must drop it there too - the allowlist is read from the environment, not from these defaults.
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@ OWNER_DISCORD_ID=changeme-your-discord-user-id
|
|||||||
# Comma-separated origins allowed to call the API (CORS). Both Asura domains
|
# Comma-separated origins allowed to call the API (CORS). Both Asura domains
|
||||||
# plus Demonic, Comix, Kagane, and the two novel sites. Add/remove as the
|
# plus Demonic, Comix, Kagane, and the two novel sites. Add/remove as the
|
||||||
# sites' hostnames change.
|
# sites' hostnames change.
|
||||||
ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to,https://novelfull.com,https://lightnovelworld.net
|
ALLOWED_ORIGINS=https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to,https://novelfull.com,https://lightnovelworld.net
|
||||||
|
|
||||||
# Password for the bundled Postgres container, and therefore half of the
|
# Password for the bundled Postgres container, and therefore half of the
|
||||||
# DATABASE_URL compose builds for the backend. Generate one:
|
# DATABASE_URL compose builds for the backend. Generate one:
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Guidance for OpenCode (and Claude Code) working in this repo.
|
|||||||
|
|
||||||
Read-progress tracker for two libraries — manga and novels — behind one self-hosted Go backend. Two separate Violentmonkey userscripts inject on-page UI (floating button + slide-in panel) and sync progress, so bookmarks unify across sites and devices:
|
Read-progress tracker for two libraries — manga and novels — behind one self-hosted Go backend. Two separate Violentmonkey userscripts inject on-page UI (floating button + slide-in panel) and sync progress, so bookmarks unify across sites and devices:
|
||||||
|
|
||||||
- `manga-bookmark.user.js` — **asurascans.com** (current domain; asuracomic.net 301s here), **demonicscans.org**, **comix.to**, **kagane.to**.
|
- `manga-bookmark.user.js` — **asurascans.com** (asuracomic.net is dropped: its deep links 301 to the asurascans.com root, discarding the path), **demonicscans.org**, **comix.to**, **kagane.to**.
|
||||||
- `novel-bookmark.user.js` — **novelfull.com**, **lightnovelworld.net**.
|
- `novel-bookmark.user.js` — **novelfull.com**, **lightnovelworld.net**.
|
||||||
|
|
||||||
One backend, one `bookmarks` table: a `kind` column (`manga`|`novel`) splits the libraries and the web UI switches between them. Rows are keyed `<site>:<series_id>`.
|
One backend, one `bookmarks` table: a `kind` column (`manga`|`novel`) splits the libraries and the web UI switches between them. Rows are keyed `<site>:<series_id>`.
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ TOKEN_KEY=<paste output of: openssl rand -hex 32>
|
|||||||
OWNER_DISCORD_ID=<discord user id>
|
OWNER_DISCORD_ID=<discord user id>
|
||||||
|
|
||||||
# CORS allowlist — leave as-is unless a site changes hostname.
|
# CORS allowlist — leave as-is unless a site changes hostname.
|
||||||
ALLOWED_ORIGINS=https://asuracomic.net,https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to
|
ALLOWED_ORIGINS=https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to,https://novelfull.com,https://lightnovelworld.net
|
||||||
|
|
||||||
# Required — password for the bundled Postgres container. Compose builds the
|
# Required — password for the bundled Postgres container. Compose builds the
|
||||||
# backend's DATABASE_URL out of it and has no fallback for either.
|
# backend's DATABASE_URL out of it and has no fallback for either.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Manga Bookmark
|
# Manga Bookmark
|
||||||
|
|
||||||
Track manga read-progress on **asurascans.com** (a.k.a. asuracomic.net),
|
Track manga read-progress on **asurascans.com**,
|
||||||
**demonicscans.org**, **comix.to**, and **kagane.to** from a phone (Bromite /
|
**demonicscans.org**, **comix.to**, and **kagane.to** from a phone (Bromite /
|
||||||
mobile Chromium), synced to a self-hosted Go backend so bookmarks unify across
|
mobile Chromium), synced to a self-hosted Go backend so bookmarks unify across
|
||||||
all four sites and all devices.
|
all four sites and all devices.
|
||||||
@@ -274,11 +274,12 @@ the backend acquires, stores and serves every Cover from its own origin
|
|||||||
| **Kagane** (`kagane.to`) | `/series/<uuid>` | `/series/<uuid>/reader/<bookUuid>` | `<uuid>` |
|
| **Kagane** (`kagane.to`) | `/series/<uuid>` | `/series/<uuid>/reader/<bookUuid>` | `<uuid>` |
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- **`asuracomic.net` deep links are dead (re-checked 2026-07-25).** They 301 to
|
- **`asuracomic.net` is no longer matched (deep links dead, re-checked
|
||||||
the `asurascans.com` **root**, discarding the path, at the edge — before the
|
2026-07-25).** They 301 to the `asurascans.com` **root**, discarding the path,
|
||||||
userscript gets a document — so nothing client-side can rescue them. Reach
|
at the edge — before the userscript gets a document — so nothing client-side
|
||||||
series through `asurascans.com`. The host stays matched in case the redirect
|
can rescue them. The backend rejects stored addresses on that host too, since
|
||||||
starts preserving paths again.
|
the poller pins each Site to one hostname. Reach series through
|
||||||
|
`asurascans.com`.
|
||||||
- Asura `og:title` carries a `Chapter N - Read Online \| Asura Scans` suffix that
|
- Asura `og:title` carries a `Chapter N - Read Online \| Asura Scans` suffix that
|
||||||
the adapter strips; Demonic chapter `og:title` is `<Title> Chapter N`.
|
the adapter strips; Demonic chapter `og:title` is `<Title> Chapter N`.
|
||||||
- Demonic's `<slug>` is identical on `/manga/…` and the canonical `/title/…`
|
- Demonic's `<slug>` is identical on `/manga/…` and the canonical `/title/…`
|
||||||
|
|||||||
+6
-6
@@ -33,7 +33,7 @@ const testCoverBaseURL = "https://bookmarks.test"
|
|||||||
func testConfig() Config {
|
func testConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
TokenKey: testTokenKey,
|
TokenKey: testTokenKey,
|
||||||
AllowedOrigins: []string{"https://asuracomic.net", "https://demonicscans.org"},
|
AllowedOrigins: []string{"https://asurascans.com", "https://demonicscans.org"},
|
||||||
Port: "8080",
|
Port: "8080",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +169,7 @@ func TestAuthAccepted(t *testing.T) {
|
|||||||
func TestCORSPreflight(t *testing.T) {
|
func TestCORSPreflight(t *testing.T) {
|
||||||
srv := newTestServer(t)
|
srv := newTestServer(t)
|
||||||
req := httptest.NewRequest(http.MethodOptions, "/bookmarks/asura:foo-1", nil)
|
req := httptest.NewRequest(http.MethodOptions, "/bookmarks/asura:foo-1", nil)
|
||||||
req.Header.Set("Origin", "https://asuracomic.net")
|
req.Header.Set("Origin", "https://asurascans.com")
|
||||||
req.Header.Set("Access-Control-Request-Method", "PUT")
|
req.Header.Set("Access-Control-Request-Method", "PUT")
|
||||||
rr := httptest.NewRecorder()
|
rr := httptest.NewRecorder()
|
||||||
srv.ServeHTTP(rr, req)
|
srv.ServeHTTP(rr, req)
|
||||||
@@ -177,7 +177,7 @@ func TestCORSPreflight(t *testing.T) {
|
|||||||
if rr.Code != http.StatusNoContent {
|
if rr.Code != http.StatusNoContent {
|
||||||
t.Fatalf("preflight status = %d, want 204", rr.Code)
|
t.Fatalf("preflight status = %d, want 204", rr.Code)
|
||||||
}
|
}
|
||||||
if got := rr.Header().Get("Access-Control-Allow-Origin"); got != "https://asuracomic.net" {
|
if got := rr.Header().Get("Access-Control-Allow-Origin"); got != "https://asurascans.com" {
|
||||||
t.Fatalf("Allow-Origin = %q, want reflected origin", got)
|
t.Fatalf("Allow-Origin = %q, want reflected origin", got)
|
||||||
}
|
}
|
||||||
if got := rr.Header().Get("Access-Control-Allow-Methods"); got == "" {
|
if got := rr.Header().Get("Access-Control-Allow-Methods"); got == "" {
|
||||||
@@ -204,11 +204,11 @@ func TestBookmarkRoundTrip(t *testing.T) {
|
|||||||
key := "asura:solo-leveling-123"
|
key := "asura:solo-leveling-123"
|
||||||
in := store.Bookmark{
|
in := store.Bookmark{
|
||||||
Title: "Solo Leveling",
|
Title: "Solo Leveling",
|
||||||
SeriesURL: "https://asuracomic.net/series/solo-leveling-123",
|
SeriesURL: "https://asurascans.com/series/solo-leveling-123",
|
||||||
Cover: "https://asuracomic.net/cover.jpg",
|
Cover: "https://asurascans.com/cover.jpg",
|
||||||
LastChapter: "Chapter 10",
|
LastChapter: "Chapter 10",
|
||||||
LastChapterNum: 10,
|
LastChapterNum: 10,
|
||||||
LastChapterURL: "https://asuracomic.net/series/solo-leveling-123/chapter/10",
|
LastChapterURL: "https://asurascans.com/series/solo-leveling-123/chapter/10",
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(in)
|
body, _ := json.Marshal(in)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ services:
|
|||||||
# Owner's Discord user ID — required. Seeds the owner Reader (the
|
# Owner's Discord user ID — required. Seeds the owner Reader (the
|
||||||
# administrator); every other Reader registers on their first login.
|
# administrator); every other Reader registers on their first login.
|
||||||
OWNER_DISCORD_ID: ${OWNER_DISCORD_ID:?set OWNER_DISCORD_ID in .env}
|
OWNER_DISCORD_ID: ${OWNER_DISCORD_ID:?set OWNER_DISCORD_ID in .env}
|
||||||
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://asuracomic.net,https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to,https://novelfull.com,https://lightnovelworld.net}
|
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://asurascans.com,https://demonicscans.org,https://comix.to,https://kagane.to,https://novelfull.com,https://lightnovelworld.net}
|
||||||
# The bookmarks database. Host is the compose service name; the password
|
# The bookmarks database. Host is the compose service name; the password
|
||||||
# comes from .env so it is never committed.
|
# comes from .env so it is never committed.
|
||||||
DATABASE_URL: ${DATABASE_URL:-postgres://bookmarks:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/bookmarks?sslmode=disable}
|
DATABASE_URL: ${DATABASE_URL:-postgres://bookmarks:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/bookmarks?sslmode=disable}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// @author you
|
// @author you
|
||||||
// @downloadURL https://bookmark-api.violetcrown.my.id/u/__API_TOKEN__/manga-bookmark.user.js
|
// @downloadURL https://bookmark-api.violetcrown.my.id/u/__API_TOKEN__/manga-bookmark.user.js
|
||||||
// @updateURL https://bookmark-api.violetcrown.my.id/u/__API_TOKEN__/manga-bookmark.user.js
|
// @updateURL https://bookmark-api.violetcrown.my.id/u/__API_TOKEN__/manga-bookmark.user.js
|
||||||
// @match https://asuracomic.net/*
|
|
||||||
// @match https://asurascans.com/*
|
// @match https://asurascans.com/*
|
||||||
// @match https://demonicscans.org/*
|
// @match https://demonicscans.org/*
|
||||||
// @match https://comix.to/*
|
// @match https://comix.to/*
|
||||||
@@ -112,12 +111,10 @@
|
|||||||
|
|
||||||
const asura = {
|
const asura = {
|
||||||
site: "asura",
|
site: "asura",
|
||||||
// asuracomic.net deep links 301 to the asurascans.com *root*, dropping the
|
// asuracomic.net is not matched: its deep links 301 to the asurascans.com
|
||||||
// path, and that happens at the edge before this script gets a document —
|
// *root* at the edge, discarding the path, so this script never sees a
|
||||||
// so those URLs cannot be handled here at all (checked 2026-07-25). It stays
|
// series document there (re-checked 2026-07-25).
|
||||||
// matched in case the redirect starts preserving paths again; until then,
|
matches: (loc) => /(^|\.)asurascans\.com$/.test(loc.hostname),
|
||||||
// reach series through asurascans.com.
|
|
||||||
matches: (loc) => /(^|\.)asurascans\.com$|(^|\.)asuracomic\.net$/.test(loc.hostname),
|
|
||||||
detect(loc) {
|
detect(loc) {
|
||||||
const path = loc.pathname;
|
const path = loc.pathname;
|
||||||
// /comics/<slug-hash>/chapter/<n>
|
// /comics/<slug-hash>/chapter/<n>
|
||||||
|
|||||||
@@ -120,6 +120,14 @@ test("asura.detect returns other for non-series paths", () => {
|
|||||||
assert.equal(asura.detect(loc("https://asurascans.com/bookmarks")).type, "other");
|
assert.equal(asura.detect(loc("https://asurascans.com/bookmarks")).type, "other");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("asura.matches accepts only asurascans.com", () => {
|
||||||
|
assert.equal(asura.matches({ hostname: "asurascans.com" }), true);
|
||||||
|
assert.equal(asura.matches({ hostname: "www.asurascans.com" }), true);
|
||||||
|
// Dead domain: deep links 301 to the asurascans.com root, discarding the path.
|
||||||
|
assert.equal(asura.matches({ hostname: "asuracomic.net" }), false);
|
||||||
|
assert.equal(asura.matches({ hostname: "asurascans.com.evil.example" }), false);
|
||||||
|
});
|
||||||
|
|
||||||
test("asura.latestChapterFromAnchors takes the highest and skips the First Chapter shortcut", () => {
|
test("asura.latestChapterFromAnchors takes the highest and skips the First Chapter shortcut", () => {
|
||||||
const best = asura.latestChapterFromAnchors([
|
const best = asura.latestChapterFromAnchors([
|
||||||
{ href: "/comics/solo-leveling-059befe1/chapter/1", text: "Chapter 1" },
|
{ href: "/comics/solo-leveling-059befe1/chapter/1", text: "Chapter 1" },
|
||||||
|
|||||||
Reference in New Issue
Block a user