{"id":20032680,"url":"https://github.com/lucko/synapse","last_synced_at":"2025-05-05T05:30:41.340Z","repository":{"id":145748479,"uuid":"115013025","full_name":"lucko/synapse","owner":"lucko","description":null,"archived":false,"fork":false,"pushed_at":"2021-02-16T14:25:31.000Z","size":66,"stargazers_count":5,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-08T17:09:36.606Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lucko.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-12-21T14:14:08.000Z","updated_at":"2024-01-04T15:49:27.000Z","dependencies_parsed_at":"2023-04-04T14:47:27.214Z","dependency_job_id":null,"html_url":"https://github.com/lucko/synapse","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucko%2Fsynapse","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucko%2Fsynapse/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucko%2Fsynapse/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lucko%2Fsynapse/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lucko","download_url":"https://codeload.github.com/lucko/synapse/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252445627,"owners_count":21749090,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T09:38:44.129Z","updated_at":"2025-05-05T05:30:41.332Z","avatar_url":"https://github.com/lucko.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# synapse\n\nA work-in-progress API for interacting with Bukkit permission plugins.\n\n### Aims\n\n* **Intuitive**: the API should be easy for less experienced programmers to understand (no more than Bukkit itself is).\n* **Flexible**: permission plugins support concepts such as expiry times and contexts to varying extents (e.g. not at all, or only for certain things - users only, not for prefixes). The API should elegantly accommodate both ends of the scale.\n* **Simple**: aim to satisfy 80% of use-cases, not absolutely everything.\n\n### Design decisions\n\nThese aren't set in stone, just what I've done so far.\n\n#### Async friendly, but not with CompletableFutures\n\nThe API is written with consideration that some operations will require database queries and therefore should not be executed on the main thread. The idiomatic thing to do would be to return a `CompletableFuture` from these methods. \n\nHowever, in my experience, the learning curve for CompletableFuture is too steep for most users (especially those that are less experienced with Java and/or concurrent programming).\n\nInstead, synapse \"wraps\" CompletableFuture with its own interfaces.\n\n```java\npublic interface FutureAction {\n    void whenComplete(Plugin plugin, Runnable callback);\n    void join();\n    CompletableFuture\u003cVoid\u003e asFuture();\n}\n\npublic interface FutureResult\u003cT\u003e {\n    void whenComplete(Plugin plugin, Consumer\u003c? super T\u003e callback);\n    T join();\n    CompletableFuture\u003cT\u003e asFuture();\n}\n```\n\nThese interfaces provide the most common use-cases:\n* run a callback on the main thread once complete (takes `Plugin` as a parameter to run the callback via the Bukkit scheduler)\n* block until complete (if on an async thread already)\n\n#### Tied to Bukkit, not platform agnostic\n\nMaybe this is a mistake, but at the moment it has two advantages:\n\n1. synapse methods can accept `Player` parameters - this keeps things simple for users, and prevents mistakes. (not a huge deal I suppose, but it's nice to have)\n2. Means we can support the `whenComplete` methods on the wrapper Future as mentioned above.\n\nI'm open to changing this, but we'd need to find an acceptable alternative to points 1 \u0026 (especially) 2 above.\n\n#### Property system\n\nThe `Property` API is my attempt at solving the **flexible** aim. (\"permission plugins support concepts such as expiry times and contexts to varying extents (e.g. not at all, or only for certain things - users only, not for prefixes). The API should elegantly accommodate both ends of the scale\")\n\nIt's not perfect (and could maybe use a better name), but I think it works. There are some examples below, so you decide for yourself. :)\n\nThe `PermissionService` interface has a method to check which properties are supported for certain operations, and the `PropertyBuilder` has similar methods too. This allows users to decide how to react when the level of support for a certain feature (property) differs.\n\n### Example usage\n\n```java\npackage me.lucko.test;\n\nimport com.google.common.collect.ImmutableSet;\nimport me.lucko.synapse.context.Context;\nimport me.lucko.synapse.context.ContextCalculator;\nimport me.lucko.synapse.context.ContextService;\nimport me.lucko.synapse.permission.PermissionService;\nimport me.lucko.synapse.permission.membership.GroupMembership;\nimport me.lucko.synapse.permission.node.PermissionNode;\nimport me.lucko.synapse.permission.property.Property;\nimport me.lucko.synapse.permission.subject.Group;\nimport me.lucko.synapse.permission.subject.User;\nimport org.bukkit.GameMode;\nimport org.bukkit.entity.Player;\nimport org.bukkit.plugin.java.JavaPlugin;\n\nimport java.time.Instant;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Locale;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\n\npublic class Example extends JavaPlugin {\n\n  private PermissionService permissions;\n  private ContextService contexts;\n\n  @Override\n  public void onEnable() {\n    // 'PermissionService' is intended to replace the functionality provided by the\n    // Vault Permissions API.\n    // It should be easily implementable by (or on behalf of) all existing plugins.\n    // In theory, we should even be able to use Vault to implement it, and vice-versa.\n    this.permissions = getServer().getServicesManager().load(PermissionService.class);\n\n    // 'ContextService' is intended for modern (permission) plugins.\n    // I guess it'll mostly target PEX and LP - but synapse will provide a default\n    // implementation too, so other plugins can make use of contexts even if a supported\n    // perms plugin isn't installed.\n    this.contexts = getServer().getServicesManager().load(ContextService.class);\n\n    // We can register context calculators quite easily:\n    // This is bound to our plugin instance, so the provider can automatically cleanup\n    // if this plugin gets unloaded.\n    this.contexts.registerContext(this, \"myplugin:gamemode\", new ContextCalculator() {\n      @Override\n      public void calculate(Player target, Consumer\u003cString\u003e consumer) {\n        consumer.accept(target.getGameMode().name().toLowerCase(Locale.ROOT));\n      }\n\n      @Override\n      public Collection\u003cString\u003e possibleValues() {\n        return Arrays.stream(GameMode.values())\n            .map(gameMode -\u003e gameMode.name().toLowerCase(Locale.ROOT))\n            .collect(Collectors.toList());\n      }\n    });\n  }\n\n  public void doSomethingWithPermissions(Player player) {\n    // easily get an object containing all permissions data for a player.\n    User user = this.permissions.users().get(player);\n\n    // then set a permission!\n    user.setPermission(\"test\").whenComplete(this, () -\u003e {\n      player.sendMessage(\"wow you have a new permission!!\");\n    });\n\n    // set permissions with special properties - like negation or an expiry!\n    Instant expiryTime = Instant.now().plus(1, ChronoUnit.HOURS);\n    user.setPermission(\"example.permission\", props -\u003e\n        props.with(Property.NEGATED, true).with(Property.EXPIRY, expiryTime)\n    );\n\n    // get groups!\n    Group adminGroup = this.permissions.groups().get(\"admin\");\n    if (adminGroup == null) {\n      throw new RuntimeException(\"oh no!\");\n    }\n\n    // add users to groups, again - with properties!\n    user.addGroup(adminGroup, props -\u003e props.with(Property.REQUIRED_WORLD, \"nether\"));\n\n    // also supports contexts!\n    user.setPermission(\"fly.allow\", props -\u003e\n        props.with(Property.REQUIRED_CONTEXT, ImmutableSet.of(Context.of(\"gamemode\", \"creative\")))\n    );\n\n    // have a look at their permissions\n    for (PermissionNode permission : user.getPermissions()) {\n      player.sendMessage(\"you have permission \" + permission.getPermission());\n      player.sendMessage(\"it has the following properties! \" + permission.properties());\n    }\n\n    // and their group memberships!\n    for (GroupMembership membership : user.getGroups()) {\n      player.sendMessage(\"you are in the group \" + membership.getGroup().getName());\n      player.sendMessage(\"it has the following properties! \" + membership.properties());\n    }\n\n    // query prefixes/suffixes/metadata\n    String prefix = user.getPrefix();\n    String suffix = user.getSuffix();\n    String homes = user.getMetadata(\"homes\");\n\n    // and set these in the same way...\n    user.setPrefix(\"[ADMIN]\", props -\u003e props.with(Property.REQUIRED_SERVER, \"survival\"));\n  }\n\n  public void doSomethingWithContexts(Player player) {\n    // you can query contexts for players quite easily!\n    Set\u003cContext\u003e contexts = this.contexts.queryContexts(player);\n\n    for (Context context : contexts) {\n      player.sendMessage(context.key() + \" -\u003e \" + context.value());\n    }\n  }\n}\n\n```","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucko%2Fsynapse","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flucko%2Fsynapse","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flucko%2Fsynapse/lists"}