├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── maven.yml │ └── release.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── bukkit ├── pom.xml └── src │ └── main │ ├── java │ └── dev │ │ └── magicmq │ │ └── pyspigot │ │ └── bukkit │ │ ├── BukkitListener.java │ │ ├── PySpigot.java │ │ ├── command │ │ ├── BukkitPluginCommand.java │ │ └── package-info.java │ │ ├── config │ │ ├── BukkitPluginConfig.java │ │ ├── BukkitProjectOptionsConfig.java │ │ ├── BukkitScriptOptionsConfig.java │ │ └── package-info.java │ │ ├── event │ │ ├── ScriptEvent.java │ │ ├── ScriptExceptionEvent.java │ │ ├── ScriptLoadEvent.java │ │ ├── ScriptUnloadEvent.java │ │ ├── custom │ │ │ ├── CustomEvent.java │ │ │ └── package-info.java │ │ └── package-info.java │ │ ├── manager │ │ ├── command │ │ │ ├── BukkitCommandManager.java │ │ │ ├── BukkitScriptCommand.java │ │ │ └── package-info.java │ │ ├── config │ │ │ ├── BukkitConfigManager.java │ │ │ ├── BukkitScriptConfig.java │ │ │ └── package-info.java │ │ ├── listener │ │ │ ├── BukkitListenerManager.java │ │ │ ├── BukkitScriptEventExecutor.java │ │ │ ├── BukkitScriptEventListener.java │ │ │ └── package-info.java │ │ ├── messaging │ │ │ ├── PluginMessageManager.java │ │ │ ├── ScriptPluginMessageListener.java │ │ │ └── package-info.java │ │ ├── placeholder │ │ │ ├── PlaceholderManager.java │ │ │ ├── ScriptPlaceholder.java │ │ │ └── package-info.java │ │ ├── protocol │ │ │ ├── AsyncProtocolManager.java │ │ │ ├── ListenerType.java │ │ │ ├── PacketReceivingListener.java │ │ │ ├── PacketSendingListener.java │ │ │ ├── ProtocolManager.java │ │ │ ├── ScriptPacketListener.java │ │ │ └── package-info.java │ │ ├── script │ │ │ ├── BukkitScript.java │ │ │ ├── BukkitScriptInfo.java │ │ │ ├── BukkitScriptManager.java │ │ │ ├── BukkitScriptOptions.java │ │ │ └── package-info.java │ │ └── task │ │ │ ├── BukkitTaskManager.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── util │ │ ├── CommandAliasHelpTopic.java │ │ ├── ReflectionUtils.java │ │ ├── package-info.java │ │ └── player │ │ ├── BukkitCommandSender.java │ │ ├── BukkitPlayer.java │ │ └── package-info.java │ └── resources │ ├── Lib │ └── pyspigot.py │ └── plugin.yml ├── bungee ├── pom.xml └── src │ └── main │ ├── java │ └── dev │ │ └── magicmq │ │ └── pyspigot │ │ └── bungee │ │ ├── BungeeListener.java │ │ ├── PyBungee.java │ │ ├── command │ │ ├── BungeePluginCommand.java │ │ └── package-info.java │ │ ├── config │ │ ├── BungeePluginConfig.java │ │ ├── BungeeProjectOptionsConfig.java │ │ ├── BungeeScriptOptionsConfig.java │ │ └── package-info.java │ │ ├── event │ │ ├── ScriptEvent.java │ │ ├── ScriptExceptionEvent.java │ │ ├── ScriptLoadEvent.java │ │ ├── ScriptUnloadEvent.java │ │ ├── custom │ │ │ ├── CustomEvent.java │ │ │ └── package-info.java │ │ └── package-info.java │ │ ├── manager │ │ ├── command │ │ │ ├── BungeeCommandManager.java │ │ │ ├── BungeeScriptCommand.java │ │ │ └── package-info.java │ │ ├── config │ │ │ ├── BungeeConfigManager.java │ │ │ ├── BungeeScriptConfig.java │ │ │ └── package-info.java │ │ ├── listener │ │ │ ├── BungeeListenerManager.java │ │ │ ├── BungeeScriptEventListener.java │ │ │ └── package-info.java │ │ ├── protocol │ │ │ ├── ProtocolManager.java │ │ │ ├── ScriptPacketListener.java │ │ │ └── package-info.java │ │ ├── script │ │ │ ├── BungeeScriptInfo.java │ │ │ ├── BungeeScriptManager.java │ │ │ └── package-info.java │ │ └── task │ │ │ ├── BungeeTaskManager.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── util │ │ └── player │ │ ├── BungeeCommandSender.java │ │ ├── BungeePlayer.java │ │ └── package-info.java │ └── resources │ ├── Lib │ └── pyspigot.py │ └── plugin.yml ├── core ├── pom.xml └── src │ └── main │ ├── java │ └── dev │ │ └── magicmq │ │ └── pyspigot │ │ ├── PlatformAdapter.java │ │ ├── PluginListener.java │ │ ├── PyCore.java │ │ ├── command │ │ ├── PySpigotCommand.java │ │ ├── SubCommand.java │ │ ├── SubCommandMeta.java │ │ ├── package-info.java │ │ └── subcommands │ │ │ ├── HelpCommand.java │ │ │ ├── InfoCommand.java │ │ │ ├── ListScriptsCommand.java │ │ │ ├── LoadCommand.java │ │ │ ├── LoadLibraryCommand.java │ │ │ ├── ReloadAllCommand.java │ │ │ ├── ReloadCommand.java │ │ │ ├── ReloadConfigCommand.java │ │ │ ├── UnloadCommand.java │ │ │ └── package-info.java │ │ ├── config │ │ ├── PluginConfig.java │ │ ├── ProjectOptionsConfig.java │ │ ├── ScriptOptionsConfig.java │ │ └── package-info.java │ │ ├── exception │ │ ├── PluginInitializationException.java │ │ ├── ScriptExitException.java │ │ ├── ScriptInitializationException.java │ │ ├── ScriptRuntimeException.java │ │ └── package-info.java │ │ ├── manager │ │ ├── command │ │ │ ├── CommandManager.java │ │ │ ├── ScriptCommand.java │ │ │ └── package-info.java │ │ ├── config │ │ │ ├── ConfigManager.java │ │ │ ├── ScriptConfig.java │ │ │ └── package-info.java │ │ ├── database │ │ │ ├── Database.java │ │ │ ├── DatabaseManager.java │ │ │ ├── DatabaseType.java │ │ │ ├── mongo │ │ │ │ ├── MongoDatabase.java │ │ │ │ └── package-info.java │ │ │ ├── package-info.java │ │ │ └── sql │ │ │ │ ├── SqlDatabase.java │ │ │ │ └── package-info.java │ │ ├── libraries │ │ │ ├── JarClassLoader.java │ │ │ ├── LibraryManager.java │ │ │ └── package-info.java │ │ ├── listener │ │ │ ├── ListenerManager.java │ │ │ └── package-info.java │ │ ├── redis │ │ │ ├── ClientType.java │ │ │ ├── RedisManager.java │ │ │ ├── ScriptPubSubListener.java │ │ │ ├── client │ │ │ │ ├── RedisCommandClient.java │ │ │ │ ├── RedisPubSubClient.java │ │ │ │ ├── ScriptRedisClient.java │ │ │ │ └── package-info.java │ │ │ └── package-info.java │ │ ├── script │ │ │ ├── GlobalVariables.java │ │ │ ├── RunResult.java │ │ │ ├── Script.java │ │ │ ├── ScriptInfo.java │ │ │ ├── ScriptManager.java │ │ │ ├── ScriptOptions.java │ │ │ └── package-info.java │ │ └── task │ │ │ ├── RepeatingTask.java │ │ │ ├── SyncCallbackTask.java │ │ │ ├── Task.java │ │ │ ├── TaskManager.java │ │ │ └── package-info.java │ │ ├── package-info.java │ │ └── util │ │ ├── ScriptUtils.java │ │ ├── StringUtils.java │ │ ├── logging │ │ ├── JythonLogHandler.java │ │ ├── PrintStreamWrapper.java │ │ ├── ScriptFileLogger.java │ │ ├── ScriptLogger.java │ │ └── package-info.java │ │ ├── package-info.java │ │ └── player │ │ ├── CommandSenderAdapter.java │ │ └── PlayerAdapter.java │ └── resources │ ├── Lib │ ├── function.py │ └── threading_patch.py │ ├── config.yml │ └── script_options.yml ├── examples ├── clearinventory.py ├── communitychest.py ├── gameoflife.py ├── joinmessage.py ├── kickcommand.py ├── periodicmessage.py ├── ping.py ├── placeholder.py ├── protocollib.py ├── swearfilter.py └── teleport │ ├── teleport.py │ └── teleport.yml └── pom.xml /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @magicmq -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a bug report for PySpigot 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. ... 16 | 2. ... 17 | 3. ... 18 | 4. ... 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional Info (please fill in):** 27 | - Server environment: [e.g. Spigot, Paper, etc.] 28 | - Minecraft version [e.g. 1.19, 1.20] 29 | - PySpigot version [e.g. 0.4.0] 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for PySpigot 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 🔗 Linked Issue 2 | 3 | 4 | 5 | ### ❓ Type of change 6 | 7 | 8 | 9 | - [ ] 🐞 Bug fix 10 | - [ ] 👌 Enhancement (improve something existing) 11 | - [ ] ✨ New functionality 12 | - [ ] 🧹 Technical change (refactoring, updates and other improvements) 13 | 14 | ### 📚 Description 15 | 16 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: self-hosted 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - name: Set up JDK 21 20 | uses: actions/setup-java@v4 21 | with: 22 | distribution: 'temurin' 23 | java-version: '21' 24 | - name: Build with Maven 25 | run: mvn -B package --file pom.xml 26 | - name: Copy artifacts to staging directory 27 | run: mkdir staging && rsync -ar target/*.jar **/target/*.jar staging 28 | - name: Upload artifacts 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: pyspigot-build-${{ github.run_number }} 32 | path: ./staging/*.jar 33 | overwrite: true -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create Release and Upload Artifacts 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | build: 10 | runs-on: self-hosted 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Set up JDK 21 15 | uses: actions/setup-java@v4 16 | with: 17 | distribution: 'temurin' 18 | java-version: '21' 19 | - name: Build with Maven 20 | run: mvn -B package --file pom.xml 21 | - name: Maven cleanup 22 | run: mvn --batch-mode --update-snapshots verify 23 | - name: Copy artifacts to staging directory 24 | run: mkdir staging && rsync -ar --exclude 'core/target/*-shaded.jar' target/*-shaded.jar **/target/*-shaded.jar staging 25 | - name: Rename artifacts in staging directory 26 | run: | 27 | for file in staging/*-shaded.jar; do 28 | mv "$file" "${file/-shaded.jar/.jar}" 29 | done 30 | - name: Fetch tag name 31 | id: tagname 32 | uses: actions/github-script@v7 33 | with: 34 | github-token: ${{ secrets.GITHUB_TOKEN }} 35 | result-encoding: string 36 | script: | 37 | console.log("Ref was found to be " + context.payload.ref); 38 | console.log("Tag was found to be " + context.payload.ref.replace("refs/tags/", "")); 39 | return context.payload.ref.replace("refs/tags/", ""); 40 | - name: Create release and upload artifacts 41 | uses: softprops/action-gh-release@v2 42 | env: 43 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 44 | with: 45 | tag_name: ${{ steps.tagname.outputs.result }} 46 | name: ${{ steps.tagname.outputs.result }} 47 | draft: false 48 | prerelease: false 49 | files: ./staging/*.jar 50 | fail_on_unmatched_files: true 51 | # - name: Upload release asset 52 | # uses: softprops/action-gh-release@v0.1.5 53 | # with: 54 | # upload_url: $$ {{ steps.create_release.outputs.upload_url }} 55 | # asset_path: staging/ 56 | # asset_name: '**.jar' 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #IntelliJ 2 | **/target/ 3 | /.idea/ 4 | *.iml 5 | 6 | #Maven 7 | **/dependency-reduced-pom.xml 8 | 9 | 10 | # Compiled class file 11 | *.class 12 | 13 | # Log file 14 | *.log 15 | 16 | # BlueJ files 17 | *.ctxt 18 | 19 | # Mobile Tools for Java (J2ME) 20 | .mtj.tmp/ 21 | 22 | # Package Files # 23 | *.jar 24 | *.war 25 | *.nar 26 | *.ear 27 | *.zip 28 | *.tar.gz 29 | *.rar 30 | 31 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 32 | hs_err_pid* 33 | 34 | # Exclude script to deploy to local testing server 35 | deploy.py -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Apache Commons Compress 2 | Copyright 2002-2021 The Apache Software Foundation 3 | 4 | This product includes software developed at 5 | The Apache Software Foundation (https://www.apache.org/). -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/BukkitListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit; 18 | 19 | import dev.magicmq.pyspigot.PluginListener; 20 | import dev.magicmq.pyspigot.PyCore; 21 | import dev.magicmq.pyspigot.bukkit.util.player.BukkitPlayer; 22 | import dev.magicmq.pyspigot.manager.script.Script; 23 | import dev.magicmq.pyspigot.manager.script.ScriptManager; 24 | import dev.magicmq.pyspigot.util.player.PlayerAdapter; 25 | import org.bukkit.Bukkit; 26 | import org.bukkit.event.EventHandler; 27 | import org.bukkit.event.Listener; 28 | import org.bukkit.event.player.PlayerJoinEvent; 29 | import org.bukkit.event.server.PluginDisableEvent; 30 | 31 | /** 32 | * The Bukkit listener. 33 | */ 34 | public class BukkitListener extends PluginListener implements Listener { 35 | 36 | @EventHandler 37 | public void onDisable(PluginDisableEvent event) { 38 | if (PyCore.get().getConfig().doScriptUnloadOnPluginDisable()) { 39 | for (Script script : ScriptManager.get().getLoadedScripts()) { 40 | for (String depend : script.getOptions().getPluginDependencies()) { 41 | if (event.getPlugin().getName().equals(depend)) { 42 | PyCore.get().getLogger().warn("Unloading script '{}' because its plugin dependency '{}' was unloaded.", script.getName(), event.getPlugin().getName()); 43 | ScriptManager.get().unloadScript(script, false); 44 | } 45 | } 46 | } 47 | } 48 | } 49 | 50 | @EventHandler 51 | public void onJoin(PlayerJoinEvent event) { 52 | PlayerAdapter bukkitPlayer = new BukkitPlayer(event.getPlayer()); 53 | Bukkit.getScheduler().runTaskLater(PySpigot.get(), () -> this.onJoin(bukkitPlayer), 10L); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/command/BukkitPluginCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.command; 18 | 19 | import dev.magicmq.pyspigot.bukkit.util.player.BukkitCommandSender; 20 | import dev.magicmq.pyspigot.command.PySpigotCommand; 21 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 22 | import org.bukkit.command.Command; 23 | import org.bukkit.command.CommandSender; 24 | import org.bukkit.command.TabExecutor; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * The executor for the /pyspigot command. 30 | */ 31 | public class BukkitPluginCommand implements TabExecutor { 32 | 33 | private final PySpigotCommand baseCommand; 34 | 35 | public BukkitPluginCommand() { 36 | baseCommand = new PySpigotCommand(); 37 | } 38 | 39 | @Override 40 | public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { 41 | CommandSenderAdapter bukkitSender = new BukkitCommandSender(sender); 42 | baseCommand.onCommand(bukkitSender, label, args); 43 | return true; 44 | } 45 | 46 | @Override 47 | public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { 48 | CommandSenderAdapter bukkitSender = new BukkitCommandSender(sender); 49 | return baseCommand.onTabComplete(bukkitSender, args); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific implementation for the /pyspigot command. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.command; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/config/BukkitProjectOptionsConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.config; 18 | 19 | 20 | import dev.magicmq.pyspigot.config.ProjectOptionsConfig; 21 | import org.bukkit.configuration.ConfigurationSection; 22 | import org.bukkit.configuration.file.FileConfiguration; 23 | import org.bukkit.configuration.file.YamlConfiguration; 24 | 25 | import java.nio.file.Path; 26 | import java.util.LinkedHashMap; 27 | import java.util.List; 28 | import java.util.Map; 29 | 30 | /** 31 | * The Bukkit-specific implementation of the {@link dev.magicmq.pyspigot.config.ProjectOptionsConfig} class, for retrieving values from a project's project.yml file. 32 | */ 33 | public class BukkitProjectOptionsConfig implements ProjectOptionsConfig { 34 | 35 | private final FileConfiguration config; 36 | 37 | public BukkitProjectOptionsConfig(Path configPath) { 38 | this.config = YamlConfiguration.loadConfiguration(configPath.toFile()); 39 | } 40 | 41 | @Override 42 | public boolean contains(String key) { 43 | return config.contains(key); 44 | } 45 | 46 | @Override 47 | public String getMainScript(String defaultValue) { 48 | return config.getString("main", defaultValue); 49 | } 50 | 51 | @Override 52 | public boolean getEnabled(boolean defaultValue) { 53 | return config.getBoolean("enabled", defaultValue); 54 | } 55 | 56 | @Override 57 | public int getLoadPriority(int defaultValue) { 58 | return config.getInt("load-priority", defaultValue); 59 | } 60 | 61 | @Override 62 | public List getPluginDepend(List defaultValue) { 63 | if (config.contains("plugin-depend")) 64 | return config.getStringList("plugin-depend"); 65 | else 66 | return defaultValue; 67 | } 68 | 69 | @Override 70 | public boolean getFileLoggingEnabled(boolean defaultValue) { 71 | return config.getBoolean("file-logging-enabled", defaultValue); 72 | } 73 | 74 | @Override 75 | public String getMinLoggingLevel(String defaultValue) { 76 | return config.getString("min-logging-level", defaultValue); 77 | } 78 | 79 | @Override 80 | public String getPermissionDefault(String defaultValue) { 81 | return config.getString("permission-default", defaultValue); 82 | } 83 | 84 | @Override 85 | public Map getPermissions(Map defaultValue) { 86 | if (config.contains("permissions")) 87 | return getNestedMap(config.getConfigurationSection("permissions")); 88 | else 89 | return defaultValue; 90 | } 91 | 92 | private Map getNestedMap(ConfigurationSection section) { 93 | Map result = new LinkedHashMap<>(); 94 | for (String key : section.getKeys(false)) { 95 | Object value = section.get(key); 96 | if (value instanceof ConfigurationSection) 97 | result.put(key, getNestedMap((ConfigurationSection) value)); 98 | else 99 | result.put(key, value); 100 | } 101 | return result; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific implementation for PySpigot's config.yml file. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.config; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/event/ScriptEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import org.bukkit.event.Event; 21 | import org.bukkit.event.HandlerList; 22 | 23 | /** 24 | * Script event superclass. All script events inherit from this class. 25 | */ 26 | public class ScriptEvent extends Event { 27 | 28 | private static final HandlerList handlers = new HandlerList(); 29 | private final Script script; 30 | 31 | /** 32 | * 33 | * @param script The script associated with this event 34 | * @param async Whether the event is asynchronous 35 | */ 36 | public ScriptEvent(Script script, boolean async) { 37 | super(async); 38 | this.script = script; 39 | } 40 | 41 | /** 42 | * Get the script associated with this event. 43 | * @return The script associated with this event 44 | */ 45 | public Script getScript() { 46 | return script; 47 | } 48 | 49 | @Override 50 | public HandlerList getHandlers() { 51 | return handlers; 52 | } 53 | 54 | public static HandlerList getHandlerList() { 55 | return handlers; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/event/ScriptExceptionEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import org.bukkit.event.HandlerList; 21 | import org.python.core.PyException; 22 | 23 | /** 24 | * Called when a script throws an unhandled error/exception. This event could be called asynchronously if the exception occurred in an asynchronous context. To check if the event is asynchronous, call {@link org.bukkit.event.Event#isAsynchronous()} 25 | *

