Loading plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java +1 −1 Changes for plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -230,7 +230,7 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener { @Override public void chat(String line) { player.sendMessage(line); player.sendMessage(LoginBook.chatLine(line)); } @Override Loading plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginBook.java +22 −3 Changes for plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginBook.java: 22 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -4,14 +4,28 @@ import net.kyori.adventure.inventory.Book; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import java.util.regex.Pattern; /** * LoginBook is the book the gate opens on join: the bind code, a line that opens the * login page when clicked, and the reminder to use the system browser, where * WebAuthn/passkey works (the WeChat/QQ in-app browsers break it). * LoginBook renders what the gate shows a player: the book it opens on join (the bind * code, a line that opens the login page when clicked, and the reminder to use the * system browser, where WebAuthn/passkey works; the WeChat/QQ in-app browsers break * it), and the chat lines, whose web addresses open when clicked. */ final class LoginBook { // A web address in a chat line runs to the next space. The client makes nothing // in server chat clickable by itself, so a player who closed the book had to type // the login URL out by hand. private static final LegacyComponentSerializer CHAT = LegacyComponentSerializer.builder() .character(LegacyComponentSerializer.SECTION_CHAR) .extractUrls(Pattern.compile("https?://\\S+"), Style.style(NamedTextColor.AQUA, TextDecoration.UNDERLINED)) .build(); private LoginBook() { } Loading @@ -26,4 +40,9 @@ final class LoginBook { .color(NamedTextColor.GRAY)); return Book.book(Component.text("Felis Login"), Component.text("Felis"), page); } /** chatLine renders one chat line with § colour codes, its web addresses clickable. */ static Component chatLine(String line) { return CHAT.deserialize(line); } } plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginFlow.java +1 −1 Changes for plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginFlow.java: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ final class LoginFlow { interface Seat { String name(); /** chat sends one line, with § colour codes. */ /** chat sends one line, with § colour codes; a web address in it opens when clicked. */ void chat(String line); /** showCode opens the login book. It may throw for a client that refuses it. */ Loading plugins/limbo/test/best/lolicon/felis/limbo/LoginFlowTest.java +30 −0 Changes for plugins/limbo/test/best/lolicon/felis/limbo/LoginFlowTest.java: 30 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import net.kyori.adventure.inventory.Book; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.TextDecoration; import java.io.IOException; import java.io.OutputStream; Loading Loading @@ -119,6 +120,13 @@ public final class LoginFlowTest { List.of(CODE + " " + stub.panelUrl), seat.books); assertTrue("chat carries the code", seat.chat.get(0).contains(CODE)); assertTrue("chat names the panel URL", seat.chat.get(1).contains(stub.panelUrl) && seat.chat.get(2).contains(stub.panelUrl)); // The client makes nothing in server chat clickable by itself: a player who // closed the book had to type the URL out. for (int i = 1; i <= 2; i++) { List<ClickEvent> clicks = new ArrayList<>(); collectClicks(LoginBook.chatLine(seat.chat.get(i)), clicks); assertEq("chat line " + i + " opens the panel URL", List.of(ClickEvent.openUrl(stub.panelUrl)), clicks); } assertEq("no release before the link", 0, seat.releases.size()); int before = stub.hits("/link/status/"); Loading Loading @@ -357,6 +365,19 @@ public final class LoginFlowTest { collectClicks(page, clicks); assertEq("one click, opening the login page", List.of(ClickEvent.openUrl(CONSOLE + "/link")), clicks); assertTrue("the page says to use the system browser", plain(page).contains("SYSTEM browser")); // A chat line keeps its text; only the address in it turns into a link. Component line = LoginBook.chatLine("§e[Felis] 打开 §b" + CONSOLE + "/link §e完成登录(勿用微信/QQ内置浏览器)。"); assertEq("chat line text", "[Felis] 打开 " + CONSOLE + "/link 完成登录(勿用微信/QQ内置浏览器)。", plain(line)); List<Component> links = new ArrayList<>(); collectLinks(line, links); assertEq("one link in the line", 1, links.size()); assertEq("the link is the address alone", CONSOLE + "/link", plain(links.get(0))); assertEq("the link opens it", ClickEvent.openUrl(CONSOLE + "/link"), links.get(0).clickEvent()); assertEq("the link looks like one", TextDecoration.State.TRUE, links.get(0).decoration(TextDecoration.UNDERLINED)); List<ClickEvent> none = new ArrayList<>(); collectClicks(LoginBook.chatLine(LoginFlow.SIGNED_IN), none); assertEq("a line without an address has no link", List.of(), none); } // ---- fakes ---- Loading Loading @@ -642,6 +663,15 @@ public final class LoginFlowTest { return sb.toString(); } private static void collectLinks(Component c, List<Component> out) { if (c.clickEvent() != null) { out.add(c); } for (Component child : c.children()) { collectLinks(child, out); } } private static void collectClicks(Component c, List<ClickEvent> out) { if (c.clickEvent() != null) { out.add(c.clickEvent()); Loading plugins/test.sh +11 −2 Changes for plugins/test.sh: 11 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -26,8 +26,9 @@ # prompt, the op-login approval card names the account and leaves its # name for the admin to type, and the loader mods' shared /link refuses the # console and an offline-mode server before minting, fetches the code off the # server thread and answers on it, and turns any failure into one line. # InviteCardTest and OpApprovalCardTest need the # server thread and answers on it, and turns any failure into one line; the # /link reply makes the panel address a link the player can click open. # InviteCardTest, OpApprovalCardTest and LinkCodeCardTest need the # adventure jars the velocity plugin compiles # against; they are fetched from Maven Central below, pinned by version and # checked by digest (a test run against silently-substituted bytes is not a Loading Loading @@ -213,6 +214,14 @@ javac -cp "$adventure_api:$adventure_key:$examination_api" -d "$work/opcard-clas java -cp "$work/opcard-classes:$adventure_api:$adventure_key:$examination_api" \ best.lolicon.felis.velocity.OpApprovalCardTest echo "==> LinkCodeCardTest (/link reply, velocity)" mkdir -p "$work/linkcard-classes" javac -cp "$adventure_api:$adventure_key:$examination_api" -d "$work/linkcard-classes" \ plugins/velocity/src/main/java/best/lolicon/felis/velocity/LinkCodeCard.java \ plugins/velocity/test/best/lolicon/felis/velocity/LinkCodeCardTest.java java -cp "$work/linkcard-classes:$adventure_api:$adventure_key:$examination_api" \ best.lolicon.felis.velocity.LinkCodeCardTest # --- 2. production compile gates ------------------------------------------------ for module in velocity paper; do Loading Loading
plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java +1 −1 Changes for plugins/limbo/src/main/java/best/lolicon/felis/limbo/FelisLimboPlugin.java: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -230,7 +230,7 @@ public final class FelisLimboPlugin extends LimboPlugin implements Listener { @Override public void chat(String line) { player.sendMessage(line); player.sendMessage(LoginBook.chatLine(line)); } @Override Loading
plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginBook.java +22 −3 Changes for plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginBook.java: 22 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -4,14 +4,28 @@ import net.kyori.adventure.inventory.Book; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import java.util.regex.Pattern; /** * LoginBook is the book the gate opens on join: the bind code, a line that opens the * login page when clicked, and the reminder to use the system browser, where * WebAuthn/passkey works (the WeChat/QQ in-app browsers break it). * LoginBook renders what the gate shows a player: the book it opens on join (the bind * code, a line that opens the login page when clicked, and the reminder to use the * system browser, where WebAuthn/passkey works; the WeChat/QQ in-app browsers break * it), and the chat lines, whose web addresses open when clicked. */ final class LoginBook { // A web address in a chat line runs to the next space. The client makes nothing // in server chat clickable by itself, so a player who closed the book had to type // the login URL out by hand. private static final LegacyComponentSerializer CHAT = LegacyComponentSerializer.builder() .character(LegacyComponentSerializer.SECTION_CHAR) .extractUrls(Pattern.compile("https?://\\S+"), Style.style(NamedTextColor.AQUA, TextDecoration.UNDERLINED)) .build(); private LoginBook() { } Loading @@ -26,4 +40,9 @@ final class LoginBook { .color(NamedTextColor.GRAY)); return Book.book(Component.text("Felis Login"), Component.text("Felis"), page); } /** chatLine renders one chat line with § colour codes, its web addresses clickable. */ static Component chatLine(String line) { return CHAT.deserialize(line); } }
plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginFlow.java +1 −1 Changes for plugins/limbo/src/main/java/best/lolicon/felis/limbo/LoginFlow.java: 1 added line, 1 removed line. Original line number Diff line number Diff line Loading @@ -49,7 +49,7 @@ final class LoginFlow { interface Seat { String name(); /** chat sends one line, with § colour codes. */ /** chat sends one line, with § colour codes; a web address in it opens when clicked. */ void chat(String line); /** showCode opens the login book. It may throw for a client that refuses it. */ Loading
plugins/limbo/test/best/lolicon/felis/limbo/LoginFlowTest.java +30 −0 Changes for plugins/limbo/test/best/lolicon/felis/limbo/LoginFlowTest.java: 30 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ import net.kyori.adventure.inventory.Book; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.format.TextDecoration; import java.io.IOException; import java.io.OutputStream; Loading Loading @@ -119,6 +120,13 @@ public final class LoginFlowTest { List.of(CODE + " " + stub.panelUrl), seat.books); assertTrue("chat carries the code", seat.chat.get(0).contains(CODE)); assertTrue("chat names the panel URL", seat.chat.get(1).contains(stub.panelUrl) && seat.chat.get(2).contains(stub.panelUrl)); // The client makes nothing in server chat clickable by itself: a player who // closed the book had to type the URL out. for (int i = 1; i <= 2; i++) { List<ClickEvent> clicks = new ArrayList<>(); collectClicks(LoginBook.chatLine(seat.chat.get(i)), clicks); assertEq("chat line " + i + " opens the panel URL", List.of(ClickEvent.openUrl(stub.panelUrl)), clicks); } assertEq("no release before the link", 0, seat.releases.size()); int before = stub.hits("/link/status/"); Loading Loading @@ -357,6 +365,19 @@ public final class LoginFlowTest { collectClicks(page, clicks); assertEq("one click, opening the login page", List.of(ClickEvent.openUrl(CONSOLE + "/link")), clicks); assertTrue("the page says to use the system browser", plain(page).contains("SYSTEM browser")); // A chat line keeps its text; only the address in it turns into a link. Component line = LoginBook.chatLine("§e[Felis] 打开 §b" + CONSOLE + "/link §e完成登录(勿用微信/QQ内置浏览器)。"); assertEq("chat line text", "[Felis] 打开 " + CONSOLE + "/link 完成登录(勿用微信/QQ内置浏览器)。", plain(line)); List<Component> links = new ArrayList<>(); collectLinks(line, links); assertEq("one link in the line", 1, links.size()); assertEq("the link is the address alone", CONSOLE + "/link", plain(links.get(0))); assertEq("the link opens it", ClickEvent.openUrl(CONSOLE + "/link"), links.get(0).clickEvent()); assertEq("the link looks like one", TextDecoration.State.TRUE, links.get(0).decoration(TextDecoration.UNDERLINED)); List<ClickEvent> none = new ArrayList<>(); collectClicks(LoginBook.chatLine(LoginFlow.SIGNED_IN), none); assertEq("a line without an address has no link", List.of(), none); } // ---- fakes ---- Loading Loading @@ -642,6 +663,15 @@ public final class LoginFlowTest { return sb.toString(); } private static void collectLinks(Component c, List<Component> out) { if (c.clickEvent() != null) { out.add(c); } for (Component child : c.children()) { collectLinks(child, out); } } private static void collectClicks(Component c, List<ClickEvent> out) { if (c.clickEvent() != null) { out.add(c.clickEvent()); Loading
plugins/test.sh +11 −2 Changes for plugins/test.sh: 11 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -26,8 +26,9 @@ # prompt, the op-login approval card names the account and leaves its # name for the admin to type, and the loader mods' shared /link refuses the # console and an offline-mode server before minting, fetches the code off the # server thread and answers on it, and turns any failure into one line. # InviteCardTest and OpApprovalCardTest need the # server thread and answers on it, and turns any failure into one line; the # /link reply makes the panel address a link the player can click open. # InviteCardTest, OpApprovalCardTest and LinkCodeCardTest need the # adventure jars the velocity plugin compiles # against; they are fetched from Maven Central below, pinned by version and # checked by digest (a test run against silently-substituted bytes is not a Loading Loading @@ -213,6 +214,14 @@ javac -cp "$adventure_api:$adventure_key:$examination_api" -d "$work/opcard-clas java -cp "$work/opcard-classes:$adventure_api:$adventure_key:$examination_api" \ best.lolicon.felis.velocity.OpApprovalCardTest echo "==> LinkCodeCardTest (/link reply, velocity)" mkdir -p "$work/linkcard-classes" javac -cp "$adventure_api:$adventure_key:$examination_api" -d "$work/linkcard-classes" \ plugins/velocity/src/main/java/best/lolicon/felis/velocity/LinkCodeCard.java \ plugins/velocity/test/best/lolicon/felis/velocity/LinkCodeCardTest.java java -cp "$work/linkcard-classes:$adventure_api:$adventure_key:$examination_api" \ best.lolicon.felis.velocity.LinkCodeCardTest # --- 2. production compile gates ------------------------------------------------ for module in velocity paper; do Loading