Skip to content

Commit 620bb4e

Browse files
committed
release: prep v0.0.2-beta.0.26, server downloads, tracker badges, repo cleanup
1 parent 8650234 commit 620bb4e

File tree

368 files changed

+46758
-8891
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

368 files changed

+46758
-8891
lines changed

.gitignore

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,18 @@ output/
6161
Output Logs/
6262
/factorio/
6363
/WebHostLib/static/generated
64-
/WebHostLib/
64+
/WebHostLib/**/*.bak
65+
/WebHostLib/**/*.bak-*
66+
/WebHostLib/**/*.bak_*
67+
/WebHostLib/**/*.bak.*
68+
/WebHostLib/generated/
69+
/WebHostLib/static/client-sync/runtime/
70+
/WebHostLib/static/styles/**/*.css.map
71+
/sekailink-docs/
72+
/sekailink-client-plan/*.local.md
73+
/sekailink-client-plan/.XX-*
74+
/sekailink-client-plan/*.swp
75+
/sekailink-client-plan/XX-temp-tokens.md
6576
/deploy/
6677
/freeze_requirements.txt
6778
/Archipelago.zip
@@ -235,6 +246,8 @@ data/gzdoom/gzdoom.log
235246

236247
# Workspace artifacts
237248
sekailink-laptop-pack.zip
249+
sekailink-*.zip
250+
sekailink-client-plan.zip
238251
zi5XmaYK/
239252
release/
240253
docs/
@@ -244,6 +257,12 @@ client/app/node_modules/
244257
.claude/
245258
.run/
246259
.local/
260+
user_yamls/
261+
.pyxbld/
262+
263+
# Keep repository lean (no bundled binary payloads)
264+
third_party/**
265+
!third_party/README.md
247266

248267
# Bundled mono runtime is a large binary dependency; keep README in git, but not the payload.
249268
third_party/mono/sekailink-mono-linux-*/**

README.md

Lines changed: 52 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,42 @@
11
# SekaiLink
22

3-
SekaiLink is a desktop-first platform for Archipelago-style multiworld play with a low-friction UX:
4-
- create/join lobbies
5-
- manage YAML/player options
6-
- patch and launch automatically
7-
- connect emulator + tracker + client runtime
8-
- coordinate rooms, moderation, hints, and social features in one UI
3+
<p align="center">
4+
<img src="assets/img/sekailink-logo-image.png" alt="SekaiLink Logo" width="160" />
5+
</p>
6+
<p align="center">
7+
<img src="assets/img/sekailink-logo-text.png" alt="SekaiLink" width="320" />
8+
</p>
99

10-
## Current Scope
11-
- Client: Electron + React + TypeScript (`client/app`)
12-
- Runtime orchestration: Python wrappers + AP clients + emulator/tracker bridges
13-
- Server: WebHostLib + room/lobby/social APIs + generation pipeline
14-
- Admin: `client/admin-app`
10+
<p align="center">
11+
<a href="https://discord.gg/jTaefxAEDW"><img alt="Discord" src="https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white"></a>
12+
<a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-22c55e?style=for-the-badge"></a>
13+
<a href="https://sekailink.com/downloads"><img alt="Downloads" src="https://img.shields.io/badge/Downloads-Live-06b6d4?style=for-the-badge"></a>
14+
</p>
1515

16-
## Key Features (current)
17-
- Unified Game Manager (My Games / Add a Game / YAML Editor)
18-
- Lobby system with generation flow, moderation actions, room info, timer, spoiler log access
19-
- Solo mode workflow
20-
- Global and lobby chat with persistent toast notifications
21-
- Friend system (requests, accept/decline, presence, DMs)
22-
- Self-update pipeline (incremental sync + staged updates)
23-
- SteamGridDB boxart integration (client + server endpoints)
24-
- Multi-language UI foundation (EN, FR, ES, JA, ZH-CN, ZH-TW)
16+
SekaiLink is a desktop client + server stack for streamlined Archipelago multiplayer sessions:
17+
- lobby creation/join flow
18+
- YAML and player options workflow
19+
- one-click patch/launch pipeline
20+
- emulator + tracker orchestration
21+
- moderation/social features in one UI
2522

