feat: customize login and lobby spaces from the panel

This commit is contained in:
Lemon-miaow committed 2026-10-04 12:38:39 +08:00
1 parent efbbe27629
commit 10bd2ddad0
54 files changed
+1146 -158

No files matched your search

@@ -5,6 +5,7 @@ import best.lolicon.felis.link.FelisApiClient;
import best.lolicon.felis.link.LinkClient;
import best.lolicon.felis.link.LinkCode;
import best.lolicon.felis.link.LinkConfig;
import best.lolicon.felis.link.ExperienceConfig;
import best.lolicon.felis.link.LinkConfigLoader;
import com.loohp.limbo.events.EventHandler;
@@ -92,6 +93,7 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener {
private volatile Readiness readiness;
private volatile LoginFlow flow;
private ExperienceConfig experience;
@Override
public void onEnable() {
@@ -146,6 +148,7 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener {
try {
File props = new File(getDataFolder(), "felis-link.properties");
cfg = LinkConfigLoader.load(props.toPath());
experience = ExperienceConfig.load(ExperienceConfig.PATH);
} catch (IOException e) {
// Missing/half config: like the other Felis plugins, load un-crippled —
// readiness stays up, the login flow just never turns on.
@@ -161,7 +164,9 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener {
}
String consoleUrl = "https://" + panelHost;
String lobby = GateConfig.lobby(System.getenv("FELIS_LOBBY_SERVER"));
long timeoutMillis = GateConfig.loginTimeoutSeconds(System.getenv("FELIS_LOGIN_TIMEOUT_SECONDS")) * 1000L;
long timeoutMillis = GateConfig.loginTimeoutSeconds(System.getenv("FELIS_LOGIN_TIMEOUT_SECONDS") != null
? System.getenv("FELIS_LOGIN_TIMEOUT_SECONDS")
: Long.toString(experience.number("loginTimeoutSeconds", GateConfig.DEFAULT_TIMEOUT_SECONDS))) * 1000L;
this.flow = new LoginFlow(new LimboGate(), new FelisApiClient(cfg), new LinkClient(cfg),
consoleUrl, timeoutMillis, System::currentTimeMillis, LOG);
LOG.info("FelisLimbo: login flow ON — console=" + consoleUrl
@@ -187,7 +192,7 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener {
@Override
public LoginFlow.Seat player(UUID id) {
Player player = getServer().getPlayer(id);
return player != null && player.isValid() ? new LimboSeat(player) : null;
return player != null && player.isValid() ? new LimboSeat(player, experience) : null;
}
@Override
@@ -222,7 +227,7 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener {
}
/** LimboSeat is one Limbo player as the flow sees them. */
private record LimboSeat(Player player) implements LoginFlow.Seat {
private record LimboSeat(Player player, ExperienceConfig experience) implements LoginFlow.Seat {
@Override
public String name() {
return player.getName();
@@ -235,7 +240,9 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener {
@Override
public void showCode(LinkCode code, String url) {
player.openBook(LoginBook.book(code.code(), url));
if (experience.flag("openBook", true)) {
player.openBook(LoginBook.book(code.code(), url, experience));
}
}
@Override
@@ -1,5 +1,7 @@
package best.lolicon.felis.limbo;
import best.lolicon.felis.link.ExperienceConfig;
import java.util.Map;
import net.kyori.adventure.inventory.Book;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
@@ -30,15 +32,20 @@ final class LoginBook {
}
static Book book(String code, String url) {
Component page = Component.text("Felis 登录 / Login\n\n")
return book(code, url, new ExperienceConfig(Map.of()));
}
static Book book(String code, String url, ExperienceConfig settings) {
Component page = Component.text(settings.text("bookHeading", "Felis 登录 / Login") + "\n\n")
.append(Component.text("绑定码 / Code:\n"))
.append(Component.text(code + "\n\n").color(NamedTextColor.GOLD))
.append(Component.text("▶ 点此打开登录页\n▶ Open login page\n")
.append(Component.text(settings.text("bookAction", "▶ 点此打开登录页\n▶ Open login page") + "\n")
.color(NamedTextColor.AQUA)
.clickEvent(ClickEvent.openUrl(url)))
.append(Component.text("\n在系统浏览器中完成。\nUse your SYSTEM browser —\nnot WeChat / QQ (passkey\nwon't work there).")
.append(Component.text("\n" + settings.text("bookHelp", "在系统浏览器中完成。\nUse your SYSTEM browser —\nnot WeChat / QQ (passkey\nwon't work there)."))
.color(NamedTextColor.GRAY));
return Book.book(Component.text("Felis Login"), Component.text("Felis"), page);
return Book.book(Component.text(settings.text("bookTitle", "Felis Login")),
Component.text(settings.text("bookAuthor", "Felis")), page);
}
/** chatLine renders one chat line with § colour codes, its web addresses clickable. */
@@ -1,5 +1,6 @@
package best.lolicon.felis.limbo;
import best.lolicon.felis.link.ExperienceConfig;
import best.lolicon.felis.link.Control;
import best.lolicon.felis.link.ControlFrame;
import best.lolicon.felis.link.FelisApiClient;
@@ -24,6 +25,8 @@ import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -356,7 +359,22 @@ public final class LoginFlowTest {
assertEq("lobby set", "hub", GateConfig.lobby(" hub "));
}
private static void loginBook() {
private static void loginBook() throws IOException {
Path file = Files.createTempFile("felis-experience", ".json");
ExperienceConfig settings;
try {
Files.writeString(file, "{\"bookTitle\":\"My Network\",\"bookHeading\":\"欢迎 / Welcome\"}");
settings = ExperienceConfig.load(file);
} finally {
Files.deleteIfExists(file);
}
Book customized = LoginBook.book(CODE, CONSOLE + "/link", settings);
assertEq("custom book title", "My Network", plain(customized.title()));
assertTrue("custom book reads UTF-8 from the world volume", plain(customized.pages().get(0)).contains("欢迎 / Welcome"));
assertEq("custom book keeps the code", true, plain(customized.pages().get(0)).contains(CODE));
List<Component> customLinks = new ArrayList<>();
collectLinks(customized.pages().get(0), customLinks);
assertEq("custom book keeps the generated login URL", CONSOLE + "/link", customLinks.get(0).clickEvent().value());
Book book = LoginBook.book(CODE, CONSOLE + "/link");
assertEq("book title", "Felis Login", plain(book.title()));
Component page = book.pages().get(0);