From eeac083cbb92349fbb7ae4defd3816b7bf2a5a1b Mon Sep 17 00:00:00 2001 From: Jacob Gillespie Date: Sun, 8 Nov 2015 21:45:40 -0600 Subject: [PATCH] Update README, finish code, add Travis --- .travis.yml | 13 +++ README.md | 52 ++++++----- pom.xml | 31 +++---- .../discoursegroupsync/Configuration.kt | 35 ++++---- .../discoursegroupsync/DiscourseGroupSync.kt | 15 ++++ .../gg/obsidian/discoursegroupsync/Group.kt | 2 +- .../obsidian/discoursegroupsync/UUIDHelper.kt | 20 +++-- .../gg/obsidian/discoursegroupsync/User.kt | 2 +- .../obsidian/discoursegroupsync/UserManger.kt | 87 +++++++++++++++---- src/main/resources/config.yml | 19 +++- src/main/resources/plugin.yml | 2 + 11 files changed, 195 insertions(+), 83 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c84c06f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: java +jdk: +- oraclejdk8 + +deploy: + provider: releases + api_key: + secure: fUPNY9FkaCCpKytHSCCaOibPAxdJtEOsxtRXXlNgTPQ0dCXbuwJlLUwuy9bqR0ZO4t2L3UritbZ8yNrZnFiaBAxJQzLtpFOGgQdOaQUgukL4KW33oIy2+TUnV/JN/xGhXCsb6Ija9wqEVeWVTTCqDjPZYeECe46Z5NhdZsiA8RUB5IIouisnJFhM4rdY8vyLI3Xvp3WGBPxVOD+uZlALWZrUiqPhe9CJpgoOkuwijQHNb39CYtknW6Q1Z+Pg1HcukUDDpZZnHhp1lXDJOQAjdg5MAMhcpS6rk6HINPmtnmE6gCD74Iofr9NAGbLefD95UkSJUhyUOQI9mGY5nv3aCgBHIXa+9NuXsqAzkF3tXAbYAtkKf42QJzWFSRMfaBA96oADR/oCUTwVE1O+SnfGBLy2XgBWEVDZ76zaUiad7ttx+TtAhNm80ab67zBkBoMZLTKrQzy9BN5uY9B/Gc42hfiPhNwBRTlWJN8FIL8tdh3wt+nGQc5wMBKpZFm+BHPUP/GnrspbrvVka6Tfc/yavFrqOzgkbvWal54V+YHbwTozEwjTa6anIsI2bKlJwU0tlOAyHUKydiOjMDO9iV/r3y4er8nHLnbqfjEYRtFuIv5n6d9MzK0wa8eJUUlAvTMnuE82Onw2Sz9wAsFszaPSYRVkalRyV9S+HO/HMvftVU0= + file: target/DiscordBridge-*.jar + file_glob: true + on: + repo: the-obsidian/DiscordBridge + tags: true diff --git a/README.md b/README.md index ac68407..6accd8e 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,49 @@ -# DiscordBridge [![Build Status](https://travis-ci.org/the-obsidian/DiscordBridge.svg?branch=master)](https://travis-ci.org/the-obsidian/DiscordBridge) +# DiscourseGroupSync [![Build Status](https://travis-ci.org/the-obsidian/DiscourseGroupSync.svg?branch=master)](https://travis-ci.org/the-obsidian/DiscourseGroupSync) -Bridges chat between Discord and Minecraft (Bukkit/Spigot). +Synchronizes Minecraft groups with Discourse groups + +## Dependencies + +* Vault ## Installation -1. Download the [latest release](https://github.com/the-obsidian/DiscordBridge/releases) from GitHub +1. Download the [latest release](https://github.com/the-obsidian/DiscourseGroupSync/releases) from GitHub 1. Add it to your `plugins` folder -1. Either run Bukkit/Spigot once to generate `DiscordBridge/config.yml` or create it using the guide below. +1. Either run Bukkit/Spigot once to generate `DiscourseGroupSync/config.yml` or create it using the guide below. 1. All done! ## Configuration -DiscordBridge has several options that can be configured in the `config.yml` file: +DiscourseGroupSync has several options that can be configured in the `config.yml` file: ```yaml -settings: - server-id: '00000000' - channel: 'test' - username: 'username' - email: 'email@example.com' - password: 'password' +# The URL of your Discourse installation (without the trailing slash) +discourse-url: http://forum.example.com + +# A mapping between Discourse groups (by integer ID) and Minecraft groups (by name) +groups: + + # Add user to groupname when they are in Discourse group 4 + - discourse: 4 + minecraft: groupname + + # Remove user from groupname when they are not in Discourse group 4 + - discourse: 4 + minecraft: groupname + remove: true + + # Add user to guestgroup if they do not have any Discourse groups + - discourse: 0 + minecraft: guestgroup ``` -* `server-id` is the ID if your Discord server. This can be found under *Server Settings > Widget > Server ID* -* `channel` is the Discord channel name you would like to bridge with your Minecraft server -* `username` is the Discord username of your bot user -* `email` is the Discord email address of your bot user -* `password` is the Discord password of your bot user +`discourse` keys are the IDs of your chosen Discourse groups. A negative number means the absence of the group, so `discourse: -20` would target users who were not a member of group `20`. `0` is a special group meaning users who are not a member of any Discourse groups. ## Features -* Anything said in Minecraft chat will be sent to your chosen Discord channel -* Anything said in your chosen Discord channel will be sent to your Minecraft chat (with a `(discord)` suffix added to usernames) +* Synchronizes Minecraft groups with Discourse groups on player join ## Upcoming Features -* Ability to customize the display format -* Deeper integration into Minecraft chat (like supporting chat channels inside Minecraft) -* A "merge accounts" function to allow Minecraft players to associate their Discord accounts with their Minecraft accounts so that usernames are properly translated -* Ability to post messages to Discord on behalf of Discord users, rather than using a bot user (hopefully after the official API is released) +* Add more hooks for synchronization, including recurring tasks and possibly webhooks diff --git a/pom.xml b/pom.xml index 1bf8c50..46a7dc2 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/groups/public/ + + vault-repo + http://nexus.theyeticave.net/content/repositories/pub_releases + jitpack.io https://jitpack.io @@ -44,20 +48,18 @@ kotlin-stdlib ${kotlin.version} + - com.github.wareninja - discourse-api-client - 0.0.3 - - - com.google.http-client - google-http-client-gson - 1.20.0 + com.squareup.okhttp + okhttp + 2.5.0 + - com.mashape.unirest - unirest-java - 1.4.7 + net.milkbowl.vault + VaultAPI + 1.5 + compile @@ -113,10 +115,9 @@ true false - - org.jetbrains.kotlin:* - com.github.wareninja:discourse-api-client:* - + + org.spigotmc:* + diff --git a/src/main/kotlin/gg/obsidian/discoursegroupsync/Configuration.kt b/src/main/kotlin/gg/obsidian/discoursegroupsync/Configuration.kt index 47ca970..6f8bf90 100644 --- a/src/main/kotlin/gg/obsidian/discoursegroupsync/Configuration.kt +++ b/src/main/kotlin/gg/obsidian/discoursegroupsync/Configuration.kt @@ -5,32 +5,35 @@ import java.util.* class Configuration(val plugin: DiscourseGroupSync) { var DISCOURSE_URL = "" - var GROUPS: MutableMap = HashMap() + var GROUPS: HashSet = HashSet() fun load() { plugin.reloadConfig() DISCOURSE_URL = plugin.getConfig().getString("discourse-url") - if (plugin.getConfig().isList("groups")) { - for (rawDefinition in plugin.getConfig().getMapList("groups")) { - val definition = rawDefinition as Map - var discourseGroup: Int? = null - var minecraftGroup: String? = null + for (rawDefinition in plugin.getConfig().getMapList("groups")) { + val definition = rawDefinition as Map + var discourseGroup: Int? = null + var minecraftGroup: String? = null + var remove = false - if (definition.contains("discourse") && definition.getRaw("discourse") is String) { - discourseGroup = definition["discourse"] as Int - } - - if (definition.contains("minecraft")) { - minecraftGroup = definition["minecraft"] as String - } + if (definition.contains("discourse")) { + discourseGroup = definition["discourse"] as Int + } - if (discourseGroup == null || minecraftGroup == null) return + if (definition.contains("minecraft")) { + minecraftGroup = definition["minecraft"] as String + } - val group = Group(discourseGroup, minecraftGroup) - GROUPS.put(discourseGroup, group) + if (definition.contains("remove")) { + remove = definition["remove"] as Boolean } + + if (discourseGroup == null || minecraftGroup == null) continue + + val group = Group(discourseGroup, minecraftGroup, remove) + GROUPS.add(group) } } } diff --git a/src/main/kotlin/gg/obsidian/discoursegroupsync/DiscourseGroupSync.kt b/src/main/kotlin/gg/obsidian/discoursegroupsync/DiscourseGroupSync.kt index 2950d0d..6590a28 100644 --- a/src/main/kotlin/gg/obsidian/discoursegroupsync/DiscourseGroupSync.kt +++ b/src/main/kotlin/gg/obsidian/discoursegroupsync/DiscourseGroupSync.kt @@ -1,8 +1,10 @@ package gg.obsidian.discoursegroupsync +import net.milkbowl.vault.permission.Permission import org.bukkit.event.EventHandler import org.bukkit.event.Listener import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.plugin.RegisteredServiceProvider import org.bukkit.plugin.java.JavaPlugin import java.io.File @@ -10,6 +12,7 @@ class DiscourseGroupSync : JavaPlugin(), Listener { val config = Configuration(this) val userManager = UserManager(this) + var permissions: Permission? = null override fun onEnable() { val configFile = File(dataFolder, "config.yml") @@ -20,6 +23,12 @@ class DiscourseGroupSync : JavaPlugin(), Listener { config.load() + if (!setupPermissions()) { + logger.severe("Disabled due to no Vault dependency found!") + server.pluginManager.disablePlugin(this) + return + } + server.pluginManager.registerEvents(this, this) } @@ -27,4 +36,10 @@ class DiscourseGroupSync : JavaPlugin(), Listener { fun onJoin(e: PlayerJoinEvent) { userManager.syncGroups(e.player) } + + fun setupPermissions(): Boolean { + val rsp: RegisteredServiceProvider = getServer().getServicesManager().getRegistration(Permission::class.java) + permissions = rsp.provider + return permissions != null + } } diff --git a/src/main/kotlin/gg/obsidian/discoursegroupsync/Group.kt b/src/main/kotlin/gg/obsidian/discoursegroupsync/Group.kt index e4e3c08..7b5b204 100644 --- a/src/main/kotlin/gg/obsidian/discoursegroupsync/Group.kt +++ b/src/main/kotlin/gg/obsidian/discoursegroupsync/Group.kt @@ -1,3 +1,3 @@ package gg.obsidian.discoursegroupsync -data class Group(var discourseGroup: Int, var minecraftGroup: String) +data class Group(val discourseGroup: Int, val minecraftGroup: String, val remove: Boolean = false) diff --git a/src/main/kotlin/gg/obsidian/discoursegroupsync/UUIDHelper.kt b/src/main/kotlin/gg/obsidian/discoursegroupsync/UUIDHelper.kt index 2357723..ca86c33 100644 --- a/src/main/kotlin/gg/obsidian/discoursegroupsync/UUIDHelper.kt +++ b/src/main/kotlin/gg/obsidian/discoursegroupsync/UUIDHelper.kt @@ -1,22 +1,26 @@ package gg.obsidian.discoursegroupsync -import com.mashape.unirest.http.HttpResponse -import com.mashape.unirest.http.JsonNode -import com.mashape.unirest.http.Unirest +import com.squareup.okhttp.OkHttpClient +import com.squareup.okhttp.Request +import org.json.simple.JSONObject +import org.json.simple.JSONValue import java.util.* object UUIDHelper { + val PROFILE_URL = "https://sessionserver.mojang.com/session/minecraft/profile/" + val httpClient = OkHttpClient() fun uuidToUsername(uuid: UUID): String { val url = PROFILE_URL + uuid.toString().replace("-", "") - val jsonResponse: HttpResponse = Unirest.get(url).asJson() - val body = jsonResponse.body.`object` + val request = Request.Builder().url(url).get().build(); + val response = httpClient.newCall(request).execute() + val body = JSONValue.parse(response.body().string()) as JSONObject - if (body.has("error")) { + if (body.containsKey("error")) { return "" } - return body.getString("name") + return body.get("name") as String } -} \ No newline at end of file +} diff --git a/src/main/kotlin/gg/obsidian/discoursegroupsync/User.kt b/src/main/kotlin/gg/obsidian/discoursegroupsync/User.kt index 0c30de6..7f858cd 100644 --- a/src/main/kotlin/gg/obsidian/discoursegroupsync/User.kt +++ b/src/main/kotlin/gg/obsidian/discoursegroupsync/User.kt @@ -2,4 +2,4 @@ package gg.obsidian.discoursegroupsync import java.util.* -data class User(val username: String = "", val minecraftGroups: Set = HashSet(), val exists: Boolean = true) +data class User(val username: String = "", val discourseGroups: Set = HashSet(), val exists: Boolean = true) diff --git a/src/main/kotlin/gg/obsidian/discoursegroupsync/UserManger.kt b/src/main/kotlin/gg/obsidian/discoursegroupsync/UserManger.kt index 90937b2..a0b3d89 100644 --- a/src/main/kotlin/gg/obsidian/discoursegroupsync/UserManger.kt +++ b/src/main/kotlin/gg/obsidian/discoursegroupsync/UserManger.kt @@ -1,43 +1,94 @@ package gg.obsidian.discoursegroupsync -import com.mashape.unirest.http.HttpResponse -import com.mashape.unirest.http.JsonNode -import com.mashape.unirest.http.Unirest +import com.squareup.okhttp.OkHttpClient +import com.squareup.okhttp.Request import org.bukkit.entity.Player +import org.json.simple.JSONArray +import org.json.simple.JSONObject +import org.json.simple.JSONValue import java.util.* class UserManager(val plugin: DiscourseGroupSync) { + val httpClient = OkHttpClient() + fun getDiscourseUser(username: String): User { val url = plugin.config.DISCOURSE_URL + "/users/" + username + ".json" - val jsonResponse: HttpResponse = Unirest.get(url).asJson() + val request = Request.Builder().url(url).get().build(); + val response = httpClient.newCall(request).execute() - if (jsonResponse.status != 200) { + if (response.code() != 200) { return User(exists = false) } - val body = jsonResponse.body.`object` - - val minecraftGroups = HashSet() + val bodyString = response.body().string() + val body = JSONValue.parse(bodyString) as JSONObject + val user = body["user"] as JSONObject + val customGroups = user["custom_groups"] as JSONArray - val customGroups = body.getJSONArray("custom_groups") + val discourseGroups = HashSet() - for (i in (0..customGroups.length() - 1)) { - val group = customGroups.get(i) as JsonNode - val groupId = group.getObject().getInt("id") + for (g in customGroups) { + val group = g as JSONObject + val id = group.get("id") as Long + discourseGroups.add(id.toInt()) + } - if (plugin.config.GROUPS.containsKey(groupId)) { - minecraftGroups.add(plugin.config.GROUPS[groupId].minecraftGroup) - } + if (customGroups.size == 0) { + discourseGroups.add(0) } - return User(username = username, minecraftGroups = HashSet()) + return User(username = username, discourseGroups = discourseGroups) } fun syncGroups(player: Player) { val username = UUIDHelper.uuidToUsername(player.uniqueId) if (username == "") return - val discourseUser = getDiscourseUser(username) + val user = getDiscourseUser(username) + + val groupsToAdd = HashSet() + val groupsToRemove = HashSet() + + for (group in plugin.config.GROUPS) { + val absence = group.discourseGroup < 0 + val discordGroup = if (absence) Math.abs(group.discourseGroup) else group.discourseGroup + val hasGroup = user.discourseGroups.contains(discordGroup) + + if (group.discourseGroup == 0 && user.discourseGroups.size == 0) { + if (group.remove) { + groupsToRemove.add(group.minecraftGroup) + } else { + groupsToAdd.add(group.minecraftGroup) + } + continue + } + + if (hasGroup && !absence) { + if (group.remove) { + groupsToRemove.add(group.minecraftGroup) + } else { + groupsToAdd.add(group.minecraftGroup) + } + } + + if (!hasGroup && absence) { + if (group.remove) { + groupsToRemove.add(group.minecraftGroup) + } else { + groupsToAdd.add(group.minecraftGroup) + } + } + } + + for (group in groupsToAdd) { + plugin.logger.info("Adding " + username + " to group " + group) + plugin.permissions?.playerAddGroup(player, group) + } + + for (group in groupsToRemove) { + plugin.logger.info("Removing " + username + " from group " + group) + plugin.permissions?.playerRemoveGroup(player, group) + } } -} \ No newline at end of file +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index e8985ca..dace8d5 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,18 @@ +# The URL of your Discourse installation (without the trailing slash) +discourse-url: http://forum.example.com + +# A mapping between Discourse groups (by integer ID) and Minecraft groups (by name) groups: - # - discourse: 4 - # minecraft: groupname + + # Add user to groupname when they are in Discourse group 4 + - discourse: 4 + minecraft: groupname + + # Remove user from groupname when they are not in Discourse group 4 + - discourse: 4 + minecraft: groupname + remove: true + + # Add user to guestgroup if they do not have any Discourse groups + - discourse: 0 + minecraft: guestgroup diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 40086cb..b5860c2 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -7,3 +7,5 @@ website: ${project.url} main: gg.obsidian.discoursegroupsync.DiscourseGroupSync +depend: [Vault] +