26-
## Supported Game Set (currently integrated)
23+
## Downloads
24+
- Linux AppImage: `https://sekailink.com/static/downloads/sekailink-beta-install.appimage`
25+
- Windows Installer: `https://sekailink.com/static/downloads/sekailink-beta-install.exe`
26+
- Downloads page: `https://sekailink.com/downloads`
27+
28+
## Current Features
29+
- Neon-themed production UI (home, lobby, game manager, settings, profile modal)
30+
- Auto-update flow with startup check and patch pipeline
31+
- Global chat + lobby chat + toast notifications
32+
- Friends system (presence, requests, context actions)
33+
- Solo mode flow
34+
- Room moderation actions and room management
35+
- Runtime orchestration for BizHawk / PopTracker / SNI bridge
36+
- SteamGridDB boxart integration (client + server)
37+
- Multi-language foundation (EN, FR, ES, JA, ZH-CN, ZH-TW)
38+
39+
## Supported Games (Current Integration)
2740
- A Link to the Past
2841
- A Link Between Worlds
2942
- Donkey Kong Country 1/2/3
@@ -34,9 +47,8 @@ SekaiLink is a desktop-first platform for Archipelago-style multiworld play with
3447
- Lufia II Ancient Cave
3548
- Mega Man 2/3
3649
- Metroid Fusion / Metroid Zero Mission
37-
- Ocarina of Time
50+
- Ocarina of Time / Ship of Harkinian
3851
- Pokemon Crystal / Emerald / FireRed-LeafGreen / Red-Blue
39-
- Ship of Harkinian
4052
- SMZ3
4153
- Super Mario 64 / Super Mario Land 2 / Super Mario World
4254
- Super Metroid
@@ -45,47 +57,30 @@ SekaiLink is a desktop-first platform for Archipelago-style multiworld play with
4557
- Yoshi's Island
4658

4759
## Repository Layout
48-
- `client/app/` — desktop client UI + Electron shell
49-
- `client/admin-app/` — admin control panel
50-
- `WebHostLib/` — server web/API layer
51-
- `worlds/` — AP world integrations
52-
- `third_party/` — emulators, patched tools, external runtimes
53-
- `sekailink-client-plan/` — execution logs / implementation notes / roadmap docs
54-
- `sekailink-docs/` — developer and architecture docs
55-
56-
## Production VPS (non-secret operational summary)
57-
- Domain: `sekailink.com`
58-
- Admin domain: `admin.sekailink.com`
59-
- Main path: `/opt/multiworldgg`
60-
- Web stack: Apache2 reverse proxy + Gunicorn WebHost
61-
- Active services:
62-
- `multiworldgg-webhost.service`
63-
- `multiworldgg-workers.service`
64-
- `sekailink-social-bots.service`
65-
- `sekailink-llama.service`
66-
- `webmin.service`
60+
- `client/app/` : desktop app (Electron + React + TypeScript)
61+
- `client/admin-app/` : admin desktop panel
62+
- `WebHostLib/` : web UI + APIs + lobby/room endpoints
63+
- `worlds/` : world integrations
64+
- `services/` : auxiliary services (social bots, etc.)
65+
- `sekailink-client-plan/` : project docs, runbooks, implementation notes
6766

68-
Do **not** commit credentials/tokens. Use local private files (`*.local`) or a proper secret manager.
69-
70-
## Build (Client)
67+
## Build (Desktop Client)
7168
```bash
7269
cd client/app
7370
npm install
7471
npm run build
75-
npm run electron:pack:ui-prototype
72+
npm run electron:pack
73+
npm run electron:pack:win
7674
```
7775

78-
AppImage output:
79-
- `client/app/release/sekailink-UI-prototype-<version>.AppImage`
80-
8176
## Documentation
82-
- Sprint logs and integration progress: `sekailink-client-plan/`
83-
- Dev setup and release process: `sekailink-docs/DEV_SETUP.md`, `sekailink-docs/RELEASE_PROCESS.md`
84-
- Runtime integration notes: `sekailink-docs/CLIENT_AUTOLAUNCH_PLAN.md`, `sekailink-docs/POPTRACKER.md`, `sekailink-docs/BIZHAWK-CONNECTORS.md`
77+
- Main docs index: `sekailink-client-plan/README.md`
78+
- Architecture and workflows: `sekailink-client-plan/03-target-architecture.md`, `sekailink-client-plan/28-integration-workflow.md`
79+
- Release and updater notes: `sekailink-client-plan/34-updater-sprint-2026-02-11.md`
8580

