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]
+