MFF

    Minecraft Forge France
    • Récent
    • Mots-clés
    • Populaire
    • Utilisateurs
    • Groupes
    • Forge Events
      • Automatique
      • Foncé
      • Clair
    • S'inscrire
    • Se connecter

    lancer le 1.12.2.jar sans passer par le launcher officiel

    Planifier Épinglé Verrouillé Déplacé Sans suite
    1.12.2launcher
    45 Messages 3 Publieurs 3.2k Vues 3 Watching
    Charger plus de messages
    • Du plus ancien au plus récent
    • Du plus récent au plus ancien
    • Les plus votés
    Répondre
    • Répondre à l'aide d'un nouveau sujet
    Se connecter pour répondre
    Ce sujet a été supprimé. Seuls les utilisateurs avec les droits d'administration peuvent le voir.
    • baztoul76B Hors-ligne
      baztoul76
      dernière édition par

      non le minecraft.jar a existé jusqu’à la 1.6, et avant il y avait donc 5 autres versions
      mais j’ai fait mes recherches et je pense que le minecraft.jar a été remplacé par client.jar, qui se trouve dans un dossier

      je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

      1 réponse Dernière réponse Répondre Citer 0
      • U Hors-ligne
        UtopiePhysique
        dernière édition par UtopiePhysique

        Peut être, en tout cas le dossier .jar de la version à bien le main ici net.minecraft.clien.Main, fais un test au cas où cela te coûte rien 🙂

        J’avais créer un launcher en java il y à très longtemps je ne me rappel plus comment j’ai fais je sais que ça à été très rapide et j’y connaissais rien au Java, je l’avais fais pour un serveur qui avait acheter un launcher et que les URL était pas bonne et que le gars répondait plus.

        Du coup je leurs avait refait en plus optimisé, multi thread, avec barre de téléchargement avec l’avancement etc qui lancer le jar.

        Mais je sais plus sur quel jar j’avais pointé.

        D’ailleurs dans sa commande je ne vois pas de minecraft.jar ou client.jar mais je vois bien 1.12.2.jar dans version ^^

        1 réponse Dernière réponse Répondre Citer 0
        • baztoul76B Hors-ligne
          baztoul76
          dernière édition par

          non le problème vient d’une classe manquante autre que la classe Main
          je pense que le problème est dû à ma version de java

          je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

          1 réponse Dernière réponse Répondre Citer 0
          • U Hors-ligne
            UtopiePhysique
            dernière édition par UtopiePhysique

            Je ne sais pas si une version Java peut être l’origine d’un return “unknow source” et qu’il ne trouve pas l’accès au contenu d’une class d’un jar 😕

            Mais sinon sur les vieux sujet comme ici: https://stackoverflow.com/questions/14531917/launch-minecraft-from-command-line-username-and-password-as-prefix

            Le minecraft.jar était dans bin qui aujourd’hui à été remplacé par le système de version donc les .jar “minecraft.jar” on été remplacé par “X.X.X.jar”. D’ailleurs on retrouve le même contenu dans le JAR.

            Edit: ici il y à depuis 1 mois un code de start mais cela me fait une erreur également je vais voir 🙂

            https://github.com/iiiypuk/minecraft-launcher/blob/master/windows/mc_start.bat

            2020-10-24 22:19:06,592 main ERROR Error processing element Queue ([Appenders: null]): CLASS_NOT_FOUND
            2020-10-24 22:19:06,667 main ERROR Unable to locate appender "ServerGuiConsole" for logger config "root"
            Exception in thread "main" java.lang.NoClassDefFoundError: com/mojang/authlib/properties/PropertyMap
                    at net.minecraft.client.main.Main.main(SourceFile:112)
            Caused by: java.lang.ClassNotFoundException: com.mojang.authlib.properties.PropertyMap
                    at java.net.URLClassLoader.findClass(Unknown Source)
                    at java.lang.ClassLoader.loadClass(Unknown Source)
                    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
                    at java.lang.ClassLoader.loadClass(Unknown Source)
                    ... 1 more
            
            1 réponse Dernière réponse Répondre Citer 0
            • robin4002R Hors-ligne
              robin4002 Moddeurs confirmés Rédacteurs Administrateurs
              dernière édition par robin4002

              Le jar de minecraft c’est bien le fichier 1.12.2.jar qui se trouve dans le dossier version.
              C’est lui qui contient la main class.

              1 réponse Dernière réponse Répondre Citer 0
              • baztoul76B Hors-ligne
                baztoul76
                dernière édition par

                ok le script “marche” mais comme d’habitude ClassNotFoundException :

                Exception in thread "main" java.lang.NoClassDefFoundError: org/lwjgl/LWJGLException
                    at net.minecraft.client.main.Main.main(SourceFile:38) 
                Caused by: java.lang.ClassNotFoundException: org.lwjgl.LWJGLException
                    at  # <plein d'autres classes qui sont utilisées par la classe "Main">
                

                mais cette fois c’est une autre (org.lwjgl.LWJGLException)

                je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

                1 réponse Dernière réponse Répondre Citer 0
                • robin4002R Hors-ligne
                  robin4002 Moddeurs confirmés Rédacteurs Administrateurs
                  dernière édition par

                  Il te manque lwjgl dans ce cas.

                  1 réponse Dernière réponse Répondre Citer 0
                  • U Hors-ligne
                    UtopiePhysique
                    dernière édition par

                    Il faut bien ajouter tout les dépendances, tout doit être réuni, minecraft est fait en puzzle et il faut toutes les pièces pour bien le démarrer 🙂

                    1 réponse Dernière réponse Répondre Citer 0
                    • baztoul76B Hors-ligne
                      baztoul76
                      dernière édition par

                      le problème vient sûrement de la mauvaise version deminecraft car le code que j’ai trouvé sur GitHub (donné par @UtopiePhysique) fonctionne normalement en 1.16.3
                      donc je fais quelques changements et on verra bien

                      je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

                      U 1 réponse Dernière réponse Répondre Citer 0
                      • U Hors-ligne
                        UtopiePhysique @baztoul76
                        dernière édition par

                        @baztoul76 a dit dans lancer le 1.12.2.jar sans passer par le launcher officiel :

                        le problème vient sûrement de la mauvaise version deminecraft car le code que j’ai trouvé sur GitHub (donné par @UtopiePhysique) fonctionne normalement en 1.16.3
                        donc je fais quelques changements et on verra bien

                        Le code sur GitHub est valable en 1.16.2 mais il me semble en 1.12.2 (et surtout les versions précédentes) les dépendances sont différentes.

                        Il faut que tu trouves la liste entière des dépendances à exécuté au lancement et tout fonctionnera.

                        Selon la version les dépendances peuvent légèrement être différente.

                        1 réponse Dernière réponse Répondre Citer 0
                        • baztoul76B Hors-ligne
                          baztoul76
                          dernière édition par

                          en fait déjà sur le GitHub la version de LWJGL était 3.2.1 et la mienne 3.2.2 donc déjà ça commençait mal…
                          ensuite j’ai modifié et ça ne marche toujours pas

                          je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

                          1 réponse Dernière réponse Répondre Citer 0
                          • robin4002R Hors-ligne
                            robin4002 Moddeurs confirmés Rédacteurs Administrateurs
                            dernière édition par

                            Minecraft 1.12 a besoin de LWJGL 2. C’est à partir de la 1.13 que Minecraft utilise la version 3.

                            1 réponse Dernière réponse Répondre Citer 0
                            • U Hors-ligne
                              UtopiePhysique
                              dernière édition par UtopiePhysique

                              @baztoul76 a dit dans lancer le 1.12.2.jar sans passer par le launcher officiel :

                              en fait déjà sur le GitHub la version de LWJGL était 3.2.1 et la mienne 3.2.2 donc déjà ça commençait mal…
                              ensuite j’ai modifié et ça ne marche toujours pas

                              Faut vraiment prendre les dépendances similaire à ceux de minecraft, forcément tu auras pleins de trucs mis à jour, mais il faut utiliser les même que minecraft dans le cas contraire tu auras des incompatiblités, des fonctions qui auront changer d’arguments, de type ou de nom etc.

                              @robin4002 Tu as 2 fois “C:\Users\robin\AppData\Roaming.minecraft\libraries\net\sf\jopt-simple\jopt-simple\5.0.3\jopt-simple-5.0.3.jar” dans ton lancement de Forge d’ailleurs 🙂

                              J’ai fais divers test j’ai pas réussi, je sais plus comment j’avais fais 😕

                              1 réponse Dernière réponse Répondre Citer 0
                              • baztoul76B Hors-ligne
                                baztoul76
                                dernière édition par baztoul76

                                yes j’ai réussi à lancer le jeu (bon par contre je ne sais pas comment me connecter avec mon compte perso mais c’est déjà ça)
                                je vous donne le code :

                                @echo off
                                title Minecraft Launcher
                                
                                :: nom d utilisateur
                                set /p player=username ? 
                                
                                :: dossier principal
                                set MC_DIR=%AppData%\.minecraft
                                
                                :: sont ici :
                                :: saves/, resourcepacks/ et screenshots/ 
                                :: et options.txt et les fichiers servers.dat
                                set GAME_DIR=%MC_DIR%
                                
                                :: bibliothèques et ressources (pas de changements requis)
                                set ASSETS_DIR=%MC_DIR%\assets
                                set ASSETS_INDEX=1.12
                                
                                :: versions de minecraft
                                set GAME_VERSION=1.12.2
                                
                                :: avoir les archives depuis le dossier bin\
                                set NATIVES_DIR=%MC_DIR%\bin\8e36-cbe6-c36c-5456
                                
                                :: pseudo
                                set PLAYER_NAME=%player%
                                
                                :: résolution du jeu
                                set WINDOW_W=1366
                                set WINDOW_H=768
                                
                                :: arguments JVM
                                set "JVM_RAM=-Xmx2G"
                                set "JVM_ARGS=-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M"
                                
                                :: lancement de MC
                                %SystemDrive%
                                cd %MC_DIR%
                                
                                java.exe %JVM_RAM% %JVM_ARGS% ^
                                -Dos.name="Windows 10" ^
                                -Dos.version=10.0 ^
                                -Dminecraft.launcher.brand=minecraft-launcher ^
                                -Dminecraft.launcher.version=2.1.17784 ^
                                -Dminecraft.client.jar=%MC_DIR%\versions\1.12.2\1.12.2.jar ^
                                -Djava.library.path=%NATIVES_DIR% ^
                                -cp %MC_DIR%\libraries\net\minecraftforge\forge\1.12.2-14.23.5.2768\forge-1.12.2-14.23.5.2768.jar;%MC_DIR%\libraries\net\minecraft\launchwrapper\1.12\launchwrapper-1.12.jar;%MC_DIR%\libraries\org\ow2\asm\asm-all\5.2\asm-all-5.2.jar;%MC_DIR%\libraries\org\jline\jline\3.5.1\jline-3.5.1.jar;%MC_DIR%\libraries\net\java\dev\jna\jna\4.4.0\jna-4.4.0.jar;%MC_DIR%\libraries\com\typesafe\akka\akka-actor_2.11\2.3.3\akka-actor_2.11-2.3.3.jar;%MC_DIR%\libraries\com\typesafe\config\1.2.1\config-1.2.1.jar;%MC_DIR%\libraries\org\scala-lang\scala-actors-migration_2.11\1.1.0\scala-actors-migration_2.11-1.1.0.jar;%MC_DIR%\libraries\org\scala-lang\scala-compiler\2.11.1\scala-compiler-2.11.1.jar;%MC_DIR%\libraries\org\scala-lang\plugins\scala-continuations-library_2.11\1.0.2\scala-continuations-library_2.11-1.0.2.jar;%MC_DIR%\libraries\org\scala-lang\plugins\scala-continuations-plugin_2.11.1\1.0.2\scala-continuations-plugin_2.11.1-1.0.2.jar;%MC_DIR%\libraries\org\scala-lang\scala-library\2.11.1\scala-library-2.11.1.jar;%MC_DIR%\libraries\org\scala-lang\scala-parser-combinators_2.11\1.0.1\scala-parser-combinators_2.11-1.0.1.jar;%MC_DIR%\libraries\org\scala-lang\scala-reflect\2.11.1\scala-reflect-2.11.1.jar;%MC_DIR%\libraries\org\scala-lang\scala-swing_2.11\1.0.1\scala-swing_2.11-1.0.1.jar;%MC_DIR%\libraries\org\scala-lang\scala-xml_2.11\1.0.2\scala-xml_2.11-1.0.2.jar;%MC_DIR%\libraries\lzma\lzma\0.0.1\lzma-0.0.1.jar;%MC_DIR%\libraries\net\sf\jopt-simple\jopt-simple\5.0.3\jopt-simple-5.0.3.jar;%MC_DIR%\libraries\java3d\vecmath\1.5.2\vecmath-1.5.2.jar;%MC_DIR%\libraries\net\sf\trove4j\trove4j\3.0.3\trove4j-3.0.3.jar;%MC_DIR%\libraries\org\apache\maven\maven-artifact\3.5.3\maven-artifact-3.5.3.jar;%MC_DIR%\libraries\com\mojang\patchy\1.1\patchy-1.1.jar;%MC_DIR%\libraries\oshi-project\oshi-core\1.1\oshi-core-1.1.jar;%MC_DIR%\libraries\net\java\dev\jna\jna\4.4.0\jna-4.4.0.jar;%MC_DIR%\libraries\net\java\dev\jna\platform\3.4.0\platform-3.4.0.jar;%MC_DIR%\libraries\com\ibm\icu\icu4j-core-mojang\51.2\icu4j-core-mojang-51.2.jar;%MC_DIR%\libraries\net\sf\jopt-simple\jopt-simple\5.0.3\jopt-simple-5.0.3.jar;%MC_DIR%\libraries\com\paulscode\codecjorbis\20101023\codecjorbis-20101023.jar;%MC_DIR%\libraries\com\paulscode\codecwav\20101023\codecwav-20101023.jar;%MC_DIR%\libraries\com\paulscode\libraryjavasound\20101123\libraryjavasound-20101123.jar;%MC_DIR%\libraries\com\paulscode\librarylwjglopenal\20100824\librarylwjglopenal-20100824.jar;%MC_DIR%\libraries\com\paulscode\soundsystem\20120107\soundsystem-20120107.jar;%MC_DIR%\libraries\io\netty\netty-all\4.1.9.Final\netty-all-4.1.9.Final.jar;%MC_DIR%\libraries\com\google\guava\guava\21.0\guava-21.0.jar;%MC_DIR%\libraries\org\apache\commons\commons-lang3\3.5\commons-lang3-3.5.jar;%MC_DIR%\libraries\commons-io\commons-io\2.5\commons-io-2.5.jar;%MC_DIR%\libraries\commons-codec\commons-codec\1.10\commons-codec-1.10.jar;%MC_DIR%\libraries\net\java\jinput\jinput\2.0.5\jinput-2.0.5.jar;%MC_DIR%\libraries\net\java\jutils\jutils\1.0.0\jutils-1.0.0.jar;%MC_DIR%\libraries\com\google\code\gson\gson\2.8.0\gson-2.8.0.jar;%MC_DIR%\libraries\com\mojang\authlib\1.5.25\authlib-1.5.25.jar;%MC_DIR%\libraries\com\mojang\realms\1.10.22\realms-1.10.22.jar;%MC_DIR%\libraries\org\apache\commons\commons-compress\1.8.1\commons-compress-1.8.1.jar;%MC_DIR%\libraries\org\apache\httpcomponents\httpclient\4.3.3\httpclient-4.3.3.jar;%MC_DIR%\libraries\commons-logging\commons-logging\1.1.3\commons-logging-1.1.3.jar;%MC_DIR%\libraries\org\apache\httpcomponents\httpcore\4.3.2\httpcore-4.3.2.jar;%MC_DIR%\libraries\it\unimi\dsi\fastutil\7.1.0\fastutil-7.1.0.jar;%MC_DIR%\libraries\org\apache\logging\log4j\log4j-api\2.8.1\log4j-api-2.8.1.jar;%MC_DIR%\libraries\org\apache\logging\log4j\log4j-core\2.8.1\log4j-core-2.8.1.jar;%MC_DIR%\libraries\org\lwjgl\lwjgl\lwjgl\2.9.4-nightly-20150209\lwjgl-2.9.4-nightly-20150209.jar;%MC_DIR%\libraries\org\lwjgl\lwjgl\lwjgl_util\2.9.4-nightly-20150209\lwjgl_util-2.9.4-nightly-20150209.jar;%MC_DIR%\libraries\com\mojang\text2speech\1.10.3\text2speech-1.10.3.jar;%MC_DIR%\versions\1.12.2\1.12.2.jar ^
                                net.minecraft.client.main.Main ^
                                --username %PLAYER_NAME% ^
                                --version %GAME_VERSION% ^
                                --accessToken 0 --userProperties {} ^
                                --gameDir %GAME_DIR% ^
                                --assetsDir %ASSETS_DIR% ^
                                --assetIndex %ASSETS_INDEX% ^
                                --width %WINDOW_W% ^
                                --height %WINDOW_H%
                                
                                pause
                                

                                EDIT : donc j’aimerais savoir (si vous savez comment on fait sinon je me débrouillerai) si vous savez comment se connecter avec le compte MOJANG (je vais voir du côté du session ID)

                                je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

                                1 réponse Dernière réponse Répondre Citer 0
                                • U Hors-ligne
                                  UtopiePhysique
                                  dernière édition par UtopiePhysique

                                  J’avais vu qu’il y avait un argument de pseudo et mot de passe. (t’as le pseudo je crois d’ailleurs déjà)
                                  Sinon 2eme méthode de pro c’est de récup le HASH identifiant de minecraft officiel et de l’utiliser pour auto log le compte.

                                  Mais sa … Je pourrais pas t’aider 🙂

                                  Edit: Ah non c’est un token de connexion, donc il faut d’abord visiter une URL je crois pas faisable en DOS (enfin très chiant).

                                  1 réponse Dernière réponse Répondre Citer 0
                                  • baztoul76B Hors-ligne
                                    baztoul76
                                    dernière édition par

                                    oui j’ai le pseudo mais par contre le mot de passe je ne sais pas trop
                                    ça doit être le --accessToken je pense
                                    là je fais mes recherches mais je ne trou e toujours rien…

                                    je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

                                    1 réponse Dernière réponse Répondre Citer 0
                                    • U Hors-ligne
                                      UtopiePhysique
                                      dernière édition par

                                      Oui c’est ce que j’ai dis dans l’édit, normalement t’as une URL minecraft où avec le pseudo & mot de passe tu peux généré un HASH de connexion, cela à été mis en place suite à une plainte sur l’insécurité de fournir les identifiants à chaque fois partout, il y à eu un HASH pour ne stocker aucun identifiant sur le PC. 🙂

                                      1 réponse Dernière réponse Répondre Citer 0
                                      • baztoul76B Hors-ligne
                                        baztoul76
                                        dernière édition par baztoul76

                                        là je crois tenir quelque chose j’ai regardé sur spigotMC (ce post), et j’ai trouvé ce code-là :

                                        private final static String authserver = "https://authserver.mojang.com";
                                        
                                        public static String authenticate(String username, String password) throws Exception {
                                        
                                            String genClientToken = UUID.randomUUID().toString();
                                        
                                            // Setting up json POST request
                                            String payload = "{\"agent\": {\"name\": \"Minecraft\",\"version\": 1},\"username\": \"" + username
                                                    + "\",\"password\": \"" + password + "\",\"clientToken\": \"" + genClientToken + "\"}";
                                        
                                            String output = postReadURL(payload, new URL(authserver + "/authenticate"));
                                        
                                            // Setting up patterns
                                            String authBeg = "{\"accessToken\":\"";
                                            String authEnd = "\",\"clientToken\":\"";
                                            String clientEnd = "\",\"selectedProfile\"";
                                        
                                            // What we are looking for
                                            String authtoken = getStringBetween(output, authBeg, authEnd);
                                            return authtoken;
                                        }
                                        
                                        private static String postReadURL(String payload, URL url) throws Exception {
                                            HttpsURLConnection con = (HttpsURLConnection) (url.openConnection());
                                        
                                            con.setReadTimeout(15000);
                                            con.setConnectTimeout(15000);
                                            con.setRequestMethod("POST");
                                            con.setRequestProperty("Content-Type", "application/json");
                                            con.setDoInput(true);
                                            con.setDoOutput(true);
                                            OutputStream out = con.getOutputStream();
                                            out.write(payload.getBytes("UTF-8"));
                                            out.close();
                                            BufferedReader in = new BufferedReader(new tStreamReader(con.getInputStream()));
                                            String output = "";
                                            String line = null;
                                            while ((line = in.readLine()) != null) {
                                                output += line;
                                                in.close();
                                            }
                                            return output;
                                        }
                                        
                                        private static String getStringBetween(String base, String begin, String end) {
                                            Pattern patbeg = Pattern.compile(Pattern.quote(begin));
                                            Pattern patend = Pattern.compile(Pattern.quote(end));
                                        
                                            int resbeg = 0;
                                            int resend = base.length() - 1;
                                        
                                            Matcher matbeg = patbeg.matcher(base);
                                        
                                            if (matbeg.find()) resbeg = matbeg.end();
                                        
                                            Matcher matend = patend.matcher(base);
                                        
                                            if (matend.find())
                                                resend = matend.start();
                                        
                                            return base.substring(resbeg, resend);
                                        }
                                        

                                        il doit y avoir un moyen de le retranscrire en bash je pense, ou de créer un script (en python par exemple)

                                        EDIT : ou même de l’ajouter à un code, car on peut créer un script bash avec java (il faut que je revoie ça), et ça devrait marcher avec des variables (j’espère)

                                        je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

                                        1 réponse Dernière réponse Répondre Citer 0
                                        • U Hors-ligne
                                          UtopiePhysique
                                          dernière édition par UtopiePhysique

                                          En faite quand tu le fais avec Java il y aucun soucis, étant un langage objet et souple tu peux largement le faire (je l’avais fais), l’URL à l’air d’être la bonne je ne la trouver plus l’API réponds.

                                          Il suffit de questionner l’API, comme n’importe quel site et récupérer le token après et voilà ^^

                                          Mais en bash ça va être difficile d’accéder à l’url d’un site, récupérer les sources et de récupérer le token dans le json.

                                          Mais pas impossible 🙂

                                          1 réponse Dernière réponse Répondre Citer 0
                                          • baztoul76B Hors-ligne
                                            baztoul76
                                            dernière édition par

                                            je ne pensais pas à ça (regarde l’édit) j’ai dit que c’était sûrement possible de lancer un script bash depuis un programme java (j’ai déjà vu ça), et ça donnerait par exemple

                                            List<String> command;
                                            command.add("set MC_DIR=%AppData%\.minecraft");
                                            // le reste des variables
                                            command.add("java");
                                            command.add("%JVM_ARGS%");
                                            // etc...
                                            command.add("-cp");
                                            command.add("%MC_DIR%/librairies/...1.12.2.jar");
                                            command.add("--username");
                                            command.add(username);
                                            //etc...
                                            command.add("--accesToken");
                                            command.add((int) authenticate(username, password));
                                            //etc...
                                            String newCommand = new String[command.size()];
                                            for (int i = 0;i < command.size(); i++) {
                                                newCommand[i] = command.index(i);
                                            }
                                            Process p = runtime.getRuntime().exec(newCommand);
                                            p.waitFor();
                                            

                                            je pense que ça marche

                                            je ne suis pas le plus compétent en développement de mod, mais je fais de mon mieux pour aider quand même

                                            1 réponse Dernière réponse Répondre Citer 0
                                            • robin4002R robin4002 a déplacé ce sujet de Support pour les moddeurs sur
                                            • 1
                                            • 2
                                            • 3
                                            • 2 / 3
                                            • Premier message
                                              Dernier message
                                            Design by Woryk
                                            ContactMentions Légales

                                            MINECRAFT FORGE FRANCE © 2024

                                            Powered by NodeBB