8681
## Credits
87-
SekaiLink builds on upstream ecosystems and tooling:
88-
- **Archipelago** — protocol, clients, world architecture
89-
- **PopTracker** — tracker runtime and pack ecosystem
90-
- **BizHawk** — emulator core integration
91-
- plus other world/emulator/tool maintainers referenced in `THIRD_PARTY_NOTICES.md`
82+
SekaiLink builds on and integrates upstream projects:
83+
- [Archipelago](https://github.com/ArchipelagoMW/Archipelago)
84+
- [PopTracker](https://github.com/black-sliver/PopTracker)
85+
- [BizHawk](https://github.com/TASEmulators/BizHawk)
86+

WebHostLib/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# WebHost
2+
3+
## Contribution Guidelines
4+
**Thank you for your interest in contributing to the MultiworldGG website!**
5+
Much of the content on the website is generated automatically, but there are some things
6+
that need a personal touch. For those things, we rely on contributions from both the core
7+
team and the community. The current primary maintainer of the website is TreZc0_.
8+
9+
### Small Changes
10+
Little changes like adding a button or a couple new select elements are perfectly fine.
11+
Tweaks to style specific to a PR's content are also probably not a problem. For example, if
12+
you build a new page which needs two side by side tables, and you need to write a CSS file
13+
specific to your page, that is perfectly reasonable.
14+
15+
### Content Additions
16+
Once you develop a new feature or add new content the website, make a pull request. It will
17+
be reviewed by the community and there will probably be some discussion around it. Depending
18+
on the size of the feature, and if new styles are required, there may be an additional step
19+
before the PR is accepted wherein Farrak works with the designer to implement styles.

WebHostLib/__init__.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import base64
2+
import os
3+
import socket
4+
import typing
5+
import uuid
6+
7+
from flask import Flask
8+
from flask_socketio import SocketIO
9+
from flask_caching import Cache
10+
from flask_compress import Compress
11+
from pony.flask import Pony
12+
from werkzeug.routing import BaseConverter
13+
14+
from Utils import title_sorted, get_file_safe_name,world_list_sorted
15+
16+
UPLOAD_FOLDER = os.path.relpath('uploads')
17+
LOGS_FOLDER = os.path.relpath('logs')
18+
os.makedirs(LOGS_FOLDER, exist_ok=True)
19+
20+
app = Flask(__name__)
21+
Pony(app)
22+
socketio = SocketIO(
23+
app,
24+
cors_allowed_origins="*",
25+
async_mode="gevent",
26+
message_queue=os.environ.get("REDIS_URL", "redis://127.0.0.1:6379/0"),
27+
logger=False,
28+
engineio_logger=False,
29+
)
30+
31+
app.jinja_env.filters['any'] = any
32+
app.jinja_env.filters['all'] = all
33+
app.jinja_env.filters['get_file_safe_name'] = get_file_safe_name
34+
35+
# overwrites of flask default config
36+
app.config["DEBUG"] = False
37+
app.config["PORT"] = 80
38+
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
39+
app.config["MAX_CONTENT_LENGTH"] = 64 * 1024 * 1024 # 64 megabyte limit
40+
# if you want to deploy, make sure you have a non-guessable secret key
41+
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
42+
app.config["SESSION_PERMANENT"] = True
43+
app.config["MAX_FORM_MEMORY_SIZE"] = 2 * 1024 * 1024 # 2 MB, needed for large option pages such as SC2
44+
45+
# custom config
46+
app.config["SELFHOST"] = True # application process is in charge of running the websites
47+
app.config["GENERATORS"] = 8 # maximum concurrent world gens
48+
app.config["HOSTERS"] = 8 # maximum concurrent room hosters
49+
app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms.
50+
app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections
51+
app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections
52+
app.config["SELFGEN"] = True # application process is in charge of scheduling Generations.
53+
# at what amount of worlds should scheduling be used, instead of rolling in the web-thread
54+
app.config["JOB_THRESHOLD"] = 1
55+
# after what time in seconds should generation be aborted, freeing the queue slot. Can be set to None to disable.
56+
app.config["JOB_TIME"] = 600
57+
# memory limit for generator processes in bytes
58+
app.config["GENERATOR_MEMORY_LIMIT"] = 4294967296
59+
app.config['SESSION_PERMANENT'] = True
60+
# set worlds requested to be removed by maintainer as hidden by default
61+
app.config['HIDDEN_WEBWORLDS'] = ["Super Mario World", "Sonic Adventure 2 Battle", "Celeste 64", "Donkey Kong Country 3", "Celeste (Open World)"]
62+
63+
# waitress uses one thread for I/O, these are for processing of views that then get sent
64+
# sekailink.xyz uses gunicorn + nginx; ignoring this option
65+
app.config["WAITRESS_THREADS"] = 10
66+
# a default that just works. sekailink.xyz runs on postgresql
67+
app.config["PONY"] = {
68+
'provider': 'sqlite',
69+
'filename': os.path.abspath('ap.db3'),
70+
'create_db': True
71+
}
72+
app.config["MAX_ROLL"] = 20
73+
app.config["CACHE_TYPE"] = "SimpleCache"
74+
app.config["CACHE_DEFAULT_TIMEOUT"] = 300 # 5 minutes default
75+
app.config["CACHE_KEY_PREFIX"] = "multiworld_"
76+
app.config["HOST_ADDRESS"] = ""
77+
app.config["ASSET_RIGHTS"] = False
78+
app.config["MONITORING_ADMIN_TOKEN"] = None # Admin token for monitoring API endpoints
79+
app.config["TERMS_VERSION"] = "v1"
80+
app.config["LOBBY_TIMEOUT_SECONDS"] = 6 * 60 * 60
81+
app.config["LOBBY_EMPTY_TIMEOUT_SECONDS"] = 60 * 60
82+
app.config["DEV_LOGIN_ENABLED"] = True
83+
84+
cache = Cache()
85+
Compress(app)
86+
87+
88+
def to_python(value: str) -> uuid.UUID:
89+
return uuid.UUID(bytes=base64.urlsafe_b64decode(value + '=='))
90+
91+
92+
def to_url(value: uuid.UUID) -> str:
93+
return base64.urlsafe_b64encode(value.bytes).rstrip(b'=').decode('ascii')
94+
95+
96+
class B64UUIDConverter(BaseConverter):
97+
98+
def to_python(self, value: str) -> uuid.UUID:
99+
return to_python(value)
100+
101+
def to_url(self, value: typing.Any) -> str:
102+
assert isinstance(value, uuid.UUID)
103+
return to_url(value)
104+
105+
106+
# short UUID
107+
app.url_map.converters["suuid"] = B64UUIDConverter
108+
app.jinja_env.filters["suuid"] = to_url
109+
app.jinja_env.filters["title_sorted"] = title_sorted
110+
app.jinja_env.filters["world_list_sorted"] = world_list_sorted
111+
112+
113+
def register() -> None:
114+
"""Import submodules, triggering their registering on flask routing.
115+
Note: initializes worlds subsystem."""
116+
import importlib
117+
118+
from werkzeug.utils import find_modules
119+
# has automatic patch integration
120+
import worlds.Files
121+
app.jinja_env.filters['is_applayercontainer'] = worlds.Files.is_ap_player_container
122+
123+
from WebHostLib.customserver import run_server_process
124+
import WebHostLib.lobbies
125+
import WebHostLib.realtime
126+
127+
for module in find_modules("WebHostLib", include_packages=True):
128+
importlib.import_module(module)
129+
130+
from . import api
131+
app.register_blueprint(api.api_endpoints)

WebHostLib/api/__init__.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
"""API endpoints package."""
2+
from typing import List, Tuple
3+
4+
from flask import Blueprint
5+
6+
from ..models import Seed, Slot
7+
8+
api_endpoints = Blueprint('api', __name__, url_prefix="/api")
9+
10+
11+
def get_players(seed: Seed) -> List[Tuple[str, str]]:
12+
return [(slot.player_name, slot.game) for slot in seed.slots.order_by(Slot.player_id)]
13+
14+
# trigger endpoint registration
15+
from . import datapackage, generate, monitoring, room, tracker, user, sphere_tracker

WebHostLib/api/datapackage.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from flask import abort
2+
3+
from Utils import restricted_loads
4+
from WebHostLib import cache
5+
from WebHostLib.models import GameDataPackage
6+
from . import api_endpoints
7+
8+
9+
@api_endpoints.route('/datapackage')
10+
@cache.cached()
11+
def get_datapackage():
12+
from worlds import network_data_package
13+
return network_data_package
14+
15+
16+
@api_endpoints.route('/datapackage/<string:checksum>')
17+
@cache.memoize(timeout=3600)
18+
def get_datapackage_by_checksum(checksum: str):
19+
package = GameDataPackage.get(checksum=checksum)
20+
if package:
21+
return restricted_loads(package.data)
22+
return abort(404)
23+
24+
25+
@api_endpoints.route('/datapackage_checksum')
26+
@cache.cached()
27+
def get_datapackage_checksums():
28+
from worlds import network_data_package
29+
version_package = {
30+
game: game_data["checksum"] for game, game_data in network_data_package["games"].items()
31+
}
32+
return version_package

0 commit comments

Comments
 (0)