chore: drop asuracomic.net from the userscript, CORS allowlist and docs #97
+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
|
||||
# plus Demonic, Comix, Kagane, and the two novel sites. Add/remove as the
|
||||
# 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
|
||||
# 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:
|
||||
|
||||
- `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**.
|
||||
|
||||
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>
|
||||
|
||||
# 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
|
||||
# backend's DATABASE_URL out of it and has no fallback for either.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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 /
|
||||
mobile Chromium), synced to a self-hosted Go backend so bookmarks unify across
|
||||
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>` |
|
||||
|
||||
Notes:
|
||||
- **`asuracomic.net` deep links are dead (re-checked 2026-07-25).** They 301 to
|
||||
the `asurascans.com` **root**, discarding the path, at the edge — before the
|
||||
userscript gets a document — so nothing client-side can rescue them. Reach
|
||||
series through `asurascans.com`. The host stays matched in case the redirect
|
||||
starts preserving paths again.
|
||||
- **`asuracomic.net` is no longer matched (deep links dead, re-checked
|
||||
2026-07-25).** They 301 to the `asurascans.com` **root**, discarding the path,
|
||||
at the edge — before the userscript gets a document — so nothing client-side
|
||||
can rescue them. The backend rejects stored addresses on that host too, since
|
||||
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
|
||||
the adapter strips; Demonic chapter `og:title` is `<Title> Chapter N`.
|
||||
- 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 {
|
||||
return Config{
|
||||
TokenKey: testTokenKey,
|
||||
AllowedOrigins: []string{"https://asuracomic.net", "https://demonicscans.org"},
|
||||
AllowedOrigins: []string{"https://asurascans.com", "https://demonicscans.org"},
|
||||
Port: "8080",
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ func TestAuthAccepted(t *testing.T) {
|
||||
func TestCORSPreflight(t *testing.T) {
|
||||
srv := newTestServer(t)
|
||||
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")
|
||||
rr := httptest.NewRecorder()
|
||||
srv.ServeHTTP(rr, req)
|
||||
@@ -177,7 +177,7 @@ func TestCORSPreflight(t *testing.T) {
|
||||
if rr.Code != http.StatusNoContent {
|
||||
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)
|
||||
}
|
||||
if got := rr.Header().Get("Access-Control-Allow-Methods"); got == "" {
|
||||
@@ -204,11 +204,11 @@ func TestBookmarkRoundTrip(t *testing.T) {
|
||||
key := "asura:solo-leveling-123"
|
||||
in := store.Bookmark{
|
||||
Title: "Solo Leveling",
|
||||
SeriesURL: "https://asuracomic.net/series/solo-leveling-123",
|
||||
Cover: "https://asuracomic.net/cover.jpg",
|
||||
SeriesURL: "https://asurascans.com/series/solo-leveling-123",
|
||||
Cover: "https://asurascans.com/cover.jpg",
|
||||
LastChapter: "Chapter 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)
|
||||
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ services:
|
||||
# Owner's Discord user ID — required. Seeds the owner Reader (the
|
||||
# administrator); every other Reader registers on their first login.
|
||||
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
|
||||
# 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}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// @author you
|
||||
// @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
|
||||
// @match https://asuracomic.net/*
|
||||
// @match https://asurascans.com/*
|
||||
// @match https://demonicscans.org/*
|
||||
// @match https://comix.to/*
|
||||
@@ -112,12 +111,10 @@
|
||||
|
||||
const asura = {
|
||||
site: "asura",
|
||||
// asuracomic.net deep links 301 to the asurascans.com *root*, dropping the
|
||||
// path, and that happens at the edge before this script gets a document —
|
||||
// so those URLs cannot be handled here at all (checked 2026-07-25). It stays
|
||||
// matched in case the redirect starts preserving paths again; until then,
|
||||
// reach series through asurascans.com.
|
||||
matches: (loc) => /(^|\.)asurascans\.com$|(^|\.)asuracomic\.net$/.test(loc.hostname),
|
||||
// asuracomic.net is not matched: its deep links 301 to the asurascans.com
|
||||
// *root* at the edge, discarding the path, so this script never sees a
|
||||
// series document there (re-checked 2026-07-25).
|
||||
matches: (loc) => /(^|\.)asurascans\.com$/.test(loc.hostname),
|
||||
detect(loc) {
|
||||
const path = loc.pathname;
|
||||
// /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");
|
||||
});
|
||||
|
||||
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", () => {
|
||||
const best = asura.latestChapterFromAnchors([
|
||||
{ href: "/comics/solo-leveling-059befe1/chapter/1", text: "Chapter 1" },
|
||||
|
||||
Reference in New Issue
Block a user