26 | * The exception will be a {@link org.python.core.PyException}, which will include Java exceptions thrown by calls to Java code from scripts. Use {@link org.python.core.PyException#getCause} to determine if there was an underlying Java exception. 27 | */ 28 | public class ScriptExceptionEvent extends ScriptEvent { 29 | 30 | private static final HandlerList handlers = new HandlerList(); 31 | private final PyException exception; 32 | private boolean reportException; 33 | 34 | /** 35 | * 36 | * @param script The script that caused the error/exception 37 | * @param exception The {@link org.python.core.PyException} that was thrown 38 | * @param async Whether the exception occurred in an asychronous context 39 | */ 40 | public ScriptExceptionEvent(Script script, PyException exception, boolean async) { 41 | super(script, async); 42 | this.exception = exception; 43 | this.reportException = true; 44 | } 45 | 46 | /** 47 | * Get the {@link org.python.core.PyException} that was thrown. 48 | * @return The {@link org.python.core.PyException} that was thrown 49 | */ 50 | public PyException getException() { 51 | return exception; 52 | } 53 | 54 | /** 55 | * Get if the exception should be reported to console and/or a script's log file. 56 | * @return True if the exception should be reported to console and/or a script's log file, false if otherwise 57 | */ 58 | public boolean doReportException() { 59 | return reportException; 60 | } 61 | 62 | /** 63 | * Set if the exception should be reported to console and/or the script's log file. 64 | * @param reportException Whether the exception should be reported. 65 | */ 66 | public void setReportException(boolean reportException) { 67 | this.reportException = reportException; 68 | } 69 | 70 | @Override 71 | public HandlerList getHandlers() { 72 | return handlers; 73 | } 74 | 75 | public static HandlerList getHandlerList() { 76 | return handlers; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/event/ScriptLoadEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import org.bukkit.event.HandlerList; 21 | 22 | /** 23 | * Called when a script is loaded. This event fires at the end of a load operation on a script. The event will not fire for scripts that fail to load. Therefore, it is safe to assume the script within this event is currently running. 24 | */ 25 | public class ScriptLoadEvent extends ScriptEvent { 26 | 27 | private static final HandlerList handlers = new HandlerList(); 28 | 29 | /** 30 | * 31 | * @param script The script that was loaded 32 | */ 33 | public ScriptLoadEvent(Script script) { 34 | super(script, false); 35 | } 36 | 37 | @Override 38 | public HandlerList getHandlers() { 39 | return handlers; 40 | } 41 | 42 | public static HandlerList getHandlerList() { 43 | return handlers; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/event/ScriptUnloadEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import org.bukkit.event.HandlerList; 21 | 22 | /** 23 | * Called when a script is unloaded. 24 | */ 25 | public class ScriptUnloadEvent extends ScriptEvent { 26 | 27 | private static final HandlerList handlers = new HandlerList(); 28 | 29 | private final boolean error; 30 | 31 | /** 32 | * 33 | * @param script The script that was unloaded 34 | * @param error Whether the script was unloaded due to an error 35 | */ 36 | public ScriptUnloadEvent(Script script, boolean error) { 37 | super(script, false); 38 | this.error = error; 39 | } 40 | 41 | /** 42 | * Get if this unload event was due to a script error. 43 | * @return True if the script was unloaded due to an error/exception, false if otherwise 44 | */ 45 | public boolean isError() { 46 | return error; 47 | } 48 | 49 | @Override 50 | public HandlerList getHandlers() { 51 | return handlers; 52 | } 53 | 54 | public static HandlerList getHandlerList() { 55 | return handlers; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/event/custom/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the custom event for scripts to use. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.event.custom; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/event/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains Bukkit-specific script events. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.event; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific command manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.manager.command; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/config/BukkitConfigManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.config; 18 | 19 | import dev.magicmq.pyspigot.manager.config.ConfigManager; 20 | import dev.magicmq.pyspigot.manager.config.ScriptConfig; 21 | 22 | import java.io.IOException; 23 | import java.nio.file.Path; 24 | 25 | /** 26 | * The Bukkit-specific implementation of the config manager. 27 | */ 28 | public class BukkitConfigManager extends ConfigManager { 29 | 30 | private static BukkitConfigManager instance; 31 | 32 | private BukkitConfigManager() { 33 | super(); 34 | } 35 | 36 | @Override 37 | protected ScriptConfig loadConfigImpl(Path configFile, String defaults) throws IOException { 38 | BukkitScriptConfig config = new BukkitScriptConfig(configFile.toFile(), defaults); 39 | config.load(); 40 | return config; 41 | } 42 | 43 | /** 44 | * Get the singleton instance of this BukkitConfigManager. 45 | * @return The instance 46 | */ 47 | public static BukkitConfigManager get() { 48 | if (instance == null) 49 | instance = new BukkitConfigManager(); 50 | return instance; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/config/BukkitScriptConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.config; 18 | 19 | import dev.magicmq.pyspigot.exception.ScriptRuntimeException; 20 | import dev.magicmq.pyspigot.manager.config.ScriptConfig; 21 | import dev.magicmq.pyspigot.manager.script.Script; 22 | import dev.magicmq.pyspigot.util.ScriptUtils; 23 | import org.bukkit.configuration.InvalidConfigurationException; 24 | import org.bukkit.configuration.file.YamlConfiguration; 25 | 26 | import java.io.File; 27 | import java.io.IOException; 28 | import java.nio.file.Path; 29 | import java.nio.file.Paths; 30 | 31 | /** 32 | * A class representing a script configuration file, for the Bukkit implementation. 33 | * @see org.bukkit.configuration.file.YamlConfiguration 34 | */ 35 | public class BukkitScriptConfig extends YamlConfiguration implements ScriptConfig { 36 | 37 | private final File configFile; 38 | private final String defaults; 39 | 40 | /** 41 | * 42 | * @param configFile The configuration file 43 | * @param defaults A YAML-formatted string containing the desired default values for the configuration 44 | */ 45 | public BukkitScriptConfig(File configFile, String defaults) { 46 | this.configFile = configFile; 47 | this.defaults = defaults; 48 | } 49 | 50 | @Override 51 | public File getConfigFile() { 52 | return configFile; 53 | } 54 | 55 | @Override 56 | public Path getConfigPath() { 57 | return Paths.get(configFile.getAbsolutePath()); 58 | } 59 | 60 | @Override 61 | public void load() throws IOException { 62 | try { 63 | super.load(configFile); 64 | if (defaults != null) { 65 | YamlConfiguration defaultConfig = new YamlConfiguration(); 66 | defaultConfig.loadFromString(defaults); 67 | this.setDefaults(defaultConfig); 68 | } 69 | } catch (InvalidConfigurationException e) { 70 | Script script = ScriptUtils.getScriptFromCallStack(); 71 | throw new ScriptRuntimeException(script, "Unhandled exception when loading configuration '" + configFile.getName() + "'", e); 72 | } 73 | } 74 | 75 | @Override 76 | public void reload() throws IOException { 77 | load(); 78 | } 79 | 80 | @Override 81 | public void save() throws IOException { 82 | this.save(configFile); 83 | reload(); 84 | } 85 | 86 | /** 87 | * Sets the specified path to the given value only if the path is not already set in the config file. Any specified default values are ignored when checking if the path is set. 88 | * @see org.bukkit.configuration.ConfigurationSection#set(String, Object) 89 | * @param path Path of the object to set 90 | * @param value Value to set the path to 91 | * @return True if the path was set to the value (in other words the path was not previously set), false if the path was not set to the value (in other words the path was already previously set) 92 | */ 93 | public boolean setIfNotExists(String path, Object value) { 94 | if (!super.isSet(path)) { 95 | super.set(path, value); 96 | return true; 97 | } 98 | return false; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific config manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.manager.config; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/listener/BukkitScriptEventExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.listener; 18 | 19 | import dev.magicmq.pyspigot.bukkit.event.ScriptExceptionEvent; 20 | import dev.magicmq.pyspigot.manager.script.Script; 21 | import dev.magicmq.pyspigot.manager.script.ScriptManager; 22 | import org.bukkit.event.Event; 23 | import org.bukkit.event.Listener; 24 | import org.bukkit.plugin.EventExecutor; 25 | import org.python.core.Py; 26 | import org.python.core.PyException; 27 | import org.python.core.PyObject; 28 | import org.python.core.ThreadState; 29 | 30 | /** 31 | * Represents a Bukkit event executor for script event listeners. 32 | * @see org.bukkit.plugin.EventExecutor 33 | */ 34 | public class BukkitScriptEventExecutor implements EventExecutor { 35 | 36 | private final BukkitScriptEventListener scriptEventListener; 37 | private final Class eventClass; 38 | 39 | /** 40 | * 41 | * @param scriptEventListener The {@link BukkitScriptEventListener} associated with this ScriptEventExecutor 42 | * @param eventClass The Bukkit event associated with this ScriptEventExecutor. Should be a {@link Class} of the Bukkit event 43 | */ 44 | public BukkitScriptEventExecutor(BukkitScriptEventListener scriptEventListener, Class eventClass) { 45 | this.scriptEventListener = scriptEventListener; 46 | this.eventClass = eventClass; 47 | } 48 | 49 | /** 50 | * Called internally when the event occurs. 51 | * @param listener The listener associated with this EventExecutor 52 | * @param event The event that occurred 53 | */ 54 | public void execute(Listener listener, Event event) { 55 | if (eventClass.isAssignableFrom(event.getClass())) { 56 | if (event instanceof ScriptExceptionEvent scriptExceptionEvent) { 57 | Script script = scriptExceptionEvent.getScript(); 58 | if (scriptEventListener.getScript().equals(script)) { 59 | String listenerFunctionName = scriptEventListener.getListenerFunction().__code__.co_name; 60 | String exceptionFunctionName = scriptExceptionEvent.getException().traceback.tb_frame.f_code.co_name; 61 | if (listenerFunctionName.equals(exceptionFunctionName)) { 62 | return; 63 | } 64 | } 65 | } 66 | 67 | try { 68 | Py.setSystemState(scriptEventListener.getScript().getInterpreter().getSystemState()); 69 | ThreadState threadState = Py.getThreadState(scriptEventListener.getScript().getInterpreter().getSystemState()); 70 | PyObject parameter = Py.java2py(event); 71 | scriptEventListener.getListenerFunction().__call__(threadState, parameter); 72 | } catch (PyException exception) { 73 | ScriptManager.get().handleScriptException(scriptEventListener.getScript(), exception, "Error when executing event listener"); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/listener/BukkitScriptEventListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.listener; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import org.bukkit.event.Event; 21 | import org.bukkit.event.Listener; 22 | import org.python.core.PyFunction; 23 | 24 | /** 25 | * A dummy Bukkit Listener that holds an event a script is currently listening to. 26 | * @see org.bukkit.event.Listener 27 | */ 28 | public class BukkitScriptEventListener implements Listener { 29 | 30 | private final Script script; 31 | private final PyFunction listenerFunction; 32 | private final Class event; 33 | private final BukkitScriptEventExecutor eventExecutor; 34 | 35 | /** 36 | * 37 | * @param script The script listening to events within this listener 38 | * @param listenerFunction The script function that should be called when the event occurs 39 | * @param event The Bukkit event associated with this listener. Should be a {@link Class} of the Bukkit event 40 | */ 41 | public BukkitScriptEventListener(Script script, PyFunction listenerFunction, Class event) { 42 | this.script = script; 43 | this.listenerFunction = listenerFunction; 44 | this.event = event; 45 | this.eventExecutor = new BukkitScriptEventExecutor(this, event); 46 | } 47 | 48 | /** 49 | * Get the script associated with this listener. 50 | * @return The script associated with this listener. 51 | */ 52 | public Script getScript() { 53 | return script; 54 | } 55 | 56 | /** 57 | * Get the script function that should be called when the event occurs. 58 | * @return The script function that should be called when the event occurs 59 | */ 60 | public PyFunction getListenerFunction() { 61 | return listenerFunction; 62 | } 63 | 64 | /** 65 | * Get the Bukkit event associated with this listener. 66 | *

67 | * Note: Because of the way scripts register events, this will be a {@link Class} of the Bukkit event, which essentially represents its type. 68 | * @return The Bukkit event associated with this listener. 69 | */ 70 | public Class getEvent() { 71 | return event; 72 | } 73 | 74 | /** 75 | * Get the {@link BukkitScriptEventExecutor} associated with this script event listener. 76 | * @return The {@link BukkitScriptEventExecutor} associated with this script event listener 77 | */ 78 | public BukkitScriptEventExecutor getEventExecutor() { 79 | return eventExecutor; 80 | } 81 | 82 | /** 83 | * Prints a representation of this BukkitScriptEventListener in string format, including the event being listened to by the listener 84 | * @return A string representation of the ScriptEventListener 85 | */ 86 | @Override 87 | public String toString() { 88 | return String.format("BukkitScriptEventListener[Event: %s]", event.getName()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/listener/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific listener manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.manager.listener; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/messaging/ScriptPluginMessageListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.messaging; 18 | 19 | 20 | import dev.magicmq.pyspigot.manager.script.Script; 21 | import org.bukkit.entity.Player; 22 | import org.bukkit.plugin.messaging.PluginMessageListener; 23 | import org.python.core.Py; 24 | import org.python.core.PyFunction; 25 | import org.python.core.PyObject; 26 | import org.python.core.ThreadState; 27 | 28 | /** 29 | * A class that represents a script plugin message listener listening on a single channel. 30 | */ 31 | public class ScriptPluginMessageListener implements PluginMessageListener { 32 | 33 | private final Script script; 34 | private final PyFunction function; 35 | private final String channel; 36 | 37 | /** 38 | * 39 | * @param script The script associated with this ScriptPluginMessageListener 40 | * @param function The function that should be called when a message is received on the given channel 41 | * @param channel The channel this listener is listening on 42 | */ 43 | public ScriptPluginMessageListener(Script script, PyFunction function, String channel) { 44 | this.script = script; 45 | this.function = function; 46 | this.channel = channel; 47 | } 48 | 49 | /** 50 | * Get the script associated with this ScriptPluginMessageListener. 51 | * @return The script associated with this ScriptPluginMessageListener 52 | */ 53 | public Script getScript() { 54 | return script; 55 | } 56 | 57 | /** 58 | * Get the channel this listener is listening on. 59 | * @return The channel being listened on 60 | */ 61 | public String getChannel() { 62 | return channel; 63 | } 64 | 65 | /** 66 | * Get the channel this listener is listening on, formatted in compliance with Bukkit's NamespacedKey format, for registering/unregistering purposes. 67 | * @return The channel being listened on, formatted in NamedspacedKey format 68 | */ 69 | public String getFormattedChannel() { 70 | return "PySpigot:" + script.getName() + "_" + channel; 71 | } 72 | 73 | /** 74 | * Called internally when a message is received on the registered channel. 75 | *

76 | * Note that although the channel is passed as a parameter, only messages received on the registered channel will result in a call to this method. 77 | * @param channel Channel that the message was sent through 78 | * @param player Source of the message 79 | * @param message The raw message that was sent 80 | */ 81 | @Override 82 | public void onPluginMessageReceived(String channel, Player player, byte[] message) { 83 | if (channel.equals(getFormattedChannel())) { 84 | Py.setSystemState(script.getInterpreter().getSystemState()); 85 | ThreadState threadState = Py.getThreadState(script.getInterpreter().getSystemState()); 86 | PyObject[] parameters = Py.javas2pys(channel, player, message); 87 | function.__call__(threadState, parameters[0], parameters[1], parameters[2]); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/messaging/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Contains all classes related to plugin message listeners. 19 | */ 20 | package dev.magicmq.pyspigot.bukkit.manager.messaging; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/placeholder/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to PlaceholderAPI. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.manager.placeholder; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/protocol/ListenerType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.protocol; 18 | 19 | /** 20 | * An enum representing the type of protocol listener that a script has registered. 21 | */ 22 | public enum ListenerType { 23 | 24 | /** 25 | * A normal listner. 26 | */ 27 | NORMAL, 28 | 29 | /** 30 | * An asynchronous listener. 31 | */ 32 | ASYNCHRONOUS, 33 | 34 | /** 35 | * An asynchronous timeout listener. 36 | */ 37 | ASYNCHRONOUS_TIMEOUT 38 | 39 | } 40 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/protocol/PacketReceivingListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.protocol; 18 | 19 | import com.comphenix.protocol.PacketType; 20 | import com.comphenix.protocol.events.ListenerPriority; 21 | import com.comphenix.protocol.events.PacketEvent; 22 | import dev.magicmq.pyspigot.manager.script.Script; 23 | import org.python.core.PyFunction; 24 | 25 | /** 26 | * A listener that listens for packets received by the server from the client. 27 | * @see ScriptPacketListener 28 | */ 29 | public class PacketReceivingListener extends ScriptPacketListener { 30 | 31 | /** 32 | * 33 | * @param script The script associated with this packet listener 34 | * @param function The function to be called when the packet is received 35 | * @param packetType The packet type to listen for 36 | * @param listenerPriority The {@link com.comphenix.protocol.events.ListenerPriority} of this listener 37 | * @param listenerType The {@link ListenerType} of this listener 38 | */ 39 | public PacketReceivingListener(Script script, PyFunction function, PacketType packetType, ListenerPriority listenerPriority, ListenerType listenerType) { 40 | super(script, function, packetType, listenerPriority, listenerType); 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | @Override 47 | public void onPacketReceiving(PacketEvent event) { 48 | super.callToScript(event); 49 | } 50 | 51 | /** 52 | * Prints a representation of this PacketReceivingListener in string format, including the packet type listened to by the listener 53 | * @return A string representation of the PacketReceivingListener 54 | */ 55 | @Override 56 | public String toString() { 57 | String superString = super.toString(); 58 | superString = superString.substring(0, superString.length() - 1); 59 | 60 | return String.format("%s, Listener Type: PacketReceivingListener]", superString); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/protocol/PacketSendingListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.protocol; 18 | 19 | import com.comphenix.protocol.PacketType; 20 | import com.comphenix.protocol.events.ListenerPriority; 21 | import com.comphenix.protocol.events.PacketEvent; 22 | import dev.magicmq.pyspigot.manager.script.Script; 23 | import org.python.core.PyFunction; 24 | 25 | /** 26 | * A listener that listens for packets sent by the server to the client. 27 | * @see ScriptPacketListener 28 | */ 29 | public class PacketSendingListener extends ScriptPacketListener { 30 | 31 | /** 32 | * 33 | * @param script The script associated with this packet listener 34 | * @param function The function to be called when the packet is sent 35 | * @param packetType The packet type to listen for 36 | * @param listenerPriority The {@link com.comphenix.protocol.events.ListenerPriority} of this listener 37 | * @param listenerType The {@link ListenerType} of this listener 38 | */ 39 | public PacketSendingListener(Script script, PyFunction function, PacketType packetType, ListenerPriority listenerPriority, ListenerType listenerType) { 40 | super(script, function, packetType, listenerPriority, listenerType); 41 | } 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | @Override 47 | public void onPacketSending(PacketEvent event) { 48 | super.callToScript(event); 49 | } 50 | 51 | /** 52 | * Prints a representation of this PacketSendingListener in string format, including the packet type listened to by the listener 53 | * @return A string representation of the PacketSendingListener 54 | */ 55 | @Override 56 | public String toString() { 57 | String superString = super.toString(); 58 | superString = superString.substring(0, superString.length() - 1); 59 | 60 | return String.format("%s, Listener Type: PacketSendingListener]", superString); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/protocol/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to ProtocolLib and the protocol manager for Bukkit. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.manager.protocol; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/script/BukkitScript.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.script; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import dev.magicmq.pyspigot.manager.script.ScriptOptions; 21 | import org.bukkit.Bukkit; 22 | import org.bukkit.permissions.Permission; 23 | 24 | import java.nio.file.Path; 25 | 26 | /** 27 | * An extension of the base {@link Script} class that includes Bukkit-specific code for initializing script permissions. 28 | */ 29 | public class BukkitScript extends Script { 30 | 31 | /** 32 | * 33 | * @param path The path that corresponds to the file where the script lives 34 | * @param name The name of this script. Should contain its extension (.py) 35 | * @param options The {@link ScriptOptions} for this script 36 | * @param project True if this script is a multi-file project, false if it is a single-file script 37 | */ 38 | public BukkitScript(Path path, String name, BukkitScriptOptions options, boolean project) { 39 | super(path, name, options, project); 40 | } 41 | 42 | /** 43 | * Adds the script's permission (from its options) to the server. 44 | */ 45 | public void initPermissions() { 46 | BukkitScriptOptions options = (BukkitScriptOptions) getOptions(); 47 | for (Permission permission : options.getPermissions()) { 48 | try { 49 | Bukkit.getPluginManager().addPermission(permission); 50 | } catch (IllegalArgumentException exception) { 51 | getLogger().warn("The permission '{}' is already defined by another plugin/script.", permission.getName()); 52 | } 53 | } 54 | } 55 | 56 | /** 57 | * Removes the script's permissions from the server. 58 | */ 59 | public void removePermissions() { 60 | BukkitScriptOptions options = (BukkitScriptOptions) getOptions(); 61 | for (Permission permission : options.getPermissions()) { 62 | Bukkit.getPluginManager().removePermission(permission); 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/script/BukkitScriptInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.script; 18 | 19 | import dev.magicmq.pyspigot.bukkit.PySpigot; 20 | import dev.magicmq.pyspigot.bukkit.manager.placeholder.PlaceholderManager; 21 | import dev.magicmq.pyspigot.bukkit.manager.placeholder.ScriptPlaceholder; 22 | import dev.magicmq.pyspigot.bukkit.manager.protocol.ProtocolManager; 23 | import dev.magicmq.pyspigot.manager.script.Script; 24 | import dev.magicmq.pyspigot.manager.script.ScriptInfo; 25 | import net.kyori.adventure.text.Component; 26 | import net.kyori.adventure.text.TextComponent; 27 | import net.kyori.adventure.text.format.NamedTextColor; 28 | 29 | import java.util.List; 30 | 31 | /** 32 | * The Bukkit-specific implementation of the {@link dev.magicmq.pyspigot.manager.script.ScriptInfo} class, for printing information related to Bukkit-specific managers. 33 | */ 34 | public class BukkitScriptInfo extends ScriptInfo { 35 | 36 | @Override 37 | protected void printPlatformManagerInfo(Script script, TextComponent.Builder appendTo) { 38 | if (PySpigot.get().isPlaceholderApiAvailable()) { 39 | ScriptPlaceholder placeholder = PlaceholderManager.get().getPlaceholder(script); 40 | if (placeholder != null) { 41 | appendTo.append(Component.text().append(Component.text("Registered placeholders: ", NamedTextColor.GOLD)).append(Component.text(placeholder.toString()))); 42 | appendTo.appendNewline(); 43 | } else { 44 | appendTo.append(Component.text().append(Component.text("Registered placeholders: ", NamedTextColor.GOLD)).append(Component.text("None"))); 45 | appendTo.appendNewline(); 46 | } 47 | } 48 | 49 | if (PySpigot.get().isProtocolLibAvailable()) { 50 | List packetTypes = ProtocolManager.get().getPacketListeners(script) 51 | .stream() 52 | .map(Object::toString) 53 | .toList(); 54 | appendTo.append(Component.text().append(Component.text("Listening to packet types: ", NamedTextColor.GOLD)).append(Component.text(packetTypes.toString()))); 55 | appendTo.appendNewline(); 56 | 57 | List packetTypesAsync = ProtocolManager.get().async().getAsyncPacketListeners(script) 58 | .stream() 59 | .map(Object::toString) 60 | .toList(); 61 | appendTo.append(Component.text().append(Component.text("Listening to packet types (async): ", NamedTextColor.GOLD)).append(Component.text(packetTypesAsync.toString()))); 62 | appendTo.appendNewline(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/script/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific script manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.manager.script; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/task/BukkitTaskManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.manager.task; 18 | 19 | import dev.magicmq.pyspigot.bukkit.PySpigot; 20 | import dev.magicmq.pyspigot.manager.task.RepeatingTask; 21 | import dev.magicmq.pyspigot.manager.task.SyncCallbackTask; 22 | import dev.magicmq.pyspigot.manager.task.Task; 23 | import dev.magicmq.pyspigot.manager.task.TaskManager; 24 | import org.bukkit.Bukkit; 25 | import org.bukkit.scheduler.BukkitTask; 26 | 27 | /** 28 | * The Bukkit-specific implementation of the task manager. 29 | */ 30 | public class BukkitTaskManager extends TaskManager { 31 | 32 | private static BukkitTaskManager instance; 33 | 34 | private BukkitTaskManager() { 35 | super(); 36 | } 37 | 38 | @Override 39 | protected synchronized BukkitTask runTaskImpl(Task task) { 40 | return Bukkit.getScheduler().runTask(PySpigot.get(), task); 41 | } 42 | 43 | @Override 44 | protected synchronized BukkitTask runTaskAsyncImpl(Task task) { 45 | return Bukkit.getScheduler().runTaskAsynchronously(PySpigot.get(), task); 46 | } 47 | 48 | @Override 49 | protected synchronized BukkitTask runTaskLaterImpl(Task task, long delay) { 50 | return Bukkit.getScheduler().runTaskLater(PySpigot.get(), task, delay); 51 | } 52 | 53 | @Override 54 | protected synchronized BukkitTask runTaskLaterAsyncImpl(Task task, long delay) { 55 | return Bukkit.getScheduler().runTaskLaterAsynchronously(PySpigot.get(), task, delay); 56 | } 57 | 58 | @Override 59 | protected synchronized BukkitTask scheduleRepeatingTaskImpl(RepeatingTask task, long delay, long interval) { 60 | return Bukkit.getScheduler().runTaskTimer(PySpigot.get(), task, delay, interval); 61 | } 62 | 63 | @Override 64 | protected synchronized BukkitTask scheduleAsyncRepeatingTaskImpl(RepeatingTask task, long delay, long interval) { 65 | return Bukkit.getScheduler().runTaskTimerAsynchronously(PySpigot.get(), task, delay, interval); 66 | } 67 | 68 | @Override 69 | protected synchronized BukkitTask runSyncCallbackTaskImpl(SyncCallbackTask task) { 70 | return Bukkit.getScheduler().runTaskAsynchronously(PySpigot.get(), task); 71 | } 72 | 73 | @Override 74 | protected synchronized BukkitTask runSyncCallbackTaskLaterImpl(SyncCallbackTask task, long delay) { 75 | return Bukkit.getScheduler().runTaskLaterAsynchronously(PySpigot.get(), task, delay); 76 | } 77 | 78 | @Override 79 | protected synchronized BukkitTask runSyncCallbackImpl(Runnable runnable) { 80 | return Bukkit.getScheduler().runTask(PySpigot.get(), runnable); 81 | } 82 | 83 | @Override 84 | protected synchronized void stopTaskImpl(BukkitTask platformTask) { 85 | platformTask.cancel(); 86 | } 87 | 88 | @Override 89 | protected String describeTask(BukkitTask platformTask) { 90 | return String.format("BukkitTask[taskId: %d, isSync: %b]", platformTask.getTaskId(), platformTask.isSync()); 91 | } 92 | 93 | /** 94 | * Get the singleton instance of this BukkitTaskManager. 95 | * @return The instance 96 | */ 97 | public static BukkitTaskManager get() { 98 | if (instance == null) 99 | instance = new BukkitTaskManager(); 100 | return instance; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/manager/task/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific task manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.manager.task; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit implementation for PySpigot. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/util/CommandAliasHelpTopic.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.util; 18 | 19 | import org.bukkit.ChatColor; 20 | import org.bukkit.command.CommandSender; 21 | import org.bukkit.help.HelpMap; 22 | import org.bukkit.help.HelpTopic; 23 | 24 | /** 25 | * Represents a help topic for an alias of a command. 26 | *

27 | * Copied from org.bukkit.craftbukkit.help.CommandAliasHelpTopic 28 | */ 29 | public class CommandAliasHelpTopic extends HelpTopic { 30 | 31 | private final String aliasFor; 32 | private final HelpMap helpMap; 33 | 34 | public CommandAliasHelpTopic(String alias, String aliasFor, HelpMap helpMap) { 35 | this.aliasFor = aliasFor.startsWith("/") ? aliasFor : "/" + aliasFor; 36 | this.helpMap = helpMap; 37 | this.name = alias.startsWith("/") ? alias : "/" + alias; 38 | if (this.name.equals(this.aliasFor)) 39 | throw new IllegalArgumentException("Command '" + this.name + "' cannot be alias for itself"); 40 | this.shortText = ChatColor.YELLOW + "Alias for " + ChatColor.WHITE + this.aliasFor; 41 | } 42 | 43 | public String getFullText(CommandSender forWho) { 44 | StringBuilder sb = new StringBuilder(this.shortText); 45 | HelpTopic aliasForTopic = this.helpMap.getHelpTopic(this.aliasFor); 46 | if (aliasForTopic != null) { 47 | sb.append("\n"); 48 | sb.append(aliasForTopic.getFullText(forWho)); 49 | } 50 | 51 | return sb.toString(); 52 | } 53 | 54 | public boolean canSee(CommandSender commandSender) { 55 | if (this.amendedPermission == null) { 56 | HelpTopic aliasForTopic = this.helpMap.getHelpTopic(this.aliasFor); 57 | return aliasForTopic != null ? aliasForTopic.canSee(commandSender) : false; 58 | } else { 59 | return commandSender.hasPermission(this.amendedPermission); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/util/ReflectionUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.util; 18 | 19 | import org.bukkit.Bukkit; 20 | 21 | import java.lang.reflect.Field; 22 | import java.lang.reflect.Method; 23 | 24 | /** 25 | * A utility class to simplify reflection for working with CraftBukkit and NMS classes. 26 | */ 27 | public final class ReflectionUtils { 28 | 29 | private static final String MC_VERSION; 30 | 31 | static { 32 | String[] split = Bukkit.getServer().getClass().getPackage().getName().split("\\."); 33 | MC_VERSION = split.length >= 4 ? split[3] : ""; 34 | } 35 | 36 | private ReflectionUtils() {} 37 | 38 | public static Class getNMSClass(String packageName, String className) throws ClassNotFoundException { 39 | try { 40 | return Class.forName("net.minecraft." + packageName + "." + className); 41 | } catch (ClassNotFoundException ignored) { 42 | return Class.forName("net.minecraft.server." + MC_VERSION + "." + className); 43 | } 44 | } 45 | 46 | public static Class getCraftBukkitClass(String className) throws ClassNotFoundException { 47 | try { 48 | return Class.forName("org.bukkit.craftbukkit." + className); 49 | } catch (ClassNotFoundException ignored) { 50 | return Class.forName("org.bukkit.craftbukkit." + MC_VERSION + "." + className); 51 | } 52 | } 53 | 54 | public static Class getCraftBukkitClass(String packageName, String className) throws ClassNotFoundException { 55 | try { 56 | return Class.forName("org.bukkit.craftbukkit." + packageName + "." + className); 57 | } catch (ClassNotFoundException ignored) { 58 | return Class.forName("org.bukkit.craftbukkit." + MC_VERSION + "." + packageName + "." + className); 59 | } 60 | } 61 | 62 | public static Method getMethod(Class clazz, String methodName) { 63 | for (Method method : clazz.getDeclaredMethods()) { 64 | if (method.getName().equals(methodName)) 65 | return method; 66 | } 67 | return null; 68 | } 69 | 70 | public static Field getField(Class clazz, String fieldName) { 71 | for (Field field : clazz.getDeclaredFields()) { 72 | if (field.getName().equals(fieldName)) 73 | return field; 74 | } 75 | return null; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/util/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains Bukkit-specific utility classes. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.util; -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/util/player/BukkitCommandSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.util.player; 18 | 19 | import dev.magicmq.pyspigot.bukkit.PySpigot; 20 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 21 | import net.kyori.adventure.audience.Audience; 22 | import net.kyori.adventure.text.Component; 23 | import org.bukkit.command.CommandSender; 24 | import org.bukkit.entity.Player; 25 | 26 | /** 27 | * A wrapper for the Bukkit {@link org.bukkit.command.CommandSender} class. 28 | */ 29 | public class BukkitCommandSender implements CommandSenderAdapter { 30 | 31 | private final CommandSender sender; 32 | 33 | /** 34 | * 35 | * @param sender The Bukkit CommandSender 36 | */ 37 | public BukkitCommandSender(CommandSender sender) { 38 | this.sender = sender; 39 | } 40 | 41 | @Override 42 | public boolean hasPermission(String permission) { 43 | return sender.hasPermission(permission); 44 | } 45 | 46 | @Override 47 | public void sendMessage(Component message) { 48 | Audience sender = PySpigot.get().getAdventure().sender(this.sender); 49 | sender.sendMessage(message); 50 | } 51 | 52 | @Override 53 | public boolean isPlayer() { 54 | return sender instanceof Player; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/util/player/BukkitPlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bukkit.util.player; 18 | 19 | 20 | import dev.magicmq.pyspigot.bukkit.PySpigot; 21 | import dev.magicmq.pyspigot.util.player.PlayerAdapter; 22 | import net.kyori.adventure.audience.Audience; 23 | import net.kyori.adventure.text.Component; 24 | import org.bukkit.entity.Player; 25 | 26 | /** 27 | * A wrapper for the Bukkit {@link org.bukkit.entity.Player} class. 28 | */ 29 | public class BukkitPlayer implements PlayerAdapter { 30 | 31 | private final Player player; 32 | 33 | /** 34 | * 35 | * @param player The Bukkit Player 36 | */ 37 | public BukkitPlayer(Player player) { 38 | this.player = player; 39 | } 40 | 41 | @Override 42 | public boolean hasPermission(String permission) { 43 | return player.hasPermission(permission); 44 | } 45 | 46 | @Override 47 | public void sendMessage(Component message) { 48 | Audience player = PySpigot.get().getAdventure().player(this.player); 49 | player.sendMessage(message); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /bukkit/src/main/java/dev/magicmq/pyspigot/bukkit/util/player/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains Bukkit-specific player utility classes. 3 | */ 4 | package dev.magicmq.pyspigot.bukkit.util.player; -------------------------------------------------------------------------------- /bukkit/src/main/resources/Lib/pyspigot.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper module for more easy access to PySpigot's managers. 3 | """ 4 | from dev.magicmq.pyspigot.bukkit import PySpigot 5 | from dev.magicmq.pyspigot.manager.script import ScriptManager 6 | from dev.magicmq.pyspigot.manager.script import GlobalVariables 7 | from dev.magicmq.pyspigot.manager.listener import ListenerManager 8 | from dev.magicmq.pyspigot.manager.command import CommandManager 9 | from dev.magicmq.pyspigot.manager.task import TaskManager 10 | from dev.magicmq.pyspigot.manager.config import ConfigManager 11 | from dev.magicmq.pyspigot.manager.database import DatabaseManager 12 | from dev.magicmq.pyspigot.manager.redis import RedisManager 13 | 14 | def script_manager(): 15 | """Get the script manager for loading, unloading, and reloading scripts.""" 16 | return ScriptManager.get() 17 | 18 | def global_variables(): 19 | """Get the global variables manager for setting and getting global variables.""" 20 | return GlobalVariables.get() 21 | 22 | def listener_manager(): 23 | """Get the listener manager for registering and unregistering event listeners.""" 24 | return ListenerManager.get() 25 | 26 | def command_manager(): 27 | """Get the command manager for registering and unregistering commands.""" 28 | return CommandManager.get() 29 | 30 | def task_manager(): 31 | """Get the task manager for scheduling and unscheduling tasks (synchronous and asynchronous).""" 32 | return TaskManager.get() 33 | 34 | def config_manager(): 35 | """Get the config manager for writing to and reading from config files.""" 36 | return ConfigManager.get() 37 | 38 | def database_manager(): 39 | """Get the database manager for connecting to and interacting with databases.""" 40 | return DatabaseManager.get() 41 | 42 | def redis_manager(): 43 | """Get the redis manager for connecting to and interacting with redis servers.""" 44 | return RedisManager.get() 45 | 46 | def protocol_manager(): 47 | """Get the protocol manager for working with ProtocolLib (registering/unregistering packet listeners, sending packets, etc.). Note: this function will return None if ProtocolLib is not available on the server.""" 48 | if PySpigot.get().isProtocolLibAvailable(): 49 | from dev.magicmq.pyspigot.bukkit.manager.protocol import ProtocolManager 50 | return ProtocolManager.get() 51 | else: return None 52 | 53 | def placeholder_manager(): 54 | """Get the placeholder manager for registering/unregistering PlaceholderAPI placeholders. Note: this function will return None if PlaceholderAPI is not available on the server.""" 55 | if PySpigot.get().isPlaceholderApiAvailable(): 56 | from dev.magicmq.pyspigot.bukkit.manager.placeholder import PlaceholderManager 57 | return PlaceholderManager.get() 58 | else: return None 59 | 60 | # Convenience variables for ease of access 61 | 62 | script = script_manager() 63 | scripts = script_manager() 64 | sm = script_manager() 65 | 66 | global_vars = global_variables() 67 | gv = global_variables() 68 | 69 | listener = listener_manager() 70 | listeners = listener_manager() 71 | lm = listener_manager() 72 | event = listener_manager() 73 | events = listener_manager() 74 | em = listener_manager() 75 | 76 | command = command_manager() 77 | commands = command_manager() 78 | cm = command_manager() 79 | 80 | scheduler = task_manager() 81 | scm = task_manager() 82 | tasks = task_manager() 83 | tm = task_manager() 84 | 85 | config = config_manager() 86 | configs = config_manager() 87 | com = config_manager() 88 | 89 | database = database_manager() 90 | 91 | redis = redis_manager() 92 | 93 | protocol = protocol_manager() 94 | protocol_lib = protocol_manager() 95 | protocols = protocol_manager() 96 | pm = protocol_manager() 97 | 98 | placeholder = placeholder_manager() 99 | placeholder_api = placeholder_manager() 100 | placeholders = placeholder_manager() 101 | plm = placeholder_manager() -------------------------------------------------------------------------------- /bukkit/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ${project.pluginName} 2 | version: ${project.version} 3 | author: magicmq 4 | main: dev.magicmq.pyspigot.bukkit.PySpigot 5 | load: STARTUP 6 | api-version: '1.13' 7 | softdepend: [ProtocolLib, PlaceholderAPI] 8 | commands: 9 | pyspigot: 10 | description: The main command for PySpigot. 11 | aliases: [ps] -------------------------------------------------------------------------------- /bungee/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | dev.magicmq 6 | pyspigot 7 | 0.9.1-SNAPSHOT 8 | 9 | 10 | pyspigot-bungee 11 | Python scripting engine for BungeeCord proxy servers 12 | 13 | 14 | 21 15 | 21 16 | UTF-8 17 | PyBungee 18 | 19 | 20 | 21 | 22 | dev.magicmq 23 | pyspigot-core 24 | 25 | 26 | org.bstats 27 | bstats-bungeecord 28 | 3.0.2 29 | compile 30 | 31 | 32 | net.kyori 33 | adventure-platform-bungeecord 34 | 4.3.4 35 | compile 36 | 37 | 38 | net.md-5 39 | bungeecord-api 40 | 1.21-R0.2 41 | provided 42 | 43 | 44 | dev.simplix 45 | protocolize-api 46 | 2.4.3 47 | provided 48 | 49 | 50 | 51 | 52 | 53 | 54 | dev.magicmq 55 | jython-compile-maven-plugin 56 | 57 | 58 | org.apache.maven.plugins 59 | maven-resources-plugin 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 65 | 66 | org.apache.maven.plugins 67 | maven-jar-plugin 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-shade-plugin 72 | 73 | 74 | org.apache.maven.plugins 75 | maven-source-plugin 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-javadoc-plugin 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-release-plugin 84 | 85 | 86 | org.sonatype.plugins 87 | nexus-staging-maven-plugin 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/BungeeListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee; 18 | 19 | import dev.magicmq.pyspigot.PluginListener; 20 | import dev.magicmq.pyspigot.bungee.util.player.BungeePlayer; 21 | import dev.magicmq.pyspigot.util.player.PlayerAdapter; 22 | import net.md_5.bungee.api.ProxyServer; 23 | import net.md_5.bungee.api.event.PostLoginEvent; 24 | import net.md_5.bungee.api.plugin.Listener; 25 | import net.md_5.bungee.event.EventHandler; 26 | 27 | import java.util.concurrent.TimeUnit; 28 | 29 | /** 30 | * The BungeeCord listener. 31 | */ 32 | public class BungeeListener extends PluginListener implements Listener { 33 | 34 | @EventHandler 35 | public void onJoin(PostLoginEvent event) { 36 | PlayerAdapter bungeePlayer = new BungeePlayer(event.getPlayer()); 37 | ProxyServer.getInstance().getScheduler().schedule(PyBungee.get(), () -> this.onJoin(bungeePlayer), 500L, TimeUnit.MILLISECONDS); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/command/BungeePluginCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.command; 18 | 19 | import dev.magicmq.pyspigot.bungee.util.player.BungeeCommandSender; 20 | import dev.magicmq.pyspigot.command.PySpigotCommand; 21 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 22 | import net.md_5.bungee.api.CommandSender; 23 | import net.md_5.bungee.api.plugin.Command; 24 | import net.md_5.bungee.api.plugin.TabExecutor; 25 | 26 | /** 27 | * The executor for the /pybungee command. 28 | */ 29 | public class BungeePluginCommand extends Command implements TabExecutor { 30 | 31 | private final PySpigotCommand baseCommand; 32 | 33 | public BungeePluginCommand() { 34 | super("pybungee", null, "pb"); 35 | 36 | baseCommand = new PySpigotCommand(); 37 | } 38 | 39 | @Override 40 | public void execute(CommandSender sender, String[] args) { 41 | CommandSenderAdapter bungeeSender = new BungeeCommandSender(sender); 42 | baseCommand.onCommand(bungeeSender, getName(), args); 43 | } 44 | 45 | public Iterable onTabComplete(CommandSender sender, String[] args) { 46 | CommandSenderAdapter bungeeSender = new BungeeCommandSender(sender); 47 | return baseCommand.onTabComplete(bungeeSender, args); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the Bukkit-specific implementation for the /pybungee command. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.command; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/config/BungeeProjectOptionsConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.config; 18 | 19 | import dev.magicmq.pyspigot.bungee.PyBungee; 20 | import dev.magicmq.pyspigot.config.ProjectOptionsConfig; 21 | import net.md_5.bungee.config.Configuration; 22 | import net.md_5.bungee.config.ConfigurationProvider; 23 | import net.md_5.bungee.config.YamlConfiguration; 24 | 25 | import java.io.IOException; 26 | import java.nio.file.Path; 27 | import java.util.List; 28 | import java.util.Map; 29 | 30 | /** 31 | * The Bungee-specific implementation of the {@link dev.magicmq.pyspigot.config.ProjectOptionsConfig} class, for retrieving values from a project's project.yml file. 32 | */ 33 | public class BungeeProjectOptionsConfig implements ProjectOptionsConfig { 34 | 35 | private Configuration config; 36 | 37 | public BungeeProjectOptionsConfig(Path configPath) { 38 | try { 39 | config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configPath.toFile()); 40 | } catch (IOException e) { 41 | PyBungee.get().getPlatformLogger().error("Error when loading the project.yml file", e); 42 | config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(""); 43 | } 44 | } 45 | 46 | @Override 47 | public boolean contains(String key) { 48 | return config.contains(key); 49 | } 50 | 51 | @Override 52 | public String getMainScript(String defaultValue) { 53 | return config.getString("main", defaultValue); 54 | } 55 | 56 | @Override 57 | public boolean getEnabled(boolean defaultValue) { 58 | return config.getBoolean("enabled", defaultValue); 59 | } 60 | 61 | @Override 62 | public int getLoadPriority(int defaultValue) { 63 | return config.getInt("load-priority", defaultValue); 64 | } 65 | 66 | @Override 67 | public List getPluginDepend(List defaultValue) { 68 | if (config.contains("plugin-depend")) 69 | return config.getStringList("plugin-depend"); 70 | else 71 | return defaultValue; 72 | } 73 | 74 | @Override 75 | public boolean getFileLoggingEnabled(boolean defaultValue) { 76 | return config.getBoolean("file-logging-enabled", defaultValue); 77 | } 78 | 79 | @Override 80 | public String getMinLoggingLevel(String defaultValue) { 81 | return config.getString("min-logging-level", defaultValue); 82 | } 83 | 84 | /** 85 | * No-op implementation 86 | */ 87 | @Override 88 | public String getPermissionDefault(String defaultValue) { 89 | //Plugin permissions are not implemented in BungeeCord 90 | return null; 91 | } 92 | 93 | /** 94 | * No-op implementation 95 | */ 96 | @Override 97 | public Map getPermissions(Map defaultValue) { 98 | //Plugin permissions are not implemented in BungeeCord 99 | return null; 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the BungeeCord-specific config manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.config; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/event/ScriptEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import net.md_5.bungee.api.plugin.Event; 21 | 22 | /** 23 | * Script event superclass. All script events inherit from this class. 24 | */ 25 | public class ScriptEvent extends Event { 26 | 27 | private final Script script; 28 | 29 | /** 30 | * 31 | * @param script The script associated with this event 32 | */ 33 | public ScriptEvent(Script script) { 34 | this.script = script; 35 | } 36 | 37 | /** 38 | * Get the script associated with this event. 39 | * @return The script associated with this event 40 | */ 41 | public Script getScript() { 42 | return script; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/event/ScriptExceptionEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import org.python.core.PyException; 21 | 22 | /** 23 | * Called when a script throws an unhandled error/exception. 24 | *

25 | * The exception will be a {@link PyException}, which will include Java exceptions thrown by calls to Java code from scripts. Use {@link PyException#getCause} to determine if there was an underlying Java exception. 26 | */ 27 | public class ScriptExceptionEvent extends ScriptEvent { 28 | 29 | private final PyException exception; 30 | private boolean reportException; 31 | 32 | /** 33 | * 34 | * @param script The script that caused the error/exception 35 | * @param exception The {@link PyException} that was thrown 36 | */ 37 | public ScriptExceptionEvent(Script script, PyException exception) { 38 | super(script); 39 | this.exception = exception; 40 | this.reportException = true; 41 | } 42 | 43 | /** 44 | * Get the {@link PyException} that was thrown. 45 | * @return The {@link PyException} that was thrown 46 | */ 47 | public PyException getException() { 48 | return exception; 49 | } 50 | 51 | /** 52 | * Get if the exception should be reported to console and/or a script's log file. 53 | * @return True if the exception should be reported to console and/or a script's log file, false if otherwise 54 | */ 55 | public boolean doReportException() { 56 | return reportException; 57 | } 58 | 59 | /** 60 | * Set if the exception should be reported to console and/or the script's log file. 61 | * @param reportException Whether the exception should be reported. 62 | */ 63 | public void setReportException(boolean reportException) { 64 | this.reportException = reportException; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/event/ScriptLoadEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | 21 | /** 22 | * Called when a script is loaded. This event fires at the end of a load operation on a script. The event will not fire for scripts that fail to load. Therefore, it is safe to assume the script within this event is currently running. 23 | */ 24 | public class ScriptLoadEvent extends ScriptEvent { 25 | 26 | /** 27 | * 28 | * @param script The script that was loaded 29 | */ 30 | public ScriptLoadEvent(Script script) { 31 | super(script); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/event/ScriptUnloadEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.event; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | 21 | /** 22 | * Called when a script is unloaded. 23 | */ 24 | public class ScriptUnloadEvent extends ScriptEvent { 25 | 26 | private final boolean error; 27 | 28 | /** 29 | * 30 | * @param script The script that was unloaded 31 | * @param error Whether the script was unloaded due to an error 32 | */ 33 | public ScriptUnloadEvent(Script script, boolean error) { 34 | super(script); 35 | this.error = error; 36 | } 37 | 38 | /** 39 | * Get if this unload event was due to a script error. 40 | * @return True if the script was unloaded due to an error/exception, false if otherwise 41 | */ 42 | public boolean isError() { 43 | return error; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/event/custom/CustomEvent.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.event.custom; 18 | 19 | import dev.magicmq.pyspigot.bungee.event.ScriptEvent; 20 | import dev.magicmq.pyspigot.util.ScriptUtils; 21 | import net.md_5.bungee.api.plugin.Cancellable; 22 | import org.python.core.Py; 23 | import org.python.core.PyObject; 24 | 25 | /** 26 | * A custom event that scripts may instantiate and call for other plugins/scripts to listen to. 27 | */ 28 | public class CustomEvent extends ScriptEvent implements Cancellable { 29 | 30 | private final String name; 31 | private final PyObject data; 32 | 33 | private boolean cancelled; 34 | 35 | /** 36 | *

37 | * Note: This class should be instantiated from scripts only! 38 | * @param name The name of the event being created. Can be used to create subtypes of the generic custom event 39 | * @param data The data to attach to the event 40 | */ 41 | public CustomEvent(String name, PyObject data) { 42 | super(ScriptUtils.getScriptFromCallStack()); 43 | this.name = name; 44 | this.data = data; 45 | 46 | cancelled = false; 47 | } 48 | 49 | /** 50 | * Get the name of this event. 51 | * @return The name of this event 52 | */ 53 | public String getName() { 54 | return name; 55 | } 56 | 57 | /** 58 | * Get the data attached to this event. 59 | * @return The data attached to this event 60 | */ 61 | public PyObject getData() { 62 | return data; 63 | } 64 | 65 | /** 66 | * Attempt to convert the data attached to this event to a provided type. 67 | * @param clazz The type that the data should be converted to 68 | * @return An object of the specified type representing the converted data 69 | * @throws org.python.core.PyException If the data could not be converted to the provided type 70 | */ 71 | public Object getDataAsType(String clazz) { 72 | return Py.tojava(data, clazz); 73 | } 74 | 75 | /** 76 | * Attempt to convert the data attached to this event to a provided type. 77 | * @param clazz The type that the data should be converted to 78 | * @return An object of the specified type representing the converted data 79 | * @param The type to which the data should be converted 80 | * @throws org.python.core.PyException If the data could not be converted to the provided type 81 | */ 82 | public T getDataAsType(Class clazz) { 83 | return Py.tojava(data, clazz); 84 | } 85 | 86 | /** 87 | * {@inheritDoc} 88 | */ 89 | @Override 90 | public boolean isCancelled() { 91 | return cancelled; 92 | } 93 | 94 | /** 95 | * {@inheritDoc} 96 | */ 97 | @Override 98 | public void setCancelled(boolean cancelled) { 99 | this.cancelled = cancelled; 100 | } 101 | } 102 | 103 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/event/custom/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the custom event for scripts to use. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.event.custom; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/event/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains BungeeCord-specific script events. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.event; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/command/BungeeCommandManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.manager.command; 18 | 19 | import dev.magicmq.pyspigot.bungee.PyBungee; 20 | import dev.magicmq.pyspigot.manager.command.CommandManager; 21 | import dev.magicmq.pyspigot.manager.command.ScriptCommand; 22 | import dev.magicmq.pyspigot.manager.script.Script; 23 | import net.md_5.bungee.api.ProxyServer; 24 | import org.python.core.PyFunction; 25 | 26 | import java.util.List; 27 | 28 | /** 29 | * The BungeeCord-specific implementation of the command manager. 30 | */ 31 | public class BungeeCommandManager extends CommandManager { 32 | 33 | private static BungeeCommandManager instance; 34 | 35 | private BungeeCommandManager() { 36 | super(); 37 | } 38 | 39 | @Override 40 | protected ScriptCommand registerCommandImpl(Script script, PyFunction commandFunction, PyFunction tabFunction, String name, String description, String usage, List aliases, String permission) { 41 | BungeeScriptCommand newCommand = new BungeeScriptCommand(script, commandFunction, tabFunction, name, aliases, permission); 42 | ProxyServer.getInstance().getPluginManager().registerCommand(PyBungee.get(), newCommand); 43 | return newCommand; 44 | } 45 | 46 | @Override 47 | protected void unregisterCommandImpl(ScriptCommand command) { 48 | ProxyServer.getInstance().getPluginManager().unregisterCommand((BungeeScriptCommand) command); 49 | } 50 | 51 | @Override 52 | protected void unregisterCommandsImpl(List commands) { 53 | commands.forEach(this::unregisterCommandImpl); 54 | } 55 | 56 | /** 57 | * Get the singleton instance of this BungeeCommandManager. 58 | * @return The instance 59 | */ 60 | public static BungeeCommandManager get() { 61 | if (instance == null) 62 | instance = new BungeeCommandManager(); 63 | return instance; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the BungeeCord-specific command manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.manager.command; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/config/BungeeConfigManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.manager.config; 18 | 19 | 20 | import dev.magicmq.pyspigot.manager.config.ConfigManager; 21 | import dev.magicmq.pyspigot.manager.config.ScriptConfig; 22 | 23 | import java.io.IOException; 24 | import java.nio.file.Path; 25 | 26 | /** 27 | * The BungeeCord-specific implementation of the config manager. 28 | */ 29 | public class BungeeConfigManager extends ConfigManager { 30 | 31 | private static BungeeConfigManager instance; 32 | 33 | private BungeeConfigManager() { 34 | super(); 35 | } 36 | 37 | @Override 38 | protected ScriptConfig loadConfigImpl(Path configFile, String defaults) throws IOException { 39 | BungeeScriptConfig config = new BungeeScriptConfig(configFile.toFile(), defaults); 40 | config.load(); 41 | return config; 42 | } 43 | 44 | /** 45 | * Get the singleton instance of this BungeeConfigManager. 46 | * @return The instance 47 | */ 48 | public static BungeeConfigManager get() { 49 | if (instance == null) 50 | instance = new BungeeConfigManager(); 51 | return instance; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the BungeeCord-specific config manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.manager.config; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/listener/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the BungeeCord-specific listener manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.manager.listener; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/protocol/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to Protocolize and the protocol manager for BungeeCord. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.manager.protocol; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/script/BungeeScriptInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.manager.script; 18 | 19 | import dev.magicmq.pyspigot.bungee.PyBungee; 20 | import dev.magicmq.pyspigot.bungee.manager.protocol.ProtocolManager; 21 | import dev.magicmq.pyspigot.manager.script.Script; 22 | import dev.magicmq.pyspigot.manager.script.ScriptInfo; 23 | import net.kyori.adventure.text.Component; 24 | import net.kyori.adventure.text.TextComponent; 25 | import net.kyori.adventure.text.format.NamedTextColor; 26 | 27 | import java.util.List; 28 | 29 | /** 30 | * The BungeeCord-specific implementation of the {@link dev.magicmq.pyspigot.manager.script.ScriptInfo} class, for printing information related to BungeeCord-specific managers. 31 | */ 32 | public class BungeeScriptInfo extends ScriptInfo { 33 | 34 | @Override 35 | protected void printPlatformManagerInfo(Script script, TextComponent.Builder appendTo) { 36 | if (PyBungee.get().isProtocolizeAvailable()) { 37 | List packetTypes = ProtocolManager.get().getPacketListeners(script) 38 | .stream() 39 | .map(Object::toString) 40 | .toList(); 41 | appendTo.append(Component.text().append(Component.text("Listening to packets: ", NamedTextColor.GOLD)).append(Component.text(packetTypes.toString()))); 42 | appendTo.appendNewline(); 43 | } 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/script/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the BungeeCord-specific script manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.manager.script; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/manager/task/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the BungeeCord-specific task manager implementation. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.manager.task; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the BungeeCord implementation for PySpigot. 3 | */ 4 | package dev.magicmq.pyspigot.bungee; -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/util/player/BungeeCommandSender.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.util.player; 18 | 19 | import dev.magicmq.pyspigot.bungee.PyBungee; 20 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 21 | import net.kyori.adventure.audience.Audience; 22 | import net.kyori.adventure.text.Component; 23 | import net.md_5.bungee.api.CommandSender; 24 | import net.md_5.bungee.api.connection.ProxiedPlayer; 25 | 26 | /** 27 | * A wrapper for the BungeeCord {@link net.md_5.bungee.api.CommandSender} class. 28 | */ 29 | public class BungeeCommandSender implements CommandSenderAdapter { 30 | 31 | private final CommandSender sender; 32 | 33 | /** 34 | * 35 | * @param sender The BungeeCord CommandSender 36 | */ 37 | public BungeeCommandSender(CommandSender sender) { 38 | this.sender = sender; 39 | } 40 | 41 | @Override 42 | public boolean hasPermission(String permission) { 43 | return sender.hasPermission(permission); 44 | } 45 | 46 | @Override 47 | public void sendMessage(Component message) { 48 | Audience sender = PyBungee.get().getAdventure().sender(this.sender); 49 | sender.sendMessage(message); 50 | } 51 | 52 | @Override 53 | public boolean isPlayer() { 54 | return sender instanceof ProxiedPlayer; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/util/player/BungeePlayer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.bungee.util.player; 18 | 19 | 20 | import dev.magicmq.pyspigot.bungee.PyBungee; 21 | import dev.magicmq.pyspigot.util.player.PlayerAdapter; 22 | import net.kyori.adventure.audience.Audience; 23 | import net.kyori.adventure.text.Component; 24 | import net.md_5.bungee.api.connection.ProxiedPlayer; 25 | 26 | /** 27 | * A wrapper for the BungeeCord {@link net.md_5.bungee.api.connection.ProxiedPlayer} class. 28 | */ 29 | public class BungeePlayer implements PlayerAdapter { 30 | 31 | private final ProxiedPlayer player; 32 | 33 | /** 34 | * 35 | * @param player The BungeeCord ProxiedPlayer 36 | */ 37 | public BungeePlayer(ProxiedPlayer player) { 38 | this.player = player; 39 | } 40 | 41 | @Override 42 | public boolean hasPermission(String permission) { 43 | return player.hasPermission(permission); 44 | } 45 | 46 | @Override 47 | public void sendMessage(Component message) { 48 | Audience player = PyBungee.get().getAdventure().sender(this.player); 49 | player.sendMessage(message); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /bungee/src/main/java/dev/magicmq/pyspigot/bungee/util/player/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains BungeeCord-specific player utility classes. 3 | */ 4 | package dev.magicmq.pyspigot.bungee.util.player; -------------------------------------------------------------------------------- /bungee/src/main/resources/Lib/pyspigot.py: -------------------------------------------------------------------------------- 1 | """ 2 | A helper module for more easy access to PySpigot's managers. 3 | """ 4 | from dev.magicmq.pyspigot.bungee import PyBungee 5 | from dev.magicmq.pyspigot.manager.script import ScriptManager 6 | from dev.magicmq.pyspigot.manager.script import GlobalVariables 7 | from dev.magicmq.pyspigot.manager.listener import ListenerManager 8 | from dev.magicmq.pyspigot.manager.command import CommandManager 9 | from dev.magicmq.pyspigot.manager.task import TaskManager 10 | from dev.magicmq.pyspigot.manager.config import ConfigManager 11 | from dev.magicmq.pyspigot.manager.database import DatabaseManager 12 | from dev.magicmq.pyspigot.manager.redis import RedisManager 13 | 14 | def script_manager(): 15 | """Get the script manager for loading, unloading, and reloading scripts.""" 16 | return ScriptManager.get() 17 | 18 | def global_variables(): 19 | """Get the global variables manager for setting and getting global variables.""" 20 | return GlobalVariables.get() 21 | 22 | def listener_manager(): 23 | """Get the listener manager for registering and unregistering event listeners.""" 24 | return ListenerManager.get() 25 | 26 | def command_manager(): 27 | """Get the command manager for registering and unregistering commands.""" 28 | return CommandManager.get() 29 | 30 | def task_manager(): 31 | """Get the task manager for scheduling and unscheduling tasks (synchronous and asynchronous).""" 32 | return TaskManager.get() 33 | 34 | def config_manager(): 35 | """Get the config manager for writing to and reading from config files.""" 36 | return ConfigManager.get() 37 | 38 | def database_manager(): 39 | """Get the database manager for connecting to and interacting with databases.""" 40 | return DatabaseManager.get() 41 | 42 | def redis_manager(): 43 | """Get the redis manager for connecting to and interacting with redis servers.""" 44 | return RedisManager.get() 45 | 46 | def protocol_manager(): 47 | """Get the protocol manager for working with ProtocolLib (registering/unregistering packet listeners, sending packets, etc.). Note: this function will return None if ProtocolLib is not available on the server.""" 48 | if PyBungee.get().isProtocolizeAvailable(): 49 | from dev.magicmq.pyspigot.bungee.manager.protocol import ProtocolManager 50 | return ProtocolManager.get() 51 | else: return None 52 | 53 | # Convenience variables for ease of access 54 | 55 | script = script_manager() 56 | scripts = script_manager() 57 | sm = script_manager() 58 | 59 | global_vars = global_variables() 60 | gv = global_variables() 61 | 62 | listener = listener_manager() 63 | listeners = listener_manager() 64 | lm = listener_manager() 65 | event = listener_manager() 66 | events = listener_manager() 67 | em = listener_manager() 68 | 69 | command = command_manager() 70 | commands = command_manager() 71 | cm = command_manager() 72 | 73 | scheduler = task_manager() 74 | scm = task_manager() 75 | tasks = task_manager() 76 | tm = task_manager() 77 | 78 | config = config_manager() 79 | configs = config_manager() 80 | com = config_manager() 81 | 82 | database = database_manager() 83 | 84 | redis = redis_manager() 85 | 86 | protocol = protocol_manager() 87 | protocol_lib = protocol_manager() 88 | protocols = protocol_manager() 89 | pm = protocol_manager() -------------------------------------------------------------------------------- /bungee/src/main/resources/plugin.yml: -------------------------------------------------------------------------------- 1 | name: ${project.pluginName} 2 | version: ${project.version} 3 | author: magicmq 4 | main: dev.magicmq.pyspigot.bungee.PyBungee 5 | softDepends: ['Protocolize'] -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/PluginListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot; 18 | 19 | 20 | import dev.magicmq.pyspigot.util.StringUtils; 21 | import dev.magicmq.pyspigot.util.player.PlayerAdapter; 22 | import net.kyori.adventure.text.Component; 23 | import net.kyori.adventure.text.event.ClickEvent; 24 | import net.kyori.adventure.text.event.HoverEvent; 25 | import net.kyori.adventure.text.format.NamedTextColor; 26 | import net.kyori.adventure.text.format.TextDecoration; 27 | 28 | /** 29 | * The primary listener for PySpigot. Methods called in this class are called via platform-specific listeners. 30 | */ 31 | public class PluginListener { 32 | 33 | protected void onJoin(PlayerAdapter player) { 34 | if (PyCore.get().getConfig().shouldShowUpdateMessages()) { 35 | if (player.hasPermission("pyspigot.admin")) { 36 | String latest = PyCore.get().getSpigotVersion(); 37 | if (latest != null) { 38 | StringUtils.Version currentVersion = new StringUtils.Version(PyCore.get().getVersion()); 39 | StringUtils.Version latestVersion = new StringUtils.Version(latest); 40 | if (currentVersion.compareTo(latestVersion) < 0) { 41 | player.sendMessage(buildMessage(latest)); 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | private Component buildMessage(String version) { 49 | Component pluginPage = Component.text("Download the latest version here.") 50 | .color(NamedTextColor.RED) 51 | .decorate(TextDecoration.UNDERLINED) 52 | .clickEvent(ClickEvent.openUrl("https://spigotmc.org/resources/pyspigot.111006/")) 53 | .hoverEvent(HoverEvent.showText(Component.text("Click to go to the PySpigot plugin page", NamedTextColor.GOLD))); 54 | 55 | return Component.text() 56 | .append(Component.text("You're running an outdated version of PySpigot. The latest version is " + version + ". ", NamedTextColor.RED)) 57 | .append(pluginPage) 58 | .build(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/SubCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.command; 18 | 19 | 20 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 21 | 22 | import java.util.Collections; 23 | import java.util.List; 24 | 25 | public interface SubCommand { 26 | 27 | boolean onCommand(CommandSenderAdapter sender, String[] args); 28 | 29 | default List onTabComplete(CommandSenderAdapter sender, String[] args) { 30 | return Collections.emptyList(); 31 | } 32 | } -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/SubCommandMeta.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.command; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Retention(RetentionPolicy.RUNTIME) 25 | @Target(ElementType.TYPE) 26 | public @interface SubCommandMeta { 27 | 28 | String command(); 29 | 30 | String[] aliases() default {}; 31 | 32 | String permission() default ""; 33 | 34 | boolean playerOnly() default false; 35 | 36 | String usage() default ""; 37 | 38 | String description() default "No description provided."; 39 | 40 | } -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to PySpigot's plugin commands. Nothing is documented in this package as no code in this package should be accessed externally. 3 | */ 4 | package dev.magicmq.pyspigot.command; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/subcommands/InfoCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.command.subcommands; 18 | 19 | import dev.magicmq.pyspigot.command.SubCommand; 20 | import dev.magicmq.pyspigot.command.SubCommandMeta; 21 | import dev.magicmq.pyspigot.manager.script.Script; 22 | import dev.magicmq.pyspigot.manager.script.ScriptManager; 23 | import dev.magicmq.pyspigot.manager.script.ScriptOptions; 24 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 25 | import net.kyori.adventure.text.Component; 26 | import net.kyori.adventure.text.TextComponent; 27 | import net.kyori.adventure.text.format.NamedTextColor; 28 | 29 | import java.nio.file.Path; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | @SubCommandMeta( 34 | command = "info", 35 | aliases = {"scriptinfo", "projectinfo"}, 36 | permission = "pyspigot.command.info", 37 | description = "Print information about a script or project, including uptime, registered listeners, commands, and more info", 38 | usage = "" 39 | ) 40 | public class InfoCommand implements SubCommand { 41 | 42 | @Override 43 | public boolean onCommand(CommandSenderAdapter sender, String[] args) { 44 | if (args.length > 0) { 45 | if (ScriptManager.get().isScriptRunning(args[0])) { 46 | Script script = ScriptManager.get().getScriptByName(args[0]); 47 | TextComponent scriptInfo = ScriptManager.get().getScriptInfo().printScriptInfo(script); 48 | sender.sendMessage(scriptInfo); 49 | } else { 50 | if (args[0].endsWith(".py")) { 51 | Path scriptPath = ScriptManager.get().getScriptPath(args[0]); 52 | if (scriptPath != null) { 53 | ScriptOptions options = ScriptManager.get().getScriptOptions(scriptPath); 54 | TextComponent scriptInfo = ScriptManager.get().getScriptInfo().printOfflineScriptInfo(args[0], scriptPath, options); 55 | sender.sendMessage(scriptInfo); 56 | } else 57 | sender.sendMessage(Component.text("No script found in the scripts folder with the name '" + args[0] + "'.", NamedTextColor.RED)); 58 | } else { 59 | Path projectPath = ScriptManager.get().getProjectPath(args[0]); 60 | if (projectPath != null) { 61 | ScriptOptions options = ScriptManager.get().getProjectOptions(projectPath); 62 | TextComponent projectInfo = ScriptManager.get().getScriptInfo().printOfflineScriptInfo(args[0], projectPath, options); 63 | sender.sendMessage(projectInfo); 64 | } else 65 | sender.sendMessage(Component.text("No project found in the projects folder with the name '" + args[0] + "'.", NamedTextColor.RED)); 66 | } 67 | } 68 | return true; 69 | } 70 | return false; 71 | } 72 | 73 | @Override 74 | public List onTabComplete(CommandSenderAdapter sender, String[] args) { 75 | if (args.length > 0) { 76 | return List.copyOf(ScriptManager.get().getAllScriptNames()); 77 | } else { 78 | return List.of(); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/subcommands/LoadLibraryCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.command.subcommands; 18 | 19 | import dev.magicmq.pyspigot.command.SubCommand; 20 | import dev.magicmq.pyspigot.command.SubCommandMeta; 21 | import dev.magicmq.pyspigot.manager.libraries.LibraryManager; 22 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 23 | import net.kyori.adventure.text.Component; 24 | import net.kyori.adventure.text.format.NamedTextColor; 25 | 26 | @SubCommandMeta( 27 | command = "loadlibrary", 28 | aliases = {"loadlib"}, 29 | permission = "pyspigot.command.loadlibrary", 30 | description = "Load a library as the specified file name in the java-libs folder.", 31 | usage = "" 32 | ) 33 | public class LoadLibraryCommand implements SubCommand { 34 | 35 | @Override 36 | public boolean onCommand(CommandSenderAdapter sender, String[] args) { 37 | if (args.length > 0) { 38 | LibraryManager.LoadResult result = LibraryManager.get().loadLibrary(args[0]); 39 | if (result == LibraryManager.LoadResult.FAILED_FILE) 40 | sender.sendMessage(Component.text("File '" + args[0] + "' not found. Did you make sure to include the extension (.jar)?", NamedTextColor.RED)); 41 | else if (result == LibraryManager.LoadResult.FAILED_LOADED) 42 | sender.sendMessage(Component.text("This library appears to be already loaded.", NamedTextColor.RED)); 43 | else if (result == LibraryManager.LoadResult.FAILED_ERROR) 44 | sender.sendMessage(Component.text("Loading library failed unexpectedly. Please see console for details.", NamedTextColor.RED)); 45 | else 46 | sender.sendMessage(Component.text("Successfully loaded library '" + args[0] + "'. Scripts can now use this library.", NamedTextColor.GREEN)); 47 | return true; 48 | } 49 | return false; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/subcommands/ReloadAllCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.command.subcommands; 18 | 19 | import dev.magicmq.pyspigot.PyCore; 20 | import dev.magicmq.pyspigot.command.SubCommand; 21 | import dev.magicmq.pyspigot.command.SubCommandMeta; 22 | import dev.magicmq.pyspigot.manager.libraries.LibraryManager; 23 | import dev.magicmq.pyspigot.manager.script.ScriptManager; 24 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 25 | import net.kyori.adventure.text.Component; 26 | import net.kyori.adventure.text.format.NamedTextColor; 27 | 28 | @SubCommandMeta( 29 | command = "reloadall", 30 | aliases = {"reset", "restart", "reboot", "resetall"}, 31 | permission = "pyspigot.command.reloadall", 32 | description = "Perform a complete reload of the plugin, including configs, libraries, and all scripts." 33 | ) 34 | public class ReloadAllCommand implements SubCommand { 35 | 36 | @Override 37 | public boolean onCommand(CommandSenderAdapter sender, String[] args) { 38 | ScriptManager.get().unloadScripts(); 39 | PyCore.get().reloadConfigs(); 40 | LibraryManager.get().reload(); 41 | ScriptManager.get().loadScripts(); 42 | sender.sendMessage(Component.text("All scripts, plugin config, and script_options.yml have been reloaded.", NamedTextColor.GREEN)); 43 | return true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/subcommands/ReloadConfigCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.command.subcommands; 18 | 19 | import dev.magicmq.pyspigot.PyCore; 20 | import dev.magicmq.pyspigot.command.SubCommand; 21 | import dev.magicmq.pyspigot.command.SubCommandMeta; 22 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 23 | import net.kyori.adventure.text.Component; 24 | import net.kyori.adventure.text.format.NamedTextColor; 25 | 26 | @SubCommandMeta( 27 | command = "reloadconfig", 28 | aliases = {"configreload"}, 29 | permission = "pyspigot.command.reloadconfig", 30 | description = "Reload the config. This command has no effect on already loaded scripts." 31 | ) 32 | public class ReloadConfigCommand implements SubCommand { 33 | 34 | @Override 35 | public boolean onCommand(CommandSenderAdapter sender, String[] args) { 36 | PyCore.get().reloadConfigs(); 37 | sender.sendMessage(Component.text("Configuration has been reloaded.", NamedTextColor.GREEN)); 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/subcommands/UnloadCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.command.subcommands; 18 | 19 | import dev.magicmq.pyspigot.command.SubCommand; 20 | import dev.magicmq.pyspigot.command.SubCommandMeta; 21 | import dev.magicmq.pyspigot.manager.script.ScriptManager; 22 | import dev.magicmq.pyspigot.util.player.CommandSenderAdapter; 23 | import net.kyori.adventure.text.Component; 24 | import net.kyori.adventure.text.format.NamedTextColor; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | @SubCommandMeta( 30 | command = "unload", 31 | aliases = {"stop"}, 32 | permission = "pyspigot.command.unload", 33 | description = "Unload a script or project with the specified name", 34 | usage = "" 35 | ) 36 | public class UnloadCommand implements SubCommand { 37 | 38 | @Override 39 | public boolean onCommand(CommandSenderAdapter sender, String[] args) { 40 | if (args.length > 0) { 41 | if (args[0].endsWith(".py")) { 42 | if (ScriptManager.get().isScriptRunning(args[0])) { 43 | boolean success = ScriptManager.get().unloadScript(args[0]); 44 | if (success) 45 | sender.sendMessage(Component.text("Successfully unloaded script '" + args[0] + "'.", NamedTextColor.GREEN)); 46 | else 47 | sender.sendMessage(Component.text("There was an error when unloading script '" + args[0] + "'. See console for details.", NamedTextColor.RED)); 48 | } else { 49 | sender.sendMessage(Component.text("No running script found with the name '" + args[0] + "'.", NamedTextColor.RED)); 50 | } 51 | } else { 52 | if (ScriptManager.get().isScriptRunning(args[0])) { 53 | boolean success = ScriptManager.get().unloadScript(args[0]); 54 | if (success) 55 | sender.sendMessage(Component.text("Successfully unloaded project '" + args[0] + "'.", NamedTextColor.GREEN)); 56 | else 57 | sender.sendMessage(Component.text("There was an error when unloading project '" + args[0] + "'. See console for details.", NamedTextColor.RED)); 58 | } else { 59 | sender.sendMessage(Component.text("No running project found with the name '" + args[0] + "'.", NamedTextColor.RED)); 60 | } 61 | } 62 | return true; 63 | } 64 | return false; 65 | } 66 | 67 | @Override 68 | public List onTabComplete(CommandSenderAdapter sender, String[] args) { 69 | if (args.length > 0) { 70 | return List.copyOf(ScriptManager.get().getLoadedScriptNames()); 71 | } else { 72 | return List.of(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/command/subcommands/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all subcommands for the plugin. 3 | */ 4 | package dev.magicmq.pyspigot.command.subcommands; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/config/PluginConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.config; 18 | 19 | import java.time.format.DateTimeFormatter; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Properties; 24 | 25 | public interface PluginConfig { 26 | 27 | void reload(); 28 | 29 | boolean getMetricsEnabled(); 30 | 31 | long getScriptLoadDelay(); 32 | 33 | HashMap getLibraryRelocations(); 34 | 35 | DateTimeFormatter getLogTimestamp(); 36 | 37 | boolean doScriptActionLogging(); 38 | 39 | boolean doVerboseRedisLogging(); 40 | 41 | boolean doScriptUnloadOnPluginDisable(); 42 | 43 | String scriptOptionMainScript(); 44 | 45 | boolean scriptOptionEnabled(); 46 | 47 | int scriptOptionLoadPriority(); 48 | 49 | List scriptOptionPluginDepend(); 50 | 51 | boolean scriptOptionFileLoggingEnabled(); 52 | 53 | String scriptOptionMinLoggingLevel(); 54 | 55 | String scriptOptionPermissionDefault(); 56 | 57 | Map scriptOptionPermissions(); 58 | 59 | boolean shouldShowUpdateMessages(); 60 | 61 | String jythonLoggingLevel(); 62 | 63 | boolean patchThreading(); 64 | 65 | boolean loadJythonOnStartup(); 66 | 67 | Properties getJythonProperties(); 68 | 69 | String[] getJythonArgs(); 70 | } 71 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/config/ProjectOptionsConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.config; 18 | 19 | import java.util.List; 20 | import java.util.Map; 21 | 22 | /** 23 | * Loads and provides the project.yml file for accessing a project's options. 24 | */ 25 | public interface ProjectOptionsConfig { 26 | 27 | /** 28 | * Check if the project.yml file contains a particular key. 29 | * @param key The key to check 30 | * @return True if the key exists, false if it does not 31 | */ 32 | boolean contains(String key); 33 | 34 | String getMainScript(String defaultValue); 35 | 36 | /** 37 | * Get if the project is enabled. 38 | * @param defaultValue The default value if the project.yml does not have this option defined 39 | * @return True if the project is enabled, false if it is not, or the default value if not explicitly defined 40 | */ 41 | boolean getEnabled(boolean defaultValue); 42 | 43 | /** 44 | * Get the load priority for the project. 45 | * @param defaultValue The default value if the project.yml does not have this option defined 46 | * @return The load priority of the project, or the default value if not explicitly defined 47 | */ 48 | int getLoadPriority(int defaultValue); 49 | 50 | /** 51 | * Get the plugin dependencies for the project. 52 | * @param defaultValue The default value if the project.yml does not have this option defined 53 | * @return A String list of plugin dependencies for the project, or the default value if not explicitly defined 54 | */ 55 | List getPluginDepend(List defaultValue); 56 | 57 | /** 58 | * Get if file logging is enabled for the project. 59 | * @param defaultValue The default value if the project.yml does not have this option defined 60 | * @return True if the project has file logging enabled, flase if it does not, or the default value if not explicitly defined 61 | */ 62 | boolean getFileLoggingEnabled(boolean defaultValue); 63 | 64 | /** 65 | * Get the minimum logging level for the project. 66 | * @param defaultValue The default value if the project.yml does not have this option defined 67 | * @return The minimum logging level for the project, or the default value if not explicitly defined 68 | */ 69 | String getMinLoggingLevel(String defaultValue); 70 | 71 | /** 72 | * Get the default permission level for the project. 73 | * @param defaultValue The default value if the project.yml does not have this script option defined 74 | * @return The default permission level for the project, or the default value if not explicitly defined 75 | */ 76 | String getPermissionDefault(String defaultValue); 77 | 78 | /** 79 | * Get a section (formatted in the same way as a spigot plugin.yml) representing the permissions for the project. 80 | * @param defaultValue The default value if the project.yml does not have this option defined 81 | * @return A nested Map representing the project's permissions, or the default value if not explicitly defined 82 | */ 83 | Map getPermissions(Map defaultValue); 84 | 85 | } 86 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the {@link dev.magicmq.pyspigot.config.PluginConfig} class, which is used to access values from the config.yml 3 | */ 4 | package dev.magicmq.pyspigot.config; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/exception/PluginInitializationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.exception; 18 | 19 | import java.io.Serial; 20 | 21 | /** 22 | * Thrown if an exception occurs during initialization of PySpigot. 23 | *

24 | * This is a wrapper class for various checked exceptions associated with reflective calls ({@link java.lang.NoSuchMethodException}, {@link java.lang.NoSuchFieldException}, etc.). 25 | */ 26 | public class PluginInitializationException extends RuntimeException { 27 | 28 | @Serial 29 | private static final long serialVersionUID = -8133956334847368719L; 30 | 31 | /** 32 | * 33 | * @param message The detail message 34 | * @param cause The cause 35 | */ 36 | public PluginInitializationException(String message, Throwable cause) { 37 | super(message, cause); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/exception/ScriptExitException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.exception; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | 21 | /** 22 | * Thrown if a {@link org.python.core.Py#SystemExit} is handled in {@link dev.magicmq.pyspigot.util.ScriptUtils#handleException(Script, Throwable)}. 23 | *

24 | * Used to signal to the ScriptManager that the script should be unloaded. 25 | */ 26 | @SuppressWarnings("serial") 27 | public class ScriptExitException extends Exception { 28 | 29 | private final Script script; 30 | 31 | /** 32 | * 33 | * @param script The script that exited 34 | */ 35 | public ScriptExitException(Script script) { 36 | this.script = script; 37 | } 38 | 39 | /** 40 | * Get the script that exited. 41 | * @return The script that exited 42 | */ 43 | public Script getScript() { 44 | return script; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/exception/ScriptInitializationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.exception; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | 21 | import java.io.Serial; 22 | 23 | /** 24 | * Thrown if an exception occurs when loading or running a script. 25 | *

26 | * This is a wrapper class for checked exceptions such as {@link java.io.IOException}. 27 | */ 28 | public class ScriptInitializationException extends Exception { 29 | 30 | @Serial 31 | private static final long serialVersionUID = 3601572161262479369L; 32 | private final Script script; 33 | 34 | /** 35 | * 36 | * @param script The script that was being loaded 37 | * @param message The detail message 38 | * @param cause The cause 39 | */ 40 | public ScriptInitializationException(Script script, String message, Throwable cause) { 41 | super(message, cause); 42 | this.script = script; 43 | } 44 | 45 | /** 46 | * Get the script that was being loaded. 47 | * @return The script that was being loaded 48 | */ 49 | public Script getScript() { 50 | return script; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/exception/ScriptRuntimeException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.exception; 18 | 19 | 20 | import dev.magicmq.pyspigot.manager.script.Script; 21 | 22 | import java.io.Serial; 23 | 24 | /** 25 | * Thrown if an error/exception is thrown or an illegal operation is performed when a script interacts with PySpigot. 26 | *

27 | * This class also serves as a wrapper for some checked exceptions, including {@link java.io.IOException}. 28 | */ 29 | public class ScriptRuntimeException extends RuntimeException { 30 | 31 | @Serial 32 | private static final long serialVersionUID = -1309086963783269924L; 33 | private final Script script; 34 | 35 | /** 36 | * 37 | * @param script The script that threw or is associated with this exception 38 | */ 39 | public ScriptRuntimeException(Script script) { 40 | this.script = script; 41 | } 42 | 43 | /** 44 | * 45 | * @param script The script that threw or is associated with this exception 46 | * @param message The detail message 47 | */ 48 | public ScriptRuntimeException(Script script, String message) { 49 | super(message); 50 | this.script = script; 51 | } 52 | 53 | /** 54 | * 55 | * @param script The script that threw or is associated with this exception 56 | * @param message The detail message 57 | * @param cause The cause 58 | */ 59 | public ScriptRuntimeException(Script script, String message, Throwable cause) { 60 | super(message, cause); 61 | this.script = script; 62 | } 63 | 64 | /** 65 | * Get the script that threw or is associated with this exception. 66 | * @return The script that threw or is associated with this exception 67 | */ 68 | public Script getScript() { 69 | return script; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/exception/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all PySpigot-specific exceptions. 3 | */ 4 | package dev.magicmq.pyspigot.exception; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/command/ScriptCommand.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.manager.command; 18 | 19 | 20 | import dev.magicmq.pyspigot.manager.script.Script; 21 | 22 | /** 23 | * A command registered by a script. Meant to be implemented by platform-specific classes that also implement or extend an API's command executor class/interface. 24 | */ 25 | public interface ScriptCommand { 26 | 27 | /** 28 | * Get the script associated with this command. 29 | * @return The script associated with this command 30 | */ 31 | Script getScript(); 32 | 33 | /** 34 | * Get the name of this command. 35 | * @return The name of this command 36 | */ 37 | String getName(); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/command/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to Script commands. 3 | */ 4 | package dev.magicmq.pyspigot.manager.command; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/config/ScriptConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.manager.config; 18 | 19 | 20 | import java.io.File; 21 | import java.io.IOException; 22 | import java.nio.file.Path; 23 | 24 | /** 25 | * Represents a config file belonging to a script. Meant to be implemented by a class that also implements/extends a platform-specific configuration API. 26 | */ 27 | public interface ScriptConfig { 28 | 29 | /** 30 | * Get the file associated with this configuration. 31 | * @return The file associated with this configuration 32 | */ 33 | File getConfigFile(); 34 | 35 | /** 36 | * Get the absolute path of the file associated with this configuration. 37 | * @return The path of the file 38 | */ 39 | Path getConfigPath(); 40 | 41 | /** 42 | * Loads the config from the configuration file. Will also set defaults for the configuration, if they were specified. 43 | * @throws IOException If there was an exception when loading the file 44 | */ 45 | void load() throws IOException; 46 | 47 | /** 48 | * Reload the configuration. Will read all changes made to the configuration file since the configuration was last loaded/reloaded. 49 | * @throws IOException If there was an exception when loading the file 50 | */ 51 | void reload()throws IOException; 52 | 53 | /** 54 | * Save the configuration to its associated file. For continuity purposes, the configuration is also reloaded from the file after saving. 55 | * @throws IOException If there is an IOException when saving the file 56 | */ 57 | void save() throws IOException; 58 | 59 | /** 60 | * Sets the specified path to the given value only if the path is not already set in the config file. Any specified default values are ignored when checking if the path is set. 61 | * @param path Path of the object to set 62 | * @param value Value to set the path to 63 | * @return True if the path was set to the value (in other words the path was not previously set), false if the path was not set to the value (in other words the path was already previously set) 64 | */ 65 | boolean setIfNotExists(String path, Object value); 66 | 67 | } 68 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/config/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to script config files. 3 | */ 4 | package dev.magicmq.pyspigot.manager.config; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/database/Database.java: -------------------------------------------------------------------------------- 1 | package dev.magicmq.pyspigot.manager.database; 2 | 3 | import dev.magicmq.pyspigot.manager.script.Script; 4 | 5 | /** 6 | * Represents a database to which a script is connected and can read/write. 7 | */ 8 | public abstract class Database { 9 | 10 | private static int dbIdIncrement; 11 | 12 | private final Script script; 13 | private final int databaseId; 14 | 15 | /** 16 | * 17 | * @param script The script associated with this database connection 18 | */ 19 | public Database(Script script) { 20 | this.script = script; 21 | this.databaseId = dbIdIncrement++; 22 | } 23 | 24 | /** 25 | * Opens a connection to the database. 26 | * @return True if opening the connection to the database was successful, false if otherwise 27 | */ 28 | public abstract boolean open(); 29 | 30 | /** 31 | * Closes a connection to the database. 32 | * @return True if closing the connection to the database was successful, false if otherwise 33 | */ 34 | public abstract boolean close(); 35 | 36 | /** 37 | * Get the script associated with this database connection. 38 | * @return The script associated with this database connection 39 | */ 40 | public Script getScript() { 41 | return script; 42 | } 43 | 44 | /** 45 | * Get the ID of this database connection. 46 | * @return The ID 47 | */ 48 | public int getDatabaseId() { 49 | return databaseId; 50 | } 51 | 52 | /** 53 | * Prints a representation of this Database in string format. 54 | * @return A string representation of this Database 55 | */ 56 | public abstract String toString(); 57 | } 58 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/database/DatabaseType.java: -------------------------------------------------------------------------------- 1 | package dev.magicmq.pyspigot.manager.database; 2 | 3 | import dev.magicmq.pyspigot.manager.database.mongo.MongoDatabase; 4 | import dev.magicmq.pyspigot.manager.database.sql.SqlDatabase; 5 | 6 | /** 7 | * Utility enum to represent different database types available for scripts to use. 8 | */ 9 | public enum DatabaseType { 10 | 11 | /** 12 | * An SQL database type. 13 | */ 14 | SQL(SqlDatabase.class, /*Host, port, database, user, password*/ "jdbc:mysql://%s:%s/%s?user=%s&password=%s"), 15 | 16 | /** 17 | * A MongoDB database type. 18 | */ 19 | MONGO_DB(MongoDatabase.class, /*User, password, host, port*/ "mongodb://%s:%s@%s:%s"), 20 | 21 | /** 22 | * A MongoDB database type without authentication. 23 | */ 24 | MONGO_DB_NO_AUTH(MongoDatabase.class, "mongodb://%s:%s"); 25 | 26 | private final Class dbClass; 27 | private final String uri; 28 | 29 | /** 30 | * 31 | * @param dbClass The class associated with the database 32 | * @param uri The URI scheme used to connect to the database 33 | */ 34 | DatabaseType(Class dbClass, String uri) { 35 | this.dbClass = dbClass; 36 | this.uri = uri; 37 | } 38 | 39 | /** 40 | * Get the class that pertains to the database type. Will be a subclass of {@link Database} 41 | * @return The class associated with the database type 42 | */ 43 | public Class getDbClass() { 44 | return dbClass; 45 | } 46 | 47 | /** 48 | * Get the URI scheme associated with the database type. 49 | * @return The URI scheme associated with the database type 50 | */ 51 | public String getUri() { 52 | return uri; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/database/mongo/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Contains classes related to MongoDB. 19 | */ 20 | package dev.magicmq.pyspigot.manager.database.mongo; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/database/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Contains all classes related to database connections. 19 | */ 20 | package dev.magicmq.pyspigot.manager.database; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/database/sql/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Contains classes related to SQL databases. 19 | */ 20 | package dev.magicmq.pyspigot.manager.database.sql; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/libraries/JarClassLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.manager.libraries; 18 | 19 | import java.net.MalformedURLException; 20 | import java.net.URL; 21 | import java.net.URLClassLoader; 22 | import java.nio.file.Path; 23 | 24 | /** 25 | * Utility class for assisting with loading Jar files into the classpath. 26 | */ 27 | public class JarClassLoader extends URLClassLoader { 28 | 29 | static { 30 | ClassLoader.registerAsParallelCapable(); 31 | } 32 | 33 | /** 34 | * Initialize a new JarClassLoader using a parent class loader. 35 | * @param parentClassLoader The parent class loader to use 36 | */ 37 | public JarClassLoader(ClassLoader parentClassLoader) { 38 | super(new URL[]{}, parentClassLoader); 39 | } 40 | 41 | /** 42 | * Add a new Jar to the classpath. 43 | * @param file The Jar file to add to the classpath 44 | * @throws MalformedURLException If the file has an invalid URL 45 | */ 46 | public void addJarToClasspath(Path file) throws MalformedURLException { 47 | addURL(file.toUri().toURL()); 48 | } 49 | 50 | /** 51 | * Check if a Jar file is in the classpath. 52 | * @param file The Jar file to check 53 | * @return True if the Jar file is already in the classpath, false if otherwise 54 | */ 55 | public boolean isJarInClassPath(Path file) throws MalformedURLException { 56 | URL jarURL = file.toUri().toURL(); 57 | URL[] loaded = getURLs(); 58 | for (URL url : loaded) { 59 | if (url.equals(jarURL)) 60 | return true; 61 | } 62 | return false; 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/libraries/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to Jar libraries. 3 | */ 4 | package dev.magicmq.pyspigot.manager.libraries; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/listener/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to script event listeners. 3 | */ 4 | package dev.magicmq.pyspigot.manager.listener; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/redis/ClientType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.manager.redis; 18 | 19 | import dev.magicmq.pyspigot.manager.redis.client.RedisCommandClient; 20 | import dev.magicmq.pyspigot.manager.redis.client.RedisPubSubClient; 21 | import dev.magicmq.pyspigot.manager.redis.client.ScriptRedisClient; 22 | 23 | /** 24 | * Utility enum to represent different types of redis clients available for scripts to use. 25 | */ 26 | public enum ClientType { 27 | 28 | /** 29 | * A basic client type, used for initiating a standard RedisClient for further custom usage. 30 | */ 31 | BASIC(ScriptRedisClient.class), 32 | 33 | /** 34 | * A command client type, used for executing redis commands. 35 | */ 36 | COMMAND(RedisCommandClient.class), 37 | 38 | /** 39 | * A pub/sub client type, used for publishing and subscribing to redis messaging. 40 | */ 41 | PUB_SUB(RedisPubSubClient.class); 42 | 43 | private final Class clientClass; 44 | 45 | /** 46 | * 47 | * @param clientClass The class associated with the client type 48 | */ 49 | ClientType(Class clientClass) { 50 | this.clientClass = clientClass; 51 | } 52 | 53 | /** 54 | * Get the class that pertains to the client type. Will be a subclass of {@link ScriptRedisClient} 55 | * @return The class associated with the client type 56 | */ 57 | public Class getClientClass() { 58 | return clientClass; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/redis/client/RedisCommandClient.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.manager.redis.client; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import io.lettuce.core.ClientOptions; 21 | import io.lettuce.core.RedisURI; 22 | import io.lettuce.core.api.StatefulRedisConnection; 23 | import io.lettuce.core.api.async.RedisAsyncCommands; 24 | import io.lettuce.core.api.sync.RedisCommands; 25 | 26 | /** 27 | * Extension of the {@link ScriptRedisClient} that provides ability to issue commands. 28 | * @see io.lettuce.core.api.StatefulRedisConnection 29 | */ 30 | public class RedisCommandClient extends ScriptRedisClient { 31 | 32 | private StatefulRedisConnection connection; 33 | 34 | /** 35 | * 36 | * @param script The script to which this ScriptRedisCommandClient belongs 37 | * @param redisURI The URI that specifies the connection details to the server 38 | * @param clientOptions The {@link io.lettuce.core.ClientOptions} that should be used for the RedisClient 39 | */ 40 | public RedisCommandClient(Script script, RedisURI redisURI, ClientOptions clientOptions) { 41 | super(script, redisURI, clientOptions); 42 | } 43 | 44 | /** 45 | * {@inheritDoc} 46 | */ 47 | @Override 48 | public void open() { 49 | super.open(); 50 | connection = client.connect(); 51 | } 52 | 53 | /** 54 | * Get the underlying connection for this RedisCommandClient. 55 | * @return The connection associated with this RedisCommandClient 56 | */ 57 | public StatefulRedisConnection getConnection() { 58 | return connection; 59 | } 60 | 61 | /** 62 | * Get the {@link io.lettuce.core.api.sync.RedisCommands} object for executing commands synchronously. 63 | * @return A RedisCommands object for executing commands 64 | * @see io.lettuce.core.api.StatefulRedisConnection#sync() 65 | */ 66 | public RedisCommands getCommands() { 67 | return connection.sync(); 68 | } 69 | 70 | /** 71 | * Get the {@link io.lettuce.core.api.async.RedisAsyncCommands} object for executing commands asynchronously. 72 | * @return A RedisAsyncCommands object for executing commands 73 | * @see io.lettuce.core.api.StatefulRedisConnection#async() 74 | */ 75 | public RedisAsyncCommands getAsyncCommands() { 76 | return connection.async(); 77 | } 78 | 79 | /** 80 | * Prints a representation of this RedisCommandClient in string format, including listeners 81 | * @return A string representation of the RedisCommandClient 82 | */ 83 | @Override 84 | public String toString() { 85 | return String.format("RedisCommandClient[ID: %d, Connection: %s]", getClientId(), connection.toString()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/redis/client/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Contains all the redis clients that can be used by scripts. 19 | */ 20 | package dev.magicmq.pyspigot.manager.redis.client; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/redis/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Contains all classes related to redis. 19 | */ 20 | package dev.magicmq.pyspigot.manager.redis; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/script/RunResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.manager.script; 18 | 19 | /** 20 | * An enum class representing various outcomes of running a script or project, including run failures. 21 | */ 22 | public enum RunResult { 23 | 24 | /** 25 | * Returned if the script/project loaded successfully. 26 | */ 27 | SUCCESS, 28 | 29 | /** 30 | * Returned if the script/project was not loaded because it was disabled as per its script options in script_options.yml 31 | */ 32 | FAIL_DISABLED, 33 | 34 | /** 35 | * Returned if the script/project was not loaded because it has one or more missing plugin dependencies. 36 | */ 37 | FAIL_PLUGIN_DEPENDENCY, 38 | 39 | /** 40 | * Returned if the script/project was loaded but failed during runtime due to an error. 41 | */ 42 | FAIL_ERROR, 43 | 44 | /** 45 | * Returned if a script/project is already loaded with the same name. 46 | */ 47 | FAIL_DUPLICATE, 48 | 49 | /** 50 | * Returned if a script/project was not found with the given name. 51 | */ 52 | FAIL_SCRIPT_NOT_FOUND, 53 | 54 | /** 55 | * Returned if a main script file was not found for a multi-file project. 56 | */ 57 | FAIL_NO_MAIN 58 | } 59 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/script/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to scripts themselves. Other manager classes that scripts utilize are located in different packages. 3 | */ 4 | package dev.magicmq.pyspigot.manager.script; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/task/RepeatingTask.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.manager.task; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | import dev.magicmq.pyspigot.manager.script.ScriptManager; 21 | import org.python.core.Py; 22 | import org.python.core.PyException; 23 | import org.python.core.PyFunction; 24 | import org.python.core.PyObject; 25 | import org.python.core.ThreadState; 26 | 27 | /** 28 | * Represents a repeating task defined by a script. 29 | * @param The platform-specific scheduled task type. For example, {@code BukkitTask} for Bukkit, and {@code ScheduledTask} for BungeeCord 30 | */ 31 | public class RepeatingTask extends Task { 32 | 33 | private final long interval; 34 | 35 | /** 36 | * 37 | * @param script The script associated with this repeating task 38 | * @param function The script function that should be called every time the repeating task executes 39 | * @param functionArgs Any arguments that should be passed to the function 40 | * @param async True if the task is asynchronous, false if otherwise 41 | * @param delay The delay, in ticks, to wait until running the task 42 | * @param interval The interval, in ticks, between each repeat of the task 43 | */ 44 | public RepeatingTask(Script script, PyFunction function, Object[] functionArgs, boolean async, long delay, long interval) { 45 | super(script, function, functionArgs, async, delay); 46 | this.interval = interval; 47 | } 48 | 49 | /** 50 | * {@inheritDoc} 51 | */ 52 | @Override 53 | public void run() { 54 | try { 55 | Py.setSystemState(script.getInterpreter().getSystemState()); 56 | ThreadState threadState = Py.getThreadState(script.getInterpreter().getSystemState()); 57 | 58 | if (functionArgs != null) { 59 | PyObject[] pyObjects = Py.javas2pys(functionArgs); 60 | function.__call__(threadState, pyObjects); 61 | } else { 62 | function.__call__(threadState); 63 | } 64 | } catch (PyException e) { 65 | ScriptManager.get().handleScriptException(script, e, "Error while executing repeating task"); 66 | } 67 | } 68 | 69 | /** 70 | * Prints a representation of this RepeatingTask in string format, including the task ID, if it is async, delay (if applicable), and interval (if applicable) 71 | * @return A string representation of the RepeatingTask 72 | */ 73 | @Override 74 | public String toString() { 75 | return String.format("RepeatingTask[Platform Task: %s, Async: %b, Delay: %d, Interval: %d]", TaskManager.getTyped().describeTask(platformTask), async, (int) delay, (int) interval); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/manager/task/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains all classes related to script tasks. 3 | */ 4 | package dev.magicmq.pyspigot.manager.task; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains the main plugin class. 3 | */ 4 | package dev.magicmq.pyspigot; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/StringUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.util; 18 | 19 | import java.time.Duration; 20 | 21 | /** 22 | * A utility class for various methods/classes related to Strings. 23 | */ 24 | public final class StringUtils { 25 | 26 | private StringUtils() {} 27 | 28 | public static String replaceLastOccurrence(String string, String toReplace, String replaceWith) { 29 | return string.replaceFirst("(?s)" + toReplace + "(?!.*?" + toReplace + ")", replaceWith); 30 | } 31 | 32 | public static String formatDuration(Duration duration) { 33 | long days = duration.toDaysPart(); 34 | long hours = duration.toHoursPart(); 35 | long minutes = duration.toMinutesPart(); 36 | long seconds = duration.toSecondsPart(); 37 | 38 | return days + "d" + hours + "h" + minutes + "m" + seconds + "s"; 39 | } 40 | 41 | public static class Version implements Comparable { 42 | 43 | private final String version; 44 | 45 | public Version(String version) { 46 | if (version.contains("SNAPSHOT")) 47 | version = version.substring(0, version.indexOf("-")); 48 | this.version = version; 49 | } 50 | 51 | public final String getVersion() { 52 | return version; 53 | } 54 | 55 | @Override 56 | public int compareTo(StringUtils.Version that) { 57 | String[] thisParts = this.getVersion().split("\\."); 58 | String[] thatParts = that.getVersion().split("\\."); 59 | int length = Math.max(thisParts.length, thatParts.length); 60 | for (int i = 0; i < length; i++) { 61 | int thisPart = i < thisParts.length ? 62 | Integer.parseInt(thisParts[i]) : 0; 63 | int thatPart = i < thatParts.length ? 64 | Integer.parseInt(thatParts[i]) : 0; 65 | if (thisPart < thatPart) 66 | return -1; 67 | if (thisPart > thatPart) 68 | return 1; 69 | } 70 | return 0; 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/logging/JythonLogHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.util.logging; 18 | 19 | 20 | import dev.magicmq.pyspigot.PyCore; 21 | 22 | import java.util.logging.Handler; 23 | import java.util.logging.Level; 24 | import java.util.logging.LogRecord; 25 | 26 | /** 27 | * A simple log handler that captures any log messages submitted to Jython's logger (by Jython) and forwards them to PySpigot's plugin logger. 28 | */ 29 | public class JythonLogHandler extends Handler { 30 | 31 | private static final String LOG_MESSAGE_FORMAT = "[%s] %s"; 32 | 33 | /** 34 | * Intercepts a log record, modifies the message, and forwards it to PySpigot's logger. 35 | *

36 | * The original logger name is inserted at the beginning of the log message in square brackets to denote the message originated from a Jython logger. 37 | * @param record The LogRecord to forward 38 | */ 39 | @Override 40 | public void publish(LogRecord record) { 41 | record.setMessage(String.format(LOG_MESSAGE_FORMAT, record.getLoggerName(), record.getMessage())); 42 | 43 | if (record.getLevel() == Level.FINEST || record.getLevel() == Level.FINER || record.getLevel() == Level.FINE) 44 | PyCore.get().getLogger().trace(record.getMessage(), record.getThrown()); 45 | else if (record.getLevel() == Level.CONFIG) 46 | PyCore.get().getLogger().debug(record.getMessage(), record.getThrown()); 47 | else if (record.getLevel() == Level.INFO) 48 | PyCore.get().getLogger().info(record.getMessage(), record.getThrown()); 49 | else if (record.getLevel() == Level.WARNING) 50 | PyCore.get().getLogger().warn(record.getMessage(), record.getThrown()); 51 | else 52 | PyCore.get().getLogger().error(record.getMessage(), record.getThrown()); 53 | } 54 | 55 | /** 56 | * No-op implementation 57 | */ 58 | @Override 59 | public void flush() {} 60 | 61 | /** 62 | * No-op implementation 63 | */ 64 | @Override 65 | public void close() throws SecurityException {} 66 | } 67 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/logging/PrintStreamWrapper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.util.logging; 18 | 19 | import dev.magicmq.pyspigot.manager.script.Script; 20 | 21 | import java.io.OutputStream; 22 | import java.io.PrintStream; 23 | import java.nio.charset.StandardCharsets; 24 | import java.util.Arrays; 25 | import java.util.logging.Level; 26 | 27 | /** 28 | * A wrapper class that captures print statements and errors/exceptions from scripts and redirects them to the script's logger. 29 | */ 30 | public class PrintStreamWrapper extends PrintStream { 31 | 32 | private final Script script; 33 | private final Level level; 34 | private final String prefix; 35 | 36 | /** 37 | * 38 | * @param out The parent OutputStream, usually System.out or System.err 39 | * @param script The script to which this PrintStreamWrapper belongs 40 | * @param level The logging level, usually Level.INFO for stdout and Level.SEVERE for stderr 41 | * @param prefix The prefix to include before the log message, usually [STDOUT] for stdout and [STDERR] for stderr 42 | */ 43 | public PrintStreamWrapper(OutputStream out, Script script, Level level, String prefix) { 44 | super(out); 45 | this.script = script; 46 | this.level = level; 47 | this.prefix = prefix; 48 | } 49 | 50 | /** 51 | * Captures writes to the PrintStream, converts the bytes into readable text (truncating according to the specified length and offset), and logs the text to the script's logger. This method also strips carriage returns/new line characters from the end of the text, because the script logger already inserts a new line when logging. 52 | * @param buf A byte array 53 | * @param off Offset from which to start taking bytes 54 | * @param len Number of bytes to write 55 | */ 56 | @Override 57 | public void write(byte[] buf, int off, int len) { 58 | byte[] toLog = Arrays.copyOfRange(buf, off, len); 59 | String string = new String(toLog, StandardCharsets.UTF_8); 60 | string = string.replaceAll("\\R$", ""); 61 | if (level == Level.INFO) 62 | script.getLogger().info(prefix + " " + string); 63 | else if (level == Level.SEVERE) 64 | script.getLogger().error(prefix + " " + string); 65 | } 66 | 67 | /** 68 | * {@inheritDoc} 69 | */ 70 | @Override 71 | public void write(byte[] buf) { 72 | this.write(buf, 0, buf.length); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/logging/ScriptFileLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.util.logging; 18 | 19 | 20 | import dev.magicmq.pyspigot.PyCore; 21 | import dev.magicmq.pyspigot.manager.script.Script; 22 | 23 | import java.io.IOException; 24 | import java.io.PrintWriter; 25 | import java.io.StringWriter; 26 | import java.nio.file.Path; 27 | import java.time.ZoneId; 28 | import java.time.ZonedDateTime; 29 | import java.util.logging.FileHandler; 30 | import java.util.logging.Formatter; 31 | import java.util.logging.LogRecord; 32 | import java.util.logging.Logger; 33 | 34 | /** 35 | * A Logger that logs messages to a script's log file. 36 | */ 37 | public class ScriptFileLogger extends Logger { 38 | 39 | private final FileHandler handler; 40 | 41 | /** 42 | * 43 | * @param script The script associated with this ScriptFileLogger 44 | */ 45 | public ScriptFileLogger(Script script) throws IOException { 46 | super(script.getName(), null); 47 | 48 | Path scriptLogsFolder = PyCore.get().getDataFolderPath().resolve("logs"); 49 | Path logFilePath = scriptLogsFolder.resolve(script.getLogFileName()); 50 | 51 | this.handler = new FileHandler(logFilePath.toString(), true); 52 | handler.setFormatter(new ScriptFileLogger.ScriptLogFormatter()); 53 | handler.setEncoding("UTF-8"); 54 | this.addHandler(handler); 55 | } 56 | 57 | /** 58 | * Closes the FileHandler for this logger. Should only be called if script file logging is enabled. 59 | */ 60 | public void closeFileHandler() { 61 | if (handler != null) 62 | handler.close(); 63 | } 64 | 65 | /** 66 | * A {@link Formatter} to log script messages to their respective log file. 67 | */ 68 | private static class ScriptLogFormatter extends Formatter { 69 | 70 | /** 71 | * Formats a LogRecord into an appropriate format for the script's log file. 72 | * @param record The log record to be formatted. 73 | * @return A String of formatted text that will be logged to the script's log file 74 | */ 75 | @Override 76 | public String format(LogRecord record) { 77 | StringBuilder builder = new StringBuilder(); 78 | 79 | ZonedDateTime zdt = ZonedDateTime.ofInstant(record.getInstant(), ZoneId.systemDefault()); 80 | builder.append("[" + zdt.format(PyCore.get().getConfig().getLogTimestamp()) + "] "); 81 | 82 | builder.append("[" + record.getLevel().getLocalizedName() + "] "); 83 | 84 | builder.append(super.formatMessage(record)); 85 | 86 | String throwable = ""; 87 | if (record.getThrown() != null) { 88 | StringWriter sw = new StringWriter(); 89 | PrintWriter pw = new PrintWriter(sw); 90 | pw.println(); 91 | record.getThrown().printStackTrace(pw); 92 | pw.close(); 93 | throwable = sw.toString(); 94 | } else { 95 | throwable += "\n"; 96 | } 97 | builder.append(throwable); 98 | 99 | return builder.toString(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/logging/package-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /** 18 | * Contains utility classes related to script logging. 19 | */ 20 | package dev.magicmq.pyspigot.util.logging; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains utility classes for the plugin and for scripts. 3 | */ 4 | package dev.magicmq.pyspigot.util; -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/player/CommandSenderAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.util.player; 18 | 19 | 20 | import net.kyori.adventure.text.Component; 21 | 22 | /** 23 | * A utility class that wraps a platform-specific command sender object. 24 | */ 25 | public interface CommandSenderAdapter { 26 | 27 | /** 28 | * Check if the command sender has a permission via a platform-specific implementation. 29 | * @param permission The permission to check 30 | * @return True if the command sender has the permission, false if it does not 31 | */ 32 | boolean hasPermission(String permission); 33 | 34 | /** 35 | * Send a message to the command sender via a platform-specific implementation. 36 | * @param message The message to send 37 | */ 38 | void sendMessage(Component message); 39 | 40 | /** 41 | * Check if the command sender is a player via a platform-specific implementation. 42 | * @return True if the command sender is a player, false if it is not 43 | */ 44 | boolean isPlayer(); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /core/src/main/java/dev/magicmq/pyspigot/util/player/PlayerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 magicmq 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package dev.magicmq.pyspigot.util.player; 18 | 19 | 20 | import net.kyori.adventure.text.Component; 21 | 22 | /** 23 | * A utility class that wraps a platform-specific player object. 24 | */ 25 | public interface PlayerAdapter { 26 | 27 | /** 28 | * Check if the player has a permission via a platform-specific implementation. 29 | * @param permission The permission to check 30 | * @return True if the player has the permission, false if they do not 31 | */ 32 | boolean hasPermission(String permission); 33 | 34 | /** 35 | * Send a message to the player via a platform-specific implementation. 36 | * @param message The message to send 37 | */ 38 | void sendMessage(Component message); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /core/src/main/resources/Lib/function.py: -------------------------------------------------------------------------------- 1 | """ 2 | This is a helper module that wraps a variety of functional interfaces in the java.util.function package, for easier use in Python. 3 | 4 | This module should be particularly useful when working with some libraries that make use of functional interfaces (such as NBT-API). 5 | """ 6 | import java.util.function.BiConsumer 7 | import java.util.function.BiFunction 8 | import java.util.function.BiPredicate 9 | import java.util.function.Consumer 10 | import java.util.function.Function 11 | import java.util.function.Predicate 12 | import java.util.function.Supplier 13 | 14 | """ 15 | Represents an operation that accepts two input arguments and returns no result. This is the two-arity specialization of Consumer. Unlike most other functional interfaces, BiConsumer is expected to operate via side-effects. 16 | """ 17 | class BiConsumer(java.util.function.BiConsumer): 18 | 19 | """ 20 | Initialize a new BiConsumer. 21 | 22 | Arguments: 23 | function (callable): A function that accepts two arguments and returns nothing. 24 | """ 25 | def __init__(self, function): 26 | self.accept = function 27 | 28 | 29 | """ 30 | Represents a function that accepts two arguments and produces a result. This is the two-arity specialization of Function. 31 | """ 32 | class BiFunction(java.util.function.BiFunction): 33 | 34 | """ 35 | Initializes a new BiFunction. 36 | 37 | Arguments: 38 | function (callable): A function that accepts two arguments and returns a value. 39 | """ 40 | def __init__(self, function): 41 | self.apply = function 42 | 43 | 44 | """ 45 | Represents a predicate (boolean-valued function) of two arguments. This is the two-arity specialization of Predicate. 46 | """ 47 | class BiPredicate(java.util.function.BiPredicate): 48 | 49 | """ 50 | Initializes a new BiPredicate. 51 | 52 | Arguments: 53 | function (callable): A function that accepts two arguments and returns either True or False. 54 | """ 55 | def __init__(self, function): 56 | self.test = function 57 | 58 | 59 | """ 60 | Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects. 61 | """ 62 | class Consumer(java.util.function.Consumer): 63 | 64 | """ 65 | Initializes a new Consumer. 66 | 67 | Arguments: 68 | function (callable): A function that accepts one argument and returns nothing. 69 | """ 70 | def __init__(self, function): 71 | self.accept = function 72 | 73 | 74 | """ 75 | Represents a function that accepts one argument and produces a result. 76 | """ 77 | class Function(java.util.function.Function): 78 | 79 | """ 80 | Initializes a new Function. 81 | 82 | Arguments: 83 | function (callable): A function that accepts one argument and returns a value. 84 | """ 85 | def __init__(self, function): 86 | self.apply = function 87 | 88 | 89 | """ 90 | Represents a predicate (boolean-valued function) of one argument. 91 | """ 92 | class Predicate(java.util.function.Predicate): 93 | 94 | """ 95 | Initializes a new Predicate. 96 | 97 | Arguments: 98 | function (callable): A function that accepts one argument and returns either True or False. 99 | """ 100 | def __init__(self, function): 101 | self.test = function 102 | 103 | 104 | """ 105 | Represents a supplier of results. 106 | 107 | There is no requirement that a new or distinct result be returned each time the supplier is invoked. 108 | """ 109 | class Supplier(java.util.function.Supplier): 110 | 111 | """ 112 | Initializes a new Supplier. 113 | 114 | Arguments: 115 | function (callable): A function that accepts no arguments and returns a value. 116 | """ 117 | def __init__(self, function): 118 | self.get = function -------------------------------------------------------------------------------- /core/src/main/resources/Lib/threading_patch.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module patches the _pickSomeNonDaemonThread function in the threading module. _pickSomeNonDaemonThread 3 | is called in the exit function of threading._MainThread to fetch all non-daemon threads seen by the threading 4 | module. The exit function blocks/waits for the thread to die (via thread.join()). 5 | 6 | The patch applies a condition that excludes returning any thread that is part of Bukkit's scheduler thread 7 | pool. These threads are identified by the name "Craft Scheduler Thread" and are kept alive for an arbitrary 8 | period of time. Thus, they should not be waited on to die with thread.join(). 9 | 10 | PySpigot imports this module into all scripts just prior to script unload, if "debug-options.patch-threading" 11 | is "true" in the PySpigot config.yml. The patch is only applied if the script imported threading at an earlier 12 | point in time (I.E. sys.modules contains "threading"). 13 | 14 | For more information, see https://github.com/magicmq/pyspigot/issues/18#issue-3012022678 15 | """ 16 | import sys 17 | 18 | 19 | def _patch(): 20 | """patches the _pickSomeNonDaemonThread function in the threading module.""" 21 | if "threading" in sys.modules: 22 | import threading 23 | 24 | def _pickSomeNonDaemonThreadPatched(): 25 | for t in threading.enumerate(): 26 | # In addition to excluding daemon threads and dead threads, exclude Craft Scheduler Threads 27 | if not t.isDaemon() and t.isAlive() and "Craft Scheduler Thread" not in t.getName(): 28 | return t 29 | return None 30 | 31 | threading._pickSomeNonDaemonThread = _pickSomeNonDaemonThreadPatched -------------------------------------------------------------------------------- /core/src/main/resources/config.yml: -------------------------------------------------------------------------------- 1 | # If false, will disable collection of metrics information by bStats for PySpigot. You may also disable bStats server-wide in the bStats config.yml under /plugins/bStats. 2 | metrics-enabled: true 3 | # The delay for loading scripts (in ticks) after the server finishes loading. 4 | script-load-delay: 20 5 | # List of relocation rules for libraries in the libs folder. Format as | 6 | library-relocations: [] 7 | # Date/time format for timestamps in script log files, written in Java's SimpleDateFormat pattern: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html 8 | log-timestamp-format: 'MMM dd yyyy HH:mm:ss' 9 | # If true, will print log messages to console every time a script is loaded, run, and unloaded. 10 | script-action-logging: true 11 | # If true, will log all redis events to the console and to a script's logger. If false, will only log reconnect events (reconnect attempts and failures) 12 | verbose-redis-logging: true 13 | # If true, scripts will be automatically unloaded if a plugin the script depends on is unloaded. This is especially useful to ensure script shutdown tasks that require a depending plugin complete successfully (prior to the plugin being unloaded). 14 | script-unload-on-plugin-disable: true 15 | # Options that pertain to Jython. Changing options in this section requires a server restart. 16 | jython-options: 17 | # If true, the Jython runtime will be initialized during plugin load/server start. If false, the Jython runtime will not be initialized until the first script is loaded. 18 | init-on-startup: true 19 | # A list of system properties that will be passed to Jython. For a complete list, see https://javadoc.io/doc/org.python/jython-standalone/latest/org/python/core/RegistryKey.html 20 | properties: 21 | - 'python.cachedir.skip=true' 22 | # A list of args to pass to Jython when initialized. Equivalent to sys.argv in Python. 23 | args: 24 | - '' 25 | # Default values for script options. If one or more options are not defined in the script_options.yml for the script, then PySpigot will fall back to these values. 26 | script-option-defaults: 27 | # For projects, the main script file for the project. 28 | main: 'main.py' 29 | # Whether the script is enabled 30 | enabled: true 31 | # An integer load priority for the script 32 | load-priority: 1 33 | # A list of plugins the script depends on 34 | plugin-depend: [] 35 | # Whether script log messages should be logged to its respective log file 36 | file-logging-enabled: true 37 | # The minimum level to log to the console and to the script's log file 38 | min-logging-level: 'INFO' 39 | # The default permission level for permissions 40 | permission-default: 'op' 41 | # Advanced debug options for scripts 42 | debug-options: 43 | # If true, will print stack traces for all script-related exceptions to the server console 44 | print-stack-traces: false 45 | # If true, the plugin will show messages in console and on join (to players with the permission pyspigot.admin) when a newer version of PySpigot is available to download on spigotmc.org. 46 | show-update-messages: true 47 | # The logging level for Jython internals. Can be useful to set this to FINE or ALL for debugging purposes. Note: the server's root logger will also need to be configured to accept debug messages for Jython's debug messages to show. 48 | jython-logging-level: 'INFO' 49 | # If true, PySpigot will patch the threading module on script unload (if it's being used in the script) in order to prevent the server from hanging. For more information, see https://github.com/magicmq/pyspigot/issues/18#issue-3012022678 50 | patch-threading: true -------------------------------------------------------------------------------- /core/src/main/resources/script_options.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/magicmq/pyspigot/6a7f41c8b5018764b4c7a92f33722b6d1416b891/core/src/main/resources/script_options.yml -------------------------------------------------------------------------------- /examples/clearinventory.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from org.bukkit.entity import Player 3 | from org.bukkit import ChatColor 4 | 5 | player_only_message = '&cThis command can only be executed by a player!' 6 | no_permission_message = '&cInsufficient permissions!' 7 | clear_inventory_message = '&aYour inventory has been cleared.' 8 | 9 | def clear_inventory_command(sender, label, args): 10 | if isinstance(sender, Player): 11 | if sender.hasPermission('script.clearinventory'): 12 | sender.getInventory().clear() 13 | sender.sendMessage(ChatColor.translateAlternateColorCodes('&', clear_inventory_message)) 14 | else: 15 | sender.sendMessage(ChatColor.translateAlternateColorCodes('&', no_permission_message)) 16 | else: 17 | sender.sendMessage(ChatColor.translateAlternateColorCodes('&', player_only_message)) 18 | return True 19 | 20 | ps.command.registerCommand(clear_inventory_command, 'clear_inventory', 'Clear your inventory', '/clear_inventory', 21 | ['cinventory', 'cleari', 'purgeinventory', 'purge_inventory', 'pi', 'purgei', 'pinventory']) -------------------------------------------------------------------------------- /examples/communitychest.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from org.bukkit.entity import Player 3 | from org.bukkit import ChatColor 4 | from org.bukkit import Material 5 | from org.bukkit import Bukkit 6 | 7 | save_interval = 1800 8 | inventory_name = '&6&lCommunity Chest' 9 | 10 | inventory_config = ps.config.loadConfig('communitychest.yml') 11 | 12 | save_task_id = 0 13 | inventory = None 14 | 15 | def community_chest_command(sender, label, args): 16 | if isinstance(sender, Player): 17 | if sender.hasPermission('script.communitychest'): 18 | sender.openInventory(inventory) 19 | else: 20 | sender.sendMessage(ChatColor.RED + 'Insufficient permissions!') 21 | else: 22 | sender.sendMessage(ChatColor.RED + 'This command can only be executed by a player!') 23 | return True 24 | 25 | ps.command.registerCommand(community_chest_command, 'communitychest', 'Open the community chest', '/communitychest', 26 | ['freechest', 'cc']) 27 | 28 | def load_inventory(): 29 | items = {} 30 | for key in inventory_config.getKeys(False): 31 | slot = int(key) 32 | item = inventory_config.getItemStack(key) 33 | items[slot] = item 34 | 35 | global inventory 36 | inventory = Bukkit.createInventory(None, 54, ChatColor.translateAlternateColorCodes('&', inventory_name)) 37 | 38 | for slot in items: 39 | inventory.setItem(slot, items[slot]) 40 | 41 | def save_inventory(): 42 | for key in inventory_config.getKeys(False): 43 | inventory_config.set(key, None) 44 | 45 | items = inventory.getContents() 46 | for index, item in enumerate(items): 47 | if item is not None and item.getType() is not Material.AIR: 48 | inventory_config.set(str(index), item) 49 | 50 | inventory_config.save() 51 | 52 | load_inventory() 53 | save_task_id = ps.scheduler.scheduleAsyncRepeatingTask(save_inventory, save_interval * 20, save_interval * 20) 54 | 55 | # Called automatically when the script is stopped (on server shutdown, script unload, etc.) 56 | def stop(): 57 | ps.scheduler.stopTask(save_task_id) 58 | save_inventory() -------------------------------------------------------------------------------- /examples/joinmessage.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from org.bukkit.event.player import PlayerJoinEvent 3 | from org.bukkit import Bukkit 4 | from org.bukkit import ChatColor 5 | 6 | message_delay = 20 7 | join_message = 'aYou joined the server!' 8 | join_notify_message = '&c%player% &ahas joined the server!' 9 | 10 | def join_event(event): 11 | player = event.getPlayer() 12 | if player.hasPermission('joinmessage.permission'): 13 | notify_admins(player) 14 | if message_delay > 0: 15 | ps.scheduler.runTaskLater(lambda: player.sendMessage(ChatColor.translateAlternateColorCodes('&', join_message)), message_delay) 16 | else: 17 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', join_message)) 18 | 19 | def notify_admins(joined): 20 | for player in Bukkit.getOnlinePlayers(): 21 | if player.hasPermission('joinmessage.admin'): 22 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', join_notify_message.replace('%player%', joined.getName()))) 23 | 24 | ps.listener.registerListener(join_event, PlayerJoinEvent) -------------------------------------------------------------------------------- /examples/kickcommand.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from org.bukkit import Bukkit 3 | from org.bukkit import ChatColor 4 | 5 | kick_message = '&cYou have been kicked by %player%' 6 | 7 | def kick_command(sender, label, args): 8 | if len(args) > 0: 9 | to_kick = Bukkit.getPlayer(args[0]) 10 | if to_kick is not None: 11 | to_kick.kickPlayer(ChatColor.translateAlternateColorCodes('&', kick_message.replace('%player%', sender.getName()))) 12 | else: 13 | sender.sendMessage('Player ' + args[0] + ' not found') 14 | else: 15 | sender.sendMessage('Usage: /kickplayer ') 16 | return True 17 | 18 | def kick_command_tab(sender, alias, args): 19 | if len(args) > 0: 20 | return [player.getName() for player in Bukkit.getOnlinePlayers()] 21 | 22 | ps.command.registerCommand(kick_command, kick_command_tab, 'kickplayer') -------------------------------------------------------------------------------- /examples/periodicmessage.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from org.bukkit import Bukkit 3 | from org.bukkit import ChatColor 4 | 5 | message = '&aThis is a test message!' 6 | 7 | def task(): 8 | for player in Bukkit.getOnlinePlayers(): 9 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', message)) 10 | 11 | task_id = ps.scheduler.scheduleRepeatingTask(task, 0, 100) -------------------------------------------------------------------------------- /examples/ping.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from org.bukkit.event.player import AsyncPlayerChatEvent 3 | from org.bukkit.entity import Player 4 | from org.bukkit import NamespacedKey 5 | from org.bukkit import ChatColor 6 | from org.bukkit.persistence import PersistentDataType 7 | from org.bukkit import Bukkit 8 | from org.bukkit import Sound 9 | 10 | ping_toggle_key = NamespacedKey('script_ping', 'toggled') 11 | 12 | # Configurable variables 13 | toggle_on_message = '&aChat pings toggled on.' 14 | toggle_off_message = '&cChat pings toggled off.' 15 | notification_sound = Sound.BLOCK_NOTE_BLOCK_BELL 16 | notification_volume = 0.5 17 | notification_pitch = 1.122 18 | 19 | # Listen for chat event 20 | def on_chat(event): 21 | player = event.getPlayer() 22 | message = event.getMessage() 23 | for to_notify in Bukkit.getOnlinePlayers(): 24 | if to_notify.hasPermission('ping.nonotify'): 25 | return 26 | 27 | data_container = to_notify.getPersistentDataContainer() 28 | if data_container.has(ping_toggle_key, PersistentDataType.STRING): 29 | value = data_container.get(ping_toggle_key, PersistentDataType.STRING) 30 | if value == 'off': return 31 | 32 | for word in message.split(' '): 33 | if levenshtein_distance(to_notify.getName(), word) <= 3 and to_notify is not player: 34 | to_notify.playSound(to_notify.getLocation(), notification_sound, notification_volume, 35 | notification_pitch) 36 | 37 | # Command to toggle chat pings 38 | def toggle_ping_command(sender, label, args): 39 | if sender.hasPermission('ping.toggle'): 40 | if isinstance(sender, Player): 41 | toggle_ping(sender) 42 | else: 43 | sender.sendMessage(ChatColor.RED + 'This command can only be executed by a player!') 44 | else: 45 | sender.sendMessage(ChatColor.RED + 'Insufficient permissions!') 46 | return True 47 | 48 | def toggle_ping(player): 49 | data_container = player.getPersistentDataContainer() 50 | if data_container.has(ping_toggle_key, PersistentDataType.STRING): 51 | value = data_container.get(ping_toggle_key, PersistentDataType.STRING) 52 | if value == 'on': 53 | data_container.set(ping_toggle_key, PersistentDataType.STRING, 'off') 54 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', toggle_off_message)) 55 | elif value == 'off': 56 | data_container.set(ping_toggle_key, PersistentDataType.STRING, 'on') 57 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', toggle_on_message)) 58 | else: 59 | data_container.set(ping_toggle_key, PersistentDataType.STRING, 'off') 60 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', toggle_off_message)) 61 | 62 | # See https://en.wikipedia.org/wiki/Levenshtein_distance for an in-depth explanation of Levenshtein distance 63 | def levenshtein_distance(str1, str2): 64 | matrix = [[0] * (len(str2) + 1) for _ in range(len(str1) + 1)] 65 | 66 | for i in range(len(str1) + 1): 67 | matrix[i][0] = i 68 | for j in range(len(str2) + 1): 69 | matrix[0][j] = j 70 | 71 | for i in range(1, len(str1) + 1): 72 | for j in range(1, len(str2) + 1): 73 | cost = 0 if str1[i - 1] == str2[j - 1] else 1 74 | matrix[i][j] = min( 75 | matrix[i - 1][j] + 1, 76 | matrix[i][j - 1] + 1, 77 | matrix[i - 1][j - 1] + cost 78 | ) 79 | 80 | return matrix[len(str1)][len(str2)] 81 | 82 | ps.listener.registerListener(on_chat, AsyncPlayerChatEvent) 83 | ps.command.registerCommand(toggle_ping_command, 'toggleping', 'Toggle chat pings on/off.', '/toggleping', 84 | ['tp', 'togglenotify', 'tn', 'togglechatping', 'togglechatnotify']) -------------------------------------------------------------------------------- /examples/placeholder.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | 3 | #The placeholder in this example scrpipt will be %script:placeholder_%. For example, %script:placeholder_player_banned% to get if the player is banned 4 | def replacer(offline_player, placeholder): 5 | if offline_player is not None: 6 | if placeholder == 'player_banned': 7 | return 'Banned' if offline_player.isBanned() else 'Not banned' 8 | elif placeholder == 'is_online': 9 | return 'Online' if offline_player.isOnline() else 'Not online' 10 | elif placeholder == 'has_player_before': 11 | return 'Played before' if offline_player.hasPlayedBefore() else 'Not played before' 12 | elif placeholder == 'uuid': 13 | return offline_player.getUniqueId().toString() 14 | 15 | placeholder = ps.placeholder.registerPlaceholder(replacer) -------------------------------------------------------------------------------- /examples/protocollib.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from com.comphenix.protocol import PacketType 3 | from org.bukkit import Bukkit 4 | from org.bukkit import ChatColor 5 | 6 | chat_notify_message = '&c%player% &atried to chat, but it was cancelled. Message: &r%message%' 7 | 8 | def chat_packet_received(event): 9 | player = event.getPlayer() 10 | packet = event.getPacket() 11 | if not player.hasPermission('script.chat'): 12 | message = packet.getStrings().read(0) 13 | event.setCancelled(True) 14 | notify_admins(player, message) 15 | 16 | def notify_admins(chatted, message): 17 | for player in Bukkit.getOnlinePlayers(): 18 | if player.hasPermission('script.admin'): 19 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', chat_notify_message.replace('%player%', chatted.getName()).replace('%message%', message))) 20 | 21 | ps.protocol.registerPacketListener(chat_packet_received, PacketType.Play.Client.CHAT) -------------------------------------------------------------------------------- /examples/swearfilter.py: -------------------------------------------------------------------------------- 1 | import pyspigot as ps 2 | from org.bukkit.event.player import AsyncPlayerChatEvent 3 | from org.bukkit.event import EventPriority 4 | from org.bukkit import Bukkit 5 | from org.bukkit import ChatColor 6 | import urllib2 7 | import re 8 | 9 | admin_notify_message = '&cPlayer %player% sent a message that was censored! Message: &r%message%' 10 | swear_word_pattern = '' 11 | 12 | # Normally, any I/O operations (such as fetching web pages) should be done asynchronously, but it's okay to do it sync in this case, 13 | # since script loading is done when the server first starts. Doing this at any other time would cause noticeable server lag. 14 | def init_swear_words(): 15 | swear_words = [] 16 | swear_words_url = 'http://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/en' 17 | data = urllib2.urlopen(swear_words_url) 18 | for line in data: 19 | swear_words.append(line.replace('\n', '')) 20 | data.close() 21 | 22 | global swear_word_pattern 23 | swear_word_pattern = '|'.join(re.escape(word) for word in swear_words) 24 | 25 | def on_chat(event): 26 | player = event.getPlayer() 27 | if player.hasPermission('swearfilter.bypass'): 28 | return 29 | 30 | chat_message = event.getMessage() 31 | censored_message = censor_string(chat_message) 32 | 33 | if censored_message != chat_message: 34 | event.setMessage(censored_message) 35 | notify_admins(player, chat_message) 36 | 37 | def censor_string(input): 38 | def replace(match): 39 | return '*' * len(match.group()) 40 | 41 | censored_string = re.sub(swear_word_pattern, replace, input, flags=re.IGNORECASE) 42 | return censored_string 43 | 44 | def notify_admins(offender, message): 45 | for player in Bukkit.getOnlinePlayers(): 46 | if player.hasPermission('swearfilter.admin'): 47 | player.sendMessage(ChatColor.translateAlternateColorCodes('&', admin_notify_message 48 | .replace('%player%', offender.getName()) 49 | .replace('%message%', message))) 50 | 51 | init_swear_words() 52 | 53 | ps.listener.registerListener(on_chat, AsyncPlayerChatEvent, EventPriority.HIGHEST) -------------------------------------------------------------------------------- /examples/teleport/teleport.yml: -------------------------------------------------------------------------------- 1 | inventory: 2 | size: 9 3 | title: '&e&lClick to Teleport' 4 | items: 5 | '0': 6 | location: 7 | world: 'world' 8 | x: 0.5 9 | y: 64 10 | z: 0.5 11 | yaw: 0 12 | pitch: 0 13 | location-name: 'Spawn' 14 | item: 15 | material: 'ENDER_PEARL' 16 | amount: 1 17 | name: '&c&lSpawn' 18 | lore: 19 | - '&6Click to teleport to spawn.' 20 | glowing: true 21 | '1': 22 | location: 23 | world: 'world' 24 | x: 128 25 | y: 64 26 | z: 128 27 | yaw: 0 28 | pitch: 0 29 | location-name: 'Warp Point 1' 30 | item: 31 | material: 'ENDER_PEARL' 32 | amount: 1 33 | name: '&c&lWarp Point 1' 34 | lore: 35 | - '&6Click to teleport to warp point 1.' 36 | '2': 37 | location: 38 | world: 'world' 39 | x: 256 40 | y: 64 41 | z: 256 42 | yaw: 0 43 | pitch: 0 44 | location-name: 'Warp Point 2' 45 | item: 46 | material: 'ENDER_PEARL' 47 | amount: 1 48 | name: '&c&lWarp Point 2' 49 | lore: 50 | - '&6Click to teleport to warp point 2.' 51 | '3': 52 | location: 53 | world: 'world' 54 | x: 512 55 | y: 64 56 | z: 512 57 | yaw: 0 58 | pitch: 0 59 | location-name: 'Warp Point 3' 60 | item: 61 | material: 'ENDER_PEARL' 62 | amount: 1 63 | name: '&c&lWarp Point 3' 64 | lore: 65 | - '&6Click to teleport to warp point 3.' 66 | '4': 67 | location: 68 | world: 'world' 69 | x: 1024 70 | y: 64 71 | z: 1024 72 | yaw: 0 73 | pitch: 0 74 | location-name: 'Warp Point 4' 75 | item: 76 | material: 'ENDER_PEARL' 77 | amount: 1 78 | name: '&c&lWarp Point 4' 79 | lore: 80 | - '&6Click to teleport to warp point 4.' 81 | misc: 82 | teleport-message: '&6You have been teleported to &e%name%&6.' --------------------------------------------------------------------------------