Compare commits
5
Commits
b2ec0951f4
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f657bea81 | ||
|
|
76bd7d0b70 | ||
|
|
f32eb93289 | ||
|
|
5b8109b556 | ||
|
|
ec80e76cdc |
@@ -78,7 +78,7 @@ body:
|
||||
id: where
|
||||
attributes:
|
||||
label: Where are you running it, and in what browser?
|
||||
placeholder: stereotypicalcat.github.io/gacha-event-tracker — Firefox 141 on Android
|
||||
placeholder: localhost:3000 (container image) — Firefox 141 on Android
|
||||
validations:
|
||||
required: false
|
||||
|
||||
|
||||
+72
-61
@@ -20,9 +20,9 @@ jobs:
|
||||
name: Typecheck, test, feed
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: https://github.com/oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3"
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
# Restore-only: refresh.yml owns writing this cache, and a miss just
|
||||
# returns the pre-existing fallback behaviour.
|
||||
- name: Restore refresh bookkeeping
|
||||
uses: actions/cache/restore@v4
|
||||
uses: https://github.com/actions/cache/restore@v4
|
||||
with:
|
||||
path: snapshots/*.state.json
|
||||
key: refresh-state-
|
||||
@@ -105,8 +105,8 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: check
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
- uses: https://github.com/oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3"
|
||||
- run: bun install --frozen-lockfile
|
||||
@@ -114,19 +114,19 @@ jobs:
|
||||
# This is the job whose output is deployed, so this is the one that must
|
||||
# see the refresh bookkeeping; see the same step in `check`.
|
||||
- name: Restore refresh bookkeeping
|
||||
uses: actions/cache/restore@v4
|
||||
uses: https://github.com/actions/cache/restore@v4
|
||||
with:
|
||||
path: snapshots/*.state.json
|
||||
key: refresh-state-
|
||||
restore-keys: refresh-state-
|
||||
|
||||
- name: Build
|
||||
# Pages serves from /<repo>/, so the app is built with a matching base
|
||||
# href. Built at the domain root it would 404 on every asset.
|
||||
env:
|
||||
BASE_PATH: /${{ github.event.repository.name }}/
|
||||
# Built at the domain root, because the container image is what gets
|
||||
# deployed and `serve.ts` serves from `/`. A BASE_PATH belongs here only
|
||||
# if this artefact is ever hosted under a subpath again — it was set to
|
||||
# /<repo>/ for GitHub Pages, which this pipeline no longer deploys to.
|
||||
run: bun run build
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: https://github.com/actions/upload-artifact@v4
|
||||
with:
|
||||
name: site
|
||||
path: public/
|
||||
@@ -141,68 +141,79 @@ jobs:
|
||||
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
|
||||
permissions:
|
||||
contents: read
|
||||
# Kept as a statement of intent, but it does not grant what this job
|
||||
# needs: on Gitea the registry does not accept the Actions token at all,
|
||||
# so `packages: write` on it authorises nothing. See the login step.
|
||||
packages: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: https://github.com/docker/setup-buildx-action@v3
|
||||
|
||||
- name: Lowercase image name
|
||||
id: img
|
||||
# GHCR rejects any uppercase in a repository name, and
|
||||
# github.repository preserves the owner's casing verbatim.
|
||||
run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
||||
# Gitea's container registry, like GHCR before it, rejects any uppercase
|
||||
# in a repository name, and github.repository preserves the owner's
|
||||
# casing verbatim. The host is read from github.server_url rather than
|
||||
# hardcoded, so a fork on another Gitea instance pushes to its own
|
||||
# registry instead of this one.
|
||||
env:
|
||||
SERVER_URL: ${{ github.server_url }}
|
||||
run: |
|
||||
host="${SERVER_URL#https://}"
|
||||
host="${host#http://}"
|
||||
host="${host%%/*}"
|
||||
# Two outputs on purpose: login wants the bare host, the tags want the
|
||||
# full path. Passing the full path as the registry authenticates
|
||||
# against a host that does not exist.
|
||||
echo "host=${host}" >> "$GITHUB_OUTPUT"
|
||||
echo "name=${host}/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- uses: docker/login-action@v3
|
||||
# Why not `secrets.GITHUB_TOKEN`, which is what this was and what the
|
||||
# GHCR version used: a Gitea package belongs to a **user or org owner,
|
||||
# not to a repository**, so the ephemeral repo-scoped Actions token
|
||||
# carries no authority over it and the registry refuses it. GitLab hands
|
||||
# out working per-job registry credentials ($CI_REGISTRY_PASSWORD, which
|
||||
# .gitlab-ci.yml uses) and Gitea deliberately does not, so this needs a
|
||||
# real access token with package write scope, stored as REGISTRY_TOKEN.
|
||||
#
|
||||
# The username must be the token's owner, hence repository_owner rather
|
||||
# than github.actor: the image is pushed into the owner's namespace, and
|
||||
# actor is whoever happened to trigger the run — a different person on a
|
||||
# dispatch, and the wrong account entirely on a fork.
|
||||
- name: Check the registry credential exists
|
||||
# Without this the failure is a bare `unauthorized` from the daemon,
|
||||
# which says nothing about what to do. It also tells us something we do
|
||||
# not currently know: whether the old token was rejected or was simply
|
||||
# never set.
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
run: |
|
||||
if [ -z "${REGISTRY_TOKEN:-}" ]; then
|
||||
echo "::error::REGISTRY_TOKEN is not set. Create an access token at" \
|
||||
"${{ github.server_url }}/user/settings/applications with the" \
|
||||
"package read+write scope, then add it to this repository under" \
|
||||
"Settings → Actions → Secrets as REGISTRY_TOKEN."
|
||||
exit 1
|
||||
fi
|
||||
echo "REGISTRY_TOKEN is set (${#REGISTRY_TOKEN} chars)"
|
||||
|
||||
- uses: https://github.com/docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
registry: ${{ steps.img.outputs.host }}
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
|
||||
- uses: docker/build-push-action@v6
|
||||
- uses: https://github.com/docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: |
|
||||
${{ steps.img.outputs.name }}:latest
|
||||
${{ steps.img.outputs.name }}:${{ github.sha }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
# Deploying to Pages needs two one-time steps that CI cannot do for itself:
|
||||
#
|
||||
# 1. Settings → Pages → Source: "GitHub Actions".
|
||||
# The default GITHUB_TOKEN cannot do this. Creating a Pages site needs
|
||||
# `administration: write`, which is not a permission a workflow can grant
|
||||
# GITHUB_TOKEN, so `configure-pages` with enablement: true fails with
|
||||
# "Resource not accessible by integration".
|
||||
# 2. Settings → Secrets and variables → Actions → Variables:
|
||||
# set DEPLOY_PAGES to "true".
|
||||
#
|
||||
# Gated on that variable so the pipeline stays green for anyone who does not
|
||||
# want Pages, rather than failing on every push forever.
|
||||
pages:
|
||||
name: Deploy to Pages
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: >-
|
||||
github.ref == 'refs/heads/main' &&
|
||||
github.event_name != 'pull_request' &&
|
||||
vars.DEPLOY_PAGES == 'true'
|
||||
permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deploy.outputs.page_url }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: site
|
||||
path: public
|
||||
- uses: actions/configure-pages@v5
|
||||
- uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: public
|
||||
- id: deploy
|
||||
uses: actions/deploy-pages@v4
|
||||
# Registry cache rather than `type=gha`. Gitea does expose the Actions
|
||||
# cache API, but buildx's gha backend is the least reliable corner of
|
||||
# that compatibility surface, and we are already authenticated to a
|
||||
# registry that can hold the layers.
|
||||
cache-from: type=registry,ref=${{ steps.img.outputs.name }}:buildcache
|
||||
cache-to: type=registry,ref=${{ steps.img.outputs.name }}:buildcache,mode=max
|
||||
|
||||
@@ -22,9 +22,9 @@ on:
|
||||
type: string
|
||||
default: ""
|
||||
# `force` is refused on the scheduled run and only works here, on a
|
||||
# dispatch, because a dispatch is a person and GitHub records which one —
|
||||
# a schedule that forces every cycle is just a shorter interval with extra
|
||||
# steps. Prefer pairing it with `only`.
|
||||
# dispatch, because a dispatch is a person and the forge records which one
|
||||
# — a schedule that forces every cycle is just a shorter interval with
|
||||
# extra steps. Prefer pairing it with `only`.
|
||||
force:
|
||||
description: "Ask sources before their 6h floor is up (one run only)"
|
||||
type: boolean
|
||||
@@ -51,23 +51,22 @@ jobs:
|
||||
refresh:
|
||||
name: Fetch sources and rebuild the feed
|
||||
runs-on: ubuntu-latest
|
||||
# A fork must not point this at the wikis on a schedule. Same shape as the
|
||||
# DEPLOY_PAGES gate in ci.yml: off by default for anyone but this repo,
|
||||
# while a fork owner can still dispatch it by hand and take responsibility.
|
||||
# A fork must not point this at the wikis on a schedule: off by default for
|
||||
# anyone but this repo, while a fork owner can still dispatch it by hand and
|
||||
# take responsibility.
|
||||
if: >-
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
github.repository == 'StereotypicalCat/gacha-event-tracker'
|
||||
github.repository == 'lucasw89/gacha-event-tracker'
|
||||
permissions:
|
||||
# Commit the refreshed snapshots.
|
||||
contents: write
|
||||
# Dispatch ci.yml afterwards: a push made with GITHUB_TOKEN deliberately
|
||||
# does not trigger other workflows, so without this the fresh data would
|
||||
# sit in the repo undeployed until someone pushed by hand.
|
||||
# Dispatch ci.yml afterwards; see that step for why it is still here on a
|
||||
# forge that does not suppress triggers on a token-made push.
|
||||
actions: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: https://github.com/actions/checkout@v4
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
- uses: https://github.com/oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.3"
|
||||
|
||||
@@ -79,7 +78,7 @@ jobs:
|
||||
# record of the last check — the twelve-hour schedule still keeps us well
|
||||
# inside the etiquette floor.
|
||||
- name: Restore refresh bookkeeping
|
||||
uses: actions/cache/restore@v4
|
||||
uses: https://github.com/actions/cache/restore@v4
|
||||
with:
|
||||
path: snapshots/*.state.json
|
||||
key: refresh-state-
|
||||
@@ -123,7 +122,7 @@ jobs:
|
||||
# requests we did make, lost. `run_attempt` increments per attempt.
|
||||
- name: Save refresh bookkeeping
|
||||
if: always()
|
||||
uses: actions/cache/save@v4
|
||||
uses: https://github.com/actions/cache/save@v4
|
||||
with:
|
||||
path: snapshots/*.state.json
|
||||
key: refresh-state-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
@@ -154,8 +153,8 @@ jobs:
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config user.name "gitea-actions[bot]"
|
||||
git config user.email "gitea-actions[bot]@noreply.lucaswinther.info"
|
||||
git add -- snapshots
|
||||
git commit -m "chore(data): refresh source snapshots" \
|
||||
-m "Automated fetch from ${{ github.workflow }} run ${{ github.run_id }}."
|
||||
@@ -176,14 +175,32 @@ jobs:
|
||||
echo "::error::could not push refreshed snapshots after 3 attempts"
|
||||
exit 1
|
||||
|
||||
# ci.yml owns typecheck, tests, the feed sanity check, the image and the
|
||||
# Pages deploy. Dispatching it is how the refreshed data reaches the site
|
||||
# ci.yml owns typecheck, tests, the feed sanity check and the image.
|
||||
# Dispatching it is how the refreshed data reaches the deployed container
|
||||
# without any of that logic being copied here.
|
||||
#
|
||||
# There is no `gh` on a Gitea runner, so this is the REST dispatch the CLI
|
||||
# would have made. Note the difference from GitHub that makes this step
|
||||
# look redundant: Gitea does *not* suppress workflow triggers on a push
|
||||
# made with the Actions token, so the commit above will usually have
|
||||
# started ci.yml already. Dispatching anyway is deliberate — it is the only
|
||||
# thing that guarantees publication if that behaviour is ever configured
|
||||
# off, and a duplicate run is harmless because ci.yml's concurrency group
|
||||
# is per-ref with cancel-in-progress, so the second supersedes the first.
|
||||
- name: Publish the refreshed feed
|
||||
if: steps.diff.outputs.changed == 'true' && inputs.dry_run != true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gh workflow run ci.yml --ref "${{ github.ref_name }}"
|
||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
API: ${{ github.server_url }}/api/v1
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -sS --fail-with-body -X POST \
|
||||
-H "Authorization: token $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
"$API/repos/$REPO/actions/workflows/ci.yml/dispatches" \
|
||||
-d "{\"ref\":\"$BRANCH\"}"
|
||||
|
||||
# A source that has failed three cycles running is broken, not down: that
|
||||
# game's calendar has been built from a checked-in fixture for a day and a
|
||||
|
||||
@@ -97,7 +97,7 @@ bun run parse endfield-wikigg-events fixtures/endfield/wikigg-events-2026-08-15.
|
||||
bun test test/dates.test.ts
|
||||
bun test --test-name-pattern "year-less"
|
||||
|
||||
# Hosting under a subpath (GitHub Pages)
|
||||
# Hosting under a subpath (CI does not set this — the image serves from /)
|
||||
BASE_PATH=/gacha-event-tracker/ bun run build
|
||||
```
|
||||
|
||||
@@ -348,11 +348,11 @@ AI-training crawlers. Our use is a low-rate personal aggregator with attribution
|
||||
training, and no `User-agent: *` rule applies to our paths. Keep it that way: do not raise the fetch
|
||||
rate, and do not add an LLM that consumes page content.
|
||||
|
||||
**game8.co does not answer a GitHub Actions runner** (confirmed 2026-08-17). Its edge returns
|
||||
**game8.co did not answer a GitHub Actions runner** (confirmed 2026-08-17). Its edge returned
|
||||
`202 Accepted` with a bot-management body to every one of the nine game8 sources, from the first
|
||||
scheduled cycle onward — `last confirmed: never` — while the same URLs return `200` and parse
|
||||
cleanly from a normal address. So `robots.txt` permits us and the network does not, and those nine
|
||||
games have only ever been built from checked-in fixtures in CI.
|
||||
cleanly from a normal address. So `robots.txt` permitted us and the network did not, and those nine
|
||||
games had only ever been built from checked-in fixtures in CI.
|
||||
|
||||
The per-host spacing above does not fix this and was not meant to: a 202 on the very first request
|
||||
of a cycle is address reputation, not rate. **Do not work around it.** Browser-shaped headers, a
|
||||
@@ -361,6 +361,16 @@ same reason `uma.moe` was declined below — and unlike `uma.moe` we would be do
|
||||
`robots.txt` was welcoming, which makes it worse, not better. The legitimate options are to run the
|
||||
refresh from an address game8 will serve, or to find those games another source.
|
||||
|
||||
**CI moved to a Gitea runner on 2026-09-16, which is the first of those two options — and its result
|
||||
is not yet known.** A different address is exactly the variable this failure turns on, so the nine
|
||||
game8 sources may now refresh on schedule, or may not. Nothing here should be rewritten on the
|
||||
strength of that hope: the tense above stays past because the GitHub evidence is real and the Gitea
|
||||
evidence does not exist yet. Establish it the same way it was established before — let a cycle run,
|
||||
then read `git log` on `snapshots/` for which hosts actually landed — and only then update this
|
||||
section, `README.md` § Status, `docs/SOURCES.md` § Method and `docs/FEEDBACK.md`'s P0 row together.
|
||||
A single manual `--only` dispatch is enough to answer it for one source; note that `--dry-run`
|
||||
cannot, because it makes no requests at all by design.
|
||||
|
||||
A source whose ToS forbids automated access does not get an adapter. Flag it and ask.
|
||||
|
||||
**Sources assessed and declined** (2026-08-17, extended 2026-08-19), so these are not
|
||||
|
||||
@@ -26,23 +26,33 @@ review queue are specified but not built.
|
||||
| Input sanitization at the ingest boundary | Built, tested |
|
||||
| Scheduled refresh — robots, snapshots, commit-on-change | Built, tested offline |
|
||||
| Web interface, daily checklists, offline support | Built |
|
||||
| Static server, Docker image, GitHub + GitLab CI | Built |
|
||||
| Static server, Docker image, Gitea + GitLab CI | Built |
|
||||
| SQLite, review queue | Specified in `docs/`, not built |
|
||||
|
||||
The feed is still a static JSON file rather than a database read: the refresh job commits the raw
|
||||
pages it fetched, CI rebuilds the feed from them, and a clean checkout with no snapshots falls back
|
||||
to the checked-in fixtures — so the build stays offline and reproducible either way.
|
||||
|
||||
**The scheduled refresh does not reach every source.** game8.co answers the GitHub Actions runner
|
||||
with a bot-management `202` instead of the page, so the nine Game8 sources only move when someone
|
||||
runs `bun run refresh` by hand from an address it will talk to. The wiki sources refresh on schedule
|
||||
**The scheduled refresh does not reach every source.** game8.co answered the GitHub Actions runner
|
||||
with a bot-management `202` instead of the page, so the nine Game8 sources only moved when someone
|
||||
ran `bun run refresh` by hand from an address it will talk to. The wiki sources refresh on schedule
|
||||
as intended. `docs/SOURCES.md` records which hosts answer the runner and which do not; the footer
|
||||
shows when each lane was last refreshed, so a stale one is visible rather than silent.
|
||||
|
||||
Since the move to Gitea the refresh runs from a **different address**, which is one of the two
|
||||
remedies `AGENTS.md` § Scraping conduct allows for this. Whether game8.co serves that address is
|
||||
**not yet established** — it needs one real single-source run on the runner, and until that has
|
||||
happened the paragraph above still describes what we know.
|
||||
|
||||
## Try it
|
||||
|
||||
Live at **<https://stereotypicalcat.github.io/gacha-event-tracker/>**, deployed from `main` by the
|
||||
Pages job below.
|
||||
Deployed from `main` as a container image, published to
|
||||
`gitea.lucaswinther.info/lucasw89/gacha-event-tracker:latest` by the CI job below. Run it with
|
||||
`docker run -p 3000:3000 gitea.lucaswinther.info/lucasw89/gacha-event-tracker:latest` and open
|
||||
<http://localhost:3000>.
|
||||
|
||||
> The previous GitHub Pages deploy at <https://stereotypicalcat.github.io/gacha-event-tracker/> is no
|
||||
> longer updated by this pipeline; it holds whatever `main` looked like when the repo moved.
|
||||
|
||||
Or run it yourself:
|
||||
|
||||
@@ -298,9 +308,9 @@ page is sanitized at the ingest boundary before it reaches the feed, the browser
|
||||
|
||||
## Found a problem, or want something?
|
||||
|
||||
- **[Report a problem](https://github.com/StereotypicalCat/gacha-event-tracker/issues/new?template=bug_report.yml)**
|
||||
- **[Report a problem](https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker/issues/new?template=bug_report.yml)**
|
||||
— a wrong date, a missing event, a lost tick, anything the app got wrong.
|
||||
- **[Request a feature](https://github.com/StereotypicalCat/gacha-event-tracker/issues/new?template=feature_request.yml)**
|
||||
- **[Request a feature](https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker/issues/new?template=feature_request.yml)**
|
||||
— including a game it does not cover yet, which is the most common ask by a distance.
|
||||
|
||||
Both are forms rather than a blank box, for one reason: nothing you mark, type or tick ever leaves
|
||||
@@ -331,15 +341,38 @@ Blank issues are still open for anything that fits neither form. The templates t
|
||||
|
||||
Both `.github/workflows/ci.yml` and `.gitlab-ci.yml` run the same gates on every push — typecheck,
|
||||
tests, and a feed sanity check — then build and publish a container image from the default branch.
|
||||
GitHub Actions can also deploy to Pages, but that needs two one-time steps it cannot do for itself —
|
||||
the default `GITHUB_TOKEN` is not allowed to create a Pages site:
|
||||
|
||||
1. **Settings → Pages → Source: GitHub Actions.**
|
||||
2. **Settings → Secrets and variables → Actions → Variables:** add `DEPLOY_PAGES` = `true`.
|
||||
The workflows run on **Gitea Actions**, not GitHub's. They stay under `.github/workflows/` because
|
||||
Gitea reads that directory natively, and because `test/refresh.test.ts` asserts on those two files by
|
||||
path — three defects that live in YAML and fail silently otherwise. Two consequences worth knowing
|
||||
before editing them:
|
||||
|
||||
Until then the `pages` job is skipped and the pipeline stays green. Pages is unavailable for private
|
||||
repositories on the free plan. Both steps are done here, and the deploy lands at
|
||||
<https://stereotypicalcat.github.io/gacha-event-tracker/>.
|
||||
- **`uses:` steps are written as full URLs** (`https://github.com/actions/checkout@v4`). A bare
|
||||
`actions/checkout@v4` resolves against the instance's `DEFAULT_ACTIONS_URL`, which is configuration
|
||||
we do not control from here. The runner needs outbound access to github.com to fetch them.
|
||||
- **The image is the deploy artefact.** There is no `pages` job: Gitea has no Pages equivalent, so
|
||||
the container image published to Gitea's registry is what gets deployed. `bun run build` therefore
|
||||
runs without `BASE_PATH` — the image serves from `/`.
|
||||
|
||||
The image lands at `gitea.lucaswinther.info/lucasw89/gacha-event-tracker`, tagged `latest` and with
|
||||
the commit SHA. The registry host is derived from `github.server_url` rather than hardcoded, so a
|
||||
fork on another instance pushes to its own registry.
|
||||
|
||||
**Pushing the image needs one one-time step CI cannot do for itself.** A Gitea package belongs to a
|
||||
*user or org owner*, not to a repository, so the ephemeral Actions token has no authority over it and
|
||||
the registry refuses it — `secrets.GITHUB_TOKEN` does not work here, unlike on GHCR and unlike
|
||||
GitLab's `$CI_REGISTRY_PASSWORD`. So:
|
||||
|
||||
1. Create an access token at **Settings → Applications** with the **package** read + write scope.
|
||||
2. Add it to the repository under **Settings → Actions → Secrets** as **`REGISTRY_TOKEN`**.
|
||||
|
||||
The `image` job checks for that secret before it tries to log in, and fails with those instructions
|
||||
rather than a bare `unauthorized` from the Docker daemon. The username it logs in with is
|
||||
`github.repository_owner`, not `github.actor` — the image goes to the owner's namespace, and the
|
||||
actor is merely whoever triggered the run.
|
||||
|
||||
One consequence of packages being owner-scoped: the pushed image is **not** automatically associated
|
||||
with this repository. Linking it is a manual step on the package's own settings page.
|
||||
|
||||
The feed job fails if the event count collapses, or if any single source parses to nothing — nine
|
||||
healthy sources hide a tenth that has gone quiet, and the total stays comfortably over the floor
|
||||
@@ -354,13 +387,19 @@ pipeline always means the code changed rather than a wiki being down.
|
||||
|
||||
### Refreshing the data
|
||||
|
||||
GitHub Actions only — the GitLab pipeline still runs the gates, but nothing there fetches.
|
||||
Gitea Actions only — the GitLab pipeline still runs the gates, but nothing there fetches.
|
||||
`.github/workflows/refresh.yml` runs `bun run refresh` twice a day (and on demand, with a dry-run
|
||||
input). It fetches each source at most once per cycle, and **commits only when a page's bytes
|
||||
actually changed** — a `304`, an identical body, or a fetch that fails to parse all leave the
|
||||
working tree clean and produce no commit. When something did change it commits the raw snapshots and
|
||||
dispatches `ci.yml`, which typechecks, tests, rebuilds the feed and deploys through the path that
|
||||
already existed; none of that logic is duplicated.
|
||||
dispatches `ci.yml`, which typechecks, tests, rebuilds the feed and republishes the image through the
|
||||
path that already existed; none of that logic is duplicated.
|
||||
|
||||
That dispatch is a REST call rather than `gh workflow run`, there being no `gh` on a Gitea runner.
|
||||
It is also, strictly, belt and braces: unlike GitHub, Gitea does **not** suppress workflow triggers
|
||||
on a push made with the Actions token, so the snapshot commit will usually have started `ci.yml`
|
||||
already. A duplicate run is harmless — `ci.yml`'s concurrency group is per-ref with
|
||||
`cancel-in-progress`, so the second supersedes the first.
|
||||
|
||||
A body that yields zero events is rejected and the previous snapshot kept, so a wiki redesign shows
|
||||
up as a stale timestamp rather than an empty calendar. One source being down is a warning; every
|
||||
|
||||
+10
-6
@@ -37,7 +37,7 @@ are no users.
|
||||
```
|
||||
|
||||
**That is the design, and the scheduler half of it is not built yet.** What runs today is
|
||||
`scripts/refresh-sources.ts` on a GitHub Actions cron, writing snapshots to disk, and a feed built
|
||||
`scripts/refresh-sources.ts` on a Gitea Actions cron, writing snapshots to disk, and a feed built
|
||||
from those files rather than from SQLite — see § Today. The stages, the layering and the review gate
|
||||
are unchanged by that; only what wakes them up and where they land is.
|
||||
|
||||
@@ -230,8 +230,10 @@ gates and publish it.
|
||||
|
||||
Assets resolve against a `<base href>` substituted at build time, the feed URL resolves against
|
||||
`document.baseURI` so deep links work, and the service worker derives its paths from its own
|
||||
registration scope. `BASE_PATH=/gacha-event-tracker/ bun run build` for GitHub Pages; without it a
|
||||
subpath deploy 404s on every asset.
|
||||
registration scope. `BASE_PATH=/gacha-event-tracker/ bun run build` for any host that serves the app
|
||||
from a subpath; without it a subpath deploy 404s on every asset. CI no longer sets it — the container
|
||||
image is what gets deployed and `serve.ts` serves from `/` — but the mechanism stays, because a
|
||||
subpath deploy is a hosting decision rather than a build one.
|
||||
|
||||
### Offline
|
||||
|
||||
@@ -263,9 +265,11 @@ mode of the `fetch()` that follows it, and a mismatch is not a no-op — the bro
|
||||
preload and fetches the feed a second time, which is worse than not preloading. If that link is ever
|
||||
edited, count the feed requests in a real browser; nothing in the test suite can see this.
|
||||
|
||||
Compression is the server's job and both hosts do it: GitHub Pages transparently, and `serve.ts` —
|
||||
which is what the Docker image runs — by negotiating `accept-encoding` per request. It matters more
|
||||
than it sounds: the bundle is 344 KB raw and 100 KB gzipped.
|
||||
Compression is the server's job, and `serve.ts` — which is what the Docker image runs, and so what
|
||||
serves the deployed site — does it by negotiating `accept-encoding` per request. It matters more
|
||||
than it sounds: the bundle is 344 KB raw and 100 KB gzipped. A static host put in front of the built
|
||||
`public/` instead must do its own; GitHub Pages did this transparently while it was the deploy
|
||||
target, which is why nothing here ever had to.
|
||||
|
||||
### The theme, before the bundle arrives
|
||||
|
||||
|
||||
+1
-1
@@ -299,7 +299,7 @@ diagnosis in each item still holds — what changed is whether it has been acted
|
||||
|
||||
| Item | Status |
|
||||
|---|---|
|
||||
| P0 refresh pipeline | **Diagnosed, half acted on.** game8.co answers a GitHub Actions runner with `202` and a bot-management body, so those eight sources have only ever built from fixtures in CI — see `AGENTS.md` § Scraping conduct, including why it is not to be worked around. The `broken` tier now makes a source failing three cycles fail the run. Step 5 (a build assertion on snapshot age) is **not built** |
|
||||
| P0 refresh pipeline | **Diagnosed, half acted on.** game8.co answered a GitHub Actions runner with `202` and a bot-management body, so those eight sources had only ever built from fixtures in CI — see `AGENTS.md` § Scraping conduct, including why it is not to be worked around. CI moved to a Gitea runner on 2026-09-16, which changes the address this failure turns on; whether game8 serves it is **unverified**, and the row stays as-is until a cycle has run. The `broken` tier now makes a source failing three cycles fail the run. Step 5 (a build assertion on snapshot age) is **not built** |
|
||||
| P1a Arknights | **Done.** `arknights-akwiki-events`, via the new `akwiki` parser |
|
||||
| P1b `NextUp` → three | **Done** (2026-08-18). One headline and two behind it, off `nextToExpire` |
|
||||
| P1b cap the long list | **Done** (2026-08-18). Both sections cap at six with "show all N" — truncation of the view, not a re-sort |
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ on the section and leave every `h4` beneath it unable to take it back — publis
|
||||
|
||||
- Send `If-None-Match` / `If-Modified-Since` from `sources.etag` / `last_modified`. A `304` ends
|
||||
the run as `skipped_unchanged`.
|
||||
- `User-Agent: gacha-event-tracker/1.0 (+https://github.com/<owner>/gacha-event-tracker)`.
|
||||
- `User-Agent: gacha-event-tracker/1.0 (+https://gitea.lucaswinther.info/<owner>/gacha-event-tracker)`.
|
||||
- Honor `robots.txt`; cache parsed robots per host for 24h. **Fail closed** — a `robots.txt` that
|
||||
5xxs or times out means "do not fetch", because a permission we could not read is not a
|
||||
permission we have. A 404 means no restrictions.
|
||||
|
||||
+22
-12
@@ -27,23 +27,33 @@ the ruling.
|
||||
## Method
|
||||
|
||||
Every page below was fetched from this machine with the refresh runner's own `User-Agent`
|
||||
(`gacha-event-tracker/1.0 (+https://github.com/StereotypicalCat/gacha-event-tracker)`), one or two
|
||||
requests per host, `robots.txt` first. No browser-shaped headers, no proxy, no JS execution —
|
||||
(`gacha-event-tracker/1.0 (+https://github.com/StereotypicalCat/gacha-event-tracker)` — the contact
|
||||
URL at the time; the repo has since moved to Gitea and the runner now sends that address instead),
|
||||
one or two requests per host, `robots.txt` first. No browser-shaped headers, no proxy, no JS execution —
|
||||
anything that only answers a browser is treated as closed, per the `uma.moe` precedent. Where an
|
||||
existing parser could plausibly read a page, it was run against the fetched bytes offline
|
||||
(`src/ingest/parsers/index.ts`) rather than guessed at.
|
||||
|
||||
Two things this method cannot tell you, and both matter:
|
||||
|
||||
- **Whether a host answers the GitHub Actions runner.** game8.co does not (`AGENTS.md` § Scraping
|
||||
conduct). The only hard evidence we hold is what CI has actually committed: `git log` on
|
||||
`snapshots/` shows `github-actions[bot]` landing **arknights.wiki.gg**, **endfield.wiki.gg** and
|
||||
**bluearchive.wiki** (Miraheze). Fandom sources have never refreshed in CI, because `robots.txt`
|
||||
itself is challenged from a datacentre address and the gate fails closed — and as of 2026-08-19
|
||||
that challenge covers every Fandom wiki from *any* address tried here, not just CI's.
|
||||
Of the 2026-08-19 additions, **Stella Sora** is Miraheze and so is the one likely to refresh in CI;
|
||||
**IOP Wiki** is permissive but unproven; **Chaos Zero Nightmare** and **Umamusume** are game8 and
|
||||
therefore blind in CI by construction.
|
||||
- **Whether a host answers the CI runner.** This is a property of the runner's address, not of the
|
||||
page, so it has to be re-established whenever that address changes — and it just did: CI moved from
|
||||
GitHub Actions to a Gitea runner (`AGENTS.md` § Scraping conduct). **Everything in this bullet
|
||||
below is evidence about the old GitHub address.** Treat it as the prior, not the current state,
|
||||
until a cycle has run on Gitea.
|
||||
|
||||
game8.co did not answer the GitHub runner. The only hard evidence we held was what CI had actually
|
||||
committed: `git log` on `snapshots/` shows `github-actions[bot]` landing **arknights.wiki.gg**,
|
||||
**endfield.wiki.gg** and **bluearchive.wiki** (Miraheze). Fandom sources never refreshed in CI,
|
||||
because `robots.txt` itself is challenged from a datacentre address and the gate fails closed — and
|
||||
as of 2026-08-19 that challenge covered every Fandom wiki from *any* address tried here, not just
|
||||
CI's. Of the 2026-08-19 additions, **Stella Sora** is Miraheze and so was the one likely to refresh
|
||||
in CI; **IOP Wiki** is permissive but unproven; **Chaos Zero Nightmare** and **Umamusume** are
|
||||
game8 and were therefore blind in CI by construction.
|
||||
|
||||
The Gitea runner may well be served where GitHub's was not — that is one of the two remedies
|
||||
`AGENTS.md` allows — but "may well be" is not evidence. The check is the same one that produced the
|
||||
list above: run a cycle, then read `git log` on `snapshots/` for what actually landed.
|
||||
- **Whether a page will still look like this in six weeks.** Each recommendation below names the
|
||||
assertion `canParse` should make, so a redesign fails the source loudly instead of emptying a lane.
|
||||
|
||||
@@ -836,7 +846,7 @@ if it ever prints a year (§ 13). None is worth a pass until something changes u
|
||||
## Appendix — reproducing the checks
|
||||
|
||||
```bash
|
||||
UA='gacha-event-tracker/1.0 (+https://github.com/StereotypicalCat/gacha-event-tracker)'
|
||||
UA='gacha-event-tracker/1.0 (+https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker)'
|
||||
|
||||
# conduct
|
||||
curl -sS -A "$UA" https://iopwiki.com/robots.txt
|
||||
|
||||
@@ -55,7 +55,7 @@ import { RobotsCache, type FetchLike } from "../src/ingest/robots.ts";
|
||||
import { decodeBody, SnapshotStore } from "../src/ingest/snapshots.ts";
|
||||
|
||||
const DEFAULT_CONTACT =
|
||||
"https://github.com/StereotypicalCat/gacha-event-tracker";
|
||||
"https://gitea.lucaswinther.info/lucasw89/gacha-event-tracker";
|
||||
|
||||
export const DEFAULT_USER_AGENT = `gacha-event-tracker/1.0 (+${process.env["REFRESH_CONTACT_URL"] ?? DEFAULT_CONTACT})`;
|
||||
|
||||
|
||||
@@ -23,10 +23,12 @@ const ROOT_DIR = resolve(ROOT);
|
||||
* times the bytes and, on anything slower than a laptop on wifi, three times the
|
||||
* download.
|
||||
*
|
||||
* GitHub Pages compresses for us, so the deployed site never had this problem;
|
||||
* the Docker image serves through this file and did. An image whose payload is
|
||||
* 3x the site's is not a placeholder detail — it is the only thing a self-hoster
|
||||
* ever sees.
|
||||
* This used to be the self-hoster's problem only: GitHub Pages compressed for
|
||||
* us, so the deployed site never had it, while the Docker image serves through
|
||||
* this file and did. Since the move to Gitea the image *is* the deploy, so this
|
||||
* is now the only compression the deployed site gets — there is no host behind
|
||||
* it doing the job silently. Serving 3x the bytes is not a placeholder detail
|
||||
* when it is what every reader gets.
|
||||
*/
|
||||
const COMPRESSIBLE = new Set([
|
||||
".html",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "czn-game8-events",
|
||||
"url": "https://game8.co/games/Chaos-Zero-Nightmare/archives/559899",
|
||||
"contentHash": "449c1adacd9c75d18e68e6a6676e20ed51414a5b63ea1a2728ec64e628b75aa1",
|
||||
"bytes": 404171,
|
||||
"etag": "W/\"449c1adacd9c75d18e68e6a6676e20ed\"",
|
||||
"contentHash": "5de7e376ae563fd4e1cc94935f8e0430fa8bd64f22f6f11edb970eb250a2c963",
|
||||
"bytes": 404144,
|
||||
"etag": "W/\"5de7e376ae563fd4e1cc94935f8e0430\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:51.621Z",
|
||||
"contentChangedAt": "2026-09-16T17:28:13.478Z",
|
||||
"eventCount": 7,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "endfield-game8-events",
|
||||
"url": "https://game8.co/games/Arknights-Endfield/archives/535443",
|
||||
"contentHash": "cbe9ab72e81b7636945dcad1c2a0864cbc8b6d77d1a8a5049495c56cff6f4472",
|
||||
"bytes": 525644,
|
||||
"etag": "W/\"cbe9ab72e81b7636945dcad1c2a0864c\"",
|
||||
"contentHash": "0716ae84362e7dd5ecc90ea556cd8287d1e85c41c331ffeddd40b2bcf5edf13d",
|
||||
"bytes": 526206,
|
||||
"etag": "W/\"0716ae84362e7dd5ecc90ea556cd8287\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:34.114Z",
|
||||
"eventCount": 7,
|
||||
"contentChangedAt": "2026-09-16T17:27:57.135Z",
|
||||
"eventCount": 5,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "fgo-fandom-events",
|
||||
"url": "https://fategrandorder.fandom.com/api.php?action=parse&page=Event_List_(US)&prop=text&formatversion=2&format=json",
|
||||
"contentHash": "36723fb04f1e1802ca0deb55a3a7ae4a645e30643c0a86ff80b346a7dc2556bb",
|
||||
"bytes": 656313,
|
||||
"contentHash": "27799879471d4ac7da246d306a77e666e4a25677e659aeb90ceb115826d4a57b",
|
||||
"bytes": 656274,
|
||||
"etag": null,
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:44.553Z",
|
||||
"contentChangedAt": "2026-09-16T17:28:06.501Z",
|
||||
"eventCount": 5,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "genshin-fandom-events",
|
||||
"url": "https://genshin-impact.fandom.com/api.php?action=parse&page=Event&prop=text&formatversion=2&format=json",
|
||||
"contentHash": "28463835cac0ab18f1f67cb994c66195e47c041b45c7d14f752d8a0a2a7c900b",
|
||||
"contentHash": "2ef4d132ff1799ffeddf7a17c74d927e78e0e7475713aff0c925b23dcfaf26c3",
|
||||
"bytes": 65059,
|
||||
"etag": null,
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:42.274Z",
|
||||
"contentChangedAt": "2026-09-16T17:28:04.577Z",
|
||||
"eventCount": 20,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "genshin-game8-events",
|
||||
"url": "https://game8.co/games/Genshin-Impact/archives/301601",
|
||||
"contentHash": "52d3d51149df4a2759606b0421bb67d5af69bd4428078b3b5c6b065eb2a0072b",
|
||||
"bytes": 522544,
|
||||
"etag": "W/\"52d3d51149df4a2759606b0421bb67d5\"",
|
||||
"contentHash": "bba1861b5f1199a26cfea48cf1f1f04b04e64f8122593f4ed14e5243de7107af",
|
||||
"bytes": 522978,
|
||||
"etag": "W/\"bba1861b5f1199a26cfea48cf1f1f04b\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:26.091Z",
|
||||
"contentChangedAt": "2026-09-16T17:27:49.749Z",
|
||||
"eventCount": 12,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
+155
-155
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "gfl2-iopwiki-events",
|
||||
"url": "https://iopwiki.com/wiki/GFL2_Events",
|
||||
"contentHash": "083470ae1c873151e2f8c046f09d0f473a826348431c661625e62d04ddcdd665",
|
||||
"bytes": 281511,
|
||||
"contentHash": "0075c1740d17a840dd66288bec142d4c94c101e628710fe4688457e2f448cb55",
|
||||
"bytes": 281510,
|
||||
"etag": null,
|
||||
"lastModified": "Tue, 15 Sep 2026 22:25:39 GMT",
|
||||
"contentChangedAt": "2026-09-16T01:01:45.802Z",
|
||||
"lastModified": "Wed, 16 Sep 2026 13:47:07 GMT",
|
||||
"contentChangedAt": "2026-09-16T17:28:07.981Z",
|
||||
"eventCount": 2,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "hi3-arustats-events",
|
||||
"url": "https://www.arustats.com/en-us/hi3/timeline",
|
||||
"contentHash": "2e54ea3abc51653a63be845a763c54db50005dcc82e7fcb25920add74ac81222",
|
||||
"contentHash": "eebafac12fd5011e62fe08d1f51bef44e414e860a1ef1ca12225f9a37cf7d27e",
|
||||
"bytes": 75548,
|
||||
"etag": null,
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:56.329Z",
|
||||
"contentChangedAt": "2026-09-16T17:28:17.942Z",
|
||||
"eventCount": 20,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "hsr-game8-events",
|
||||
"url": "https://game8.co/games/Honkai-Star-Rail/archives/408749",
|
||||
"contentHash": "b4a7c1ce74e29e080325981a0153d1d581d16d2b96bb1ac5485d7a192f016ad6",
|
||||
"bytes": 428366,
|
||||
"etag": "W/\"b4a7c1ce74e29e080325981a0153d1d5\"",
|
||||
"contentHash": "680f058ded1110b9888111ec608741d333002e170389a7ed3a713d82aa791f1a",
|
||||
"bytes": 428416,
|
||||
"etag": "W/\"680f058ded1110b9888111ec608741d3\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:26.617Z",
|
||||
"contentChangedAt": "2026-09-16T17:27:50.297Z",
|
||||
"eventCount": 5,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "nikke-fandom-events",
|
||||
"url": "https://nikke-goddess-of-victory-international.fandom.com/api.php?action=parse&page=Event&prop=text&formatversion=2&format=json",
|
||||
"contentHash": "2f13240d728527f2891cc339e3df049486be63ddf45e56be3540216f03893878",
|
||||
"bytes": 368682,
|
||||
"contentHash": "8a3723af99c0b41f27dc6f4761ea74e9831230525ab9fc42b4b7c9f67b907c9e",
|
||||
"bytes": 371064,
|
||||
"etag": null,
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-15T20:18:37.214Z",
|
||||
"eventCount": 3,
|
||||
"contentChangedAt": "2026-09-16T17:28:12.944Z",
|
||||
"eventCount": 6,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "nikki-fandom-events",
|
||||
"url": "https://infinity-nikki.fandom.com/api.php?action=parse&page=Event&prop=text&formatversion=2&format=json",
|
||||
"contentHash": "ed045a56170260e50d204ea7319a1ec30fe96a2e4a33144c637884e5b4cb1f5c",
|
||||
"bytes": 415411,
|
||||
"contentHash": "fd53acf4b85f01f50aba480e97f73de90ebe63eecd5fbf41c1e32ddd913bc98e",
|
||||
"bytes": 415403,
|
||||
"etag": null,
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:46.979Z",
|
||||
"contentChangedAt": "2026-09-16T17:28:09.162Z",
|
||||
"eventCount": 0,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
+990
-197
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "nte-game8-events",
|
||||
"url": "https://game8.co/games/Neverness-to-Everness/archives/592073",
|
||||
"contentHash": "c05a2d7e5d76b96fe091cbb9819f63e6d43ec15853fbc4130285e3379ef28230",
|
||||
"bytes": 508168,
|
||||
"etag": "W/\"c05a2d7e5d76b96fe091cbb9819f63e6\"",
|
||||
"contentHash": "5fa64f3475ac527b3ab3eff40e833a1d43f1c277311314b770b6bea602dc4795",
|
||||
"bytes": 514544,
|
||||
"etag": "W/\"5fa64f3475ac527b3ab3eff40e833a1d\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:36.925Z",
|
||||
"eventCount": 13,
|
||||
"contentChangedAt": "2026-09-16T17:28:00.130Z",
|
||||
"eventCount": 19,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "p5x-game8-events",
|
||||
"url": "https://game8.co/games/Persona-5-Phantom-X/archives/532244",
|
||||
"contentHash": "ad66f537090227428cb4bf07ee04a8ab794083f82e12f0f7d9c49dc3aee61f08",
|
||||
"bytes": 446826,
|
||||
"etag": "W/\"ad66f537090227428cb4bf07ee04a8ab\"",
|
||||
"contentHash": "fb405e9546f197fa668c53ba8e162f59dcecac09d556c272c4162e57b2ed2660",
|
||||
"bytes": 446647,
|
||||
"etag": "W/\"fb405e9546f197fa668c53ba8e162f59\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:39.327Z",
|
||||
"contentChangedAt": "2026-09-16T17:28:02.364Z",
|
||||
"eventCount": 2,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "pgr-karendar-events",
|
||||
"url": "https://karendar.com/",
|
||||
"contentHash": "6eaed8e0aac4bc3ccbe733fe48b56e4f74c2337c3604adf444decf04937046e2",
|
||||
"bytes": 317167,
|
||||
"contentHash": "28fdd8531dc410f82b293b7195ba7b512caffcd0030bb8baaabddb86105d867a",
|
||||
"bytes": 318828,
|
||||
"etag": null,
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:58.458Z",
|
||||
"eventCount": 54,
|
||||
"contentChangedAt": "2026-09-16T17:28:19.279Z",
|
||||
"eventCount": 53,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "r1999-fandom-events",
|
||||
"url": "https://reverse1999.fandom.com/api.php?action=parse&page=Events&prop=text&formatversion=2&format=json",
|
||||
"contentHash": "06831119b0c1e9bb679d488b12701e9a055d486dba8817020bf840ab02574207",
|
||||
"bytes": 148779,
|
||||
"contentHash": "9ff919374a94065c1465ad9be37282998bd7c8aefe0ba09ebc1b96802f73fdaa",
|
||||
"bytes": 152198,
|
||||
"etag": null,
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-15T20:18:30.404Z",
|
||||
"contentChangedAt": "2026-09-16T17:28:04.945Z",
|
||||
"eventCount": 10,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Stella Sora Wiki - Stella Sora Wiki</title>
|
||||
<script>document.documentElement.className="client-js skin-theme-clientpref-os citizen-feature-autohide-navigation-clientpref-1 citizen-feature-image-dimming-clientpref-0 citizen-feature-pure-black-clientpref-0 citizen-feature-custom-font-size-clientpref-standard citizen-feature-custom-width-clientpref-standard citizen-feature-performance-mode-clientpref-1 citizen-header-position-left citizen-header-position-mobile-bottom";RLCONF={"wgBreakFrames":false,"wgSeparatorTransformTable":["",""],"wgDigitTransformTable":["",""],"wgDefaultDateFormat":"dmy","wgMonthNames":["","January","February","March","April","May","June","July","August","September","October","November","December"],"wgRequestId":"8694de07434fd22087b566ea","wgCanonicalNamespace":"","wgCanonicalSpecialPageName":false,"wgNamespaceNumber":0,"wgPageName":"Stella_Sora_Wiki","wgTitle":"Stella Sora Wiki","wgCurRevisionId":29320,"wgRevisionId":29320,"wgArticleId":1,"wgIsArticle":true,"wgIsRedirect":false,"wgAction":"view","wgUserName":null,"wgUserGroups":["*"],"wgCategories":["Stella Sora Wiki"],"wgPageViewLanguage":"en","wgPageContentLanguage":"en","wgPageContentModel":"wikitext","wgRelevantPageName":"Stella_Sora_Wiki","wgRelevantArticleId":1,"wgIsProbablyEditable":false,"wgRelevantPageIsProbablyEditable":false,"wgRestrictionEdit":["user"],"wgRestrictionMove":["user"],"wgIsMainPage":true,"wgRedirectedFrom":"Main_Page","simpleBatchUploadMaxFilesPerBatch":{"*":5,"autoconfirmed":25,"bureaucrat":500,"confirmed":50,"sysop":250,"user":5},"wgCargoDefaultQueryLimit":100,"wgCargoMapClusteringMinimum":80,"wgCargoMonthNamesShort":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"wgNoticeProject":"all","wgConfirmEditCaptchaNeededForGenericEdit":false,"wgCiteReferencePreviewsActive":true,"wgVisualEditor":{"pageLanguageCode":"en","pageLanguageDir":"ltr","pageVariantFallbacks":"en"},"wgMediaViewerOnClick":true,"wgMediaViewerEnabledByDefault":true,"wgPopupsFlags":0,"wgInternalRedirectTargetUrl":"/wiki/Stella_Sora_Wiki","wgCheckUserClientHintsHeadersJsApi":["brands","architecture","bitness","fullVersionList","mobile","model","platform","platformVersion"],"wgIsMobile":false,"wgEditSubmitButtonLabelPublish":false};
|
||||
<script>document.documentElement.className="client-js skin-theme-clientpref-os citizen-feature-autohide-navigation-clientpref-1 citizen-feature-image-dimming-clientpref-0 citizen-feature-pure-black-clientpref-0 citizen-feature-custom-font-size-clientpref-standard citizen-feature-custom-width-clientpref-standard citizen-feature-performance-mode-clientpref-1 citizen-header-position-left citizen-header-position-mobile-bottom";RLCONF={"wgBreakFrames":false,"wgSeparatorTransformTable":["",""],"wgDigitTransformTable":["",""],"wgDefaultDateFormat":"dmy","wgMonthNames":["","January","February","March","April","May","June","July","August","September","October","November","December"],"wgRequestId":"383ffe779e299cd06fa7f99d","wgCanonicalNamespace":"","wgCanonicalSpecialPageName":false,"wgNamespaceNumber":0,"wgPageName":"Stella_Sora_Wiki","wgTitle":"Stella Sora Wiki","wgCurRevisionId":29320,"wgRevisionId":29320,"wgArticleId":1,"wgIsArticle":true,"wgIsRedirect":false,"wgAction":"view","wgUserName":null,"wgUserGroups":["*"],"wgCategories":["Stella Sora Wiki"],"wgPageViewLanguage":"en","wgPageContentLanguage":"en","wgPageContentModel":"wikitext","wgRelevantPageName":"Stella_Sora_Wiki","wgRelevantArticleId":1,"wgIsProbablyEditable":false,"wgRelevantPageIsProbablyEditable":false,"wgRestrictionEdit":["user"],"wgRestrictionMove":["user"],"wgIsMainPage":true,"wgRedirectedFrom":"Main_Page","simpleBatchUploadMaxFilesPerBatch":{"*":5,"autoconfirmed":25,"bureaucrat":500,"confirmed":50,"sysop":250,"user":5},"wgCargoDefaultQueryLimit":100,"wgCargoMapClusteringMinimum":80,"wgCargoMonthNamesShort":["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],"wgNoticeProject":"all","wgConfirmEditCaptchaNeededForGenericEdit":false,"wgCiteReferencePreviewsActive":true,"wgVisualEditor":{"pageLanguageCode":"en","pageLanguageDir":"ltr","pageVariantFallbacks":"en"},"wgMediaViewerOnClick":true,"wgMediaViewerEnabledByDefault":true,"wgPopupsFlags":0,"wgInternalRedirectTargetUrl":"/wiki/Stella_Sora_Wiki","wgCheckUserClientHintsHeadersJsApi":["brands","architecture","bitness","fullVersionList","mobile","model","platform","platformVersion"],"wgIsMobile":false,"wgEditSubmitButtonLabelPublish":false};
|
||||
RLSTATE={"ext.globalCssJs.user.styles":"ready","ext.gadget.tooltip-pagestyles":"ready","ext.gadget.common-site":"ready","ext.gadget.navbox":"ready","ext.gadget.navbuttons":"ready","ext.gadget.tabber":"ready","ext.gadget.infobox":"ready","ext.gadget.variables":"ready","ext.gadget.audio-button":"ready","ext.gadget.RCSidebarStyles":"ready","site.styles":"ready","user.styles":"ready","ext.globalCssJs.user":"ready","user":"ready","user.options":"loading","skins.citizen.styles":"ready","skins.citizen.codex.styles":"ready","skins.citizen.icons":"ready","ext.CookieWarning.styles":"ready","oojs-ui-core.styles":"ready","oojs-ui.styles.indicators":"ready","mediawiki.widgets.styles":"ready","oojs-ui-core.icons":"ready","ext.MobileDetect.nomobile":"ready","ext.visualEditor.desktopArticleTarget.noscript":"ready","ext.embedVideo.styles":"ready","skins.citizen.tokens":"ready"};RLPAGEMODULES=["mediawiki.action.view.redirect","site","mediawiki.page.ready","skins.citizen.scripts","ext.centralNotice.geoIP","ext.centralNotice.startUp","ext.checkUser.clientHints","ext.CookieWarning","ext.echo.centralauth","ext.eventLogging","ext.urlShortener.toolbar","ext.visualEditor.desktopArticleTarget.init","ext.visualEditor.targetLoader","ext.embedVideo.overlay","mmv.bootstrap","ext.gadget.tooltip","ext.gadget.tooltip-default","ext.gadget.tooltip-userTime","ext.gadget.visualEditor","ext.gadget.countdown","ext.gadget.tab","ext.gadget.collapse","ext.gadget.stat-display","ext.gadget.filter-table","ext.gadget.audio-player","ext.gadget.RCSidebar","ext.gadget.TPT-check-all","ext.popups","ext.centralauth.centralautologin","ext.purge"];</script>
|
||||
<script>(RLQ=window.RLQ||[]).push(function(){mw.loader.impl(function(){return["user.options@12s5i",function($,jQuery,require,module){mw.user.tokens.set({"patrolToken":"+\\","watchToken":"+\\","csrfToken":"+\\"});
|
||||
}];});});</script>
|
||||
@@ -413,7 +413,7 @@ RLSTATE={"ext.globalCssJs.user.styles":"ready","ext.gadget.tooltip-pagestyles":"
|
||||
</div>
|
||||
<div class="stellasora-home-card stellasora-home-card--current stellasora-home-current plainlinks">
|
||||
<h2 id="Birthdays" class="stellasora-home-card__header mw-html-heading">Birthdays</h2>
|
||||
<div class="stellasora-home-card__content"><div class="stellasora-birthday-timeline"><div class="stellasora-birthday-timeline__line"></div><div class="stellasora-birthday-timeline__item stellasora-birthday-timeline__item--today stellasora-birthday-timeline__item--today-empty"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__item-empty-text">No<br />birthdays<br />today</div></div><div class="stellasora-birthday-timeline__item-date">Sep 15</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Otoha" title="Otoha"><img src="//static.wikitide.net/stellasorawiki/thumb/1/1a/Otoha-head-xxl.png/48px-Otoha-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/1/1a/Otoha-head-xxl.png/72px-Otoha-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/1/1a/Otoha-head-xxl.png/96px-Otoha-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Otoha" title="Otoha">Otoha</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Jan 14</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Canace" title="Canace"><img src="//static.wikitide.net/stellasorawiki/thumb/2/23/Canace-head-xxl.png/48px-Canace-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/2/23/Canace-head-xxl.png/72px-Canace-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/2/23/Canace-head-xxl.png/96px-Canace-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Canace" title="Canace">Canace</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Jan 22</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Fuyuka" title="Fuyuka"><img src="//static.wikitide.net/stellasorawiki/thumb/9/95/Fuyuka-head-xxl.png/48px-Fuyuka-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/9/95/Fuyuka-head-xxl.png/72px-Fuyuka-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/9/95/Fuyuka-head-xxl.png/96px-Fuyuka-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Fuyuka" title="Fuyuka">Fuyuka</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Jan 31</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Teresa" title="Teresa"><img src="//static.wikitide.net/stellasorawiki/thumb/7/7a/Teresa-head-xxl.png/48px-Teresa-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/7/7a/Teresa-head-xxl.png/72px-Teresa-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/7/7a/Teresa-head-xxl.png/96px-Teresa-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Teresa" title="Teresa">Teresa</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Feb 21</div></div></div></div>
|
||||
<div class="stellasora-home-card__content"><div class="stellasora-birthday-timeline"><div class="stellasora-birthday-timeline__line"></div><div class="stellasora-birthday-timeline__item stellasora-birthday-timeline__item--today stellasora-birthday-timeline__item--today-empty"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__item-empty-text">No<br />birthdays<br />today</div></div><div class="stellasora-birthday-timeline__item-date">Sep 16</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Otoha" title="Otoha"><img src="//static.wikitide.net/stellasorawiki/thumb/1/1a/Otoha-head-xxl.png/48px-Otoha-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/1/1a/Otoha-head-xxl.png/72px-Otoha-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/1/1a/Otoha-head-xxl.png/96px-Otoha-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Otoha" title="Otoha">Otoha</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Jan 14</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Canace" title="Canace"><img src="//static.wikitide.net/stellasorawiki/thumb/2/23/Canace-head-xxl.png/48px-Canace-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/2/23/Canace-head-xxl.png/72px-Canace-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/2/23/Canace-head-xxl.png/96px-Canace-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Canace" title="Canace">Canace</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Jan 22</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Fuyuka" title="Fuyuka"><img src="//static.wikitide.net/stellasorawiki/thumb/9/95/Fuyuka-head-xxl.png/48px-Fuyuka-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/9/95/Fuyuka-head-xxl.png/72px-Fuyuka-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/9/95/Fuyuka-head-xxl.png/96px-Fuyuka-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Fuyuka" title="Fuyuka">Fuyuka</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Jan 31</div></div><div class="stellasora-birthday-timeline__item"><div class="stellasora-birthday-timeline__item-characters"><div class="stellasora-birthday-timeline__character"><div class="stellasora-birthday-timeline__item-icon"><span class="stellasora-icon stellasora-icon--small" typeof="mw:File"><a href="/wiki/Teresa" title="Teresa"><img src="//static.wikitide.net/stellasorawiki/thumb/7/7a/Teresa-head-xxl.png/48px-Teresa-head-xxl.png" decoding="async" loading="lazy" width="48" height="48" class="mw-file-element" srcset="//static.wikitide.net/stellasorawiki/thumb/7/7a/Teresa-head-xxl.png/72px-Teresa-head-xxl.png 1.5x, //static.wikitide.net/stellasorawiki/thumb/7/7a/Teresa-head-xxl.png/96px-Teresa-head-xxl.png 2x" data-file-width="192" data-file-height="192" /></a></span></div><div class="stellasora-birthday-timeline__item-name"><a href="/wiki/Teresa" title="Teresa">Teresa</a></div></div></div><div class="stellasora-birthday-timeline__item-date">Feb 21</div></div></div></div>
|
||||
</div>
|
||||
<style data-mw-deduplicate="TemplateStyles:r2766">.mw-parser-output .stellasora-home-links__link-list--discord .stellasora-home-links__link-list-item--full::before{background-image:url("https://static.wikitide.net/commonswiki/a/aa/Discord_logo_icon.svg")}.mw-parser-output .stellasora-home-links__link-list--x .stellasora-home-links__link-list-item--full::before{background-image:url("https://upload.wikimedia.org/wikipedia/commons/5/55/Logo_X_Corp_%22Twitter%22.svg")}.mw-parser-output .stellasora-home-links__link-list--facebook .stellasora-home-links__link-list-item--full::before{background-image:url("https://upload.wikimedia.org/wikipedia/commons/9/90/Logo_Facebook_Blanco.png")}.mw-parser-output .stellasora-home-links__link-list--youtube .stellasora-home-links__link-list-item--full::before{background-image:url("https://upload.wikimedia.org/wikipedia/commons/4/42/YouTube_light_icon_%282017%29.svg")}.mw-parser-output .stellasora-home-links__link-list--google-play .stellasora-home-links__link-list-item--full::before{background-image:url("https://upload.wikimedia.org/wikipedia/commons/2/2f/Google_Play_2022_icon.svg")}.mw-parser-output .stellasora-home-links__link-list--app-store .stellasora-home-links__link-list-item--full::before{background-image:url("https://upload.wikimedia.org/wikipedia/commons/3/31/Apple_logo_white.svg")}</style><div class="stellasora-home-card stellasora-home-card--links stellasora-home-links plainlinks">
|
||||
<h2 id="Links" class="stellasora-home-card__header mw-html-heading">Links</h2>
|
||||
@@ -444,13 +444,13 @@ RLSTATE={"ext.globalCssJs.user.styles":"ready","ext.gadget.tooltip-pagestyles":"
|
||||
</div>
|
||||
<!--
|
||||
NewPP limit report
|
||||
Parsed by mw193
|
||||
Cached time: 20260915101440
|
||||
Parsed by mw182
|
||||
Cached time: 20260916100616
|
||||
Cache expiry: 1296000
|
||||
Reduced expiry: false
|
||||
Complications: [no‐toc]
|
||||
CPU time usage: 0.273 seconds
|
||||
Real time usage: 0.934 seconds
|
||||
CPU time usage: 0.239 seconds
|
||||
Real time usage: 0.316 seconds
|
||||
Preprocessor visited node count: 557/1000000
|
||||
Revision size: 558/2097152 bytes
|
||||
Post‐expand include size: 43236/2097152 bytes
|
||||
@@ -459,24 +459,24 @@ Highest expansion depth: 6/100
|
||||
Expensive parser function count: 3/99
|
||||
Unstrip recursion depth: 0/20
|
||||
Unstrip post‐expand size: 13570/5000000 bytes
|
||||
Lua time usage: 0.115/10.000 seconds
|
||||
Lua time usage: 0.104/10.000 seconds
|
||||
Lua memory usage: 1284402/52428800 bytes
|
||||
Bucket time usage: 0.089/10 seconds
|
||||
Bucket time usage: 0.008/10 seconds
|
||||
-->
|
||||
<!--
|
||||
Transclusion expansion time report (%,ms,calls,template)
|
||||
100.00% 772.728 1 -total
|
||||
61.29% 473.639 1 Template:MainPage/Recent
|
||||
53.65% 414.538 6 Template:Card
|
||||
18.19% 140.540 1 Template:MainPage/Current
|
||||
16.03% 123.869 1 Template:MainPage/Birthdays
|
||||
4.38% 33.877 4 Template:MainPage/Banner
|
||||
2.59% 20.021 1 Template:MainPage/Links
|
||||
1.04% 8.042 1 Template:MainPage/Header
|
||||
0.74% 5.723 1 Template:MainPage/Navigation
|
||||
100.00% 193.235 1 -total
|
||||
40.34% 77.946 1 Template:MainPage/Recent
|
||||
38.89% 75.152 1 Template:MainPage/Current
|
||||
29.12% 56.263 6 Template:Card
|
||||
16.98% 32.803 4 Template:MainPage/Banner
|
||||
9.92% 19.171 1 Template:MainPage/Birthdays
|
||||
6.59% 12.727 1 Template:MainPage/Links
|
||||
2.06% 3.985 1 Template:MainPage/Header
|
||||
1.79% 3.453 1 Template:MainPage/Navigation
|
||||
-->
|
||||
|
||||
<!-- Saved in parser cache with key stellasorawiki:pcache:1:|#|:idhash:canonical and timestamp 20260915101440 and revision id 29320. Rendering was triggered because: page_view
|
||||
<!-- Saved in parser cache with key stellasorawiki:pcache:1:|#|:idhash:canonical and timestamp 20260916100616 and revision id 29320. Rendering was triggered because: page_view
|
||||
-->
|
||||
</div><noscript><img src="https://stellasora.miraheze.org/wiki/Special:CentralAutoLogin/start?type=1x1&usesul3=1" alt="" width="1" height="1" style="border: none; position: absolute;"></noscript>
|
||||
<div class="printfooter" data-nosnippet="">Retrieved from "<a dir="ltr" href="https://stellasora.miraheze.org/wiki/Stella_Sora_Wiki?oldid=29320">https://stellasora.miraheze.org/wiki/Stella_Sora_Wiki?oldid=29320</a>"</div></div>
|
||||
@@ -714,7 +714,7 @@ Transclusion expansion time report (%,ms,calls,template)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>(RLQ=window.RLQ||[]).push(function(){mw.config.set({"wgHostname":"mw193","wgBackendResponseTime":188,"wgPageParseReport":{"limitreport":{"cputime":"0.273","walltime":"0.934","ppvisitednodes":{"value":557,"limit":1000000},"revisionsize":{"value":558,"limit":2097152},"postexpandincludesize":{"value":43236,"limit":2097152},"templateargumentsize":{"value":200,"limit":2097152},"expansiondepth":{"value":6,"limit":100},"expensivefunctioncount":{"value":3,"limit":99},"unstrip-depth":{"value":0,"limit":20},"unstrip-size":{"value":13570,"limit":5000000},"timingprofile":["100.00% 772.728 1 -total"," 61.29% 473.639 1 Template:MainPage/Recent"," 53.65% 414.538 6 Template:Card"," 18.19% 140.540 1 Template:MainPage/Current"," 16.03% 123.869 1 Template:MainPage/Birthdays"," 4.38% 33.877 4 Template:MainPage/Banner"," 2.59% 20.021 1 Template:MainPage/Links"," 1.04% 8.042 1 Template:MainPage/Header"," 0.74% 5.723 1 Template:MainPage/Navigation"]},"scribunto":{"limitreport-timeusage":{"value":"0.115","limit":"10.000"},"limitreport-memusage":{"value":1284402,"limit":52428800}},"bucket":{"limitreport-run-time":{"value":"0.089","limit":"10"}},"cachereport":{"origin":"mw193","timestamp":"20260915101440","ttl":1296000,"transientcontent":false}}});});</script>
|
||||
<script>(RLQ=window.RLQ||[]).push(function(){mw.config.set({"wgHostname":"mw182","wgBackendResponseTime":196,"wgPageParseReport":{"limitreport":{"cputime":"0.239","walltime":"0.316","ppvisitednodes":{"value":557,"limit":1000000},"revisionsize":{"value":558,"limit":2097152},"postexpandincludesize":{"value":43236,"limit":2097152},"templateargumentsize":{"value":200,"limit":2097152},"expansiondepth":{"value":6,"limit":100},"expensivefunctioncount":{"value":3,"limit":99},"unstrip-depth":{"value":0,"limit":20},"unstrip-size":{"value":13570,"limit":5000000},"timingprofile":["100.00% 193.235 1 -total"," 40.34% 77.946 1 Template:MainPage/Recent"," 38.89% 75.152 1 Template:MainPage/Current"," 29.12% 56.263 6 Template:Card"," 16.98% 32.803 4 Template:MainPage/Banner"," 9.92% 19.171 1 Template:MainPage/Birthdays"," 6.59% 12.727 1 Template:MainPage/Links"," 2.06% 3.985 1 Template:MainPage/Header"," 1.79% 3.453 1 Template:MainPage/Navigation"]},"scribunto":{"limitreport-timeusage":{"value":"0.104","limit":"10.000"},"limitreport-memusage":{"value":1284402,"limit":52428800}},"bucket":{"limitreport-run-time":{"value":"0.008","limit":"10"}},"cachereport":{"origin":"mw182","timestamp":"20260916100616","ttl":1296000,"transientcontent":false}}});});</script>
|
||||
<script>
|
||||
var _paq = window._paq = window._paq || [];
|
||||
if ( 1 ) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "stellasora-stellasorawiki-events",
|
||||
"url": "https://stellasora.miraheze.org/wiki/Main_Page",
|
||||
"contentHash": "015f5b0bfce1fdb9625019ce3953e58ffefa8b027cc7c294e884e7130ea6e83e",
|
||||
"contentHash": "12cf450eab698f3cb78db0cb5ea6b7d620b3e4f30867a39212cf622e263f498d",
|
||||
"bytes": 88435,
|
||||
"etag": null,
|
||||
"lastModified": "Tue, 15 Sep 2026 10:00:48 GMT",
|
||||
"contentChangedAt": "2026-09-16T01:01:46.108Z",
|
||||
"lastModified": "Wed, 16 Sep 2026 10:00:48 GMT",
|
||||
"contentChangedAt": "2026-09-16T17:28:08.401Z",
|
||||
"eventCount": 4,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "uma-game8-events",
|
||||
"url": "https://game8.co/games/Umamusume-Pretty-Derby/archives/536311",
|
||||
"contentHash": "096d6fd1c198cc88fbde296db20a46d065e621bf4e4a98ff98e4e35153430018",
|
||||
"bytes": 480194,
|
||||
"etag": "W/\"096d6fd1c198cc88fbde296db20a46d0\"",
|
||||
"contentHash": "0b7c3f7b74954fdca5c9432b42726a226ffaed3b5fb7e891f817bb6a9bef7ec4",
|
||||
"bytes": 481780,
|
||||
"etag": "W/\"0b7c3f7b74954fdca5c9432b42726a22\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:54.072Z",
|
||||
"eventCount": 2,
|
||||
"contentChangedAt": "2026-09-16T17:28:15.699Z",
|
||||
"eventCount": 4,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "wuwa-game8-events",
|
||||
"url": "https://game8.co/games/Wuthering-Waves/archives/453473",
|
||||
"contentHash": "1cd647f606bd4636365b793467d8c188a252cd6263b58c5962c876fd07439daa",
|
||||
"bytes": 694682,
|
||||
"etag": "W/\"1cd647f606bd4636365b793467d8c188\"",
|
||||
"contentHash": "fff7a2b5970d9322ddf78f62277a8f8e57afcc9cfe6149a3973c062bcad1c853",
|
||||
"bytes": 694792,
|
||||
"etag": "W/\"fff7a2b5970d9322ddf78f62277a8f8e\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:29.001Z",
|
||||
"contentChangedAt": "2026-09-16T17:27:52.518Z",
|
||||
"eventCount": 8,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"sourceId": "zzz-game8-events",
|
||||
"url": "https://game8.co/games/Zenless-Zone-Zero/archives/457176",
|
||||
"contentHash": "b22c4fb4304dba3733ea2d5399f48e51497fa5c92bbaae4cdd83392846132e8d",
|
||||
"bytes": 478734,
|
||||
"etag": "W/\"b22c4fb4304dba3733ea2d5399f48e51\"",
|
||||
"contentHash": "99b4ab144e0360eba7a6be7d559489684ad4cd0b087a55a7ddb6cfd71a1ef573",
|
||||
"bytes": 478917,
|
||||
"etag": "W/\"99b4ab144e0360eba7a6be7d55948968\"",
|
||||
"lastModified": null,
|
||||
"contentChangedAt": "2026-09-16T01:01:31.279Z",
|
||||
"contentChangedAt": "2026-09-16T17:27:54.758Z",
|
||||
"eventCount": 13,
|
||||
"charset": "utf-8"
|
||||
}
|
||||
|
||||
@@ -142,12 +142,15 @@ export function agentToken(userAgent: string): string {
|
||||
*
|
||||
* RFC 9309 § 2.2.1 matches the *product token* — the header up to the first
|
||||
* `/` — not the header text. Matching anywhere in the header is actively
|
||||
* dangerous here: our contact URL contains the string `StereotypicalCat`, so a
|
||||
* `User-agent: cat` group elsewhere in the file would be treated as naming us,
|
||||
* and because a named group replaces the `*` group outright, that unrelated
|
||||
* group's rules would *discard* every rule the site actually wrote for us.
|
||||
* Erring towards obeying more rules means never letting a coincidence take a
|
||||
* `*` group away.
|
||||
* dangerous here, because our header carries a contact URL and a URL is a long
|
||||
* string of arbitrary substrings. The case that caught it: the contact URL was
|
||||
* once `…/StereotypicalCat/…`, so a `User-agent: cat` group elsewhere in the
|
||||
* file was treated as naming us, and because a named group replaces the `*`
|
||||
* group outright, that unrelated group's rules *discarded* every rule the site
|
||||
* actually wrote for us. The URL has since changed and that particular
|
||||
* coincidence is gone; the hazard is not, since the next contact URL is just as
|
||||
* arbitrary. Erring towards obeying more rules means never letting a
|
||||
* coincidence take a `*` group away.
|
||||
*
|
||||
* A robots.txt that names us with a version (`gacha-event-tracker/1.0`) is
|
||||
* still honoured: the group's own product token is compared too.
|
||||
|
||||
+6
-5
@@ -117,12 +117,13 @@ describe("static server", () => {
|
||||
});
|
||||
|
||||
/**
|
||||
* Compression, which is the whole difference between the Docker image and the
|
||||
* deployed site.
|
||||
* Compression, which used to be the whole difference between the Docker image
|
||||
* and the deployed site.
|
||||
*
|
||||
* GitHub Pages gzips on our behalf, so the bundle crosses the wire at a third of
|
||||
* its size there and did not here — and this file is what the image runs. Three
|
||||
* times the bytes is the only thing a self-hoster would ever have seen.
|
||||
* GitHub Pages gzipped on our behalf, so the bundle crossed the wire at a third
|
||||
* of its size there and did not here — and serve.ts is what the image runs.
|
||||
* Since the move to Gitea the image is the deploy, so these assertions cover
|
||||
* every reader rather than only a self-hoster: nothing else compresses now.
|
||||
*/
|
||||
describe("static server: compression", () => {
|
||||
test("gzips a text asset for a client that asks", async () => {
|
||||
|
||||
Reference in New Issue
Block a user