Compare commits

...
Author SHA1 Message Date
NotAlexNoyle bbe60a1bf4 Exclude CODEOWNERS from language statistics 2026-10-27 08:25:12 -04:00
NotAlexNoyle 06aca10e5d Add CODEOWNERS (one staff approval) 2026-10-27 08:25:12 -04:00
NotAlexNoyle 625bb0c6a9 Use live TrueOG Network config file 2026-05-27 11:22:19 -04:00
NotAlexNoyle bb8fe0f528 Fix user list on luckperms web by normalizing version number format to upstream 2026-04-24 16:01:18 -04:00
NotAlexNoyle 863d10273c Disable Vault integration 2026-04-24 15:24:29 -04:00
NotAlexNoyle a57ba166ff Merge branch 'LuckPerms:master' into master 2025-09-22 07:10:41 -04:00
蒟蒻 0b91732cd3 Prevent handleDisconnect after stopped server on Fabric (#4160) 2025-09-21 11:04:58 +01:00
An5w1r a087f14aa2 Fix classExists checks to handle NoClassDefFoundError (#4136) 2025-09-21 11:03:07 +01:00
Luck d666a2a798 Fix command rate limit for command blocks (#4094) 2025-09-21 10:58:24 +01:00
NotAlexNoyle c27cfcb10b Update shadow 2025-09-03 14:22:27 -04:00
NotAlexNoyle 52464c6bec Simplify gradle build, support java 17 2025-09-03 13:39:23 -04:00
Luck 65c42b9b09 Micro-optimize temporary node audit (#4155) 2025-08-23 15:39:49 +01:00
Luck 73d710a89e Update to Minecraft 1.21.8 2025-07-21 22:08:21 +01:00
Luck ed863b275d Update to Minecraft 1.21.7 2025-06-30 19:41:56 +01:00
Luck c20776d6aa Remove Forge capability attachment 2025-06-19 19:53:09 +01:00
Luck 3aca721f96 Rollback bytebuddy dependency version upgrade 2025-06-19 19:35:04 +01:00
Luck 8d9c5bf401 Add workaround for Forge eventbus limitation 2025-06-19 19:09:40 +01:00
Luck 0af1038d81 Update to Minecraft 1.21.6 2025-06-19 18:48:44 +01:00
Luck 51fe574a51 Move config load earlier in startup process (#4083) 2025-06-14 09:31:56 +01:00
Max Barrass bac0edd90e Fix missing closing brackets in .conf files (#4109) 2025-06-14 09:24:52 +01:00
Luck 8ab9910ef8 Downgrade jedis and h2 2025-06-01 19:32:26 +01:00
Luck 9538cb9dad Upgrade dependencies and require Java 11 2025-06-01 16:41:39 +01:00
Luck 0486bc885b Release API v5.5 2025-05-25 08:59:47 +01:00
Luck 27e7551e2b Fix adventure package relocations 2025-05-23 08:10:55 +01:00
Luck b77d5759d3 Upgrade adventure dependencies for 1.21.5 support 2025-05-22 21:15:24 +01:00
Luck 96abc0d4fe Update setup-gradle GitHub action 2025-05-10 15:04:42 +01:00
lucko 1a24a6a155 Add REST storage backend (#3939) 2025-05-10 15:01:08 +01:00
𝑩𝒊𝒒𝒖𝒂𝒕𝒆𝒓𝒏𝒊𝒐𝒏𝒔 95ea491787 Support plugins that 'provide' Vault (#4075) 2025-05-10 14:47:32 +01:00
Remigio 626d08431e Add missing IllegalArgumentExceptions to Javadocs (#4015) 2025-05-10 14:33:38 +01:00
lucko 94e7e11183 Add configurable read-only mode for commands (#4031) 2025-05-10 14:32:47 +01:00
Luck f64bf04ba4 Update mod loader dependencies 2025-05-08 19:45:14 +01:00
Luck 7744913df2 Update to Minecraft 1.21.5 2025-03-27 19:46:43 +00:00
lucko 6b7283ac83 Improve context manager caching (#4050) 2025-03-20 19:20:16 +00:00
Luck 9bc8a61e2d Add file secret config adapter (#3913) 2025-02-22 15:57:44 +00:00
Luck 5e24a7068c Mark plugin msg channel as optional on Forge (#3954) 2025-02-22 15:28:18 +00:00
Luck f351369a51 Update CI action versions 2025-02-13 09:59:59 +00:00
Luck 1bf0deb5a5 Add unit tests for messaging service messages 2025-01-20 22:46:52 +00:00
Luck 5b82837b80 Update Sponge module to API 12 2025-01-05 14:38:15 +00:00
Luck 0fef481d48 Add more unit tests 2024-12-22 09:51:11 +00:00
Luck f2040ab815 Update to Minecraft 1.21.4 2024-12-07 16:21:26 +00:00
Luck b18717edef Remove NeoForge capability attachment 2024-12-04 20:39:55 +00:00
Luck 29c93fac4f Add note about shading LuckPerms API into plugin jars 2024-12-04 11:27:14 +00:00
Luck 2e67011f45 Refactor some unit tests 2024-12-04 11:25:06 +00:00
Luck a92e04f605 Recreate individual sql tables if they are missing (#3995) 2024-11-09 12:09:23 +00:00
Luck 8d65693d9a Update to Minecraft 1.21.3 2024-10-26 10:07:03 +01:00
Luck 8973abd7f1 Support older signature alg if running on Java 8 2024-10-23 23:19:05 +01:00
Luck 9420efd3ac Switch to ECDSA keys for web editor socket 2024-10-23 22:52:20 +01:00
Alex c773126a0f Simplify ExpiringSet implementation (#3969) 2024-10-21 21:09:40 +01:00
Luck 9d80de5d5c Fix MongoStorage nodeFromDoc integer cast (#3846) 2024-08-24 09:55:32 +01:00
3TUSK 9bf30fc839 Fix login configuration phase on NeoForge (#3961) 2024-08-22 22:27:29 +01:00
Luck 136a4859f3 Update to Minecraft 1.21.1 2024-08-12 19:45:38 +01:00
Jason Penilla 642f1d3019 Port to NeoForge (#3950) 2024-08-12 19:11:27 +01:00
Luck b21753f5e9 Use builtin Fabric world change event 2024-07-07 21:31:34 +01:00
Luck 9cc129466b Init VerboseHandler earlier (#3932) 2024-07-07 21:22:40 +01:00
Drex 55440cac01 Fix memory leak in Fabric MixinUser (#3931) 2024-07-07 21:15:41 +01:00
lucko 9e7a3d26e4 Improve efficiency of action log queries (#3917) 2024-06-16 21:11:23 +01:00
Luck d748817351 Update Forge to MC 1.21 2024-06-16 10:45:47 +01:00
Wolfdv1andWolfdv1 f984ebbb46 Update Fabric to MC 1.21 (#3918)
Co-authored-by: Wolfdv1 <Wolfdv1@github.com>
2024-06-16 10:45:09 +01:00
Luck 9805f8b2c4 Bump adventure-platform to 4.3.3 2024-06-02 22:17:12 +01:00
Luckandpowercas_gamer c3802c2a5a Fix Forge plugin msg messenger (#3894)
Co-authored-by: powercas_gamer <cas@mizule.dev>
2024-05-26 14:19:36 +01:00
Ossi Erkkilä b5400f885b Add support for Sponge API 11 (#3896) 2024-05-26 13:58:39 +01:00
bloodmc 5c1ea5633e Update Forge to MC 1.20.6 (#3883) 2024-05-08 23:09:02 +01:00
bloodmc 484b04c17b Add support for Sponge API 9+ (#3885) 2024-05-06 18:20:47 +01:00
Jason Penilla d74a4dcb7e Set mappings namespace to skip remapping on Paper 1.20.5+ (#3881) 2024-04-30 10:31:16 +01:00
Luck 2361ea43ee Update Fabric to MC 1.20.6 2024-04-29 20:05:54 +01:00
Luck f86585c3cc Update Dockerfile to use Java 21 2024-04-25 22:57:03 +01:00
Drex 7cb60f59fe Update Fabric to MC 1.20.5 (#3824) 2024-04-23 21:57:20 +01:00
Luck a43f4a11b0 Update Fabric dependencies 2024-04-17 21:25:15 +01:00
powercas_gamer 1dffd462a5 Fix mongodb secrets being logged (#3850, #3851) 2024-03-15 20:23:32 +00:00
lucko 8c6586f008 Add custom payload message to API (#3840) 2024-03-06 21:12:33 +00:00
Luck 79273a8bcc Add PostNetworkSyncEvent 2024-02-18 22:40:58 +00:00
Gilberto Garcia 7d89c97907 Don't read nodes twice in SqlStorage (#3836) 2024-02-15 20:03:02 +00:00
Emilia Kond 96008f6338 Add modifyTrack method to TrackManager (#3823) 2024-01-23 17:44:20 +00:00
powercas_gamer e6599a2662 Fix MongoDB uri showing in logs if set using system property (#3808) 2024-01-06 11:31:59 +00:00
AoElite 0703b9856f Small performance optimization in BukkitPlatformListener (#3806) 2024-01-06 11:24:44 +00:00
Luck 5f95b386cb Update adventure-platform for MC 1.20.4 support 2024-01-06 11:23:43 +00:00
Luck 754283c053 Fix Fabric command manager mixin (#3791) 2023-12-12 18:57:44 +00:00
Luck 5f87de3569 Upgrade Fabric/Forge to MC 1.20.4 2023-12-11 19:29:39 +00:00
Luck 43f4d490bb Upgrade Fabric to MC 1.20.3 2023-12-06 20:55:25 +00:00
Luck f14798b368 Upgrade gradle version 2023-12-06 20:35:01 +00:00
bloodmc 00ed76cfc0 Update Forge to 1.20.2 (#3787) 2023-12-06 08:27:08 +00:00
Misode f78e2d6c52 Update Minecraft wiki links in config file comments (#3758) 2023-11-06 22:00:47 +00:00
huanmeng_qwq f999f9f234 Fix duplicate UUID delete with Mongo storage (#3777) 2023-11-06 21:57:08 +00:00
Luck 84fce987d5 Change healthcheck details return type 2023-10-22 21:08:45 +01:00
Luck 3707d2f03c Add healthcheck functionality to API 2023-10-22 20:17:37 +01:00
Luck 822c796e1c Update adventure-platform for MC 1.20.2 support 2023-10-02 19:30:17 +01:00
Luck 331a328771 Upgrade Fabric to MC 1.20.2 2023-10-02 19:28:19 +01:00
362 changed files with 13570 additions and 5529 deletions

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
.gitea/CODEOWNERS linguist-detectable=false
+3
View File
@@ -0,0 +1,3 @@
# Pull requests request review from the eligible staff team(s).
# One approval from either an Admin or Developer satisfies branch protection.
* @trueog/Admins @trueog/Developers
-2
View File
@@ -1,2 +0,0 @@
github: lucko
patreon: luckdev
-63
View File
@@ -1,63 +0,0 @@
name: 🐛 ​ Bug report
description: Report an issue or problem with the way LuckPerms is functioning.
labels: ["type: issue"]
body:
- type: markdown
attributes:
value: "Before going any further, please check to see if your problem is fixed by updating to a newer version. The latest development builds can be obtained from https://luckperms.net/download"
- type: textarea
id: description
attributes:
label: Description
description: Please provide a short description of the issue in the space below.
placeholder: Description goes here!
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: Reproduction Steps
description: Please provide a clear list of steps we should follow in order to reproduce your issue in the space below.
placeholder: List reproduction steps here, please!
validations:
required: true
- type: textarea
id: expected-behaviour
attributes:
label: Expected Behaviour
description: Please provide a description of what you expected to happen.
placeholder: Describe what you expected to happen here.
validations:
required: true
- type: input
id: server-info
attributes:
label: Server Details
description: Please provide a description of the server details, including type and specific version number.
placeholder: 'git-Paper-124 (MC: 1.17.1)'
validations:
required: true
- type: input
id: luckperms-version
attributes:
label: LuckPerms Version
description: Please provide the specific, precise version number of LuckPerms you are using to reproduce this bug.
placeholder: v5.3.70
validations:
required: true
- type: textarea
id: logs
attributes:
label: Logs and Configs
description: If you think it would be helpful, please also include a pastebin of any relevant config files or log output. You should use https://gist.github.com/ to upload, then include the link below.
placeholder: https://gist.github.com/HelpfulBugReporter/ThisIsAVeryUsefulLogOutput
validations:
required: false
- type: textarea
id: extra-info
attributes:
label: Extra Details
description: Please include any other relevant details in the space below.
placeholder: I was able to reproduce this only at the end of a rainbow.
validations:
required: false
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: 💬 ​ Ask a question
url: https://discord.gg/luckperms
about: We use Discord to provide support for the project. If you have a question, need help, or aren't sure if you've found a bug or not, please come and talk to us there!
-31
View File
@@ -1,31 +0,0 @@
name: 💡 ​ Suggestion
description: Suggest an idea for an improvement or change to LuckPerms.
labels: ["type: suggestion"]
body:
- type: markdown
attributes:
value: "Before going any further, please check to see if your suggestion has already been added by checking the [wiki](https://luckperms.net/wiki/Home). The latest development builds can be obtained from https://luckperms.net/download"
- type: textarea
id: description
attributes:
label: Description
description: Please provide a short description of your suggestion in the space below.
placeholder: Hello, perhaps you should...
validations:
required: true
- type: textarea
id: proposed-behaviour
attributes:
label: Proposed Behaviour
description: Please provide a short explanation of how the feature should work / be changed, and how this will affect the project.
placeholder: It would...
validations:
required: true
- type: textarea
id: extra-info
attributes:
label: Extra Details
description: Please include any other relevant details in the space below.
placeholder: I think this should only function at the end of a rainbow.
validations:
required: false
-127
View File
@@ -1,127 +0,0 @@
name: Build Gradle and Publish Docker image
on:
push:
branches:
- 'master'
tags:
- 'v*'
pull_request:
branches:
- 'master'
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-gradle:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Gradle
uses: gradle/gradle-build-action@v2
- name: Run build and tests with Gradle wrapper
run: ./gradlew test build -PdockerTests
- name: Publish test report
uses: mikepenz/action-junit-report@v3
if: success() || failure()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
annotate_notice: true
detailed_summary: true
- name: Upload all artifacts
uses: actions/upload-artifact@v3
with:
name: jars
path: |
bukkit/loader/build/libs/LuckPerms-Bukkit-*.jar
bukkit-legacy/loader/build/libs/LuckPerms-Bukkit-Legacy-*.jar
bungee/loader/build/libs/LuckPerms-Bungee-*.jar
sponge/loader/build/libs/LuckPerms-Sponge-*.jar
nukkit/loader/build/libs/LuckPerms-Nukkit-*.jar
velocity/build/libs/LuckPerms-Velocity-*.jar
fabric/build/libs/LuckPerms-Fabric-*.jar
forge/loader/build/libs/LuckPerms-Forge-*.jar
standalone/loader/build/libs/LuckPerms-Standalone-*.jar
- name: Upload standalone artifact
uses: actions/upload-artifact@v3
with:
name: standalone-binary
path: standalone/loader/build/libs/LuckPerms-Standalone-*.jar
build-docker:
needs: build-gradle
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Retrieve saved standalone jar artifact
uses: actions/download-artifact@v3
with:
name: standalone-binary
path: standalone/docker/
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Log in to the Container registry
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Determine the LuckPerms version
run: |
echo "luckperms_version=$(git describe --tags | awk -F "-" '{print $1 "." $2}')" >> "$GITHUB_ENV"
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
latest=${{ github.ref == 'refs/heads/master' }}
tags: |
type=schedule
type=ref,event=branch
type=ref,event=tag
type=ref,event=pr
type=raw,enable=${{ github.ref == 'refs/heads/master' }},value=${{ env.luckperms_version }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: standalone/docker/
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
-26
View File
@@ -1,26 +0,0 @@
### :speech_balloon: Looking for support?
Details about support for the project can be found [here, on the Wiki Homepage](https://luckperms.net/wiki/Home#support).
### :bug: Reporting bugs?
Before reporting a bug or issue, please make sure that the issue is actually being caused by or related to LuckPerms. We get a lot of reports which are caused by other software - please double check!
If you're unsure, feel free to ask using the above resources BEFORE making a report.
Bugs or issues should be reported using the [GitHub Issues tab](https://github.com/LuckPerms/LuckPerms/issues).
### :pencil: Want to contribute code?
#### Pull Requests
If you make any changes or improvements to the plugin which you think would be beneficial to others, please consider making a pull request to merge your changes back into the upstream project. (especially if your changes are bug fixes!)
LuckPerms loosely follows the [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). Generally, try to copy the style of code found in the class you're editing.
If you're considering submitting a substantial pull request, please open an issue so we can discuss the change before starting work on the contribution. Most pull requests are happily accepted, but larger changes may have an impact on the maintainability of the project, and require more consideration.
#### Project Layout
The project is split up into a few separate modules.
* **API** - The public, semantically versioned API used by other plugins wishing to integrate with and retrieve data from LuckPerms. This module (for the most part) does not contain any implementation itself, and is provided by the plugin.
* **Common** - The common module contains most of the code which implements the respective LuckPerms plugins. This abstract module reduces duplicated code throughout the project.
* **Bukkit, BungeeCord, Fabric, Forge, Nukkit, Sponge & Velocity** - Each use the common module to implement plugins on the respective server platforms.
+1 -1
View File
@@ -24,7 +24,7 @@ For more information, see the wiki article on [Why LuckPerms?](https://luckperms
LuckPerms uses Gradle to handle dependencies & building.
#### Requirements
* Java 17 JDK or newer
* Java 21 JDK or newer
* Git
#### Compiling from source
-119
View File
@@ -1,119 +0,0 @@
group = 'net.luckperms'
project.version = '5.4'
jar {
manifest {
attributes('Automatic-Module-Name': 'net.luckperms.api')
}
}
dependencies {
compileOnly 'org.checkerframework:checker-qual:3.21.2'
compileOnly 'org.jetbrains:annotations:23.1.0'
}
// Only used occasionally for deployment - not needed for normal builds.
/*
if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePassword')) {
apply plugin: 'signing'
javadoc {
title = 'LuckPerms API (v' + project.ext.apiVersion + ')'
options.overview = 'javadoc/overview.html'
options.encoding = 'UTF-8'
options.charSet = 'UTF-8'
options.links(
'https://checkerframework.org/api/',
'https://javadoc.io/static/org.jetbrains/annotations/23.0.0/'
)
options.addStringOption('Xdoclint:none', '-quiet')
options.addStringOption('-since', '5.0,5.1,5.2,5.3,5.4')
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
options.links.add('https://docs.oracle.com/en/java/javase/11/docs/api/')
} else {
options.links.add('https://docs.oracle.com/javase/8/docs/api/')
}
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier 'javadoc'
from javadoc.destinationDir
}
task sourcesJar(type: Jar) {
classifier 'sources'
from sourceSets.main.allSource
}
artifacts {
archives javadocJar
archives sourcesJar
}
publishing {
repositories {
maven {
def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/'
def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots/'
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
credentials {
username sonatypeUsername
password sonatypePassword
}
}
}
publications {
mavenJava(MavenPublication) {
artifactId = 'api'
from components.java
artifact sourcesJar
artifact javadocJar
pom {
name = 'LuckPerms API'
description = 'A permissions plugin for Minecraft servers.'
url = 'https://luckperms.net'
licenses {
license {
name = 'MIT'
url = 'https://opensource.org/licenses/MIT'
}
}
developers {
developer {
id = 'lucko'
name = 'Luck'
url = 'https://lucko.me'
email = 'git@lucko.me'
}
}
scm {
connection = 'scm:git:https://github.com/LuckPerms/LuckPerms.git'
developerConnection = 'scm:git:git@github.com:LuckPerms/LuckPerms.git'
url = 'https://github.com/LuckPerms/LuckPerms'
}
issueManagement {
system = 'GitHub'
url = 'https://github.com/LuckPerms/LuckPerms/issues'
}
}
}
}
}
signing {
useGpgCmd()
sign configurations.archives
sign publishing.publications.mavenJava
required = true
}
}
*/
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<p>
LuckPerms is a permissions plugin for Minecraft servers. It allows server admins to control what
LuckPerms is a permissions manager for Minecraft servers. It allows server admins to control what
features players can use by creating groups and assigning permissions.
</p>
<h4>Useful Links</h4>
@@ -26,6 +26,7 @@
package net.luckperms.api;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import net.luckperms.api.context.ContextCalculator;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.event.EventBus;
@@ -40,6 +41,7 @@ import net.luckperms.api.model.user.User;
import net.luckperms.api.model.user.UserManager;
import net.luckperms.api.node.NodeBuilderRegistry;
import net.luckperms.api.node.matcher.NodeMatcherFactory;
import net.luckperms.api.platform.Health;
import net.luckperms.api.platform.Platform;
import net.luckperms.api.platform.PlayerAdapter;
import net.luckperms.api.platform.PluginMetadata;
@@ -219,6 +221,17 @@ public interface LuckPerms {
*/
@NonNull CompletableFuture<Void> runUpdateTask();
/**
* Executes a health check.
*
* <p>This task checks if the LuckPerms implementation is running and
* whether it has a connection to the database (if applicable).</p>
*
* @return the health status
* @since 5.5
*/
@NonNull Health runHealthCheck();
/**
* Registers a {@link MessengerProvider} for use by the platform.
*
@@ -255,4 +268,14 @@ public interface LuckPerms {
@Internal
@NonNull NodeMatcherFactory getNodeMatcherFactory();
/**
* Gets the {@link ActionFilterFactory}.
*
* @return the action filter factory
* @since 5.5
*/
@Internal
@NonNull
ActionFilterFactory getActionFilterFactory();
}
@@ -79,7 +79,8 @@ public final class LuckPermsProvider {
" a) the LuckPerms plugin is not installed or it failed to enable\n" +
" b) the plugin in the stacktrace does not declare a dependency on LuckPerms\n" +
" c) the plugin in the stacktrace is retrieving the API before the plugin 'enable' phase\n" +
" (call the #get method in onEnable, not the constructor!)\n";
" (call the #get method in onEnable, not the constructor!)\n" +
" d) the plugin in the stacktrace is incorrectly 'shading' the LuckPerms API into its jar\n";
NotLoadedException() {
super(MESSAGE);
@@ -28,6 +28,7 @@ package net.luckperms.api.actionlog;
import net.luckperms.api.LuckPermsProvider;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import java.time.Instant;
import java.util.Optional;
@@ -35,7 +36,10 @@ import java.util.UUID;
/**
* Represents a logged action.
*
* <p>API users should not implement this interface directly.</p>
*/
@NonExtendable
public interface Action extends Comparable<Action> {
/**
@@ -81,6 +85,7 @@ public interface Action extends Comparable<Action> {
/**
* Represents the source of an action.
*/
@NonExtendable
interface Source {
/**
@@ -102,6 +107,7 @@ public interface Action extends Comparable<Action> {
/**
* Represents the target of an action.
*/
@NonExtendable
interface Target {
/**
@@ -126,7 +132,7 @@ public interface Action extends Comparable<Action> {
@NonNull Type getType();
/**
* Represents the type of a {@link Target}.
* Represents the type of {@link Target}.
*/
enum Type {
USER, GROUP, TRACK
@@ -25,6 +25,7 @@
package net.luckperms.api.actionlog;
import net.luckperms.api.actionlog.filter.ActionFilter;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.Unmodifiable;
@@ -40,7 +41,11 @@ import java.util.UUID;
* You can add to the log using the {@link ActionLogger}, and then request an updated copy.</p>
*
* <p>All methods are thread safe, and return immutable and thread safe collections.</p>
*
* @deprecated Use {@link ActionLogger#queryActions(ActionFilter)} or
* {@link ActionLogger#queryActions(ActionFilter, int, int)} instead.
*/
@Deprecated
public interface ActionLog {
/**
@@ -25,9 +25,11 @@
package net.luckperms.api.actionlog;
import net.luckperms.api.messaging.MessagingService;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.util.Page;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
@@ -46,43 +48,78 @@ public interface ActionLogger {
* Gets a {@link ActionLog} instance from the plugin storage.
*
* @return a log instance
* @deprecated Use {@link #queryActions(ActionFilter)} or {@link #queryActions(ActionFilter, int, int)} instead. These methods
* are more efficient (they don't load the full action log into memory) and allow for pagination.
*/
@Deprecated
@NonNull CompletableFuture<ActionLog> getLog();
/**
* Submits a log entry to the plugin to be handled.
* Gets all actions from the action log matching the given {@code filter}.
*
* <p>This method submits the log to the storage provider and broadcasts
* it.</p>
* <p>If the filter is {@code null}, all actions will be returned.</p>
*
* <p>It is therefore roughly equivalent to calling
* {@link #submitToStorage(Action)} and {@link #broadcastAction(Action)},
* however, using this method is preferred to making the calls individually.</p>
* <p>Unlike {@link #queryActions(ActionFilter, int, int)}, this method does not implement any pagination and will return
* all entries at once.</p>
*
* <p>If you want to submit a log entry but don't know which method to pick,
* @param filter the filter, optional
* @return the actions
* @since 5.5
*/
@NonNull CompletableFuture<List<Action>> queryActions(@NonNull ActionFilter filter);
/**
* Gets a page of actions from the action log matching the given {@code filter}.
*
* <p>If the filter is {@code null}, all actions will be returned.</p>
*
* @param filter the filter, optional
* @param pageSize the size of the page
* @param pageNumber the page number
* @return the page of actions
* @since 5.5
*/
@NonNull CompletableFuture<Page<Action>> queryActions(@NonNull ActionFilter filter, int pageSize, int pageNumber);
/**
* Submits a logged action to LuckPerms.
*
* <p>This method submits the action to the storage provider to be persisted in the action log.
* It also broadcasts it to administrator players on the current instance and to admins on other
* connected servers if a messaging service is configured.</p>
*
* <p>It is roughly equivalent to calling
* {@link #submitToStorage(Action)} followed by {@link #broadcastAction(Action)},
* however using this method is preferred to making the calls individually.</p>
*
* <p>If you want to submit an action log entry but don't know which method to pick,
* use this one.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is submitted
*/
@NonNull CompletableFuture<Void> submit(@NonNull Action entry);
/**
* Submits a log entry to the plugins storage handler.
* Submits a logged action to LuckPerms and persists it in the storage backend.
*
* <p>This method does not broadcast the action or send it through the messaging service.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is submitted
*/
@NonNull CompletableFuture<Void> submitToStorage(@NonNull Action entry);
/**
* Submits a log entry to the plugins log broadcasting handler.
* Submits a logged action to LuckPerms and broadcasts it to administrators.
*
* <p>If enabled, this method will also dispatch the log entry via the
* plugins {@link MessagingService}.</p>
* <p>The broadcast is made to administrator players on the current instance
* and to admins on other connected servers if a messaging service is configured.</p>
*
* <p>This method does not save the action to the plugin storage backend.</p>
*
* @param entry the entry to submit
* @return a future which will complete when the action is done
* @return a future which will complete when the action is broadcasted
*/
@NonNull CompletableFuture<Void> broadcastAction(@NonNull Action entry);
@@ -0,0 +1,114 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.actionlog.filter;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.actionlog.Action;
import org.jetbrains.annotations.ApiStatus.NonExtendable;
import java.util.UUID;
import java.util.function.Predicate;
/**
* A predicate filter which matches certain {@link Action}s.
*
* <p>API users should not implement this interface directly.</p>
*
* @since 5.5
*/
@NonExtendable
public interface ActionFilter extends Predicate<Action> {
/**
* Gets an {@link ActionFilter} which matches any action.
*
* @return the matcher
*/
static ActionFilter any() {
return LuckPermsProvider.get().getActionFilterFactory().any();
}
/**
* Gets an {@link ActionFilter} which matches actions with a specific source user.
*
* @param uniqueId the source user unique id
* @return the matcher
*/
static ActionFilter source(UUID uniqueId) {
return LuckPermsProvider.get().getActionFilterFactory().source(uniqueId);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific user.
*
* @param uniqueId the target user unique id
* @return the matcher
*/
static ActionFilter user(UUID uniqueId) {
return LuckPermsProvider.get().getActionFilterFactory().user(uniqueId);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific group.
*
* @param name the target group name
* @return the matcher
*/
static ActionFilter group(String name) {
return LuckPermsProvider.get().getActionFilterFactory().group(name);
}
/**
* Gets an {@link ActionFilter} which matches actions which target a specific track.
*
* @param name the target track name
* @return the matcher
*/
static ActionFilter track(String name) {
return LuckPermsProvider.get().getActionFilterFactory().track(name);
}
/**
* Gets an {@link ActionFilter} which matches actions which contain a specific search query in the source name,
* target name or description.
*
* @param query the search query
* @return the matcher
*/
static ActionFilter search(String query) {
return LuckPermsProvider.get().getActionFilterFactory().search(query);
}
/**
* Tests to see if the given {@link Action} matches the filter.
*
* @param action the action to test
* @return true if the action matched
*/
@Override
boolean test(Action action);
}
@@ -0,0 +1,88 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.actionlog.filter;
import org.jetbrains.annotations.ApiStatus.Internal;
import java.util.UUID;
/**
* A factory which creates {@link ActionFilter}s.
*
* @since 5.5
*/
@Internal
public interface ActionFilterFactory {
/**
* Gets an {@link ActionFilter} which matches any action.
*
* @return the matcher
*/
ActionFilter any();
/**
* Gets an {@link ActionFilter} which matches actions with a specific source user.
*
* @param uniqueId the source user unique id
* @return the matcher
*/
ActionFilter source(UUID uniqueId);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific user.
*
* @param uniqueId the target user unique id
* @return the matcher
*/
ActionFilter user(UUID uniqueId);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific group.
*
* @param name the target group name
* @return the matcher
*/
ActionFilter group(String name);
/**
* Gets an {@link ActionFilter} which matches actions which target a specific track.
*
* @param name the target track name
* @return the matcher
*/
ActionFilter track(String name);
/**
* Gets an {@link ActionFilter} which matches actions which contain a specific search query in the source name,
* target name or description.
*
* @param query the search query
* @return the matcher
*/
ActionFilter search(String query);
}
@@ -0,0 +1,29 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* {@link net.luckperms.api.actionlog.Action} filters.
*/
package net.luckperms.api.actionlog.filter;
@@ -33,7 +33,11 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.UUID;
/**
* Called when a log entry is received via the MessagingService
* Called when a log entry is received via the MessagingService.
*
* <p>Note: listening to this event is the same as listening to the {@link LogBroadcastEvent}
* and filtering for {@link LogBroadcastEvent#getOrigin() origin} =
* {@link net.luckperms.api.event.log.LogBroadcastEvent.Origin#REMOTE REMOTE}.</p>
*/
public interface LogReceiveEvent extends LuckPermsEvent {
@@ -0,0 +1,59 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.event.messaging;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import net.luckperms.api.messaging.MessagingService;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Called when a custom payload message is received via the {@link MessagingService}.
*
* <p>This event is effectively the 'other end' of
* {@link MessagingService#sendCustomMessage(String, String)}.</p>
*
* @since 5.5
*/
public interface CustomMessageReceiveEvent extends LuckPermsEvent {
/**
* Gets the channel id.
*
* @return the channel id
*/
@Param(0)
@NonNull String getChannelId();
/**
* Gets the custom payload that was sent.
*
* @return the custom payload
*/
@Param(1)
@NonNull String getPayload();
}
@@ -0,0 +1,29 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Events relating to the {@link net.luckperms.api.messaging.MessagingService}.
*/
package net.luckperms.api.event.messaging;
@@ -0,0 +1,81 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.event.sync;
import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
/**
* Called after a network synchronisation task has been completed.
*
* <p>Note: the generic {@link PostSyncEvent} will also be called for {@link SyncType#FULL full syncs}.</p>
*
* @since 5.5
*/
public interface PostNetworkSyncEvent extends LuckPermsEvent {
/**
* Gets the ID of the sync request
*
* @return the id of the sync request
*/
@Param(0)
@NonNull UUID getSyncId();
/**
* Gets the sync type.
*
* @return the sync type
*/
@Param(1)
@NonNull SyncType getType();
/**
* Gets if a sync occurred.
*
* <p>For {@link SyncType} = {@link SyncType#FULL FULL}, this method always returns true.</p>
*
* <p>For {@link SyncType} = {@link SyncType#SPECIFIC_USER SPECIFIC_USER}, this method returns true if the
* user in question was online/loaded in memory at the time, and false otherwise.</p>
*
* @return if a sync occurred
*/
@Param(2)
boolean didSyncOccur();
/**
* Gets the unique id of the specific user that has been synced, if applicable.
*
* @return the unique id of the specific user
*/
@Param(3)
@Nullable UUID getSpecificUserUniqueId();
}
@@ -28,7 +28,10 @@ package net.luckperms.api.event.sync;
import net.luckperms.api.event.LuckPermsEvent;
/**
* Called when an sync task has been completed
* Called after a full synchronisation task has been completed.
*
* <p>Note: this event is also called after synchronisations that were triggered over the network.
* In other words, this event will be called in addition to {@link PostNetworkSyncEvent}.</p>
*/
public interface PostSyncEvent extends LuckPermsEvent {
@@ -29,11 +29,15 @@ import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
import net.luckperms.api.event.util.Param;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.UUID;
/**
* Called before a received network sync task runs
* Called after a request for synchronisation has been received via the messaging service,
* but before it has actually been completed.
*
* <p>Note: the generic {@link PreSyncEvent} will also be called for {@link SyncType#FULL full syncs}.</p>
*/
public interface PreNetworkSyncEvent extends LuckPermsEvent, Cancellable {
@@ -45,4 +49,22 @@ public interface PreNetworkSyncEvent extends LuckPermsEvent, Cancellable {
@Param(0)
@NonNull UUID getSyncId();
/**
* Gets the sync type.
*
* @return the sync type
* @since 5.5
*/
@Param(1)
@NonNull SyncType getType();
/**
* Gets the unique id of the specific user that will be synced, if applicable.
*
* @return the unique id of the specific user
* @since 5.5
*/
@Param(2)
@Nullable UUID getSpecificUserUniqueId();
}
@@ -29,7 +29,10 @@ import net.luckperms.api.event.LuckPermsEvent;
import net.luckperms.api.event.type.Cancellable;
/**
* Called before a sync task runs
* Called just before a full synchronisation task runs.
*
* <p>Note: this event is also called before synchronisations that were triggered over the network.
* In other words, this event will be called in addition to {@link PreNetworkSyncEvent}.</p>
*/
public interface PreSyncEvent extends LuckPermsEvent, Cancellable {
@@ -23,20 +23,23 @@
* SOFTWARE.
*/
package me.lucko.luckperms.fabric.event;
package net.luckperms.api.event.sync;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.server.world.ServerWorld;
/**
* Represents the type of synchronisation task.
*
* @since 5.5
*/
public enum SyncType {
// TODO: Use Fabric API alternative when merged.
public interface PlayerChangeWorldCallback {
Event<PlayerChangeWorldCallback> EVENT = EventFactory.createArrayBacked(PlayerChangeWorldCallback.class, callbacks -> (originalWorld, destination, player) -> {
for (PlayerChangeWorldCallback callback : callbacks) {
callback.onChangeWorld(originalWorld, destination, player);
}
});
/**
* A full sync will be performed - all groups, users and tracks
*/
FULL,
/**
* Only a specific user will be synced
*/
SPECIFIC_USER
void onChangeWorld(ServerWorld originalWorld, ServerWorld destination, ServerPlayerEntity player);
}
@@ -26,11 +26,12 @@
package net.luckperms.api.messaging;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.event.messaging.CustomMessageReceiveEvent;
import net.luckperms.api.model.user.User;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* A means to push changes to other servers using the platforms networking
* A means to send messages to other servers using the platforms networking
*/
public interface MessagingService {
@@ -71,4 +72,44 @@ public interface MessagingService {
*/
void pushUserUpdate(@NonNull User user);
/**
* Uses the messaging service to send a message with a custom payload.
*
* <p>The intended use case of this functionality is to allow plugins/mods
* to send <b>lightweight</b> and <b>permissions-related</b> custom messages
* between instances, piggy-backing on top of the messenger abstraction
* already built into LuckPerms.</p>
*
* <p>It is <b>not</b> intended as a full message broker replacement/abstraction.
* Note that some of the messenger implementations in LuckPerms cannot handle
* a high volume of messages being sent (for example the SQL messenger).
* Additionally, some implementations do not give any guarantees that a message
* will be delivered on time or even at all (for example the plugin message
* messengers).</p>
*
* <p>With all of that in mind, please consider that if you are using this
* functionality to send messages that have nothing to do with LuckPerms or
* permissions, or that require guarantees around delivery reliability, you
* are most likely misusing the API and would be better off building your own
* integration with a message broker.</p>
*
* <p>Whilst there is (currently) no strict validation, it is recommended
* that the channel id should use the same format as Minecraft resource locations /
* namespaced keys. For example, a plugin called "SuperRanks" sending rank-up
* notifications using custom payload messages might use the channel id
* {@code "superranks:notifications"} for this purpose.</p>
*
* <p>The payload can be any valid UTF-8 string.</p>
*
* <p>The message will be delivered asynchronously.</p>
*
* <p>Other LuckPerms instances that receive the message will publish it to API
* consumers using the {@link CustomMessageReceiveEvent}.</p>
*
* @param channelId the channel id
* @param payload the message payload
* @since 5.5
*/
void sendCustomMessage(@NonNull String channelId, @NonNull String payload);
}
@@ -0,0 +1,56 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.messenger.message.type;
import net.luckperms.api.messenger.message.Message;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* Represents a "custom payload" message.
*
* <p>Used by API consumers to send custom messages between servers.</p>
*
* @see net.luckperms.api.messaging.MessagingService#sendCustomMessage(String, String)
* @see net.luckperms.api.event.messaging.CustomMessageReceiveEvent
* @since 5.5
*/
public interface CustomMessage extends Message {
/**
* Gets the channel identifier.
*
* @return the namespace
*/
@NonNull String getChannelId();
/**
* Gets the payload.
*
* @return the payload
*/
@NonNull String getPayload();
}
@@ -63,6 +63,7 @@ public interface DisplayNameNode extends ScopedNode<DisplayNameNode, DisplayName
*
* @param displayName the display name to set
* @return the builder
* @throws IllegalArgumentException if {@code displayName} is empty
*/
static @NonNull Builder builder(@NonNull String displayName) {
return builder().displayName(displayName);
@@ -67,6 +67,7 @@ public interface InheritanceNode extends ScopedNode<InheritanceNode, Inheritance
*
* @param group the group to set
* @return the builder
* @throws IllegalArgumentException if {@code group} is not a valid group name
*/
static @NonNull Builder builder(@NonNull String group) {
return builder().group(group);
@@ -71,6 +71,7 @@ public interface MetaNode extends ScopedNode<MetaNode, MetaNode.Builder> {
* @param key the meta key to set
* @param value the meta value to set
* @return the builder
* @throws IllegalArgumentException if {@code key} is empty
*/
static @NonNull Builder builder(@NonNull String key, @NonNull String value) {
return builder().key(key).value(value);
@@ -88,6 +88,7 @@ public interface PermissionNode extends ScopedNode<PermissionNode, PermissionNod
*
* @param permission the permission to set
* @return the builder
* @throws IllegalArgumentException if {@code permission} is empty
*/
static @NonNull Builder builder(@NonNull String permission) {
return builder().permission(permission);
@@ -75,6 +75,7 @@ public interface RegexPermissionNode extends ScopedNode<RegexPermissionNode, Reg
*
* @param pattern the pattern to set
* @return the builder
* @throws IllegalArgumentException if {@code pattern} is empty
*/
static @NonNull Builder builder(@NonNull String pattern) {
return builder().pattern(pattern);
@@ -0,0 +1,51 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.platform;
import java.util.Map;
/**
* Represents the "health" status (healthcheck) of a LuckPerms implementation.
*
* @since 5.5
*/
public interface Health {
/**
* Gets if LuckPerms is healthy.
*
* @return if LuckPerms is healthy
*/
boolean isHealthy();
/**
* Gets extra metadata/details about the healthcheck result.
*
* @return details about the healthcheck status
*/
Map<String, Object> getDetails();
}
@@ -76,6 +76,7 @@ public interface Platform {
NUKKIT("Nukkit"),
VELOCITY("Velocity"),
FABRIC("Fabric"),
NEOFORGE("NeoForge"),
FORGE("Forge"),
STANDALONE("Standalone");
@@ -48,7 +48,7 @@ public interface DataTypeFilterFunction {
/**
* Creates a {@link DataTypeFilterFunction} that always returns the given
* {@code predicate}.
* {@code predicate} (commonly one of the values in {@link DataTypeFilter}).
*
* @param predicate the predicate
* @return the data type filter function
@@ -96,6 +96,27 @@ public interface TrackManager {
*/
@NonNull CompletableFuture<Void> deleteTrack(@NonNull Track track);
/**
* Loads (or creates) a track from the plugin's storage provider, applies the given {@code action},
* then saves the track's data back to storage.
*
* <p>This method effectively calls {@link #createAndLoadTrack(String)}, followed by the
* {@code action}, then {@link #saveTrack(Track)}, and returns an encapsulation of the whole
* process as a {@link CompletableFuture}. </p>
*
* @param name the name of the track
* @param action the action to apply to the track
* @return a future to encapsulate the operation
* @since 5.5
*/
default @NonNull CompletableFuture<Void> modifyTrack(@NonNull String name, @NonNull Consumer<? super Track> action) {
/* This default method is overridden in the implementation, and is just here
to demonstrate what this method does in the API sources. */
return createAndLoadTrack(name)
.thenApplyAsync(track -> { action.accept(track); return track; })
.thenCompose(this::saveTrack);
}
/**
* Loads all tracks into memory.
*
@@ -0,0 +1,53 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.luckperms.api.util;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
/**
* Represents a page of entries.
*
* @since 5.5
*/
public interface Page<T> {
/**
* Gets the entries on this page.
*
* @return the entries
*/
@NonNull List<T> entries();
/**
* Gets the total/overall number of entries (not just the number of entries on this page).
*
* @return the total number of entries
*/
int overallSize();
}
-73
View File
@@ -1,73 +0,0 @@
plugins {
alias(libs.plugins.licenser) apply false
}
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
defaultTasks 'licenseFormat', 'build'
subprojects {
apply plugin: 'java'
apply plugin: 'maven-publish'
apply plugin: 'org.cadixdev.licenser'
group = 'me.lucko.luckperms'
version = '5.4-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
tasks.withType(JavaCompile).configureEach {
options.encoding = 'UTF-8'
}
tasks.withType(Test).configureEach {
testLogging {
events = [TestLogEvent.PASSED, TestLogEvent.FAILED, TestLogEvent.SKIPPED]
exceptionFormat = TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
}
jar {
from '../LICENSE.txt'
}
def determinePatchVersion = {
// get the name of the last tag
def tagInfo = new ByteArrayOutputStream()
exec {
commandLine 'git', 'describe', '--tags'
standardOutput = tagInfo
}
tagInfo = tagInfo.toString()
if (!tagInfo.contains('-')) {
return 0
}
return tagInfo.split("-")[1]
}
project.ext.majorVersion = '5'
project.ext.minorVersion = '4'
project.ext.patchVersion = determinePatchVersion()
project.ext.apiVersion = project.ext.majorVersion + '.' + project.ext.minorVersion
project.ext.fullVersion = project.ext.apiVersion + '.' + project.ext.patchVersion
license {
header = rootProject.file('HEADER.txt')
include '**/*.java'
newLine = true
}
repositories {
mavenCentral()
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url 'https://repo.lucko.me/' }
maven { url 'https://libraries.minecraft.net/' }
}
}
+348
View File
@@ -0,0 +1,348 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import org.gradle.kotlin.dsl.provideDelegate
/* ------------------------------ Plugins ------------------------------ */
plugins {
id("java") // Import Java plugin.
id("java-library") // Import Java Library plugin.
id("com.diffplug.spotless") version "7.0.4" // Import Spotless plugin.
id("com.gradleup.shadow") version "8.3.8" // Import Shadow plugin.
id("checkstyle") // Import Checkstyle plugin.
eclipse // Import Eclipse plugin.
kotlin("jvm") version "2.1.21" // Import Kotlin JVM plugin.
}
extra["kotlinAttribute"] = Attribute.of("kotlin-tag", Boolean::class.javaObjectType)
val kotlinAttribute: Attribute<Boolean> by rootProject.extra
/* --------------------------- JDK / Kotlin ---------------------------- */
java {
sourceCompatibility = JavaVersion.VERSION_17 // Compile with JDK 17 compatibility.
toolchain { // Select Java toolchain.
languageVersion.set(JavaLanguageVersion.of(17)) // Use JDK 17.
vendor.set(JvmVendorSpec.GRAAL_VM) // Use GraalVM CE.
}
}
kotlin { jvmToolchain(17) }
/* ----------------------------- Metadata ------------------------------ */
group = "me.lucko.luckperms" // Declare bundle identifier.
version = "5.5.0-SNAPSHOT" // Declare plugin version (will be in .jar).
val apiVersion = "1.19" // Declare minecraft server target version.
/* ----------------------------- Resources ----------------------------- */
tasks.named<ProcessResources>("processResources") {
val props = mapOf("version" to version, "apiVersion" to apiVersion)
inputs.properties(props) // Indicates to rerun if version changes.
filesMatching("plugin.yml") { expand(props) }
from("LICENSE") { into("/") } // Bundle licenses into jarfiles.
}
/* ---------------------------- Repos ---------------------------------- */
repositories {
// Fix issue with lwjgl-freetype not being found on macOS / ForgeGradle issue
maven("https://libraries.minecraft.net") { content { includeModule("org.lwjgl", "lwjgl-freetype") } }
mavenCentral()
maven { url = uri("https://repo.lucko.me/") }
maven { url = uri("https://libraries.minecraft.net/") }
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let {
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
maven { url = uri("file://${dir.absolutePath}") }
} else {
mavenLocal()
}
}
}
/* ---------------------- Java project deps ---------------------------- */
dependencies {
// Root project carries no direct compile deps; subprojects declare their own.
}
/* ---------------------- Reproducible jars ---------------------------- */
tasks.withType<AbstractArchiveTask>().configureEach { // Ensure reproducible .jars
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
}
/* ----------------------------- Shadow -------------------------------- */
tasks.shadowJar {
archiveClassifier.set("") // Use empty string instead of null.
minimize()
}
tasks.jar { archiveClassifier.set("part") } // Applies to root jarfile only.
tasks.build { dependsOn(tasks.spotlessApply) } // Build depends on spotless and shadow.
/* --------------------------- Javac opts ------------------------------- */
tasks.withType<JavaCompile>().configureEach {
options.compilerArgs.add("-parameters") // Enable reflection for java code.
options.isFork = true // Run javac in its own process.
options.compilerArgs.add("-Xlint:deprecation") // Trigger deprecation warning messages.
options.encoding = "UTF-8" // Use UTF-8 file encoding.
}
/* ----------------------------- Auto Formatting ------------------------ */
spotless {
java {
eclipse().configFile("config/formatter/eclipse-java-formatter.xml") // Eclipse java formatting.
leadingTabsToSpaces() // Convert leftover leading tabs to spaces.
removeUnusedImports() // Remove imports that aren't being called.
}
kotlinGradle {
ktfmt().kotlinlangStyle().configure { it.setMaxWidth(120) } // JetBrains Kotlin formatting.
target("build.gradle.kts", "settings.gradle.kts") // Gradle files to format.
}
}
checkstyle {
toolVersion = "10.18.1" // Declare checkstyle version to use.
configFile = file("config/checkstyle/checkstyle.xml") // Point checkstyle to config file.
isIgnoreFailures = true // Don't fail the build if checkstyle does not pass.
isShowViolations = true // Show the violations in any IDE with the checkstyle plugin.
}
tasks.named("compileJava") {
dependsOn("spotlessApply") // Run spotless before compiling with the JDK.
}
tasks.named("spotlessCheck") {
dependsOn("spotlessApply") // Run spotless before checking if spotless ran.
}
/* --------------------------- LuckPerms modules ------------------------ */
subprojects {
apply(plugin = "java-library")
apply(plugin = "eclipse")
group = "me.lucko.luckperms"
version = rootProject.version
repositories {
maven("https://libraries.minecraft.net") { content { includeModule("org.lwjgl", "lwjgl-freetype") } }
mavenCentral()
maven { url = uri("https://repo.lucko.me/") }
maven { url = uri("https://libraries.minecraft.net/") }
maven { url = uri("https://repo.papermc.io/repository/maven-public/") }
maven { url = uri("file://${System.getProperty("user.home")}/.m2/repository") }
System.getProperty("SELF_MAVEN_LOCAL_REPO")?.let {
val dir = file(it)
if (dir.isDirectory) {
println("Using SELF_MAVEN_LOCAL_REPO at: $it")
maven { url = uri("file://${dir.absolutePath}") }
} else {
mavenLocal()
}
}
}
tasks.withType<Test>().configureEach {
testLogging {
events("PASSED", "FAILED", "SKIPPED")
exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
showExceptions = true
showCauses = true
showStackTraces = true
}
useJUnitPlatform()
}
tasks.withType<JavaCompile>().configureEach { options.encoding = "UTF-8" }
tasks.withType<Jar>().configureEach { from(rootProject.file("LICENSE.txt")) }
}
/* :api */
project(":api") {
dependencies {
compileOnly("org.checkerframework:checker-qual:3.49.3")
compileOnly("org.jetbrains:annotations:26.0.2")
}
tasks.jar { manifest { attributes(mapOf("Automatic-Module-Name" to "net.luckperms.api")) } }
}
/* :common */
project(":common") {
apply(plugin = "jacoco")
val patchedDir = layout.buildDirectory.dir("patchedSrc/commonMain/java")
val patchCommonForJ17 =
tasks.register<Copy>("patchCommonForJ17") {
from("src/main/java")
into(patchedDir)
filteringCharset = "UTF-8"
filesMatching("**/*.java") {
filter { line ->
if (
line.contains(".reversed()") &&
!line.contains("Comparator") &&
!line.contains("comparing") &&
!line.contains("thenComparing") &&
!line.contains("INSTANCE") &&
!line.contains("normal()")
) {
line.replace(
".reversed()",
".stream().collect(java.util.stream.Collectors.collectingAndThen(" +
"java.util.stream.Collectors.toList(), l -> { java.util.Collections.reverse(l); return l; }))",
)
} else line
}
}
}
sourceSets.named("main") {
java.setSrcDirs(emptyList<Any>())
java.srcDir(patchedDir)
}
tasks.named<JavaCompile>("compileJava") { dependsOn(patchCommonForJ17) }
dependencies {
testImplementation("org.slf4j:slf4j-simple:1.7.36")
testImplementation("org.junit.jupiter:junit-jupiter:5.13.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testImplementation("org.testcontainers:junit-jupiter:1.21.1")
testImplementation("org.mockito:mockito-core:5.18.0")
testImplementation("org.mockito:mockito-junit-jupiter:5.18.0")
testImplementation("com.h2database:h2:2.1.214")
testImplementation("org.mongodb:mongodb-driver-legacy:5.5.0")
testImplementation("org.spongepowered:configurate-yaml:3.7.3")
testImplementation("org.spongepowered:configurate-hocon:3.7.3")
testImplementation("me.lucko.configurate:configurate-toml:3.7")
testImplementation("net.luckperms:rest-api-java-client:0.1")
api(project(":api"))
api("org.checkerframework:checker-qual:3.12.0")
compileOnly(project(":common:loader-utils"))
compileOnly("com.mojang:brigadier:1.0.18")
compileOnly("org.slf4j:slf4j-api:1.7.30")
compileOnly("org.apache.logging.log4j:log4j-api:2.14.0")
api("net.kyori:adventure-api:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "checker-qual")
exclude(module = "annotations")
}
api("net.kyori:adventure-text-serializer-gson:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
exclude(module = "gson")
}
api("net.kyori:adventure-text-serializer-legacy:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:adventure-text-serializer-plain:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:adventure-text-minimessage:4.21.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
}
api("net.kyori:event-api:3.0.0") {
exclude(module = "checker-qual")
exclude(module = "guava")
}
api("com.google.code.gson:gson:2.7")
api("com.google.guava:guava:19.0")
api("com.github.ben-manes.caffeine:caffeine:3.2.0")
api("com.squareup.okhttp3:okhttp:3.14.9")
api("com.squareup.okio:okio:1.17.6")
api("net.bytebuddy:byte-buddy:1.15.11")
api("org.spongepowered:configurate-core:3.7.3") { isTransitive = false }
api("org.spongepowered:configurate-yaml:3.7.3") { isTransitive = false }
api("org.spongepowered:configurate-gson:3.7.3") { isTransitive = false }
api("org.spongepowered:configurate-hocon:3.7.3") { isTransitive = false }
api("me.lucko.configurate:configurate-toml:3.7") { isTransitive = false }
compileOnly("com.zaxxer:HikariCP:6.3.0")
compileOnly("redis.clients:jedis:5.2.0")
compileOnly("io.nats:jnats:2.21.1")
compileOnly("com.rabbitmq:amqp-client:5.25.0")
compileOnly("org.mongodb:mongodb-driver-legacy:5.5.0")
compileOnly("org.postgresql:postgresql:42.7.6")
compileOnly("org.yaml:snakeyaml:1.33")
compileOnly("net.luckperms:rest-api-java-client:0.1")
}
}
/* :common:loader-utils */
project(":common:loader-utils") {
// nothing special
}
/* :bukkit */
project(":bukkit") {
apply(plugin = "com.gradleup.shadow")
dependencies {
implementation(project(":common"))
compileOnly(project(":common:loader-utils"))
compileOnly("com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT")
compileOnly("net.kyori:adventure-platform-bukkit:4.4.0") {
exclude(module = "adventure-bom")
exclude(module = "adventure-api")
exclude(module = "adventure-nbt")
}
compileOnly("me.lucko:commodore:2.0")
compileOnly("net.milkbowl.vault:VaultAPI:1.7") { exclude(module = "bukkit") }
compileOnly("lilypad.client.connect:api:0.0.1-SNAPSHOT")
}
tasks.named<ShadowJar>("shadowJar") {
archiveFileName.set("luckperms-bukkit.jarinjar")
dependencies { include(dependency("me.lucko.luckperms:.*")) }
relocate("net.kyori.adventure", "me.lucko.luckperms.lib.adventure")
relocate("net.kyori.event", "me.lucko.luckperms.lib.eventbus")
relocate("com.github.benmanes.caffeine", "me.lucko.luckperms.lib.caffeine")
relocate("okio", "me.lucko.luckperms.lib.okio")
relocate("okhttp3", "me.lucko.luckperms.lib.okhttp3")
relocate("net.bytebuddy", "me.lucko.luckperms.lib.bytebuddy")
relocate("me.lucko.commodore", "me.lucko.luckperms.lib.commodore")
relocate("org.mariadb.jdbc", "me.lucko.luckperms.lib.mariadb")
relocate("com.mysql", "me.lucko.luckperms.lib.mysql")
relocate("org.postgresql", "me.lucko.luckperms.lib.postgresql")
relocate("com.zaxxer.hikari", "me.lucko.luckperms.lib.hikari")
relocate("com.mongodb", "me.lucko.luckperms.lib.mongodb")
relocate("org.bson", "me.lucko.luckperms.lib.bson")
relocate("redis.clients.jedis", "me.lucko.luckperms.lib.jedis")
relocate("io.nats.client", "me.lucko.luckperms.lib.nats")
relocate("com.rabbitmq", "me.lucko.luckperms.lib.rabbitmq")
relocate("org.apache.commons.pool2", "me.lucko.luckperms.lib.commonspool2")
relocate("ninja.leaping.configurate", "me.lucko.luckperms.lib.configurate")
}
}
/* :bukkit:loader */
project(":bukkit:loader") {
apply(plugin = "com.gradleup.shadow")
dependencies {
compileOnly("com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT")
implementation(project(":api"))
implementation(project(":common:loader-utils"))
}
tasks.processResources {
filesMatching("plugin.yml") { expand(mapOf("pluginVersion" to rootProject.version.toString())) }
}
tasks.named<ShadowJar>("shadowJar") {
archiveFileName.set("LuckPerms-${rootProject.version}.jar")
from(project(":bukkit").tasks.named<ShadowJar>("shadowJar").flatMap { it.archiveFile })
manifest { attributes(mapOf("paperweight-mappings-namespace" to "mojang")) }
destinationDirectory.set(rootProject.layout.buildDirectory.dir("libs"))
}
}
/* Build root produces the server jar */
tasks.named("build") { dependsOn(project(":bukkit:loader").tasks.named("shadowJar")) }
-45
View File
@@ -1,45 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
dependencies {
implementation project(':bukkit')
implementation 'com.google.code.gson:gson:2.7'
implementation 'com.google.guava:guava:19.0'
}
shadowJar {
archiveFileName = 'luckperms-bukkitlegacy.jarinjar'
dependencies {
include(dependency('me.lucko.luckperms:.*'))
include(dependency('com.google.guava:guava:.*'))
include(dependency('com.google.code.gson:gson:.*'))
}
relocate 'com.google.common', 'me.lucko.luckperms.lib.guava'
relocate 'com.google.gson', 'me.lucko.luckperms.lib.gson'
relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure'
relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus'
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql'
relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari'
relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb'
relocate 'org.bson', 'me.lucko.luckperms.lib.bson'
relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis'
relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats'
relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq'
relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2'
relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate'
}
artifacts {
archives shadowJar
}
-32
View File
@@ -1,32 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven { url 'https://repo.papermc.io/repository/maven-public/' }
}
dependencies {
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
implementation project(':api')
implementation project(':common:loader-utils')
}
processResources {
filesMatching('plugin.yml') {
expand 'pluginVersion': project.ext.fullVersion
}
}
shadowJar {
archiveFileName = "LuckPerms-Bukkit-Legacy-${project.ext.fullVersion}.jar"
from {
project(':bukkit-legacy').tasks.shadowJar.archiveFile
}
}
artifacts {
archives shadowJar
}
-55
View File
@@ -1,55 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven { url 'https://repo.papermc.io/repository/maven-public/' }
}
dependencies {
implementation project(':common')
compileOnly project(':common:loader-utils')
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
compileOnly('net.kyori:adventure-platform-bukkit:4.1.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
}
compileOnly 'me.lucko:commodore:2.0'
compileOnly('net.milkbowl.vault:VaultAPI:1.7') {
exclude(module: 'bukkit')
}
compileOnly 'lilypad.client.connect:api:0.0.1-SNAPSHOT'
}
shadowJar {
archiveFileName = 'luckperms-bukkit.jarinjar'
dependencies {
include(dependency('me.lucko.luckperms:.*'))
}
relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure'
relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus'
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql'
relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari'
relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb'
relocate 'org.bson', 'me.lucko.luckperms.lib.bson'
relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis'
relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats'
relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq'
relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2'
relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate'
}
artifacts {
archives shadowJar
}
-32
View File
@@ -1,32 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
repositories {
maven { url 'https://repo.papermc.io/repository/maven-public/' }
}
dependencies {
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
implementation project(':api')
implementation project(':common:loader-utils')
}
processResources {
filesMatching('plugin.yml') {
expand 'pluginVersion': project.ext.fullVersion
}
}
shadowJar {
archiveFileName = "LuckPerms-Bukkit-${project.ext.fullVersion}.jar"
from {
project(':bukkit').tasks.shadowJar.archiveFile
}
}
artifacts {
archives shadowJar
}
@@ -307,7 +307,7 @@ public class LPBukkitBootstrap implements LuckPermsBootstrap, LoaderBootstrap, B
try {
Class.forName("com.google.gson.JsonElement");
return false;
} catch (ClassNotFoundException e) {
} catch (Exception e) {
return true;
}
}
@@ -221,10 +221,12 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
* - https://hub.spigotmc.org/jira/browse/SPIGOT-5546
* - https://github.com/PaperMC/Paper/pull/3509
*/
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getLoader().getName(), "Vault");
if (getConfiguration().get(ConfigKeys.VAULT_ENABLED)) {
PluginManagerUtil.injectDependency(this.bootstrap.getServer().getPluginManager(), this.bootstrap.getLoader().getName(), "Vault");
// Provide vault support
tryVaultHook(false);
// Provide vault support
tryVaultHook(false);
}
}
@Override
@@ -237,6 +239,10 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
return; // already hooked
}
if (!getConfiguration().get(ConfigKeys.VAULT_ENABLED)) {
return; // disabled in config
}
try {
if (force || this.bootstrap.getServer().getPluginManager().isPluginEnabled("Vault")) {
this.vaultHookManager = new VaultHookManager(this);
@@ -347,7 +353,7 @@ public class LPBukkitPlugin extends AbstractLuckPermsPlugin {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException e) {
} catch (Exception e) {
return false;
}
}
@@ -25,85 +25,46 @@
package me.lucko.luckperms.bukkit.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import me.lucko.luckperms.bukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BukkitContextManager extends ContextManager<Player, Player> {
public class BukkitContextManager extends DetachedContextManager<Player, Player> {
public static final OptionKey<Boolean> OP_OPTION = OptionKey.of("op", Boolean.class);
// cache the creation of ContextsCache instances for online players with no expiry
private final LoadingMap<Player, QueryOptionsCache<Player>> onlineSubjectCaches = LoadingMap.of(key -> new QueryOptionsCache<>(key, this));
// cache the creation of ContextsCache instances for offline players with a 1m expiry
private final LoadingCache<Player, QueryOptionsCache<Player>> offlineSubjectCaches = CaffeineFactory.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build(key -> {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(key);
if (cache != null) {
return cache;
}
return new QueryOptionsCache<>(key, this);
});
public BukkitContextManager(LPBukkitPlugin plugin) {
super(plugin, Player.class, Player.class);
}
public void onPlayerQuit(Player player) {
this.onlineSubjectCaches.remove(player);
}
@Override
public UUID getUniqueId(Player player) {
return player.getUniqueId();
}
@Override
public QueryOptionsCache<Player> getCacheFor(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
if (subject.isOnline()) {
return this.onlineSubjectCaches.get(subject);
} else {
return this.offlineSubjectCaches.get(subject);
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(Player subject) {
Objects.requireNonNull(subject, "subject");
LuckPermsPermissible permissible = PermissibleInjector.get(subject);
if (permissible != null) {
return permissible.getQueryOptionsSupplier();
}
return null;
}
@Override
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
cache = this.offlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
}
@Override
public QueryOptions formQueryOptions(Player subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
public void customizeQueryOptions(Player subject, QueryOptions.Builder builder) {
if (subject.isOp()) {
queryOptions.option(OP_OPTION, true);
builder.option(OP_OPTION, true);
}
return queryOptions.context(contextSet).build();
}
}
@@ -32,7 +32,7 @@ import me.lucko.luckperms.bukkit.calculator.OpProcessor;
import me.lucko.luckperms.bukkit.calculator.PermissionMapProcessor;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import net.luckperms.api.query.QueryOptions;
@@ -68,7 +68,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation.
*
* This class is **thread safe**. This means that when LuckPerms is installed on the server,
* is is safe to call Player#hasPermission asynchronously.
* it is safe to call Player#hasPermission asynchronously.
*/
public class LuckPermsPermissible extends PermissibleBase {
@@ -93,7 +93,7 @@ public class LuckPermsPermissible extends PermissibleBase {
private final LPBukkitPlugin plugin;
// caches context lookups for the player
private final QueryOptionsCache<Player> queryOptionsSupplier;
private final QueryOptionsSupplier queryOptionsSupplier;
// the players previous permissible. (the one they had before this one was injected)
private PermissibleBase oldPermissible = null;
@@ -110,7 +110,7 @@ public class LuckPermsPermissible extends PermissibleBase {
this.user = Objects.requireNonNull(user, "user");
this.player = Objects.requireNonNull(player, "player");
this.plugin = Objects.requireNonNull(plugin, "plugin");
this.queryOptionsSupplier = plugin.getContextManager().getCacheFor(player);
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
injectFakeAttachmentsList();
}
@@ -293,7 +293,7 @@ public class LuckPermsPermissible extends PermissibleBase {
// the query options cache when op status changes.
// (#invalidate is a fast call)
if (this.queryOptionsSupplier != null) { // this method is called by the super class constructor, before this class has fully initialised
this.queryOptionsSupplier.invalidate();
this.queryOptionsSupplier.invalidateCache();
}
// but we don't need to do anything else in this method, unlike the CB impl.
@@ -316,6 +316,10 @@ public class LuckPermsPermissible extends PermissibleBase {
return this.plugin;
}
public QueryOptionsSupplier getQueryOptionsSupplier() {
return this.queryOptionsSupplier;
}
PermissibleBase getOldPermissible() {
return this.oldPermissible;
}
@@ -409,9 +413,7 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override public PermissionAttachment remove(int index) { throw new UnsupportedOperationException(); }
@Override public int indexOf(Object o) { throw new UnsupportedOperationException(); }
@Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); }
@Override
public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override
public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
@Override public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
}
}
@@ -30,6 +30,7 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.List;
@@ -161,6 +162,19 @@ public final class PermissibleInjector {
}
}
public static @Nullable LuckPermsPermissible get(Player player) {
PermissibleBase permissibleBase;
try {
permissibleBase = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
} catch (IllegalAccessException e) {
return null;
}
if (permissibleBase instanceof LuckPermsPermissible) {
return (LuckPermsPermissible) permissibleBase;
}
return null;
}
public static void checkInjected(Player player, PluginLogger logger) {
PermissibleBase permissibleBase;
try {
@@ -253,9 +253,6 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1L);
}
@@ -36,12 +36,25 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.server.RemoteServerCommandEvent;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import java.util.Locale;
import java.util.regex.Pattern;
public class BukkitPlatformListener implements Listener {
private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(\\w+:)?(deop|op)( .*)?$");
private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(\\w+:)?(deop|op)( .*)?$", Pattern.CASE_INSENSITIVE);
private static final boolean PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED;
static {
boolean supported = false;
try {
PluginDescriptionFile.class.getMethod("getProvides");
supported = true;
} catch (NoSuchMethodException ignored) {
}
PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED = supported;
}
private final LPBukkitPlugin plugin;
@@ -51,17 +64,17 @@ public class BukkitPlatformListener implements Listener {
@EventHandler(ignoreCancelled = true)
public void onPlayerCommand(PlayerCommandPreprocessEvent e) {
handleCommand(e.getPlayer(), e.getMessage().toLowerCase(Locale.ROOT), e);
handleCommand(e.getPlayer(), e.getMessage(), e);
}
@EventHandler(ignoreCancelled = true)
public void onServerCommand(ServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(Locale.ROOT), e);
handleCommand(e.getSender(), e.getCommand(), e);
}
@EventHandler(ignoreCancelled = true)
public void onRemoteServerCommand(RemoteServerCommandEvent e) {
handleCommand(e.getSender(), e.getCommand().toLowerCase(Locale.ROOT), e);
handleCommand(e.getSender(), e.getCommand(), e);
}
private void handleCommand(CommandSender sender, String cmdLine, Cancellable event) {
@@ -81,7 +94,15 @@ public class BukkitPlatformListener implements Listener {
@EventHandler
public void onPluginEnable(PluginEnableEvent e) {
if (e.getPlugin().getName().equalsIgnoreCase("Vault")) {
if (!this.plugin.getConfiguration().get(ConfigKeys.VAULT_ENABLED)) {
return;
}
Plugin p = e.getPlugin();
boolean shouldHook = p.getName().equalsIgnoreCase("Vault") ||
(PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED && p.getDescription().getProvides().contains("Vault"));
if (shouldHook) {
this.plugin.tryVaultHook(true);
}
}
+60 -19
View File
@@ -41,7 +41,7 @@ server: global
# - To get around this, you can use a player's uuid directly in the command, or enable this option.
# - When this is set to 'true', the server facility is used. This may use a number of methods,
# including checking the servers local cache, or making a request to the Mojang API.
use-server-uuid-cache: false
use-server-uuid-cache: true
@@ -83,7 +83,7 @@ use-server-uuid-cache: false
#
# - A H2 database is the default option.
# - If you want to edit data manually in "traditional" storage files, we suggest using YAML.
storage-method: h2
storage-method: postgresql
# The following block defines the settings for remote database storage methods.
#
@@ -99,11 +99,11 @@ data:
# The name of the database to store LuckPerms data in.
# - This must be created already. Don't worry about this setting if you're using MongoDB.
database: minecraft
database: luckperms
# Credentials for the database.
username: root
password: ''
username: '^{POSTGRES_USER}'
password: '^{POSTGRES_PASSWORD}'
# These settings apply to the MySQL connection pool.
# - The default values will be suitable for the majority of users.
@@ -132,11 +132,11 @@ data:
# from being timed out by the database or network infrastructure, measured in milliseconds.
# - The value should be less than maximum-lifetime and greater than 30000 (30 seconds).
# - Setting the value to zero will disable the keepalive functionality.
keepalive-time: 0
keepalive-time: 120000
# This setting controls the maximum number of milliseconds that the plugin will wait for a
# connection from the pool, before timing out.
connection-timeout: 5000 # 5 seconds
connection-timeout: 10000 # 10 seconds
# This setting allows you to define extra properties for connections.
#
@@ -216,7 +216,7 @@ sync-minutes: -1
# - When using a file-based storage type, LuckPerms can monitor the data files for changes, and
# automatically update when changes are detected.
# - If you don't want this feature to be active, set this option to false.
watch-files: true
watch-files: false
# Define which messaging service should be used by the plugin.
#
@@ -258,16 +258,16 @@ push-log-entries: true
# - If you have LuckPerms installed on your backend servers as well as a BungeeCord proxy, you
# should set this option to false on either your backends or your proxies, to avoid players being
# messaged twice about log entries.
broadcast-received-log-entries: true
broadcast-received-log-entries: false
# Settings for Redis.
# Settings for Keydb.
# Port 6379 is used by default; set address to "host:port" if differs
# Multiple Redis nodes can be specified in the same format as a string list under the name "addresses".
# Multiple Keydb nodes can be specified in the same format as a string list under the name "addresses".
redis:
enabled: false
enabled: true
address: localhost
username: ''
password: ''
username: 'default'
password: 'bookchin'
# Settings for Nats.
# Port 4222 is used by default; set address to "host:port" if differs
@@ -335,7 +335,7 @@ argument-based-command-permissions: false
# If the plugin should check whether senders are a member of a given group before they're able to
# edit the groups data or add/remove other users to/from it.
# Note: these limitations do not apply to the web editor!
require-sender-group-membership-to-modify: false
require-sender-group-membership-to-modify: true
# If the plugin should send log notifications to users whenever permissions are modified.
#
@@ -408,14 +408,14 @@ meta-formatting:
duplicates: first-only
start-spacer: ""
middle-spacer: " "
end-spacer: ""
end-spacer: " "
suffix:
format:
- "highest"
duplicates: first-only
start-spacer: ""
start-spacer: " "
middle-spacer: " "
end-spacer: ""
end-spacer: " "
@@ -627,8 +627,19 @@ commands-allow-op: true
# +----------------------------------------------------------------------------------------------+ #
# | Vault integration settings | #
# | | #
# | Disabled by default (vault-enabled: false). The remaining vault-* settings below only take | #
# | effect when vault-enabled is set to true. | #
# +----------------------------------------------------------------------------------------------+ #
# If the Vault API bridge should be registered.
#
# - When false (default), LuckPerms will not hook into Vault at all. Vault-dependent plugins will
# fall back to SuperPerms and will not see LuckPerms groups, prefixes, or meta.
# - When true, LuckPerms will register Vault permission and chat providers if the Vault plugin is
# installed. Required for any plugin that reads groups/prefix/suffix via the Vault API.
vault-enabled: false
# If Vault lookups for offline players on the main server thread should be enabled.
#
# LuckPerms has a "catch" for plugins attempting to perform unsafe offline player data lookups
@@ -717,5 +728,35 @@ update-client-command-list: true
register-command-list-data: true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.gamepedia.com/Commands#Target_selectors
# See here for more info: https://minecraft.wiki/w/Target_selectors
resolve-command-selectors: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: true
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: true
console: false
-47
View File
@@ -1,47 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
dependencies {
implementation project(':common')
compileOnly project(':common:loader-utils')
compileOnly 'net.md-5:bungeecord-api:1.16-R0.4'
compileOnly('net.kyori:adventure-platform-bungeecord:4.1.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
}
compileOnly 'com.imaginarycode.minecraft:RedisBungee:0.4'
}
shadowJar {
archiveFileName = 'luckperms-bungee.jarinjar'
dependencies {
include(dependency('me.lucko.luckperms:.*'))
}
relocate 'net.kyori.adventure', 'me.lucko.luckperms.lib.adventure'
relocate 'net.kyori.event', 'me.lucko.luckperms.lib.eventbus'
relocate 'com.github.benmanes.caffeine', 'me.lucko.luckperms.lib.caffeine'
relocate 'okio', 'me.lucko.luckperms.lib.okio'
relocate 'okhttp3', 'me.lucko.luckperms.lib.okhttp3'
relocate 'net.bytebuddy', 'me.lucko.luckperms.lib.bytebuddy'
relocate 'me.lucko.commodore', 'me.lucko.luckperms.lib.commodore'
relocate 'org.mariadb.jdbc', 'me.lucko.luckperms.lib.mariadb'
relocate 'com.mysql', 'me.lucko.luckperms.lib.mysql'
relocate 'org.postgresql', 'me.lucko.luckperms.lib.postgresql'
relocate 'com.zaxxer.hikari', 'me.lucko.luckperms.lib.hikari'
relocate 'com.mongodb', 'me.lucko.luckperms.lib.mongodb'
relocate 'org.bson', 'me.lucko.luckperms.lib.bson'
relocate 'redis.clients.jedis', 'me.lucko.luckperms.lib.jedis'
relocate 'io.nats.client', 'me.lucko.luckperms.lib.nats'
relocate 'com.rabbitmq', 'me.lucko.luckperms.lib.rabbitmq'
relocate 'org.apache.commons.pool2', 'me.lucko.luckperms.lib.commonspool2'
relocate 'ninja.leaping.configurate', 'me.lucko.luckperms.lib.configurate'
}
artifacts {
archives shadowJar
}
-28
View File
@@ -1,28 +0,0 @@
plugins {
alias(libs.plugins.shadow)
}
dependencies {
compileOnly 'net.md-5:bungeecord-api:1.15-SNAPSHOT'
implementation project(':api')
implementation project(':common:loader-utils')
}
processResources {
filesMatching('plugin.yml') {
expand 'pluginVersion': project.ext.fullVersion
}
}
shadowJar {
archiveFileName = "LuckPerms-Bungee-${project.ext.fullVersion}.jar"
from {
project(':bungee').tasks.shadowJar.archiveFile
}
}
artifacts {
archives shadowJar
}
@@ -302,7 +302,7 @@ public class LPBungeeBootstrap implements LuckPermsBootstrap, LoaderBootstrap, B
try {
Class.forName("com.google.gson.internal.bind.TreeTypeAdapter");
return false;
} catch (ClassNotFoundException e) {
} catch (Exception e) {
return true;
}
}
@@ -25,25 +25,13 @@
package me.lucko.luckperms.bungee.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.InlineQueryOptionsSupplier;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryOptions;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedPlayer> {
private final LoadingCache<ProxiedPlayer, QueryOptions> contextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
public class BungeeContextManager extends SimpleContextManager<ProxiedPlayer, ProxiedPlayer> {
public BungeeContextManager(LPBungeePlugin plugin) {
super(plugin, ProxiedPlayer.class, ProxiedPlayer.class);
}
@@ -52,35 +40,4 @@ public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedP
public UUID getUniqueId(ProxiedPlayer player) {
return player.getUniqueId();
}
@Override
public QueryOptionsSupplier getCacheFor(ProxiedPlayer subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return new InlineQueryOptionsSupplier<>(subject, this.contextsCache);
}
// override getContext, getQueryOptions and invalidateCache to skip the QueryOptionsSupplier
@Override
public ImmutableContextSet getContext(ProxiedPlayer subject) {
return getQueryOptions(subject).context();
}
@Override
public QueryOptions getQueryOptions(ProxiedPlayer subject) {
return this.contextsCache.get(subject);
}
@Override
protected void invalidateCache(ProxiedPlayer subject) {
this.contextsCache.invalidate(subject);
}
@Override
public QueryOptions formQueryOptions(ProxiedPlayer subject, ImmutableContextSet contextSet) {
return formQueryOptions(contextSet);
}
}
+30
View File
@@ -611,3 +611,33 @@ disable-bulkupdate: false
#
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: false
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: false
console: false
-93
View File
@@ -1,93 +0,0 @@
plugins {
id("java-library")
id("jacoco")
}
test {
useJUnitPlatform {}
}
jacocoTestReport {
dependsOn test
}
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.1'
testImplementation 'org.mockito:mockito-core:4.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:4.11.0'
testImplementation 'com.h2database:h2:2.1.214'
api project(':api')
api 'org.checkerframework:checker-qual:3.12.0'
compileOnly project(':common:loader-utils')
compileOnly 'com.mojang:brigadier:1.0.18'
compileOnly 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.apache.logging.log4j:log4j-api:2.14.0'
api('net.kyori:adventure-api:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'checker-qual')
exclude(module: 'annotations')
}
api('net.kyori:adventure-text-serializer-gson:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'gson')
}
api('net.kyori:adventure-text-serializer-legacy:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:adventure-text-serializer-plain:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api("net.kyori:adventure-text-minimessage:4.11.0") {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:event-api:3.0.0') {
exclude(module: 'checker-qual')
exclude(module: 'guava')
}
api 'com.google.code.gson:gson:2.7'
api 'com.google.guava:guava:19.0'
api 'com.github.ben-manes.caffeine:caffeine:2.9.0'
api 'com.squareup.okhttp3:okhttp:3.14.9'
api 'com.squareup.okio:okio:1.17.5'
api 'net.bytebuddy:byte-buddy:1.10.22'
api('org.spongepowered:configurate-core:3.7.2') {
transitive = false
}
api('org.spongepowered:configurate-yaml:3.7.2') {
transitive = false
}
api('org.spongepowered:configurate-gson:3.7.2') {
transitive = false
}
api('org.spongepowered:configurate-hocon:3.7.2') {
transitive = false
}
api('me.lucko.configurate:configurate-toml:3.7') {
transitive = false
}
compileOnly 'com.zaxxer:HikariCP:4.0.3'
compileOnly 'redis.clients:jedis:4.4.3'
compileOnly 'io.nats:jnats:2.16.4'
compileOnly 'com.rabbitmq:amqp-client:5.12.0'
compileOnly 'org.mongodb:mongodb-driver-legacy:4.5.0'
compileOnly 'org.postgresql:postgresql:42.6.0'
compileOnly 'org.yaml:snakeyaml:1.28'
}
-1
View File
@@ -1 +0,0 @@
// nothing special to do here (yet)!
@@ -1,108 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSortedSet;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import net.luckperms.api.actionlog.Action;
import java.util.ArrayList;
import java.util.List;
import java.util.SortedSet;
import java.util.UUID;
public class Log {
private static final Log EMPTY = new Log(ImmutableList.of());
public static Builder builder() {
return new Builder();
}
public static Log empty() {
return EMPTY;
}
private final SortedSet<LoggedAction> content;
Log(List<LoggedAction> content) {
this.content = ImmutableSortedSet.copyOf(content);
}
public SortedSet<LoggedAction> getContent() {
return this.content;
}
public SortedSet<LoggedAction> getContent(UUID actor) {
return this.content.stream()
.filter(e -> e.getSource().getUniqueId().equals(actor))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getUserHistory(UUID uniqueId) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.USER)
.filter(e -> e.getTarget().getUniqueId().isPresent() && e.getTarget().getUniqueId().get().equals(uniqueId))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getGroupHistory(String name) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.GROUP)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getTrackHistory(String name) {
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.TRACK)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
public SortedSet<LoggedAction> getSearch(String query) {
return this.content.stream()
.filter(e -> e.matchesSearch(query))
.collect(ImmutableCollectors.toSortedSet());
}
public static class Builder {
private final List<LoggedAction> content = new ArrayList<>();
public Builder add(LoggedAction e) {
this.content.add(e);
return this;
}
public Log build() {
if (this.content.isEmpty()) {
return EMPTY;
}
return new Log(this.content);
}
}
}
@@ -29,12 +29,14 @@ import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.commands.log.LogNotify;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.messaging.InternalMessagingService;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.luckperms.api.event.log.LogBroadcastEvent;
import net.luckperms.api.event.log.LogNotifyEvent;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
public class LogDispatcher {
@@ -64,7 +66,12 @@ public class LogDispatcher {
return !this.plugin.getEventDispatcher().dispatchLogBroadcast(cancelled, entry, origin);
}
private void broadcast(LoggedAction entry, LogNotifyEvent.Origin origin, Sender sender) {
// broadcast the entry to online players
private void broadcast(LoggedAction entry, LogBroadcastEvent.Origin broadcastOrigin, LogNotifyEvent.Origin origin, Sender sender) {
if (!shouldBroadcast(entry, broadcastOrigin)) {
return;
}
this.plugin.getOnlineSenders()
.filter(CommandPermission.LOG_NOTIFY::isAuthorized)
.filter(s -> {
@@ -74,41 +81,46 @@ public class LogDispatcher {
.forEach(s -> Message.LOG.send(s, entry));
}
public void dispatch(LoggedAction entry, Sender sender) {
// log the entry to storage
public CompletableFuture<Void> logToStorage(LoggedAction entry) {
if (!this.plugin.getEventDispatcher().dispatchLogPublish(false, entry)) {
this.plugin.getStorage().logAction(entry);
return this.plugin.getStorage().logAction(entry);
} else {
return CompletableFuture.completedFuture(null);
}
}
this.plugin.getMessagingService().ifPresent(service -> service.pushLog(entry));
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL, sender);
// log the entry to messaging
public CompletableFuture<Void> logToMessaging(LoggedAction entry) {
InternalMessagingService messagingService = this.plugin.getMessagingService().orElse(null);
if (messagingService != null) {
return messagingService.pushLog(entry);
} else {
return CompletableFuture.completedFuture(null);
}
}
// log the entry to storage and messaging, and broadcast it to online players
private CompletableFuture<Void> dispatch(LoggedAction entry, Sender sender, LogBroadcastEvent.Origin broadcastOrigin, LogNotifyEvent.Origin origin) {
CompletableFuture<Void> storageFuture = logToStorage(entry);
CompletableFuture<Void> messagingFuture = logToMessaging(entry);
broadcast(entry, broadcastOrigin, origin, sender);
return CompletableFuture.allOf(storageFuture, messagingFuture);
}
public CompletableFuture<Void> dispatch(LoggedAction entry, Sender sender) {
return dispatch(entry, sender, LogBroadcastEvent.Origin.LOCAL, LogNotifyEvent.Origin.LOCAL);
}
public CompletableFuture<Void> dispatchFromApi(LoggedAction entry) {
return dispatch(entry, null, LogBroadcastEvent.Origin.LOCAL_API, LogNotifyEvent.Origin.LOCAL_API);
}
public void broadcastFromApi(LoggedAction entry) {
this.plugin.getMessagingService().ifPresent(extendedMessagingService -> extendedMessagingService.pushLog(entry));
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.LOCAL_API)) {
broadcast(entry, LogNotifyEvent.Origin.LOCAL_API, null);
}
broadcast(entry, LogBroadcastEvent.Origin.LOCAL_API, LogNotifyEvent.Origin.LOCAL_API, null);
}
public void dispatchFromApi(LoggedAction entry) {
if (!this.plugin.getEventDispatcher().dispatchLogPublish(false, entry)) {
try {
this.plugin.getStorage().logAction(entry).get();
} catch (Exception e) {
this.plugin.getLogger().warn("Error whilst storing action", e);
}
}
broadcastFromApi(entry);
}
public void dispatchFromRemote(LoggedAction entry) {
if (shouldBroadcast(entry, LogBroadcastEvent.Origin.REMOTE)) {
broadcast(entry, LogNotifyEvent.Origin.REMOTE, null);
}
public void broadcastFromRemote(LoggedAction entry) {
broadcast(entry, LogBroadcastEvent.Origin.REMOTE, LogNotifyEvent.Origin.REMOTE, null);
}
}
@@ -23,54 +23,50 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.util;
package me.lucko.luckperms.common.actionlog;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.filter.PageParameters;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
/**
* A simple pagination utility
*
* @param <T> the element type
*/
public class Paginated<T> {
private final List<T> content;
public Paginated(Collection<T> content) {
this.content = ImmutableList.copyOf(content);
public class LogPage {
public static LogPage of(List<LoggedAction> content, @Nullable PageParameters params, int totalEntries) {
return new LogPage(content, params, totalEntries);
}
public List<T> getContent() {
private final List<LoggedAction> content;
private final @Nullable PageParameters params;
private final int totalEntries;
LogPage(List<LoggedAction> content, @Nullable PageParameters params, int totalEntries) {
this.content = ImmutableList.copyOf(content);
this.params = params;
this.totalEntries = totalEntries;
}
public List<LoggedAction> getContent() {
return this.content;
}
public int getMaxPages(int entriesPerPage) {
return (int) Math.ceil((double) this.content.size() / (double) entriesPerPage);
public List<Entry<LoggedAction>> getNumberedContent() {
int startIndex = this.params != null
? this.params.pageSize() * (this.params.pageNumber() - 1)
: 0;
List<Entry<LoggedAction>> numberedContent = new ArrayList<>();
for (int i = 0; i < this.content.size(); i++) {
int index = startIndex + i + 1;
numberedContent.add(new Entry<>(index, this.content.get(i)));
}
return numberedContent;
}
public List<Entry<T>> getPage(int pageNo, int pageSize) {
if (pageNo < 1) {
throw new IllegalArgumentException("pageNo cannot be less than 1: " + pageNo);
}
int first = (pageNo - 1) * pageSize;
if (this.content.size() <= first) {
throw new IllegalStateException("Content does not contain that many elements. (requested page: " + pageNo +
", page size: " + pageSize + ", page first index: " + first + ", content size: " + this.content.size() + ")");
}
int last = first + pageSize - 1;
List<Entry<T>> out = new ArrayList<>(pageSize);
for (int i = first; i <= last && i < this.content.size(); i++) {
out.add(new Entry<>(i + 1, this.content.get(i)));
}
return out;
public int getTotalEntries() {
return this.totalEntries;
}
public static final class Entry<T> {
@@ -26,6 +26,7 @@
package me.lucko.luckperms.common.actionlog;
import com.google.common.base.Strings;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.PermissionHolder;
@@ -44,10 +45,10 @@ import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
* An implementation of {@link Action} and {@link Action.Builder},
@@ -123,15 +124,11 @@ public class LoggedAction implements Action {
return ActionComparator.INSTANCE.compare(this, other);
}
public boolean matchesSearch(String query) {
query = Objects.requireNonNull(query, "query").toLowerCase(Locale.ROOT);
return this.source.name.toLowerCase(Locale.ROOT).contains(query) ||
this.target.name.toLowerCase(Locale.ROOT).contains(query) ||
this.description.toLowerCase(Locale.ROOT).contains(query);
}
public void submit(LuckPermsPlugin plugin, Sender sender) {
plugin.getLogDispatcher().dispatch(this, sender);
CompletableFuture<Void> future = plugin.getLogDispatcher().dispatch(this, sender);
if (plugin.getConfiguration().get(ConfigKeys.LOG_SYNCHRONOUSLY_IN_COMMANDS)) {
future.join();
}
}
@Override
@@ -393,14 +390,14 @@ public class LoggedAction implements Action {
}
}
public static char getTypeCharacter(Target.Type type) {
public static String getTypeString(Target.Type type) {
switch (type) {
case USER:
return 'U';
return "U";
case GROUP:
return 'G';
return "G";
case TRACK:
return 'T';
return "T";
default:
throw new AssertionError();
}
@@ -0,0 +1,60 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog.filter;
import me.lucko.luckperms.common.filter.FilterField;
import net.luckperms.api.actionlog.Action;
import java.util.UUID;
public final class ActionFields {
public static final FilterField<Action, UUID> SOURCE_UNIQUE_ID = FilterField.named(
"SOURCE_UNIQUE_ID",
action -> action.getSource().getUniqueId()
);
public static final FilterField<Action, String> SOURCE_NAME = FilterField.named(
"SOURCE_NAME",
action -> action.getSource().getName()
);
public static final FilterField<Action, Action.Target.Type> TARGET_TYPE = FilterField.named(
"TARGET_TYPE",
action -> action.getTarget().getType()
);
public static final FilterField<Action, UUID> TARGET_UNIQUE_ID = FilterField.named(
"TARGET_UNIQUE_ID",
action -> action.getTarget().getUniqueId().orElse(null)
);
public static final FilterField<Action, String> TARGET_NAME = FilterField.named(
"TARGET_NAME",
action -> action.getTarget().getName()
);
public static final FilterField<Action, String> DESCRIPTION = FilterField.named(
"DESCRIPTION",
action -> action.getDescription()
);
}
@@ -0,0 +1,70 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog.filter;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.mongo.FilterMongoBuilder;
import net.luckperms.api.actionlog.Action;
import java.util.UUID;
public final class ActionFilterMongoBuilder extends FilterMongoBuilder<Action> {
public static final ActionFilterMongoBuilder INSTANCE = new ActionFilterMongoBuilder();
private ActionFilterMongoBuilder() {
}
@Override
public String mapFieldName(FilterField<Action, ?> field) {
if (field == ActionFields.SOURCE_UNIQUE_ID) {
return "source.uniqueId";
} else if (field == ActionFields.SOURCE_NAME) {
return "source.name";
} else if (field == ActionFields.TARGET_TYPE) {
return "target.type";
} else if (field == ActionFields.TARGET_UNIQUE_ID) {
return "target.uniqueId";
} else if (field == ActionFields.TARGET_NAME) {
return "target.name";
} else if (field == ActionFields.DESCRIPTION) {
return "description";
}
throw new AssertionError(field);
}
@Override
public Object mapConstraintValue(Object value) {
if (value instanceof String | value instanceof UUID) {
return value;
} else if (value instanceof Action.Target.Type) {
return ((Action.Target.Type) value).name();
} else {
throw new IllegalArgumentException("Don't know how to map value with type: " + value.getClass().getName());
}
}
}
@@ -0,0 +1,69 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog.filter;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.sql.FilterSqlBuilder;
import net.luckperms.api.actionlog.Action;
import java.util.UUID;
public class ActionFilterSqlBuilder extends FilterSqlBuilder<Action> {
@Override
public void visitFieldName(FilterField<Action, ?> field) {
if (field == ActionFields.SOURCE_UNIQUE_ID) {
this.builder.append("actor_uuid");
} else if (field == ActionFields.SOURCE_NAME) {
this.builder.append("actor_name");
} else if (field == ActionFields.TARGET_TYPE) {
this.builder.append("type");
} else if (field == ActionFields.TARGET_UNIQUE_ID) {
this.builder.append("acted_uuid");
} else if (field == ActionFields.TARGET_NAME) {
this.builder.append("acted_name");
} else if (field == ActionFields.DESCRIPTION) {
this.builder.append("action");
} else {
throw new AssertionError(field);
}
}
@Override
public void visitConstraintValue(Object value) {
if (value instanceof String) {
this.builder.variable(((String) value));
} else if (value instanceof UUID) {
this.builder.variable(value.toString());
} else if (value instanceof Action.Target.Type) {
this.builder.variable(LoggedAction.getTypeString((Action.Target.Type) value));
} else {
throw new IllegalArgumentException("Don't know how to write value with type: " + value.getClass().getName());
}
}
}
@@ -0,0 +1,110 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.actionlog.filter;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.ConstraintFactory;
import me.lucko.luckperms.common.filter.FilterList;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.Action.Target;
import java.util.UUID;
import java.util.function.Predicate;
import java.util.regex.Pattern;
public final class ActionFilters {
private ActionFilters() {}
// all actions
public static FilterList<Action> all() {
return FilterList.empty();
}
// all actions performed by a given source (actor)
public static FilterList<Action> source(UUID uniqueId) {
return FilterList.and(
ActionFields.SOURCE_UNIQUE_ID.isEqualTo(uniqueId, ConstraintFactory.UUIDS)
);
}
// all actions affecting a given user
public static FilterList<Action> user(UUID uniqueId) {
return FilterList.and(
ActionFields.TARGET_TYPE.isEqualTo(Target.Type.USER, TARGET_TYPE_CONSTRAINT_FACTORY),
ActionFields.TARGET_UNIQUE_ID.isEqualTo(uniqueId, ConstraintFactory.UUIDS)
);
}
// all actions affecting a given group
public static FilterList<Action> group(String name) {
return FilterList.and(
ActionFields.TARGET_TYPE.isEqualTo(Target.Type.GROUP, TARGET_TYPE_CONSTRAINT_FACTORY),
ActionFields.TARGET_NAME.isEqualTo(name, ConstraintFactory.STRINGS)
);
}
// all actions affecting a given track
public static FilterList<Action> track(String name) {
return FilterList.and(
ActionFields.TARGET_TYPE.isEqualTo(Target.Type.TRACK, TARGET_TYPE_CONSTRAINT_FACTORY),
ActionFields.TARGET_NAME.isEqualTo(name, ConstraintFactory.STRINGS)
);
}
// all actions matching the given search query
public static FilterList<Action> search(String query) {
return FilterList.or(
ActionFields.SOURCE_NAME.isSimilarTo("%" + query + "%", ConstraintFactory.STRINGS),
ActionFields.TARGET_NAME.isSimilarTo("%" + query + "%", ConstraintFactory.STRINGS),
ActionFields.DESCRIPTION.isSimilarTo("%" + query + "%", ConstraintFactory.STRINGS)
);
}
private static final ConstraintFactory<Target.Type> TARGET_TYPE_CONSTRAINT_FACTORY = new ConstraintFactory<Target.Type>() {
@Override
public Predicate<Target.Type> equal(Target.Type value) {
return value::equals;
}
@Override
public Predicate<Target.Type> notEqual(Target.Type value) {
return string -> !value.equals(string);
}
@Override
public Predicate<Target.Type> similar(Target.Type value) {
Pattern pattern = Comparison.compilePatternForLikeSyntax(value.toString());
return type -> pattern.matcher(type.toString()).matches();
}
@Override
public Predicate<Target.Type> notSimilar(Target.Type value) {
Pattern pattern = Comparison.compilePatternForLikeSyntax(value.toString());
return type -> !pattern.matcher(type.toString()).matches();
}
};
}
@@ -25,6 +25,7 @@
package me.lucko.luckperms.common.api;
import me.lucko.luckperms.common.api.implementation.ApiActionFilterFactory;
import me.lucko.luckperms.common.api.implementation.ApiActionLogger;
import me.lucko.luckperms.common.api.implementation.ApiContextManager;
import me.lucko.luckperms.common.api.implementation.ApiGroupManager;
@@ -47,6 +48,7 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.LuckPermsProvider;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import net.luckperms.api.context.ContextManager;
import net.luckperms.api.messaging.MessagingService;
import net.luckperms.api.messenger.MessengerProvider;
@@ -55,6 +57,7 @@ import net.luckperms.api.model.group.GroupManager;
import net.luckperms.api.model.user.UserManager;
import net.luckperms.api.node.NodeBuilderRegistry;
import net.luckperms.api.node.matcher.NodeMatcherFactory;
import net.luckperms.api.platform.Health;
import net.luckperms.api.platform.Platform;
import net.luckperms.api.platform.PlayerAdapter;
import net.luckperms.api.platform.PluginMetadata;
@@ -172,6 +175,11 @@ public class LuckPermsApiProvider implements LuckPerms {
return this.plugin.getSyncTaskBuffer().request();
}
@Override
public @NonNull Health runHealthCheck() {
return this.plugin.runHealthCheck();
}
@Override
public @NonNull AbstractEventBus<?> getEventBus() {
return this.plugin.getEventDispatcher().getEventBus();
@@ -219,4 +227,8 @@ public class LuckPermsApiProvider implements LuckPerms {
return ApiNodeMatcherFactory.INSTANCE;
}
@Override
public @NonNull ActionFilterFactory getActionFilterFactory() {
return ApiActionFilterFactory.INSTANCE;
}
}
@@ -0,0 +1,47 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.filter.FilterList;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.filter.ActionFilter;
public class ApiActionFilter implements ActionFilter {
private final FilterList<Action> filter;
public ApiActionFilter(FilterList<Action> filter) {
this.filter = filter;
}
@Override
public boolean test(Action action) {
return this.filter.evaluate(action);
}
public FilterList<Action> getFilter() {
return this.filter;
}
}
@@ -0,0 +1,77 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.actionlog.filter.ActionFilterFactory;
import java.util.Objects;
import java.util.UUID;
public final class ApiActionFilterFactory implements ActionFilterFactory {
public static final ApiActionFilterFactory INSTANCE = new ApiActionFilterFactory();
private ApiActionFilterFactory() {
}
@Override
public ActionFilter any() {
return new ApiActionFilter(ActionFilters.all());
}
@Override
public ActionFilter source(UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uniqueId");
return new ApiActionFilter(ActionFilters.source(uniqueId));
}
@Override
public ActionFilter user(UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uniqueId");
return new ApiActionFilter(ActionFilters.user(uniqueId));
}
@Override
public ActionFilter group(String name) {
Objects.requireNonNull(name, "name");
return new ApiActionFilter(ActionFilters.group(name));
}
@Override
public ActionFilter track(String name) {
Objects.requireNonNull(name, "name");
return new ApiActionFilter(ActionFilters.track(name));
}
@Override
public ActionFilter search(String query) {
Objects.requireNonNull(query, "query");
return new ApiActionFilter(ActionFilters.search(query));
}
}
@@ -25,50 +25,63 @@
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.actionlog.Log;
import me.lucko.luckperms.common.api.ApiUtils;
import com.google.common.collect.ImmutableSortedSet;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.ActionLog;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Objects;
import java.util.SortedSet;
import java.util.UUID;
@SuppressWarnings({"unchecked", "rawtypes"})
@Deprecated
public class ApiActionLog implements ActionLog {
private final Log handle;
private final SortedSet<Action> content;
public ApiActionLog(Log handle) {
this.handle = handle;
public ApiActionLog(List<LoggedAction> content) {
this.content = ImmutableSortedSet.copyOf(content);
}
@Override
public @NonNull SortedSet<Action> getContent() {
return (SortedSet) this.handle.getContent();
return this.content;
}
@Override
public @NonNull SortedSet<Action> getContent(@NonNull UUID actor) {
Objects.requireNonNull(actor, "actor");
return (SortedSet) this.handle.getContent(actor);
return this.content.stream()
.filter(e -> e.getSource().getUniqueId().equals(actor))
.collect(ImmutableCollectors.toSortedSet());
}
@Override
public @NonNull SortedSet<Action> getUserHistory(@NonNull UUID uniqueId) {
Objects.requireNonNull(uniqueId, "uuid");
return (SortedSet) this.handle.getUserHistory(uniqueId);
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.USER)
.filter(e -> e.getTarget().getUniqueId().isPresent() && e.getTarget().getUniqueId().get().equals(uniqueId))
.collect(ImmutableCollectors.toSortedSet());
}
@Override
public @NonNull SortedSet<Action> getGroupHistory(@NonNull String name) {
Objects.requireNonNull(name, "name");
return (SortedSet) this.handle.getGroupHistory(ApiUtils.checkName(name));
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.GROUP)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
@Override
public @NonNull SortedSet<Action> getTrackHistory(@NonNull String name) {
Objects.requireNonNull(name, "name");
return (SortedSet) this.handle.getTrackHistory(ApiUtils.checkName(name));
return this.content.stream()
.filter(e -> e.getTarget().getType() == Action.Target.Type.TRACK)
.filter(e -> e.getTarget().getName().equals(name))
.collect(ImmutableCollectors.toSortedSet());
}
}
@@ -25,13 +25,22 @@
package me.lucko.luckperms.common.api.implementation;
import me.lucko.luckperms.common.actionlog.LogDispatcher;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFilters;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.actionlog.ActionLog;
import net.luckperms.api.actionlog.ActionLogger;
import net.luckperms.api.actionlog.filter.ActionFilter;
import net.luckperms.api.util.Page;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
public class ApiActionLogger implements ActionLogger {
@@ -47,22 +56,66 @@ public class ApiActionLogger implements ActionLogger {
}
@Override
@Deprecated
public @NonNull CompletableFuture<ActionLog> getLog() {
return this.plugin.getStorage().getLog().thenApply(ApiActionLog::new);
return this.plugin.getStorage().getLogPage(ActionFilters.all(), null)
.thenApply(result -> new ApiActionLog(result.getContent()));
}
@Override
public @NonNull CompletableFuture<List<Action>> queryActions(@NonNull ActionFilter filter) {
return this.plugin.getStorage().getLogPage(getFilterList(filter), null).thenApply(ActionPage::new).thenApply(Page::entries);
}
@Override
public @NonNull CompletableFuture<Page<Action>> queryActions(@NonNull ActionFilter filter, int pageSize, int pageNumber) {
return this.plugin.getStorage().getLogPage(getFilterList(filter), new PageParameters(pageSize, pageNumber)).thenApply(ActionPage::new);
}
@Override
public @NonNull CompletableFuture<Void> submit(@NonNull Action entry) {
return CompletableFuture.runAsync(() -> this.plugin.getLogDispatcher().dispatchFromApi((LoggedAction) entry), this.plugin.getBootstrap().getScheduler().async());
return this.plugin.getLogDispatcher().dispatchFromApi((LoggedAction) entry);
}
@Override
public @NonNull CompletableFuture<Void> submitToStorage(@NonNull Action entry) {
return this.plugin.getStorage().logAction(entry);
return this.plugin.getLogDispatcher().logToStorage((LoggedAction) entry);
}
@Override
public @NonNull CompletableFuture<Void> broadcastAction(@NonNull Action entry) {
return CompletableFuture.runAsync(() -> this.plugin.getLogDispatcher().broadcastFromApi((LoggedAction) entry), this.plugin.getBootstrap().getScheduler().async());
LogDispatcher dispatcher = this.plugin.getLogDispatcher();
CompletableFuture<Void> messagingFuture = dispatcher.logToStorage(((LoggedAction) entry));
dispatcher.broadcastFromApi(((LoggedAction) entry));
return messagingFuture;
}
private static FilterList<Action> getFilterList(ActionFilter filter) {
Objects.requireNonNull(filter, "filter");
if (filter instanceof ApiActionFilter) {
return ((ApiActionFilter) filter).getFilter();
} else {
throw new IllegalArgumentException("Unknown filter type: " + filter.getClass());
}
}
private static final class ActionPage implements Page<Action> {
private final LogPage page;
private ActionPage(LogPage page) {
this.page = page;
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public @NonNull List<Action> entries() {
return (List) this.page.getContent();
}
@Override
public int overallSize() {
return this.page.getTotalEntries();
}
}
}
@@ -54,4 +54,11 @@ public class ApiMessagingService implements MessagingService {
Objects.requireNonNull(user, "user");
this.handle.pushUserUpdate(ApiUser.cast(user));
}
@Override
public void sendCustomMessage(@NonNull String channelId, @NonNull String payload) {
Objects.requireNonNull(channelId, "channelId");
Objects.requireNonNull(payload, "payload");
this.handle.pushCustomPayload(channelId, payload);
}
}
@@ -26,8 +26,8 @@
package me.lucko.luckperms.common.api.implementation;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.metastacking.MetaStackDefinition;
@@ -38,6 +38,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
public class ApiTrackManager extends ApiAbstractManager<Track, net.luckperms.api.track.Track, TrackManager<?>> implements net.luckperms.api.track.TrackManager {
public ApiTrackManager(LuckPermsPlugin plugin, TrackManager<?> handle) {
@@ -74,6 +75,19 @@ public class ApiTrackManager extends ApiAbstractManager<Track, net.luckperms.api
return this.plugin.getStorage().deleteTrack(ApiTrack.cast(track), DeletionCause.API);
}
@Override
public @NonNull CompletableFuture<Void> modifyTrack(@NonNull String name, @NonNull Consumer<? super net.luckperms.api.track.Track> action) {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(action, "action");
return this.plugin.getStorage().createAndLoadTrack(name, CreationCause.API)
.thenApplyAsync(track -> {
action.accept(track.getApiProxy());
return track;
}, this.plugin.getBootstrap().getScheduler().async())
.thenCompose(track -> this.plugin.getStorage().saveTrack(track));
}
@Override
public @NonNull CompletableFuture<Void> loadAllTracks() {
return this.plugin.getStorage().loadAllTracks();
@@ -25,14 +25,13 @@
package me.lucko.luckperms.common.bulkupdate;
import me.lucko.luckperms.common.bulkupdate.action.Action;
import me.lucko.luckperms.common.bulkupdate.query.Query;
import me.lucko.luckperms.common.bulkupdate.action.BulkUpdateAction;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.model.HolderType;
import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -46,35 +45,30 @@ public final class BulkUpdate {
private final DataType dataType;
// the action to apply to the data which matches the constraints
private final Action action;
private final BulkUpdateAction action;
// a set of constraints which data must match to be acted upon
private final List<Query> queries;
// a set of filters which data must match to be acted upon
private final FilterList<Node> filters;
// update statistics of the operation (number of nodes, users and groups affected)
private final BulkUpdateStatistics statistics = new BulkUpdateStatistics();
private final boolean trackStatistics;
public BulkUpdate(DataType dataType, Action action, List<Query> queries, boolean trackStatistics) {
public BulkUpdate(DataType dataType, BulkUpdateAction action, FilterList<Node> filters, boolean trackStatistics) {
this.dataType = dataType;
this.action = action;
this.queries = queries;
this.filters = filters;
this.trackStatistics = trackStatistics;
}
/**
* Check to see if a Node instance satisfies the constrints of this query
* Check to see if a Node instance satisfies the constraints of this query
*
* @param node the node to check
* @return true if satisfied
*/
public boolean satisfiesConstraints(Node node) {
for (Query query : this.queries) {
if (!query.isSatisfiedBy(node)) {
return false;
}
}
return true;
public boolean satisfiesFilters(Node node) {
return this.filters.evaluate(node);
}
/**
@@ -84,7 +78,7 @@ public final class BulkUpdate {
* @return the transformed node, or null if the node should be deleted
*/
private Node apply(Node node) {
if (!satisfiesConstraints(node)) {
if (!satisfiesFilters(node)) {
return node; // make no change
}
@@ -129,63 +123,16 @@ public final class BulkUpdate {
return results;
}
/**
* Converts this {@link BulkUpdate} to SQL syntax
*
* @return this query in SQL form
*/
public PreparedStatementBuilder buildAsSql() {
// DELETE FROM {table} WHERE ...
// UPDATE {table} SET ... WHERE ...
PreparedStatementBuilder builder = new PreparedStatementBuilder();
// add the action
// (DELETE FROM or UPDATE)
this.action.appendSql(builder);
return appendConstraintsAsSql(builder);
}
/**
* Appends the constraints of this {@link BulkUpdate} to the provided statement builder in SQL syntax
*
* @param builder the statement builder to append the constraints to
* @return the same statement builder provided as input
*/
public PreparedStatementBuilder appendConstraintsAsSql(PreparedStatementBuilder builder) {
// if there are no constraints, just return without a WHERE clause
if (this.queries.isEmpty()) {
return builder;
}
// append constraints
builder.append(" WHERE");
for (int i = 0; i < this.queries.size(); i++) {
Query query = this.queries.get(i);
builder.append(" ");
if (i != 0) {
builder.append("AND ");
}
query.appendSql(builder);
}
return builder;
}
public DataType getDataType() {
return this.dataType;
}
public Action getAction() {
public BulkUpdateAction getAction() {
return this.action;
}
public List<Query> getQueries() {
return this.queries;
public FilterList<Node> getFilters() {
return this.filters;
}
public boolean isTrackingStatistics() {
@@ -204,12 +151,12 @@ public final class BulkUpdate {
return this.getDataType() == that.getDataType() &&
Objects.equals(this.getAction(), that.getAction()) &&
Objects.equals(this.getQueries(), that.getQueries());
Objects.equals(this.getFilters(), that.getFilters());
}
@Override
public int hashCode() {
return Objects.hash(getDataType(), getAction(), getQueries(), isTrackingStatistics());
return Objects.hash(getDataType(), getAction(), getFilters(), isTrackingStatistics());
}
@Override
@@ -217,7 +164,7 @@ public final class BulkUpdate {
return "BulkUpdate(" +
"dataType=" + this.getDataType() + ", " +
"action=" + this.getAction() + ", " +
"constraints=" + this.getQueries() + ", " +
"constraints=" + this.getFilters() + ", " +
"trackStatistics=" + this.isTrackingStatistics() + ")";
}
}
@@ -26,8 +26,12 @@
package me.lucko.luckperms.common.bulkupdate;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.bulkupdate.action.Action;
import me.lucko.luckperms.common.bulkupdate.query.Query;
import me.lucko.luckperms.common.bulkupdate.action.BulkUpdateAction;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.ConstraintFactory;
import me.lucko.luckperms.common.filter.Filter;
import me.lucko.luckperms.common.filter.FilterList;
import net.luckperms.api.node.Node;
import java.util.LinkedHashSet;
import java.util.Set;
@@ -45,18 +49,18 @@ public class BulkUpdateBuilder {
private DataType dataType = DataType.ALL;
// the action to apply to the data which matches the constraints
private Action action = null;
private BulkUpdateAction action = null;
// should the operation count the number of affected nodes, users and groups
private boolean trackStatistics = false;
// a set of constraints which data must match to be acted upon
private final Set<Query> queries = new LinkedHashSet<>();
// a set of filters which data must match to be acted upon
private final Set<Filter<Node, String>> filters = new LinkedHashSet<>();
private BulkUpdateBuilder() {
}
public BulkUpdateBuilder action(Action action) {
public BulkUpdateBuilder action(BulkUpdateAction action) {
this.action = action;
return this;
}
@@ -71,8 +75,8 @@ public class BulkUpdateBuilder {
return this;
}
public BulkUpdateBuilder query(Query query) {
this.queries.add(query);
public BulkUpdateBuilder filter(BulkUpdateField field, Comparison comparison, String value) {
this.filters.add(new Filter<>(field, ConstraintFactory.STRINGS.build(comparison, value)));
return this;
}
@@ -81,7 +85,8 @@ public class BulkUpdateBuilder {
throw new IllegalStateException("no action specified");
}
return new BulkUpdate(this.dataType, this.action, ImmutableList.copyOf(this.queries), this.trackStatistics);
FilterList<Node> filters = new FilterList<>(FilterList.LogicalOperator.AND, ImmutableList.copyOf(this.filters));
return new BulkUpdate(this.dataType, this.action, filters, this.trackStatistics);
}
@Override
@@ -89,7 +94,7 @@ public class BulkUpdateBuilder {
return "BulkUpdateBuilder(" +
"dataType=" + this.dataType + ", " +
"action=" + this.action + ", " +
"constraints=" + this.queries + ", " +
"constraints=" + this.filters + ", " +
"trackStatistics=" + this.trackStatistics + ")";
}
}
@@ -23,54 +23,45 @@
* SOFTWARE.
*/
package me.lucko.luckperms.standalone.app.integration;
package me.lucko.luckperms.common.bulkupdate;
import com.google.gson.Gson;
import me.lucko.luckperms.common.filter.FilterField;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.node.Node;
import java.util.Map;
import java.util.Locale;
/**
* An interface able to provide information about the application/plugin health.
* Represents a field being used in a bulk update
*/
public interface HealthReporter {
/**
* Polls the current health status.
*
* @return the health status
*/
Health poll();
final class Health {
private static final Gson GSON = new Gson();
private final boolean up;
private final Map<String, String> details;
Health(boolean up, Map<String, String> details) {
this.up = up;
this.details = details;
}
public boolean isUp() {
return this.up;
}
public Map<String, String> details() {
return this.details;
}
public enum BulkUpdateField implements FilterField<Node, String> {
PERMISSION {
@Override
public String toString() {
return GSON.toJson(this);
public String getValue(Node node) {
return node.getKey();
}
},
public static Health up(Map<String, String> details) {
return new Health(true, details);
SERVER {
@Override
public String getValue(Node node) {
return node.getContexts().getAnyValue(DefaultContextKeys.SERVER_KEY).orElse("global");
}
},
public static Health down(Map<String, String> details) {
return new Health(false, details);
WORLD {
@Override
public String getValue(Node node) {
return node.getContexts().getAnyValue(DefaultContextKeys.WORLD_KEY).orElse("global");
}
};
public static BulkUpdateField of(String s) {
try {
return valueOf(s.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
return null;
}
}
@@ -0,0 +1,76 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate;
import me.lucko.luckperms.common.bulkupdate.action.BulkUpdateAction;
import me.lucko.luckperms.common.bulkupdate.action.DeleteAction;
import me.lucko.luckperms.common.bulkupdate.action.UpdateAction;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.sql.FilterSqlBuilder;
import net.luckperms.api.node.Node;
public class BulkUpdateSqlBuilder extends FilterSqlBuilder<Node> {
public void visit(BulkUpdate update) {
visit(update.getAction());
visit(update.getFilters());
}
public void visit(BulkUpdateAction action) {
if (action instanceof UpdateAction) {
visit(((UpdateAction) action));
} else if (action instanceof DeleteAction) {
visit(((DeleteAction) action));
} else {
throw new UnsupportedOperationException(action.getClass().getName());
}
}
public void visit(UpdateAction action) {
this.builder.append("UPDATE {table} SET ");
visitFieldName(action.getField());
this.builder.append("=");
this.builder.variable(action.getNewValue());
}
public void visit(DeleteAction action) {
this.builder.append("DELETE FROM {table}");
}
@Override
public void visitFieldName(FilterField<Node, ?> field) {
if (field == BulkUpdateField.PERMISSION) {
this.builder.append("permission");
} else if (field == BulkUpdateField.SERVER) {
this.builder.append("server");
} else if (field == BulkUpdateField.WORLD) {
this.builder.append("world");
} else {
throw new AssertionError(field);
}
}
}
@@ -25,13 +25,12 @@
package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import net.luckperms.api.node.Node;
/**
* Represents an action to be applied to a given node.
*/
public interface Action {
public interface BulkUpdateAction {
/**
* Gets the name of this action
@@ -44,17 +43,8 @@ public interface Action {
* Applies this action to the given NodeModel, and returns the result.
*
* @param from the node to base changes from
* @return the new nodemodel instance, or null if the node should be deleted.
* @return the new node instance, or null if the node should be deleted.
*/
Node apply(Node from);
/**
* Gets this action in SQL form.
*
* Will include a placeholder for the table, as "{table}".
*
* @param builder the statement builder
*/
void appendSql(PreparedStatementBuilder builder);
}
@@ -25,10 +25,9 @@
package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import net.luckperms.api.node.Node;
public class DeleteAction implements Action {
public class DeleteAction implements BulkUpdateAction {
public static DeleteAction create() {
return new DeleteAction();
@@ -46,9 +45,4 @@ public class DeleteAction implements Action {
public Node apply(Node from) {
return null; // this action just deletes nodes, so return null
}
@Override
public void appendSql(PreparedStatementBuilder builder) {
builder.append("DELETE FROM {table}");
}
}
@@ -25,28 +25,27 @@
package me.lucko.luckperms.common.bulkupdate.action;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.bulkupdate.query.QueryField;
import me.lucko.luckperms.common.bulkupdate.BulkUpdateField;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.context.MutableContextSet;
import net.luckperms.api.node.Node;
public class UpdateAction implements Action {
public class UpdateAction implements BulkUpdateAction {
public static UpdateAction of(QueryField field, String value) {
public static UpdateAction of(BulkUpdateField field, String value) {
return new UpdateAction(field, value);
}
// the field we're updating
private final QueryField field;
private final BulkUpdateField field;
// the new value of the field
private final String value;
private final String newValue;
private UpdateAction(QueryField field, String value) {
private UpdateAction(BulkUpdateField field, String newValue) {
this.field = field;
this.value = value;
this.newValue = newValue;
}
@Override
@@ -54,11 +53,19 @@ public class UpdateAction implements Action {
return "update";
}
public BulkUpdateField getField() {
return this.field;
}
public String getNewValue() {
return this.newValue;
}
@Override
public Node apply(Node from) {
switch (this.field) {
case PERMISSION:
return NodeBuilders.determineMostApplicable(this.value)
return NodeBuilders.determineMostApplicable(this.newValue)
.value(from.getValue())
.expiry(from.getExpiry())
.context(from.getContexts())
@@ -66,8 +73,8 @@ public class UpdateAction implements Action {
case SERVER: {
MutableContextSet contexts = from.getContexts().mutableCopy();
contexts.removeAll(DefaultContextKeys.SERVER_KEY);
if (!this.value.equals("global")) {
contexts.add(DefaultContextKeys.SERVER_KEY, this.value);
if (!this.newValue.equals("global")) {
contexts.add(DefaultContextKeys.SERVER_KEY, this.newValue);
}
return from.toBuilder()
@@ -77,8 +84,8 @@ public class UpdateAction implements Action {
case WORLD: {
MutableContextSet contexts = from.getContexts().mutableCopy();
contexts.removeAll(DefaultContextKeys.WORLD_KEY);
if (!this.value.equals("global")) {
contexts.add(DefaultContextKeys.WORLD_KEY, this.value);
if (!this.newValue.equals("global")) {
contexts.add(DefaultContextKeys.WORLD_KEY, this.newValue);
}
return from.toBuilder()
@@ -89,10 +96,4 @@ public class UpdateAction implements Action {
throw new RuntimeException();
}
}
@Override
public void appendSql(PreparedStatementBuilder builder) {
builder.append("UPDATE {table} SET " + this.field.getSqlName() + "=");
builder.variable(this.value);
}
}
@@ -1,112 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate.comparison;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import java.util.regex.Pattern;
/**
* An enumeration of standard {@link Comparison}s.
*/
public enum StandardComparison implements Comparison {
EQUAL("==", "=") {
@Override
public CompiledExpression compile(String expression) {
return expression::equalsIgnoreCase;
}
},
NOT_EQUAL("!=", "!=") {
@Override
public CompiledExpression compile(String expression) {
return string -> !expression.equalsIgnoreCase(string);
}
},
SIMILAR("~~", "LIKE") {
@Override
public CompiledExpression compile(String expression) {
Pattern pattern = StandardComparison.compilePatternForLikeSyntax(expression);
return string -> pattern.matcher(string).matches();
}
},
NOT_SIMILAR("!~", "NOT LIKE") {
@Override
public CompiledExpression compile(String expression) {
Pattern pattern = StandardComparison.compilePatternForLikeSyntax(expression);
return string -> !pattern.matcher(string).matches();
}
};
public static final String WILDCARD = "%";
public static final String WILDCARD_ONE = "_";
private final String symbol;
private final String asSql;
StandardComparison(String symbol, String asSql) {
this.symbol = symbol;
this.asSql = asSql;
}
@Override
public String getSymbol() {
return this.symbol;
}
@Override
public void appendSql(PreparedStatementBuilder builder) {
builder.append(this.asSql);
}
@Override
public String toString() {
return this.symbol;
}
public static StandardComparison parseComparison(String s) {
for (StandardComparison t : values()) {
if (t.getSymbol().equals(s)) {
return t;
}
}
return null;
}
static Pattern compilePatternForLikeSyntax(String expression) {
expression = expression.replace(".", "\\.");
// convert from SQL LIKE syntax to regex
expression = expression.replace(WILDCARD_ONE, ".");
expression = expression.replace(WILDCARD, ".*");
return Pattern.compile(expression, Pattern.CASE_INSENSITIVE);
}
}
@@ -1,83 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.bulkupdate.query;
import me.lucko.luckperms.common.bulkupdate.PreparedStatementBuilder;
import me.lucko.luckperms.common.bulkupdate.comparison.Constraint;
import net.luckperms.api.context.DefaultContextKeys;
import net.luckperms.api.node.Node;
/**
* Represents a query component
*/
public class Query {
public static Query of(QueryField field, Constraint constraint) {
return new Query(field, constraint);
}
// the field this query is comparing against
private final QueryField field;
// the constraint
private final Constraint constraint;
private Query(QueryField field, Constraint constraint) {
this.field = field;
this.constraint = constraint;
}
/**
* Returns if the given node satisfies this query
*
* @param node the node
* @return true if satisfied
*/
public boolean isSatisfiedBy(Node node) {
switch (this.field) {
case PERMISSION:
return this.constraint.eval(node.getKey());
case SERVER:
return this.constraint.eval(node.getContexts().getAnyValue(DefaultContextKeys.SERVER_KEY).orElse("global"));
case WORLD:
return this.constraint.eval(node.getContexts().getAnyValue(DefaultContextKeys.WORLD_KEY).orElse("global"));
default:
throw new RuntimeException();
}
}
public void appendSql(PreparedStatementBuilder builder) {
this.constraint.appendSql(builder, this.field.getSqlName());
}
public QueryField getField() {
return this.field;
}
public Constraint getConstraint() {
return this.constraint;
}
}
@@ -193,7 +193,7 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
}
public void cleanup() {
this.cache.values().removeIf(value -> ((UsageTracked) value).usedSince(TimeUnit.MINUTES.toMillis(2)));
this.cache.values().removeIf(value -> !((UsageTracked) value).usedInTheLast(2, TimeUnit.MINUTES));
}
@Override
@@ -25,14 +25,20 @@
package me.lucko.luckperms.common.cacheddata;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
public abstract class UsageTracked {
private long lastUsed = System.currentTimeMillis();
@VisibleForTesting
protected long lastUsed = System.currentTimeMillis();
public void recordUsage() {
this.lastUsed = System.currentTimeMillis();
}
public boolean usedSince(long duration) {
return this.lastUsed > System.currentTimeMillis() - duration;
public boolean usedInTheLast(long duration, TimeUnit unit) {
return this.lastUsed > System.currentTimeMillis() - unit.toMillis(duration);
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import com.google.common.collect.ImmutableList;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.metastacking;
package me.lucko.luckperms.common.cacheddata.metastack;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -220,8 +220,11 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return track != null && origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
return origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
}
@Override
@@ -250,8 +253,11 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return track != null && !track.containsGroup(origin.getName());
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName()));
}
@Override
@@ -305,7 +311,7 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return !this.groupName.equals(origin.getName());
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && this.groupName.equals(origin.getName()));
}
@Override
@@ -0,0 +1,57 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.Nullable;
public abstract class AbstractResult<T, N extends Node, S extends AbstractResult<T, N, S>> implements Result<T, N> {
/** The node that caused the result */
protected final N node;
/** A reference to another result that this one overrides */
protected S overriddenResult;
public AbstractResult(N node, S overriddenResult) {
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
public final @Nullable N node() {
return this.node;
}
public final @Nullable S overriddenResult() {
return this.overriddenResult;
}
public final void setOverriddenResult(S overriddenResult) {
this.overriddenResult = overriddenResult;
}
}
@@ -25,30 +25,23 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.WeightNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of an integer meta lookup
*
* @param <N> the node type
*/
public final class IntegerResult<N extends Node> implements Result<Integer, N> {
public final class IntegerResult<N extends Node> extends AbstractResult<Integer, N, IntegerResult<N>> {
/** The result */
private final int result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private IntegerResult<N> overriddenResult;
public IntegerResult(int result, N node, IntegerResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -73,19 +66,6 @@ public final class IntegerResult<N extends Node> implements Result<Integer, N> {
}
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable IntegerResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(IntegerResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public boolean isNull() {
return this == NULL_RESULT;
}
@@ -25,7 +25,6 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.ChatMetaNode;
import net.luckperms.api.node.types.MetaNode;
@@ -36,19 +35,14 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* @param <N> the node type
*/
public final class StringResult<N extends Node> implements Result<String, N> {
public final class StringResult<N extends Node> extends AbstractResult<String, N, StringResult<N>> {
/** The result, nullable */
private final String result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private StringResult<N> overriddenResult;
public StringResult(String result, N node, StringResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -56,19 +50,6 @@ public final class StringResult<N extends Node> implements Result<String, N> {
return this.result;
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable StringResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(StringResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public StringResult<N> copy() {
return new StringResult<>(this.result, this.node, this.overriddenResult);
}
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.cacheddata.result;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.util.Tristate;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -36,20 +35,16 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of a {@link PermissionCalculator} lookup.
*/
public final class TristateResult implements Result<Tristate, Node> {
public final class TristateResult extends AbstractResult<Tristate, Node, TristateResult> {
/** The result */
private final Tristate result;
/** The node that caused the result */
private final Node node;
/** The permission processor that provided the result */
private final Class<? extends PermissionProcessor> processorClass;
/** A reference to another result that this one overrides */
private TristateResult overriddenResult;
private TristateResult(Tristate result, Node node, Class<? extends PermissionProcessor> processorClass) {
super(node, null);
this.result = result;
this.node = node;
this.processorClass = processorClass;
}
@@ -58,11 +53,6 @@ public final class TristateResult implements Result<Tristate, Node> {
return this.result;
}
@Override
public @Nullable Node node() {
return this.node;
}
public @Nullable Class<? extends PermissionProcessor> processorClass() {
return this.processorClass;
}
@@ -79,14 +69,6 @@ public final class TristateResult implements Result<Tristate, Node> {
}
}
public @Nullable TristateResult overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(TristateResult overriddenResult) {
this.overriddenResult = overriddenResult;
}
@Override
public String toString() {
return "TristateResult(" +
@@ -79,6 +79,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -101,7 +102,7 @@ public class CommandManager {
.build()
);
private final AtomicBoolean executingCommand = new AtomicBoolean(false);
private final ExpiringSet<UUID> playerRateLimit = new ExpiringSet<>(500, TimeUnit.MILLISECONDS);
private final Set<UUID> playerRateLimit = ExpiringSet.newExpiringSet(500, TimeUnit.MILLISECONDS);
private final TabCompletions tabCompletions;
private final Map<String, Command<?>> mainCommands;
@@ -154,11 +155,20 @@ public class CommandManager {
public CompletableFuture<Void> executeCommand(Sender sender, String label, List<String> args) {
UUID uniqueId = sender.getUniqueId();
if (this.plugin.getConfiguration().get(ConfigKeys.COMMANDS_RATE_LIMIT) && !sender.isConsole() && !this.playerRateLimit.add(uniqueId)) {
if (this.plugin.getConfiguration().get(ConfigKeys.COMMANDS_RATE_LIMIT) && !sender.isConsole() && !Sender.CONSOLE_UUID.equals(uniqueId) && !this.playerRateLimit.add(uniqueId)) {
this.plugin.getLogger().warn("Player '" + uniqueId + "' is spamming LuckPerms commands. Ignoring further inputs.");
return CompletableFuture.completedFuture(null);
}
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
Message.COMMANDS_DISABLED.send(sender);
return CompletableFuture.completedFuture(null);
}
SchedulerAdapter scheduler = this.plugin.getBootstrap().getScheduler();
List<String> argsCopy = new ArrayList<>(args);
@@ -285,6 +295,14 @@ public class CommandManager {
}
public List<String> tabCompleteCommand(Sender sender, List<String> arguments) {
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
return Collections.emptyList();
}
applyConvenienceAliases(arguments, false);
final List<Command<?>> mains = this.mainCommands.values().stream()
@@ -31,6 +31,7 @@ import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -66,7 +67,7 @@ public abstract class ChildCommand<T> extends Command<T> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -35,6 +35,7 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -114,7 +115,7 @@ public abstract class GenericChildCommand {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -86,39 +86,44 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
return;
}
final String targetArgument = args.get(0);
I targetId = null;
if (this.type == Type.TAKES_ARGUMENT_FOR_TARGET) {
targetId = parseTarget(targetArgument, plugin, sender);
if (this.type == Type.TARGETED) {
final String targetArgument = args.get(0);
I targetId = parseTarget(targetArgument, plugin, sender);
if (targetId == null) {
return;
}
}
ReentrantLock lock = getLockForTarget(targetId);
lock.lock();
try {
T target = getTarget(targetId, plugin, sender);
if (target == null) {
return;
}
ReentrantLock lock = getLockForTarget(targetId);
lock.lock();
try {
sub.execute(plugin, sender, target, args.subList(this.type.minArgs, args.size()), label);
T target = getTarget(targetId, plugin, sender);
if (target == null) {
return;
}
try {
sub.execute(plugin, sender, target, args.subList(this.type.minArgs, args.size()), label);
} catch (CommandException e) {
e.handle(sender, label, sub);
}
cleanup(target, plugin);
} finally {
lock.unlock();
}
} else {
try {
sub.execute(plugin, sender, null, args.subList(this.type.minArgs, args.size()), label);
} catch (CommandException e) {
e.handle(sender, label, sub);
}
cleanup(target, plugin);
} finally {
lock.unlock();
}
}
@Override
public List<String> tabComplete(LuckPermsPlugin plugin, Sender sender, ArgumentList args) {
switch (this.type) {
case TAKES_ARGUMENT_FOR_TARGET:
case TARGETED:
return TabCompleter.create()
.at(0, CompletionSupplier.startsWith(() -> getTargets(plugin).stream()))
.at(1, CompletionSupplier.startsWith(() -> getChildren().stream()
@@ -133,7 +138,7 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
.orElse(Collections.emptyList())
)
.complete(args);
case NO_TARGET_ARGUMENT:
case NOT_TARGETED:
return TabCompleter.create()
.at(0, CompletionSupplier.startsWith(() -> getChildren().stream()
.filter(s -> s.isAuthorized(sender))
@@ -178,21 +183,31 @@ public abstract class ParentCommand<T, I> extends Command<Void> {
return getChildren().stream().anyMatch(sc -> sc.isAuthorized(sender));
}
protected abstract List<String> getTargets(LuckPermsPlugin plugin);
protected List<String> getTargets(LuckPermsPlugin plugin) {
throw new UnsupportedOperationException();
}
protected abstract I parseTarget(String target, LuckPermsPlugin plugin, Sender sender);
protected I parseTarget(String target, LuckPermsPlugin plugin, Sender sender) {
throw new UnsupportedOperationException();
}
protected abstract ReentrantLock getLockForTarget(I target);
protected ReentrantLock getLockForTarget(I target) {
throw new UnsupportedOperationException();
}
protected abstract T getTarget(I target, LuckPermsPlugin plugin, Sender sender);
protected T getTarget(I target, LuckPermsPlugin plugin, Sender sender) {
throw new UnsupportedOperationException();
}
protected abstract void cleanup(T t, LuckPermsPlugin plugin);
protected void cleanup(T t, LuckPermsPlugin plugin) {
throw new UnsupportedOperationException();
}
public enum Type {
// e.g. /lp log sub-command....
NO_TARGET_ARGUMENT(0),
NOT_TARGETED(0),
// e.g. /lp user <USER> sub-command....
TAKES_ARGUMENT_FOR_TARGET(1);
TARGETED(1);
private final int cmdIndex;
private final int minArgs;
@@ -33,6 +33,7 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -70,7 +71,7 @@ public abstract class SingleCommand extends Command<Void> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(Component.space(), argUsages))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.build();
}
@@ -32,143 +32,143 @@ import me.lucko.luckperms.common.sender.Sender;
*/
public enum CommandPermission {
SYNC("sync", Type.NONE),
INFO("info", Type.NONE),
EDITOR("editor", Type.NONE),
VERBOSE("verbose", Type.NONE),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE),
TREE("tree", Type.NONE),
SEARCH("search", Type.NONE),
IMPORT("import", Type.NONE),
EXPORT("export", Type.NONE),
RELOAD_CONFIG("reloadconfig", Type.NONE),
BULK_UPDATE("bulkupdate", Type.NONE),
APPLY_EDITS("applyedits", Type.NONE),
TRUST_EDITOR("trusteditor", Type.NONE),
TRANSLATIONS("translations", Type.NONE),
SYNC("sync", Type.NONE, true),
INFO("info", Type.NONE, true),
EDITOR("editor", Type.NONE, true),
VERBOSE("verbose", Type.NONE, true),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE, false),
TREE("tree", Type.NONE, true),
SEARCH("search", Type.NONE, true),
IMPORT("import", Type.NONE, false),
EXPORT("export", Type.NONE, true),
RELOAD_CONFIG("reloadconfig", Type.NONE, true),
BULK_UPDATE("bulkupdate", Type.NONE, false),
APPLY_EDITS("applyedits", Type.NONE, false),
TRUST_EDITOR("trusteditor", Type.NONE, false),
TRANSLATIONS("translations", Type.NONE, true),
CREATE_GROUP("creategroup", Type.NONE),
DELETE_GROUP("deletegroup", Type.NONE),
LIST_GROUPS("listgroups", Type.NONE),
CREATE_GROUP("creategroup", Type.NONE, false),
DELETE_GROUP("deletegroup", Type.NONE, false),
LIST_GROUPS("listgroups", Type.NONE, true),
CREATE_TRACK("createtrack", Type.NONE),
DELETE_TRACK("deletetrack", Type.NONE),
LIST_TRACKS("listtracks", Type.NONE),
CREATE_TRACK("createtrack", Type.NONE, false),
DELETE_TRACK("deletetrack", Type.NONE, false),
LIST_TRACKS("listtracks", Type.NONE, true),
USER_INFO("info", Type.USER),
USER_PERM_INFO("permission.info", Type.USER),
USER_PERM_SET("permission.set", Type.USER),
USER_PERM_UNSET("permission.unset", Type.USER),
USER_PERM_SET_TEMP("permission.settemp", Type.USER),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER),
USER_PERM_CHECK("permission.check", Type.USER),
USER_PERM_CLEAR("permission.clear", Type.USER),
USER_PARENT_INFO("parent.info", Type.USER),
USER_PARENT_SET("parent.set", Type.USER),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER),
USER_PARENT_ADD("parent.add", Type.USER),
USER_PARENT_REMOVE("parent.remove", Type.USER),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER),
USER_PARENT_CLEAR("parent.clear", Type.USER),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER),
USER_META_INFO("meta.info", Type.USER),
USER_META_SET("meta.set", Type.USER),
USER_META_UNSET("meta.unset", Type.USER),
USER_META_SET_TEMP("meta.settemp", Type.USER),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER),
USER_META_SET_PREFIX("meta.setprefix", Type.USER),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER),
USER_META_CLEAR("meta.clear", Type.USER),
USER_EDITOR("editor", Type.USER),
USER_SHOW_TRACKS("showtracks", Type.USER),
USER_PROMOTE("promote", Type.USER),
USER_DEMOTE("demote", Type.USER),
USER_CLEAR("clear", Type.USER),
USER_CLONE("clone", Type.USER),
USER_INFO("info", Type.USER, true),
USER_PERM_INFO("permission.info", Type.USER, true),
USER_PERM_SET("permission.set", Type.USER, false),
USER_PERM_UNSET("permission.unset", Type.USER, false),
USER_PERM_SET_TEMP("permission.settemp", Type.USER, false),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER, false),
USER_PERM_CHECK("permission.check", Type.USER, true),
USER_PERM_CLEAR("permission.clear", Type.USER, false),
USER_PARENT_INFO("parent.info", Type.USER, true),
USER_PARENT_SET("parent.set", Type.USER, false),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER, false),
USER_PARENT_ADD("parent.add", Type.USER, false),
USER_PARENT_REMOVE("parent.remove", Type.USER, false),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER, false),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER, false),
USER_PARENT_CLEAR("parent.clear", Type.USER, false),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER, false),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER, false),
USER_META_INFO("meta.info", Type.USER, true),
USER_META_SET("meta.set", Type.USER, false),
USER_META_UNSET("meta.unset", Type.USER, false),
USER_META_SET_TEMP("meta.settemp", Type.USER, false),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER, false),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER, false),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER, false),
USER_META_SET_PREFIX("meta.setprefix", Type.USER, false),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER, false),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER, false),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER, false),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER, false),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER, false),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER, false),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER, false),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER, false),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER, false),
USER_META_CLEAR("meta.clear", Type.USER, false),
USER_EDITOR("editor", Type.USER, true),
USER_SHOW_TRACKS("showtracks", Type.USER, true),
USER_PROMOTE("promote", Type.USER, false),
USER_DEMOTE("demote", Type.USER, false),
USER_CLEAR("clear", Type.USER, false),
USER_CLONE("clone", Type.USER, false),
GROUP_INFO("info", Type.GROUP),
GROUP_PERM_INFO("permission.info", Type.GROUP),
GROUP_PERM_SET("permission.set", Type.GROUP),
GROUP_PERM_UNSET("permission.unset", Type.GROUP),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP),
GROUP_PERM_CHECK("permission.check", Type.GROUP),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP),
GROUP_PARENT_INFO("parent.info", Type.GROUP),
GROUP_PARENT_SET("parent.set", Type.GROUP),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP),
GROUP_PARENT_ADD("parent.add", Type.GROUP),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP),
GROUP_META_INFO("meta.info", Type.GROUP),
GROUP_META_SET("meta.set", Type.GROUP),
GROUP_META_UNSET("meta.unset", Type.GROUP),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP),
GROUP_META_CLEAR("meta.clear", Type.GROUP),
GROUP_EDITOR("editor", Type.GROUP),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP),
GROUP_SET_WEIGHT("setweight", Type.GROUP),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP),
GROUP_CLEAR("clear", Type.GROUP),
GROUP_RENAME("rename", Type.GROUP),
GROUP_CLONE("clone", Type.GROUP),
GROUP_INFO("info", Type.GROUP, true),
GROUP_PERM_INFO("permission.info", Type.GROUP, true),
GROUP_PERM_SET("permission.set", Type.GROUP, false),
GROUP_PERM_UNSET("permission.unset", Type.GROUP, false),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP, false),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP, false),
GROUP_PERM_CHECK("permission.check", Type.GROUP, true),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP, false),
GROUP_PARENT_INFO("parent.info", Type.GROUP, true),
GROUP_PARENT_SET("parent.set", Type.GROUP, false),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP, false),
GROUP_PARENT_ADD("parent.add", Type.GROUP, false),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP, false),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP, false),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP, false),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP, false),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP, false),
GROUP_META_INFO("meta.info", Type.GROUP, true),
GROUP_META_SET("meta.set", Type.GROUP, false),
GROUP_META_UNSET("meta.unset", Type.GROUP, false),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP, false),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP, false),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP, false),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP, false),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP, false),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP, false),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP, false),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP, false),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP, false),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP, false),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP, false),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP, false),
GROUP_META_CLEAR("meta.clear", Type.GROUP, false),
GROUP_EDITOR("editor", Type.GROUP, true),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP, true),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP, true),
GROUP_SET_WEIGHT("setweight", Type.GROUP, false),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP, false),
GROUP_CLEAR("clear", Type.GROUP, false),
GROUP_RENAME("rename", Type.GROUP, false),
GROUP_CLONE("clone", Type.GROUP, false),
TRACK_INFO("info", Type.TRACK),
TRACK_EDITOR("editor", Type.TRACK),
TRACK_APPEND("append", Type.TRACK),
TRACK_INSERT("insert", Type.TRACK),
TRACK_REMOVE("remove", Type.TRACK),
TRACK_CLEAR("clear", Type.TRACK),
TRACK_RENAME("rename", Type.TRACK),
TRACK_CLONE("clone", Type.TRACK),
TRACK_INFO("info", Type.TRACK, true),
TRACK_EDITOR("editor", Type.TRACK, true),
TRACK_APPEND("append", Type.TRACK, false),
TRACK_INSERT("insert", Type.TRACK, false),
TRACK_REMOVE("remove", Type.TRACK, false),
TRACK_CLEAR("clear", Type.TRACK, false),
TRACK_RENAME("rename", Type.TRACK, false),
TRACK_CLONE("clone", Type.TRACK, false),
LOG_RECENT("recent", Type.LOG),
LOG_USER_HISTORY("userhistory", Type.LOG),
LOG_GROUP_HISTORY("grouphistory", Type.LOG),
LOG_TRACK_HISTORY("trackhistory", Type.LOG),
LOG_SEARCH("search", Type.LOG),
LOG_NOTIFY("notify", Type.LOG),
LOG_RECENT("recent", Type.LOG, true),
LOG_USER_HISTORY("userhistory", Type.LOG, true),
LOG_GROUP_HISTORY("grouphistory", Type.LOG, true),
LOG_TRACK_HISTORY("trackhistory", Type.LOG, true),
LOG_SEARCH("search", Type.LOG, true),
LOG_NOTIFY("notify", Type.LOG, true),
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE),
SPONGE_OPTION_INFO("option.info", Type.SPONGE),
SPONGE_OPTION_SET("option.set", Type.SPONGE),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE);
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE, true),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE, false),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE, false),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE, true),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE, false),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE, false),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE, false),
SPONGE_OPTION_INFO("option.info", Type.SPONGE, true),
SPONGE_OPTION_SET("option.set", Type.SPONGE, false),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE, false),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE, false);
public static final String ROOT = "luckperms.";
@@ -176,9 +176,11 @@ public enum CommandPermission {
private final String permission;
private final Type type;
private final boolean readOnly;
CommandPermission(String node, Type type) {
CommandPermission(String node, Type type, boolean readOnly) {
this.type = type;
this.readOnly = readOnly;
if (type == Type.NONE) {
this.node = node;
@@ -205,6 +207,10 @@ public enum CommandPermission {
return this.type;
}
public boolean isReadOnly() {
return this.readOnly;
}
public enum Type {
NONE(null),
Loaded 100 of 362 files, more files were not shown because too many files have changed in this diff. Show more