From 4cc0cc334577378dd0cbcc11b7d521b054aea842 Mon Sep 17 00:00:00 2001 From: hlaaftana Date: Fri, 5 Feb 2016 15:50:49 +0200 Subject: [PATCH] A ton more, I'll provide pastebin later --- .../ml/hlaaftana/discordg/objects/API.groovy | 172 ++++++++------- .../discordg/objects/APIMapObject.groovy | 6 + .../hlaaftana/discordg/objects/Channel.groovy | 18 +- .../hlaaftana/discordg/objects/Client.groovy | 16 +- .../hlaaftana/discordg/objects/Color.groovy | 40 ++++ .../hlaaftana/discordg/objects/Colors.groovy | 41 ---- .../discordg/objects/Connection.groovy | 9 + .../discordg/objects/DefaultAvatars.groovy | 15 ++ .../{Base.groovy => DiscordObject.groovy} | 12 +- .../hlaaftana/discordg/objects/Event.groovy | 13 -- .../hlaaftana/discordg/objects/Invite.groovy | 28 +-- .../discordg/objects/MapObject.groovy | 19 ++ .../hlaaftana/discordg/objects/Member.groovy | 17 +- .../hlaaftana/discordg/objects/Message.groovy | 30 +-- .../discordg/objects/PrivateChannel.groovy | 2 +- .../hlaaftana/discordg/objects/Region.groovy | 9 + .../ml/hlaaftana/discordg/objects/Role.groovy | 13 +- .../hlaaftana/discordg/objects/Server.groovy | 63 ++++-- .../discordg/objects/TextChannel.groovy | 28 ++- .../ml/hlaaftana/discordg/objects/User.groovy | 22 +- .../discordg/objects/VoiceChannel.groovy | 27 +++ .../discordg/objects/VoiceClient.groovy | 10 + .../discordg/request/JSONRequester.groovy | 54 +++++ .../discordg/request/VoiceWSClient.groovy | 121 +++++++++++ .../discordg/request/WSClient.groovy | 27 ++- .../discordg/status/DiscordStatus.groovy | 8 + .../discordg/status/IncidentUpdate.groovy | 19 ++ .../discordg/status/Maintenance.groovy | 25 +++ .../hlaaftana/discordg/status/Schedule.groovy | 12 ++ .../discordg/status/StatusPage.groovy | 14 ++ .../hlaaftana/discordg/util/AudioUtil.groovy | 67 ++++++ .../discordg/util/ConversionUtil.groovy | 2 +- .../discordg/util/bot/CommandBot.groovy | 40 ++-- .../discordg/util/bot/RegexCommandBot.groovy | 196 ++++++++++++++++++ .../discordg/util/bot/SuffixCommandBot.groovy | 35 ++-- 35 files changed, 953 insertions(+), 277 deletions(-) create mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/APIMapObject.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/Color.groovy delete mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/Colors.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/Connection.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/DefaultAvatars.groovy rename src/main/groovy/ml/hlaaftana/discordg/objects/{Base.groovy => DiscordObject.groovy} (60%) delete mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/Event.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/MapObject.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/Region.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/objects/VoiceClient.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/request/JSONRequester.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/request/VoiceWSClient.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/status/DiscordStatus.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/status/IncidentUpdate.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/status/Maintenance.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/status/Schedule.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/status/StatusPage.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/util/AudioUtil.groovy create mode 100644 src/main/groovy/ml/hlaaftana/discordg/util/bot/RegexCommandBot.groovy diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/API.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/API.groovy index a17f9c5..011e780 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/API.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/API.groovy @@ -7,6 +7,7 @@ import groovy.json.JsonSlurper import groovy.lang.Closure import ml.hlaaftana.discordg.request.* import ml.hlaaftana.discordg.util.* +import ml.hlaaftana.discordg.objects.VoiceClient import org.eclipse.jetty.util.ssl.SslContextFactory import org.eclipse.jetty.websocket.client.ClientUpgradeRequest @@ -23,6 +24,8 @@ class API{ String password WSClient wsClient Client client + VoiceWSClient voiceWsClient + VoiceClient voiceClient Map> listeners = new HashMap>() Map readyData Map voiceData = [:] @@ -33,6 +36,7 @@ class API{ int eventThreadCount = 3 // if your bot is on tons of big servers, this might help however take up some CPU boolean ignorePresenceUpdate = false // if your bot is on tons of big servers, this might help you lose some CPU int largeThreshold // if your bot is on tons of big servers, this might help your request speeds + boolean copyReady = true // adds fullData to ready to archive the initial ready /** * Builds a new API object. This is safe to do. @@ -67,6 +71,7 @@ class API{ this.addGuildRoleDeleteListener() this.addGuildRoleUpdateListener() this.addPresenceUpdateListener() + this.addVoiceStateUpdateListener() } WSClient getWebSocketClient(){ return wsClient } @@ -238,12 +243,12 @@ class API{ * Triggers all listeners for an event. * @param event - the event object to provide. */ - void dispatchEvent(Event event){ + void dispatchEvent(String type, Map data){ this.listeners.each { Map.Entry> entry -> try{ - if (event.type == entry.key){ + if (type == entry.key){ for (c in entry.value){ - c.call(event) + c.call(data) } } }catch (ex){ @@ -263,21 +268,21 @@ class API{ // Adding built-in listeners. Look above at "removeListenersFor" to understand why I did it like this. void addGuildMemberAddListener(){ - this.addListener("guild member add", { Event e -> - Server server = e.data.server + this.addListener("guild member add", { Map d -> + Server server = d.server Map serverInReady = this.readyData["guilds"].find { it["id"] == server.id } Map serverInReady2 = serverInReady - serverInReady2["members"].add(e.data) + serverInReady2["members"].add(d) this.readyData["guilds"].remove(serverInReady) this.readyData["guilds"].add(serverInReady2) }) } void addGuildMemberRemoveListener(){ - this.addListener("guild member remove", { Event e -> + this.addListener("guild member remove", { Map d -> try{ - Server server = e.data.server - Member memberToRemove = e.data.member + Server server = d.server + Member memberToRemove = d.member Map serverInReady = this.readyData["guilds"].find { it["id"] == server.id } Map serverInReady2 = serverInReady.subMap(serverInReady.keySet()) serverInReady2["members"].remove(memberToRemove.object) @@ -288,20 +293,20 @@ class API{ } void addGuildRoleCreateListener(){ - this.addListener("guild role create", { Event e -> - Server server = e.data.server + this.addListener("guild role create", { Map d -> + Server server = d.server Map serverInReady = this.readyData["guilds"].find { it["id"] == server.id } Map serverInReady2 = serverInReady - serverInReady2["roles"].add(e.data.role.object) + serverInReady2["roles"].add(d.role.object) this.readyData["guilds"].remove(serverInReady) this.readyData["guilds"].add(serverInReady2) }) } void addGuildRoleDeleteListener(){ - this.addListener("guild role delete", { Event e -> - Server server = e.data.server - Role roleToRemove = e.data.role + this.addListener("guild role delete", { Map d -> + Server server = d.server + Role roleToRemove = d.role Map serverInReady = this.readyData["guilds"].find { it["id"] == server.id } Map serverInReady2 = serverInReady serverInReady2["roles"].remove(roleToRemove.object) @@ -311,95 +316,104 @@ class API{ } void addChannelCreateListener(){ - this.addListener("channel create", { Event e -> - Server server = e.data.server + this.addListener("channel create", { Map d -> + Server server = d.server if (server == null){ - this.readyData["private_channels"].add(e.data.fullData << ["cached_messages": []]) + this.readyData["private_channels"].add(d.fullData << ["cached_messages": []]) }else{ - this.readyData["guilds"].find { it.id == server.id }["channels"].add(e.data.fullData << ["cached_messages": []]) + this.readyData["guilds"].find { it.id == server.id }["channels"].add(d.fullData << ["cached_messages": []]) } }) } void addChannelDeleteListener(){ - this.addListener("channel delete", { Event e -> - Server server = e.data.server + this.addListener("channel delete", { Map d -> + Server server = d.server if (server == null){ - Map channelToRemove = this.readyData["private_channels"].find { it["id"] == e.data.channel.id } + Map channelToRemove = this.readyData["private_channels"].find { it["id"] == d.channel.id } this.readyData["private_channels"].remove(channelToRemove) }else{ - Map channelToRemove = this.readyData["guilds"].find { it.id == server.id }["channels"].find { it["id"] == e.data.channel.id } + Map channelToRemove = this.readyData["guilds"].find { it.id == server.id }["channels"].find { it["id"] == d.channel.id } this.readyData["guilds"].find { it.id == server.id }["channels"].remove(channelToRemove) } }) } void addChannelUpdateListener(){ - this.addListener("channel update", { Event e -> - Server server = e.data.server - Map channelToRemove = this.readyData["guilds"].find { it.id == server.id }["channels"].find { it.id == e.data.channel.id } + this.addListener("channel update", { Map d -> + Server server = d.server + Map channelToRemove = this.readyData["guilds"].find { it.id == server.id }["channels"].find { it.id == d.channel.id } Map copyOfChannelToRemove = channelToRemove - copyOfChannelToRemove << e.data.channel.object + copyOfChannelToRemove << d.channel.object this.readyData["guilds"].find { it.id == server.id }["channels"].remove(channelToRemove) this.readyData["guilds"].find { it.id == server.id }["channels"].add(copyOfChannelToRemove) }) } void addMessageCreateListener(){ - this.addListener("message create") { Event e -> - try{ - Channel channel = e.data.message.channel - this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].add(e.data.message.object) - }catch (ex){ - println e.data.fullData + this.addListener("message create") { Map d -> + TextChannel channel = d.message.channel + if (channel.server == null){ + this.readyData["private_channels"].find { it.id == channel.id }["cached_messages"].add(d.message.object) + }else{ + this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].add(d.message.object) } } } void addMessageUpdateListener(){ - this.addListener("message update") { Event e -> - if (e.data.message instanceof Message){ - Channel channel = e.data.message.channel - this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].find { it.id == e.data.message.id }?.leftShift(e.data.message.object) + this.addListener("message update") { Map d -> + if (d.message instanceof Message){ + Channel channel = d.message.channel + if (channel.server == null){ + this.readyData["private_channels"].find { it.id == channel.id }["cached_messages"].find { it.id == d.message.id }.leftShift(d.message.object) + }else{ + this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].find { it.id == d.message.id }?.leftShift(d.message.object) + } }else{ - Channel channel = e.data.channel - this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].find { it.id == e.data.message }?.leftShift([embeds: e.data.embeds]) + Channel channel = d.channel + if (channel.server == null){ + this.readyData["private_channels"].find { it.id == channel.id }["cached_messages"].find { it.id == d.message }.leftShift(embeds: d.embeds) + }else{ + this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].find { it.id == d.message }?.leftShift(embeds: d.embeds) + } } } } void addMessageDeleteListener(){ - this.addListener("message delete") { Event e -> - if (e.data.message instanceof Message){ - Channel channel = e.data.message.channel - this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].remove(this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].find { it.id == e.data.message.id }) - }else{ - Channel channel = e.data.channel - Map desiredMessage = this.readyData["guilds"].find { it.id == channel?.server?.id }?.getAt("channels")?.find { it.id == channel?.id }?.getAt("cached_messages")?.find { it.id == e.data.message } - this.readyData["guilds"].find { it.id == channel?.server?.id }?.getAt("channels")?.find { it.id == channel?.id }?.getAt("cached_messages")?.remove(desiredMessage) - } + this.addListener("message delete") { Map d -> + if (d.message instanceof Message){ + Channel channel = d.message.channel + if (channel.server == null){ + Map desiredMessage = this.readyData["private_channels"].find { it.id == d.channel.id }["cached_messages"].find { it.id == d.message.id } + this.readyData["private_channels"].find { it.id == channel.id }["cached_messages"].remove(desiredMessage) + }else{ + this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].remove(this.readyData["guilds"].find { it.id == channel.server.id }["channels"].find { it.id == channel.id }["cached_messages"].find { it.id == d.message.id }) + } + }else{} } } void addGuildCreateListener(){ - this.addListener("guild create", { Event e -> - Server server = e.data.server + this.addListener("guild create", { Map d -> + Server server = d.server this.readyData["guilds"].add(server.object) }) } void addGuildDeleteListener(){ - this.addListener("guild delete", { Event e -> - Server server = e.data.server + this.addListener("guild delete", { Map d -> + Server server = d.server Map serverToRemove = this.readyData["guilds"].find { it["id"] == server.id } this.readyData["guilds"].remove(serverToRemove) }) } void addGuildMemberUpdateListener(){ - this.addListener("guild member update", { Event e -> - Server server = e.data.server - Member member = e.data.member + this.addListener("guild member update", { Map d -> + Server server = d.server + Member member = d.member Map serverToRemove = this.readyData["guilds"].find { it["id"] == server.id } List membersToEdit = serverToRemove["members"] Map memberToEdit = membersToEdit.find { it["user"]["id"] == member.id } @@ -412,9 +426,9 @@ class API{ } void addGuildRoleUpdateListener(){ - this.addListener("guild role update", { Event e -> - Server server = e.data.server - Role role = e.data.role + this.addListener("guild role update", { Map d -> + Server server = d.server + Role role = d.role Map serverToRemove = this.readyData["guilds"].find { it["id"] == server.id } List rolesToEdit = serverToRemove["roles"] Map roleToEdit = rolesToEdit.find { it["id"] == role.id } @@ -427,8 +441,8 @@ class API{ } void addGuildUpdateListener(){ - this.addListener("guild update", { Event e -> - Map newServer = e.data.server.object + this.addListener("guild update", { Map d -> + Map newServer = d.server.object Map serverToRemove = this.readyData["guilds"].find { it["id"] == newServer.id } Map copyOfServerToRemove = serverToRemove copyOfServerToRemove << newServer @@ -438,17 +452,17 @@ class API{ } void addPresenceUpdateListener(){ - this.addListener("presence change", { Event e -> - Server server = e.data.server - if (server == null){ server = new Server(this, ["id": e.data.fullData["guild_id"]]) } - Map serverToAdd = this.readyData["guilds"].find { it["id"] == e.data.fullData["guild_id"] } + this.addListener("presence change", { Map d -> + Server server = d.server + if (server == null){ server = new Server(this, ["id": d.fullData["guild_id"]]) } + Map serverToAdd = this.readyData["guilds"].find { it["id"] == d.fullData["guild_id"] } Map copyServer = serverToAdd List members = copyServer.members.collect({ it }) - if (e.data["newUser"] != null){ + if (d["newUser"] != null){ try{ - Map m = members.find { it["user"]["id"] == e.data.newUser.id } + Map m = members.find { it["user"]["id"] == d.newUser.id } Map m2 = m - m2.user = e.data.newUser.object + m2.user = d.newUser.object serverToAdd.members.remove(m) serverToAdd.members.add(m2) }catch (ex){ @@ -457,13 +471,13 @@ class API{ } List presences = copyServer.presences.collect({ it }) try{ - Map p = presences.find { it["user"]["id"] == e.data.member.id } + Map p = presences.find { it["user"]["id"] == d.member.id } Map p2 = p - p2.status = e.data.status - if (e.data.game == ""){ + p2.status = d.status + if (d.game == ""){ p2.game = null }else{ - p2.game = [name: e.data.game] + p2.game = [name: d.game] } serverToAdd.presences.remove(p) serverToAdd.presences.add(p) @@ -477,8 +491,20 @@ class API{ } void addVoiceStateUpdateListener(){ - this.addListener "voice state change", { Event e -> - + this.addListener "voice state change", { Map d -> + Server server = d.voiceState.server + Map serverToRemove = this.readyData["guilds"].find { it["id"] == server.id } + Map copyOfServerToRemove = serverToRemove + if (d.fullData["channel_id"] != null){ + def existingVoiceState = copyOfServerToRemove.voice_states.find { it.user_id == d.fullData["user_id"] } + if (existingVoiceState != null){ copyOfServerToRemove.voice_states.remove(existingVoiceState) } + copyOfServerToRemove.voice_states.add(d.voiceState.object) + }else{ + def existingVoiceState = copyOfServerToRemove.voice_states.find { it.user_id == d.fullData["user_id"] } + if (existingVoiceState != null){ copyOfServerToRemove.voice_states.remove(existingVoiceState) } + } + this.readyData["guilds"].remove(serverToRemove) + this.readyData["guilds"].add(copyOfServerToRemove) } } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/APIMapObject.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/APIMapObject.groovy new file mode 100644 index 0000000..cd80d5f --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/APIMapObject.groovy @@ -0,0 +1,6 @@ +package ml.hlaaftana.discordg.objects + +class APIMapObject extends MapObject { + API api + APIMapObject(API api, Map object){ super(object); this.api = api } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Channel.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Channel.groovy index a83216d..918807e 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Channel.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Channel.groovy @@ -8,7 +8,7 @@ import ml.hlaaftana.discordg.util.JSONUtil * A Discord channel. * @author Hlaaftana */ -class Channel extends Base{ +class Channel extends DiscordObject{ Channel(API api, Map object){ super(api, object) } @@ -40,6 +40,10 @@ class Channel extends Base{ return this.object["permission_overwrites"].collect { new PermissionOverwrite(api, it) } } + List getInvites(){ + return JSONUtil.parse(api.requester.get("https://discordapi.com/api/channels/${this.id}/invites")).collect { new Invite(api, it) } + } + /** * Deletes the channel. */ @@ -57,7 +61,7 @@ class Channel extends Base{ return new Channel(api, api.requester.patch("https://discordapp.com/api/channels/${this.id}", ["name": (data.containsKey("name")) ? data["name"].toString() : this.getName(), "position": (data.containsKey("position")) ? data["position"] : this.getPosition(), "topic": (data.containsKey("topic")) ? data["topic"].toString() : this.getTopic()])) } - void editPermissions(Base target, def allow, def deny){ + void editPermissions(DiscordObject target, def allow, def deny){ String id = target.id String type = (target instanceof Role) "role" : "member" int allowBytes = (allow instanceof int) allow : allow.value @@ -65,21 +69,21 @@ class Channel extends Base{ api.requester.put("https://discordapp.com/api/channels/${this.id}/permissions/${id}", [allow: allowBytes, deny: denyBytes, id: id, type: type]) } - void addPermissions(Base target, def allow, def deny){ + void addPermissions(DiscordObject target, def allow, def deny){ this.editPermissions(target, allow, deny) } - void deletePermissions(Base target){ + void deletePermissions(DiscordObject target){ api.requester.delete("https://discordapp.com/api/channels/${this.id}/permissions/${target.id}") } - static class PermissionOverwrite extends Base { + static class PermissionOverwrite extends DiscordObject { PermissionOverwrite(API api, Map object){ super(api, object) } Permissions getAllowed(){ return new Permissions(this.object["allow"]) } Permissions getDenied(){ return new Permissions(this.object["deny"]) } String getType(){ return this.object["type"] } - Base getAffected(){ + DiscordObject getAffected(){ if (this.type == "role"){ List roles = [] api.client.servers.each { roles.addAll(it.roles) } @@ -89,7 +93,7 @@ class Channel extends Base{ api.client.servers.each { members.addAll(it.members) } return members.find { it.id == this.id } } - return (Base) this + return (DiscordObject) this } String getName(){ return this.affected.name } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Client.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Client.groovy index 2ea329d..70cd62e 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Client.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Client.groovy @@ -230,7 +230,7 @@ class Client{ void changeStatus(Map data) { api.wsClient.send(["op": 3, "d": ["game": ["name": data["game"]], "idle_since": (data["idle"] != null) ? System.currentTimeMillis() : null]]) for (s in this.servers){ - api.dispatchEvent(new Event("PRESENCE_UPDATE", [ + api.dispatchEvent("PRESENCE_UPDATE", [ "fullData": [ "game": (data["game"] != null) ? ["name": data["game"]] : null, "status": (data["idle"] != null) ? "online" : "idle", @@ -241,7 +241,7 @@ class Client{ "member": s.members.find { try{ it.id == this.user.id }catch (ex){ false } }, "game": (data["game"] != null) ? data["game"] : null, "status": (data["idle"] != null) ? "online" : "idle" - ])) + ]) } } @@ -281,6 +281,18 @@ class Client{ return new Invite(api, JSONUtil.parse(api.requester.post("https://discordapp.com/api/channels/${id}/invites", data))) } + List getInvitesFor(Server server){ + return server.invites + } + + List getInvitesFor(Channel channel){ + return channel.invites + } + + List getConnections(){ + return JSONUtil.parse(api.requester.get("https://discordapp.com/api/users/@me/connections")).collect { new Connection(api, it) } + } + void moveToChannel(Member member, VoiceChannel channel){ api.requester.patch("https://discordapp.com/api/guilds/${member.server.id}/members/{member.id}", ["channel_id": channel.id]) } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Color.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Color.groovy new file mode 100644 index 0000000..ea17228 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Color.groovy @@ -0,0 +1,40 @@ +package ml.hlaaftana.discordg.objects + +class Color { + static final Color DEFAULT = new Color(0) + static final Color AQUA = new Color(0x1ABC9C) + static final Color DARK_AQUA = new Color(0x11806a) + static final Color GREEN = new Color(0x2ECC71) + static final Color DARK_GREEN = new Color(0x1F8B4C) + static final Color BLUE = new Color(0x3498DB) + static final Color DARK_BLUE = new Color(0x206694) + static final Color PURPLE = new Color(0x9B59B6) + static final Color DARK_PURPLE = new Color(0x71368A) + static final Color MAGENTA = new Color(0xE91E63) + static final Color DARK_MAGENTA = new Color(0xAD1457) + static final Color GOLD = new Color(0xF1C40F) + static final Color DARK_GOLD = new Color(0xC27C0E) + static final Color ORANGE = new Color(0xE67E22) + static final Color DARK_ORANGE = new Color(0xA84300) + static final Color RED = new Color(0xE74C3C) + static final Color DARK_RED = new Color(0x992D22) + static final Color LIGHT_GRAY = new Color(0x95A5A6) + static final Color GRAY = new Color(0x607D8B) + static final Color LIGHT_BLUE_GRAY = new Color(0x979C9F) + static final Color BLUE_GRAY = new Color(0x546E7A) + static final Color LIGHT_GREY = new Color(0x95A5A6) + static final Color GREY = new Color(0x607D8B) + static final Color LIGHT_BLUE_GREY = new Color(0x979C9F) + static final Color BLUE_GREY = new Color(0x546E7A) + static final Color ABS_RED = new Color(0xFF0000) + static final Color ABS_GREEN = new Color(0x00FF00) + static final Color ABS_BLUE = new Color(0x0000FF) + static final Color ABS_YELLOW = new Color(0xFFFF00) + static final Color ABS_MAGENTA = new Color(0xFF00FF) + static final Color ABS_TURQUIOSE = new Color(0x00FFFF) + static final Color ABS_WHITE = new Color(0xFFFFFF) + static final Color ABS_BLACK = new Color(0x000000) + + int value + Color(int value){ this.value = value } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Colors.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Colors.groovy deleted file mode 100644 index ebb62cc..0000000 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Colors.groovy +++ /dev/null @@ -1,41 +0,0 @@ -package ml.hlaaftana.discordg.objects - -enum Colors { - DEFAULT(0), - AQUA(0x1ABC9C), - DARK_AQUA(0x11806a), - GREEN(0x2ECC71), - DARK_GREEN(0x1F8B4C), - BLUE(0x3498DB), - DARK_BLUE(0x206694), - PURPLE(0x9B59B6), - DARK_PURPLE(0x71368A), - MAGENTA(0xE91E63), - DARK_MAGENTA(0xAD1457), - GOLD(0xF1C40F), - DARK_GOLD(0xC27C0E), - ORANGE(0xE67E22), - DARK_ORANGE(0xA84300), - RED(0xE74C3C), - DARK_RED(0x992D22), - LIGHT_GRAY(0x95A5A6), - GRAY(0x607D8B), - LIGHT_BLUE_GRAY(0x979C9F), - BLUE_GRAY(0x546E7A), - LIGHT_GREY(0x95A5A6), - GREY(0x607D8B), - LIGHT_BLUE_GREY(0x979C9F), - BLUE_GREY(0x546E7A), - ABS_RED(0xFF0000), - ABS_GREEN(0x00FF00), - ABS_BLUE(0x0000FF), - ABS_YELLOW(0xFFFF00), - ABS_MAGENTA(0xFF00FF), - ABS_TURQUIOSE(0x00FFFF), - ABS_WHITE(0xFFFFFF), - ABS_BLACK(0x000000), - - - int value - Colors(int value){ this.value = value } -} diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Connection.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Connection.groovy new file mode 100644 index 0000000..8ec1b19 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Connection.groovy @@ -0,0 +1,9 @@ +package ml.hlaaftana.discordg.objects + +class Connection extends DiscordObject { + Connection(API api, Map object){ super(api, object) } + + List getIntegrations(){ return this.object["integrations"] } + boolean isRevoked(){ return this.object["revoked"] } + String getType(){ return this.object["type"] } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/DefaultAvatars.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/DefaultAvatars.groovy new file mode 100644 index 0000000..38e3a38 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/DefaultAvatars.groovy @@ -0,0 +1,15 @@ +package ml.hlaaftana.discordg.objects + +enum DefaultAvatars { + BLUE("b3afd12bc47a87507780ce5f53a9d6a1", 0), + GREY("0d1a93187d96a05e86444f2fc6210d95", 1), + GRAY("0d1a93187d96a05e86444f2fc6210d95", 1), + GREEN("a83f572c0b5c2d87f935ce6229be6358", 2), + YELLOW("907c319873ae4c1d56d0d0e8dce6b476", 3), + RED("8b3fac6205178732d218265987cdb0dc", 4), + + String hash + int order + DefaultAvatars(String hash, int order){ this.hash = hash; this.order = order } + static DefaultAvatars get(int order){ return DefaultAvatars.class.enumConstants.find { it.order == order } } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Base.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/DiscordObject.groovy similarity index 60% rename from src/main/groovy/ml/hlaaftana/discordg/objects/Base.groovy rename to src/main/groovy/ml/hlaaftana/discordg/objects/DiscordObject.groovy index 674b6cd..6c9c0b0 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Base.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/DiscordObject.groovy @@ -4,23 +4,21 @@ package ml.hlaaftana.discordg.objects * A basic Discord object. * @author Hlaaftana */ -class Base { - API api - Map object +class DiscordObject extends APIMapObject { /** * A Discord object with a map containing data and an API object to use. * @param api - the API object. * @param object - the map to use. */ - Base(API api, Map object){ this.object = object; this.api = api } + DiscordObject(API api, Map object){ super(api, object) } /** * @return the ID of the object. */ - String getId(){ return object["id"] } + String getId(){ return this.object["id"] } /** * @return the name of the object. */ - String getName(){ return object["name"] } + String getName(){ return this.object["name"] } String toString(){ return this.name } - boolean equals(Base other){ return this.id == other.id } + boolean equals(def other){ return this.id == other.id } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Event.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Event.groovy deleted file mode 100644 index b39973a..0000000 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Event.groovy +++ /dev/null @@ -1,13 +0,0 @@ -package ml.hlaaftana.discordg.objects - -import ml.hlaaftana.discordg.util.JSONUtil - -/** - * An object for events containing the data and the type. - * @author Hlaaftana - */ -class Event { - Map data - String type - Event (String type, Map data){ this.data = data; this.type = type } -} diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Invite.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Invite.groovy index 800a269..cbdc6e5 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Invite.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Invite.groovy @@ -6,63 +6,63 @@ import ml.hlaaftana.discordg.util.* * An invite to a Discord server. * @author Hlaaftana */ -class Invite extends Base { +class Invite extends DiscordObject { Invite(API api, Map object){ super(api, object) } /** * @return the amount of... some sort of time measure until this invite expires */ - int getMaxAge(){ return object["max_age"] } + int getMaxAge(){ return this.object["max_age"] } /** * @return the code for this invite */ - String getCode(){ return object["code"] } + String getCode(){ return this.object["code"] } /** * @return the code for this invite */ - String getId(){ return object["code"] } + String getId(){ return this.object["code"] } /** * @return the server where this invite is. This will usually return null. Use #getBaseServer(). */ Server getServer(){ - return api.client.getServerById(object["guild"]["id"]) + return api.client.getServerById(this.object["guild"]["id"]) } /** * @return a Base containing the ID and name of the server. */ - Base getBaseServer(){ return new Base(api, object["guild"]) } + DiscordObject getBaseServer(){ return new DiscordObject(api, this.object["guild"]) } /** * @return whether or not the invite was revoked. */ - boolean isRevoked(){ return object["revoked"] } + boolean isRevoked(){ return this.object["revoked"] } /** * @return when the invite was created. */ - String getCreateTimeRaw(){ return object["created_at"] } + String getRawCreateTime(){ return this.object["created_at"] } /** * @return when the invite was created. */ - Date getCreateTime(){ return ConversionUtil.toDiscordDate(object["created_at"]) } + Date getCreateTime(){ return ConversionUtil.fromJsonDate(this.object["created_at"]) } /** * @return whether or not the invite is temporary. */ - boolean isTemporary(){ return object["temporary"] } + boolean isTemporary(){ return this.object["temporary"] } /** * @return the amount of uses for this invite. */ - int getUses(){ return object["uses"] } + int getUses(){ return this.object["uses"] } /** * @return the max amount of uses for this invite. */ - int getMaxUses(){ return object["max_uses"] } + int getMaxUses(){ return this.object["max_uses"] } /** * @return the person who created the invite. */ - User getInviter(){ return new User(api, object["inviter"]) } + User getInviter(){ return new User(api, this.object["inviter"]) } /** * @return a base object containing the name, ID and type of the channel. You will, however, have to get the type by doing ".object.type". */ - Base getBaseChannel(){ return new Base(api, object["channel"]) } + DiscordObject getBaseChannel(){ return new DiscordObject(api, this.object["channel"]) } /** * Parses a URL string into an invite ID. diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/MapObject.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/MapObject.groovy new file mode 100644 index 0000000..2314733 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/MapObject.groovy @@ -0,0 +1,19 @@ +package ml.hlaaftana.discordg.objects + +/** + * A basic object with map data. + * @author Hlaaftana + */ +class MapObject { + Map object + /** + * An object with a map containing data. + * @param object - the map to use. + */ + MapObject(Map object){ this.object = object } + String toString(){ return this.object.toString() } + boolean equals(def other){ return this.object == other.object } + def putAt(def key, def value){ return this.object.putAt(key, value) } + def getAt(def key){ return this.object.getAt(key) } +} + diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Member.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Member.groovy index 6ddcda3..015bc50 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Member.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Member.groovy @@ -21,27 +21,30 @@ class Member extends User{ String getAvatarHash(){ return this.user.avatarHash } String getAvatar() { return this.user.avatar } URL getAvatarURL(){ return this.user.avatarURL } + URL getAvatarUrl(){ return this.user.avatarUrl } + String getDiscriminator(){ return this.user.discriminator } + String getDiscrim(){ return this.user.discriminator } /** * @return the User which this Member is. */ - User getUser(){ return new User(api, object["user"]) } + User getUser(){ return new User(api, this.object["user"]) } /** * @return the server the member comes from. */ - Server getServer(){ return api.client.getServerById(object["guild_id"]) } + Server getServer(){ return api.client.getServerById(this.object["guild_id"]) } /** * @return a timestamp of when the member joined the server. */ - String getJoinDateRaw(){ return object["joined_at"] } + String getRawJoinDate(){ return this.object["joined_at"] } /** * @return a Date object of when the member joined the server. */ - Date getJoinDate(){ return ConversionUtil.toDiscordDate(this.joinDateRaw) } + Date getJoinDate(){ return ConversionUtil.fromJsonDate(this.joinDateRaw) } /** * @return the roles this member has. */ List getRoles(){ - List array = object["roles"] + List array = this.object["roles"] List roles = new ArrayList() for (o in array){ for (r in this.server.roles){ @@ -56,7 +59,7 @@ class Member extends User{ */ String getGame(){ try{ - return this.server.object["presences"].find({ it.user.id == this.user.id }).game.name + return this.server.this.object["presences"].find({ it.user.id == this.user.id }).game.name }catch (ex){ return null } @@ -67,7 +70,7 @@ class Member extends User{ */ String getStatus(){ try{ - return this.server.object["presences"].find({ it.user.id == this.user.id }).status + return this.server.this.object["presences"].find({ it.user.id == this.user.id }).status }catch (ex){ return "offline" } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Message.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Message.groovy index 83102ff..7b78a24 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Message.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Message.groovy @@ -8,7 +8,7 @@ import ml.hlaaftana.discordg.util.* * A Discord message. * @author Hlaaftana */ -class Message extends Base{ +class Message extends DiscordObject{ Message(API api, Map object){ super(api, object) } @@ -20,43 +20,43 @@ class Message extends Base{ /** * @return the content of this message. */ - String getContent(){ return object["content"] } + String getContent(){ return this.object["content"] } /** * @return a raw timestamp string of when the message was created. */ - String getCreateTimeRaw(){ return object["timestamp"] } + String getRawCreateTime(){ return this.object["timestamp"] } /** * @return a Date of when the message was created. */ - Date getCreateTime(){ return ConversionUtil.toDiscordDate(this.timestampRaw) } + Date getCreateTime(){ return ConversionUtil.fromJsonDate(this.timestampRaw) } /** * @return a raw timestamp string of when the message was edited. */ - String getEditTimeRaw(){ return object["edited_timestamp"] } + String getRawEditTime(){ return this.object["edited_timestamp"] } /** * @return a Date of when the message was edited. */ - Date getEditTime(){ return ConversionUtil.toDiscordDate(this.editTimeRaw) } + Date getEditTime(){ return ConversionUtil.fromJsonDate(this.editTimeRaw) } /** * @return whether or not this message has text to speech. */ - boolean isTTS(){ return object["tts"] } + boolean isTTS(){ return this.object["tts"] } /** * @return whether or not the message mentions everyone. Yes, this method's name makes no sense, but Groovy inference is nice */ - boolean isMentionsEveryone(){ return object["mention_everyone"] } + boolean isMentionsEveryone(){ return this.object["mention_everyone"] } /** * @return a List of Attachments. */ - List getAttachments(){ return object["attachments"].collect { new Attachment(api, it) } } + List getAttachments(){ return this.object["attachments"].collect { new Attachment(api, it) } } /** * @return a List of Maps containing the embeds. Might replace with Embed objects. */ - List getEmbeds() { return object["embeds"] } + List getEmbeds() { return this.object["embeds"] } /** * @return the author of the message. */ - User getAuthor() { return new User(api, object["author"]) } + User getAuthor() { return new User(api, this.object["author"]) } /** * @return the author of the message. */ @@ -68,7 +68,7 @@ class Message extends Base{ /** * @return the channel the message is in. */ - TextChannel getTextChannel() { return api.client.getTextChannelById(object["channel_id"]) } + TextChannel getTextChannel() { return api.client.getTextChannelById(this.object["channel_id"]) } /** * @return the channel the message is in. */ @@ -85,21 +85,21 @@ class Message extends Base{ * @return the edited Message. */ Message edit(String newContent) { - return new Message(api, JSONUtil.parse(api.requester.patch("https://discordapp.com/api/channels/${object["channel_id"]}/messages/${this.id}", ["content": newContent]))) + return new Message(api, JSONUtil.parse(api.requester.patch("https://discordapp.com/api/channels/${this.object["channel_id"]}/messages/${this.id}", ["content": newContent]))) } /** * Deletes the message. */ void delete() { - api.requester.delete("https://discordapp.com/api/channels/${object["channel_id"]}/messages/${this.id}") + api.requester.delete("https://discordapp.com/api/channels/${this.object["channel_id"]}/messages/${this.id}") } // removed ack method because of discord dev request String toString(){ return this.author.name + ": " + this.content } - static class Attachment extends Base { + static class Attachment extends DiscordObject { Attachment(API api, Map object){ this.api = api; this.object = object } String getName(){ return this.object["filename"] } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/PrivateChannel.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/PrivateChannel.groovy index 2d64b32..21a0eee 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/PrivateChannel.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/PrivateChannel.groovy @@ -13,7 +13,7 @@ class PrivateChannel extends TextChannel { /** * @return the user the conversation is with. */ - User getUser(){ return new User(api, object["recipient"] )} + User getUser(){ return new User(api, this.object["recipient"] )} Server getServer(){ return null } String getTopic(){ return null } String getPosition(){ return null } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Region.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Region.groovy new file mode 100644 index 0000000..a5f9ae6 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Region.groovy @@ -0,0 +1,9 @@ +package ml.hlaaftana.discordg.objects + +class Region extends DiscordObject { + Region(API api, Map object){ super(api, object) } + + String getSampleHostname(){ return this.object["sample_hostname"] } + int getSamplePort(){ return this.object["sample_port"] } + boolean isVip(){ return this.object["vip"] } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Role.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Role.groovy index c5e0ddd..d82dad9 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Role.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Role.groovy @@ -4,7 +4,7 @@ package ml.hlaaftana.discordg.objects * A Discord server role. * @author Hlaaftana */ -class Role extends Base{ +class Role extends DiscordObject{ Role(API api, Map object){ super(api, object) } @@ -12,21 +12,22 @@ class Role extends Base{ /** * @return the color for the role as an int. */ - int getColor(){ return object["color"] } + int getColorValue(){ return this.object["color"] } + Color getColor(){ return new Color(this.object["color"]) } /** * @return whether the role is hoist or not. */ - boolean isHoist(){ return object["hoist"] } + boolean isHoist(){ return this.object["hoist"] } /** * @return whether the role is managed or not. I have no idea what this means. */ - boolean isManaged(){ return object["managed"] } + boolean isManaged(){ return this.object["managed"] } /** * @return the permission bits for this role as an int. I will replace this with a Permissions object later */ - int getPermissions(){ return object["permissions"] } + int getPermissions(){ return this.object["permissions"] } /** * @return the position index for the role. */ - int getPosition(){ return object["position"] } + int getPosition(){ return this.object["position"] } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/Server.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/Server.groovy index 93fb612..715dd7d 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/Server.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/Server.groovy @@ -10,7 +10,7 @@ import ml.hlaaftana.discordg.util.* * A Discord server/guild. * @author Hlaaftana */ -class Server extends Base { +class Server extends DiscordObject { Server(API api, Map object){ super(api, object) } @@ -18,19 +18,19 @@ class Server extends Base { /** * @return the region the server is in. */ - String getRegion(){ return object["region"] } + String getRegionId(){ return this.object["region"] } /** * @return the timestamp of when this server was created. */ - String getCreateTimeRaw(){ return object["joined_at"] } + String getRawCreateTime(){ return this.object["joined_at"] } /** * @return the timestamp of when this server was created. */ - Date getCreateTime(){ return ConversionUtil.toDiscordDate(object["joined_at"]) } + Date getCreateTime(){ return ConversionUtil.fromJsonDate(this.object["joined_at"]) } /** * @return the hash/ID of this server's icon. */ - String getIconHash(){ return object["icon"] } + String getIconHash(){ return this.object["icon"] } /** * @return the URL of the icon of this server as a string. */ @@ -47,7 +47,7 @@ class Server extends Base { */ Member getOwner() { for (m in this.members){ - if (m.id == object["owner_id"]){ + if (m.id == this.object["owner_id"]){ return m } } @@ -62,6 +62,8 @@ class Server extends Base { int getAfkTimeout(){ return this.object["afk_timeout"] } Channel getWidgetChannel(){ return this.channels.find { it.id == this.object["embed_channel_id"] } } boolean isWidgetEnabled(){ return this.object["embed_enabled"] } + VerificationLevels getVerificationLevel(){ return VerificationLevels.get(this.object["verification_level"]) } + int getRawVerificationLevel(){ return this.object["verification_level"] } /** * @return the {@literal @everyone} role for this server. @@ -74,7 +76,7 @@ class Server extends Base { * @return the edited server as a Server object. */ Server edit(Map data) { - Map copyOfData = [name: data.name, region: data.region, icon: data.icon, afk_channel_id: data.afkChannel?.id, afk_timeout: data.afkTimeout, owner_id: data.owner?.id] + Map copyOfData = [name: data.name, region: data.region, icon: data.icon, afk_channel_id: data.afkChannel?.id, afk_timeout: data.afkTimeout, owner_id: data.owner?.id, verification_level: data.verificationLevel?.level] Map copyOfCopyOfData = [:] << copyOfData // avoid concurrentmodificationexception copyOfData.entrySet().each { if (it.value == null){ @@ -204,14 +206,14 @@ class Server extends Base { * @return all channels in the server. */ List getChannels(){ - return this.object["channels"].collect { new Channel(api, it) } + return this.object["channels"].collect { (it.type == "text") ? new TextChannel(api, it) : new VoiceChannel(api, it) } } /** * @return all roles in the server. */ List getRoles() { - List array = object["roles"].collect { it } + List array = this.object["roles"].collect { it } List roles = [] for (o in array){ roles.add(new Role(api, o)) @@ -223,7 +225,7 @@ class Server extends Base { * @return all members in the server. */ List getMembers() { - List array = object["members"].collect { it } + List array = this.object["members"].collect { it } List members = [] for (o in array){ members.add(new Member(api, o)) @@ -277,6 +279,18 @@ class Server extends Base { return this.object["voice_states"].collect { new VoiceState(api, it) } } + List getInvites(){ + return JSONUtil.parse(api.requester.get("https://discordapp.com/api/guilds/${this.id}/invites")).collect { new Invite(api, it) } + } + + List getRegions(){ + return JSONUtil.parse(api.requester.get("https://discordapp.com/api/guilds/${this.id}/regions")).collect { new Region(api, it) } + } + + Region getRegion(){ + return this.regions.find { it.id == this.regionId } + } + /** * Ban a user from the server. * @param user - the User to ban. @@ -315,7 +329,7 @@ class Server extends Base { * @return the edited role. */ Role editRole(Role role, Map data) { - if (data["color"] instanceof Colors) data["color"] = data["color"].value + if (data["color"] instanceof Color) data["color"] = data["color"].value if (data["permissions"] instanceof Permissions) data["permissions"] = data["permissions"].value return new Role(api, JSONUtil.parse(api.requester.patch("https://discordapp.com/api/guilds/${this.id}/roles/${role.id}", data))) } @@ -331,13 +345,17 @@ class Server extends Base { Member getMember(User user){ return this.members.find { it.id == user.id } } Member member(User user){ return this.members.find { it.id == user.id } } - static class VoiceState extends Base { + Message sendMessage(String message, boolean tts=false){ this.defaultChannel.sendMessage(message, tts) } + Message sendFile(File file){ this.defaultChannel.sendFile(file) } + Message sendFile(String filePath){ this.defaultChannel.sendFile(filePath) } + + static class VoiceState extends DiscordObject { VoiceState(API api, Map object){ super(api, object) } - VoiceChannel getChannel(){ return api.client.getVoiceChannelById(object["channel_id"]) } - VoiceChannel getVoiceChannel(){ return api.client.getVoiceChannelById(object["channel_id"]) } - User getUser(){ return api.client.getUserById(object["user_id"]) } - Server getServer(){ return api.client.getServerById(object["guild_id"]) } + VoiceChannel getChannel(){ return api.client.getVoiceChannelById(this.object["channel_id"]) } + VoiceChannel getVoiceChannel(){ return api.client.getVoiceChannelById(this.object["channel_id"]) } + User getUser(){ return api.client.getUserById(this.object["user_id"]) } + Server getServer(){ return api.client.getServerById(this.object["guild_id"]) } Member getMember(){ return this.server.member(this.user) } boolean isDeaf(){ return this.object["deaf"] } boolean isMute(){ return this.object["mute"] } @@ -351,4 +369,17 @@ class Server extends Base { String getToken(){ return this.object["token"] } String getSessionId(){ return this.object["session_id"] } } + + static enum VerificationLevels { + NONE(0), + LOW(1), + MEDIUM(2), + HIGH(3), + TABLEFLIP(3), + + int level + VerificationLevels(int level){ this.level = level } + + static VerificationLevels get(int level){ return VerificationLevels.class.enumConstants.find { it.level == level } } + } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/TextChannel.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/TextChannel.groovy index 8f10aad..13274b9 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/TextChannel.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/TextChannel.groovy @@ -19,7 +19,7 @@ class TextChannel extends Channel { /** * @return the topic of the channel. Might be null. */ - String getTopic() { return object["topic"] } + String getTopic() { return this.object["topic"] } /** * @return a mention for the channel. */ @@ -58,17 +58,29 @@ class TextChannel extends Channel { } /** - * Get message history from the channel. Warning: this'll be quite slower each multiple of 50. + * Sends a file to a channel. + * @param filePath - the file path as a string. + * @return - the sent message as a Message object. + */ + Message sendFile(String filePath){ return this.sendFile(new File(filePath)) } + + /** + * Get message history from the channel. Warning: this'll be quite slower each multiple of 100. * @param max - the max number of messages. 100 by default. * @return a List of Message containing the messages in the channel. */ - List getLogs(int max=50) { - List logs = new ArrayList() - List array = JSONUtil.parse(api.requester.get("https://discordapp.com/api/channels/${this.id}/messages?limit=${max}")) - for (m in array){ - logs.add(new Message(api, m)) + List getLogs(int max=100) { + if (max <= 100){ + return JSONUtil.parse(api.requester.get("https://discordapp.com/api/channels/${this.id}/messages?limit=${max}")).collect { new Message(api, it) } + }else{ + List initialRequest = JSONUtil.parse(api.requester.get("https://discordapp.com/api/channels/${this.id}/messages?limit=100")).collect { new Message(api, it) } + for (int m = 1; m < (int) Math.ceil(max / 100) - 1; m++){ + initialRequest += JSONUtil.parse(api.requester.get("https://discordapp.com/api/channels/${this.id}/messages?before=${initialRequest[initialRequest.size() - 1].id}&limit=100")).collect { new Message(api, it) } + } + if (max % 100 > 0) initialRequest += JSONUtil.parse(api.requester.get("https://discordapp.com/api/channels/${this.id}/messages?before=${initialRequest[initialRequest.size() - 1].id}&limit=${max % 100}")).collect { new Message(api, it) } + else initialRequest += JSONUtil.parse(api.requester.get("https://discordapp.com/api/channels/${this.id}/messages?before=${initialRequest[initialRequest.size() - 1].id}&limit=${100}")).collect { new Message(api, it) } + return initialRequest } - return logs } List getCachedLogs(){ diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/User.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/User.groovy index a327b67..3c00a62 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/User.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/User.groovy @@ -8,7 +8,7 @@ import ml.hlaaftana.discordg.util.JSONUtil * A Discord user. * @author Hlaaftana */ -class User extends Base{ +class User extends DiscordObject{ User(API api, Map object){ super(api, object) } @@ -16,29 +16,27 @@ class User extends Base{ /** * @return the user's username. */ - String getName(){ return this.getUsername() } + String getName(){ return this.username } /** * @return the user's username. */ - String getUsername() { return object["username"] } + String getUsername() { return this.object["username"] } /** * @return the user's avatar's hash/ID. */ - String getAvatarHash(){ return object["avatar"] } + String getAvatarHash(){ return this.object["avatar"] ?: DefaultAvatars.get(Integer.parseInt(this.discriminator) % 5) } + String getRawAvatarHash(){ return this.object["avatar"] } /** * @return the user's avatar as a URL string. */ - String getAvatar() { - if (this.getAvatarHash() != null){ - return "https://discordapp.com/api/users/${this.getId()}/avatars/${this.getAvatarHash()}.jpg" - }else{ - return "" - } - } + String getAvatar() { return "https://discordapp.com/api/users/${this.id}/avatars/${this.avatarHash}.jpg" } /** * @return the user's avatar as a URL object. */ - URL getAvatarURL(){ return new URL(this.getAvatar()) } + URL getAvatarURL(){ return new URL(this.avatar) } + URL getAvatarUrl(){ return new URL(this.avatar) } + String getDiscriminator(){ return this.object["discriminator"] } + String getDiscrim(){ return this.object["discriminator"] } /** * @return a private channel for the user. If not created already, it'll create a new one. */ diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/VoiceChannel.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/VoiceChannel.groovy index 150698e..2fd14f1 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/objects/VoiceChannel.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/VoiceChannel.groovy @@ -1,5 +1,11 @@ package ml.hlaaftana.discordg.objects +import ml.hlaaftana.discordg.request.VoiceWSClient +import ml.hlaaftana.discordg.objects.VoiceClient +import org.eclipse.jetty.util.ssl.SslContextFactory +import org.eclipse.jetty.websocket.client.ClientUpgradeRequest +import org.eclipse.jetty.websocket.client.WebSocketClient + /** * A voice channel. Extends Channel. * @author Hlaaftana @@ -10,4 +16,25 @@ class VoiceChannel extends Channel{ void moveMember(Member member){ api.requester.patch("https://discordapp.com/api/guilds/${member.server.id}/members/{member.id}", ["channel_id": this.id]) } + + VoiceClient join(Map muteDeaf=[:]){ + api.wsClient.send([ + "op": 4, + "d": [ + "guild_id": this.server.id, + "channel_id": this.id, + "self_mute": muteDeaf["mute"] as boolean, + "self_deaf": muteDeaf["deaf"] as boolean, + ] + ]) + while (api.voiceData == [:]){} + api.voiceData << [ + channel: this, + guild: this.server.id + ] + Map temp = api.voiceData + api.voiceData = temp.findAll { k, v -> k != "guild_id" } + api.voiceClient = new VoiceClient(api) + return api.voiceClient + } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/objects/VoiceClient.groovy b/src/main/groovy/ml/hlaaftana/discordg/objects/VoiceClient.groovy new file mode 100644 index 0000000..e6f3768 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/objects/VoiceClient.groovy @@ -0,0 +1,10 @@ +package ml.hlaaftana.discordg.objects + +import ml.hlaaftana.discordg.util.AudioUtil + +class VoiceClient { + API api + VoiceClient(API api){ this.api = api } + + +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/request/JSONRequester.groovy b/src/main/groovy/ml/hlaaftana/discordg/request/JSONRequester.groovy new file mode 100644 index 0000000..a22e095 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/request/JSONRequester.groovy @@ -0,0 +1,54 @@ +package ml.hlaaftana.discordg.request + +import ml.hlaaftana.discordg.util.JSONUtil + +import com.mashape.unirest.http.Unirest + +class JSONRequester { + /** + * GETs from a URL. + * @param url - the URL string. + * @return the response as a string. + */ + static get(String url){ + return JSONUtil.parse(Unirest.get(url).asString().getBody()) + } + + /** + * DELETEs a URL. + * @param url - the URL string. + * @return the response as a string. + */ + static delete(String url){ + return JSONUtil.parse(Unirest.delete(url).asString().getBody()) + } + + /** + * POSTs to a URL with a body. + * @param url - the URL string. + * @param body - the body as a Map which will be converted to JSON. + * @return the response as a string. + */ + static post(String url, Map body){ + return JSONUtil.parse(Unirest.post(url).body(JSONUtil.json(body)).asString().getBody()) + } + + /** + * PATCHes a URL with a body. + * @param url - the URL string. + * @param body - the body as a Map which will be converted to JSON. + * @return the response as a string. + */ + static patch(String url, Map body){ + return JSONUtil.parse(Unirest.patch(url).body(JSONUtil.json(body)).asString().getBody()) + } + + /** + * PUTs to a URL. + * @param url - the URL string. + * @return the response as a string. + */ + static put(String url, Map body){ + return JSONUtil.parse(Unirest.put(url).body(JSONUtil.json(body)).asString().getBody()) + } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/request/VoiceWSClient.groovy b/src/main/groovy/ml/hlaaftana/discordg/request/VoiceWSClient.groovy new file mode 100644 index 0000000..b5366fb --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/request/VoiceWSClient.groovy @@ -0,0 +1,121 @@ +package ml.hlaaftana.discordg.request + +import java.nio.* + +import org.eclipse.jetty.websocket.api.Session + +import ml.hlaaftana.discordg.objects.* +import ml.hlaaftana.discordg.util.* + +import org.eclipse.jetty.websocket.api.* +import org.eclipse.jetty.websocket.api.annotations.* + +@WebSocket +class VoiceWSClient { + API api + Session session + Thread keepAliveThread + VoiceChannel channel + int ssrc + String endpoint + long ping + boolean connected + boolean speaking + VoiceWSClient(API api){ this.api = api; this.channel = api.voiceData.channel; this.endpoint = api.voiceData.endpoint } + + @OnWebSocketConnect + void onConnect(Session session){ + this.session = session + this.session.policy.maxTextMessageSize = Integer.MAX_VALUE + this.session.policy.maxTextMessageBufferSize = Integer.MAX_VALUE + this.session.policy.maxBinaryMessageSize = Integer.MAX_VALUE + this.session.policy.maxBinaryMessageBufferSize = Integer.MAX_VALUE + Map a = [ + "op": 0, + "d": [ + "token": api.token, + "server_id": channel.server.id, + "user_id": api.client.user.id, + "session_id": api.client.sessionId + ], + ] + this.send(a) + } + + @OnWebSocketMessage + void onMessage(Session session, String message) throws IOException{ + Map content = JSONUtil.parse(message) + def data = content["d"] + int op = content["op"] + def o = { it == op } + if (o(2)){ + ssrc = data["ssrc"] + int port = data["port"] + long heartbeat = data["heartbeat_interval"] + + keepAliveThread = new Thread({ + while (true){ + this.send(["op": 3, "d": System.currentTimeMillis().toString()]) + try{ Thread.sleep(heartbeat) }catch (InterruptedException ex){} + } + }) + keepAliveThread.daemon = true + keepAliveThread.start() + + api.dispatchEvent("VOICE_READY", data << ["fullData": data]) + }else if(o(3)){ + this.ping = System.currentTimeMillis() - data + api.dispatchEvent("VOICE_PING_UPDATE", ["ping": this.ping, "fullData": data]) + }else if(o(4)){ + this.connected = true + api.dispatchEvent("VOICE_CONNECTED", ["fullData": data]) + }else if(o(5)){ + api.dispatchEvent("USER_SPEAKING_UPDATE", ["speaking": data["speaking"], "user": api.client.getUserById(data["user_id"]), "fullData": data]) + }else{ + Log.info "Unhandled voice op code ${op}. Full content (please report to Hlaaftana):\n${content}" + } + } + + @OnWebSocketClose + void onClose(Session session, int code, String reason){ + Log.info "Connection closed. Reason: " + reason + ", code: " + code.toString() + try{ + this.close() + }catch (ex){ + + } + } + + @OnWebSocketError + void onError(Throwable t){ + t.printStackTrace() + } + + void send(Object message){ + try{ + if (message instanceof Map) + session.remote.sendString(JSONUtil.json(message)) + else + session.remote.sendBytes(message) + }catch (e){ + e.printStackTrace() + } + } + + void close(boolean mute=false, boolean deaf=false){ + if (keepAliveThread != null){ keepAliveThread.interrupt(); keepAliveThread = null } + + api.wsClient.send([ + "op": 4, + "d": [ + "guild_id": null, + "channel_id": null, + "self_mute": mute, + "self_deaf": deaf, + ] + ]) + connected = false + + this.session.close() + } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/request/WSClient.groovy b/src/main/groovy/ml/hlaaftana/discordg/request/WSClient.groovy index 20762f4..d2d0eb4 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/request/WSClient.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/request/WSClient.groovy @@ -54,7 +54,7 @@ class WSClient{ @OnWebSocketMessage void onMessage(Session session, String message) throws IOException{ - def clos = { + threadPool.submit({ Map content = JSONUtil.parse(message) String type = content["t"] if (api.ignorePresenceUpdate && type == "PRESENCE_UPDATE") return @@ -195,7 +195,9 @@ class WSClient{ message: new Message(api, data), sendMessage: { String cont, boolean tts=false -> eventData.message.channel.sendMessage(cont, tts) - } + }, + sendFile: { File file -> eventData.message.channel.sendFile(file) }, + sendFile: { String file -> eventData.message.channel.sendFile(file) } ] if (eventData.message.server == null){ eventData.message = new Message(api, data) @@ -258,13 +260,16 @@ class WSClient{ eventData = [ voiceState: new VoiceState(api, data) ] - if (eventData["voiceState"].user == api.client.user.id){ - if (false){ // voice connected - // + if (data["user_id"] == api.client.user.id){ + if (api.voiceWsClient != null){ // voice connected + api.voiceData.channel = api.client.getVoiceChannelById(data["channel_id"]) } + + api.voiceData.session_id = data["session_id"] } }else if (t("VOICE_SERVER_UPDATE")){ - + api.voiceData << data + eventData = data }else{ eventData = data } @@ -272,13 +277,13 @@ class WSClient{ if (Log.enableEventRegisteringCrashes) ex.printStackTrace() Log.info "Ignoring exception from event object registering" } - eventData.put("fullData", data) - Event event = new Event(type, eventData) + if (!t("READY")) eventData.put("fullData", data) + else if (api.copyReady) eventData.put("fullData", data) + Map event = eventData if (api.isLoaded()){ - api.dispatchEvent(event) + api.dispatchEvent(type, event) } - } - threadPool.submit(clos as Callable) + } as Callable) } @OnWebSocketClose diff --git a/src/main/groovy/ml/hlaaftana/discordg/status/DiscordStatus.groovy b/src/main/groovy/ml/hlaaftana/discordg/status/DiscordStatus.groovy new file mode 100644 index 0000000..ac5f86a --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/status/DiscordStatus.groovy @@ -0,0 +1,8 @@ +package ml.hlaaftana.discordg.status + +import ml.hlaaftana.discordg.request.JSONRequester + +class DiscordStatus { + static Schedule getActiveSchedule(){ return new Schedule(JSONRequester.get("https://status.discordapp.com/api/v2/scheduled-maintenances/active.json")) } + static Schedule getUpcomingSchedule(){ return new Schedule(JSONRequester.get("https://status.discordapp.com/api/v2/scheduled-maintenances/upcoming.json")) } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/status/IncidentUpdate.groovy b/src/main/groovy/ml/hlaaftana/discordg/status/IncidentUpdate.groovy new file mode 100644 index 0000000..12aea4a --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/status/IncidentUpdate.groovy @@ -0,0 +1,19 @@ +package ml.hlaaftana.discordg.status + +import ml.hlaaftana.discordg.objects.MapObject; +import ml.hlaaftana.discordg.util.ConversionUtil + +class IncidentUpdate extends MapObject { + IncidentUpdate(Map object){ super(object) } + + String getStatus(){ return this.object["status"] } + String getBody(){ return this.object["body"] } + String getRawUpdateTime(){ return this.object["updated_at"] } + Date getUpdateTime(){ return ConversionUtil.fromJsonDate(this.object["updated_at"]) } + String getRawCreateTime(){ return this.object["created_at"] } + Date getCreateTime(){ return ConversionUtil.fromJsonDate(this.object["created_at"]) } + String getRawDisplayTime(){ return this.object["display_at"] } + Date getDisplayTime(){ return ConversionUtil.fromJsonDate(this.object["display_at"]) } + String getId(){ return this.object["id"] } + String getIncidentId(){ return this.object["incident_id"] } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/status/Maintenance.groovy b/src/main/groovy/ml/hlaaftana/discordg/status/Maintenance.groovy new file mode 100644 index 0000000..bca7dfe --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/status/Maintenance.groovy @@ -0,0 +1,25 @@ +package ml.hlaaftana.discordg.status + +import ml.hlaaftana.discordg.objects.MapObject; +import ml.hlaaftana.discordg.util.ConversionUtil + +class Maintenance extends MapObject { + Maintenance(Map object){ super(object) } + + String getName(){ return this.object["name"] } + String getStatus(){ return this.object["status"] } + String getRawUpdateTime(){ return this.object["updated_at"] } + Date getUpdateTime(){ return ConversionUtil.fromJsonDate(this.object["updated_at"]) } + String getRawCreateTime(){ return this.object["created_at"] } + Date getCreateTime(){ return ConversionUtil.fromJsonDate(this.object["created_at"]) } + String getShortlink(){ return this.object["shortlink"] } + String getRawScheduleStartTime(){ return this.object["scheduled_for"] } + Date getScheduleStartTime(){ return ConversionUtil.fromJsonDate(this.object["scheduled_for"]) } + String getRawScheduleEndTime(){ return this.object["scheduled_until"] } + Date getScheduleEndTime(){ return ConversionUtil.fromJsonDate(this.object["scheduled_until"]) } + String getId(){ return this.object["id"] } + String getPageId(){ return this.object["page_id"] } + String getImpact(){ return this.object["impact"] } + List getIncidentUpdates(){ return this.object["incident_updates"].collect { new IncidentUpdate(it) } } + List getUpdates(){ return this.object["incident_updates"].collect { new IncidentUpdate(it) } } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/status/Schedule.groovy b/src/main/groovy/ml/hlaaftana/discordg/status/Schedule.groovy new file mode 100644 index 0000000..a32eae5 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/status/Schedule.groovy @@ -0,0 +1,12 @@ +package ml.hlaaftana.discordg.status + +import ml.hlaaftana.discordg.objects.MapObject; + +class Schedule extends MapObject { + Schedule(Map object){ super(object) } + + StatusPage getPage(){ return new StatusPage(this.object["page"]) } + StatusPage getStatusPage(){ return new StatusPage(this.object["page"]) } + List getScheduledMaintenances(){ return this.object["scheduled_maintanences"].collect { new Maintenance(it) } } + List getMaintenances(){ return this.object["scheduled_maintanences"].collect { new Maintenance(it) } } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/status/StatusPage.groovy b/src/main/groovy/ml/hlaaftana/discordg/status/StatusPage.groovy new file mode 100644 index 0000000..0442ca0 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/status/StatusPage.groovy @@ -0,0 +1,14 @@ +package ml.hlaaftana.discordg.status + +import ml.hlaaftana.discordg.objects.MapObject; +import ml.hlaaftana.discordg.util.ConversionUtil + +class StatusPage extends MapObject { + StatusPage(Map object){ super(object) } + + String getId(){ this.object["id"] } + String getName(){ this.object["name"] } + String getUrl(){ this.object["url"] } + String getRawUpdateTime(){ return this.object["updated_at"] } + Date getUpdateTime(){ return ConversionUtil.fromJsonDate(this.object["updated_at"]) } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/util/AudioUtil.groovy b/src/main/groovy/ml/hlaaftana/discordg/util/AudioUtil.groovy new file mode 100644 index 0000000..e09e2de --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/util/AudioUtil.groovy @@ -0,0 +1,67 @@ +package ml.hlaaftana.discordg.util + +import java.nio.* +import net.tomp2p.opuswrapper.Opus + +import com.sun.jna.Native +import com.sun.jna.ptr.PointerByReference + +// copied from https://github.com/tbocek/opus-wrapper/blob/master/src/test/java/net/tomp2p/opuswrapper/OpusExample.java +class AudioUtil { + static int sampleRate = 48000 + static int channels = 2 + static int frameSize = 960 + static int frameTimeAmount = 20 + + static { + try{ + System.loadLibrary("opus") + }catch (UnsatisfiedLinkError ex1){ + try { + File f = Native.extractFromResourcePath("opus") + System.load(f.absolutePath) + }catch (Exception ex2) { + ex1.printStackTrace() + ex2.printStackTrace() + } + } + } + + static ShortBuffer decode(List packets) { + IntBuffer error = IntBuffer.allocate(4) + PointerByReference opusDecoder = Opus.INSTANCE.opus_decoder_create(sampleRate, channels, error) + + ShortBuffer shortBuffer = ShortBuffer.allocate(1024 * 1024) + for (dataBuffer in packets) { + byte[] transferedBytes = new byte[dataBuffer.remaining()] + dataBuffer.get(transferedBytes) + int decoded = Opus.INSTANCE.opus_decode(opusDecoder, transferedBytes, transferedBytes.length, + shortBuffer, frameSize, 0) + shortBuffer.position(shortBuffer.position() + decoded) + } + shortBuffer.flip() + + Opus.INSTANCE.opus_decoder_destroy(opusDecoder) + return shortBuffer + } + + static List encode(ShortBuffer shortBuffer) { + IntBuffer error = IntBuffer.allocate(4) + PointerByReference opusEncoder = Opus.INSTANCE.opus_encoder_create(sampleRate, channels, + Opus.OPUS_APPLICATION_RESTRICTED_LOWDELAY, error) + int read = 0 + List list = new ArrayList<>() + while (shortBuffer.hasRemaining()) { + ByteBuffer dataBuffer = ByteBuffer.allocate(1024) + int toRead = Math.min(shortBuffer.remaining(), dataBuffer.remaining()) + read = Opus.INSTANCE.opus_encode(opusEncoder, shortBuffer, frameSize, dataBuffer, toRead) + dataBuffer.position(dataBuffer.position() + read) + dataBuffer.flip() + list.add(dataBuffer) + shortBuffer.position(shortBuffer.position() + frameSize) + } + Opus.INSTANCE.opus_encoder_destroy(opusEncoder) + shortBuffer.flip() + return list + } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/util/ConversionUtil.groovy b/src/main/groovy/ml/hlaaftana/discordg/util/ConversionUtil.groovy index ed2fe0b..5b7b0f3 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/util/ConversionUtil.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/util/ConversionUtil.groovy @@ -8,7 +8,7 @@ class ConversionUtil { return "data:image/jpg;base64," + image.bytes.encodeBase64().toString() } - static Date toDiscordDate(String string){ + static Date fromJsonDate(String string){ return new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSSSSXXX").parse(string) } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/util/bot/CommandBot.groovy b/src/main/groovy/ml/hlaaftana/discordg/util/bot/CommandBot.groovy index 3984623..80638d5 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/util/bot/CommandBot.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/util/bot/CommandBot.groovy @@ -1,9 +1,7 @@ package ml.hlaaftana.discordg.util.bot import ml.hlaaftana.discordg.objects.API -import ml.hlaaftana.discordg.objects.Event import ml.hlaaftana.discordg.util.Log -import ml.hlaaftana.discordg.util.bot.SuffixCommandBot.Command; /** * A simple bot implementation. @@ -62,16 +60,16 @@ class CommandBot { * @param password - the password to log in with. */ def initialize(String email="", String password=""){ - api.addListener("message create") { Event e -> + api.addListener("message create") { Map d -> for (c in commands){ for (p in c.prefixes){ for (a in c.aliases){ - if ((e.data.message.content + " ").toLowerCase().startsWith(p.toLowerCase() + a.toLowerCase() + " ")){ + if ((d.message.content + " ").toLowerCase().startsWith(p.toLowerCase() + a.toLowerCase() + " ")){ try{ if (acceptOwnCommands){ - c.run(e) - }else if (!(e.data.message.author.id == api.client.user.id)){ - c.run(e) + c.run(d) + }else if (!(d.message.author.id == api.client.user.id)){ + c.run(d) } }catch (ex){ ex.printStackTrace() @@ -115,15 +113,15 @@ class CommandBot { /** * Gets the text after the command trigger for this command. - * @param e - an event object. + * @param d - the event data. * @return the arguments as a string. */ - def args(Event e){ + def args(Map d){ try{ for (p in prefixes){ for (a in aliases){ - if ((e.data.message.content + " ").toLowerCase().startsWith(p.toLowerCase() + a.toLowerCase() + " ")){ - return e.data.message.content.substring((p + a + " ").length()) + if ((d.message.content + " ").toLowerCase().startsWith(p.toLowerCase() + a.toLowerCase() + " ")){ + return d.message.content.substring((p + a + " ").length()) } } } @@ -134,9 +132,9 @@ class CommandBot { /** * Runs the command. - * @param e - an event object. + * @param d - the event data. */ - abstract def run(Event e) + abstract def run(Map d) } /** @@ -155,8 +153,8 @@ class CommandBot { this.response = response } - def run(Event e){ - e.data.sendMessage(response) + def run(Map d){ + d.sendMessage(response) } } @@ -168,20 +166,16 @@ class CommandBot { Closure response /** - * @param response - a closure to respond with to this command. Can take one or two parameters. If it takes one, it has to be an Event object. If it takes two, the first one has to be an Event object, and the second one has to be a Command object.
- * The rest of the parameters are Command's parameters. + * @param response - a closure to respond with to this command. Can take one parameter, which is the data of the event. */ ClosureCommand(Closure response, def aliasOrAliases, def prefixOrPrefixes=CommandBot.defaultPrefix){ super(aliasOrAliases, prefixOrPrefixes) + response.delegate = this this.response = response } - def run(Event e){ - if (response.maximumNumberOfParameters > 1){ - response(e, this) - }else{ - response(e) - } + def run(Map d){ + response(d) } } } diff --git a/src/main/groovy/ml/hlaaftana/discordg/util/bot/RegexCommandBot.groovy b/src/main/groovy/ml/hlaaftana/discordg/util/bot/RegexCommandBot.groovy new file mode 100644 index 0000000..aa0e427 --- /dev/null +++ b/src/main/groovy/ml/hlaaftana/discordg/util/bot/RegexCommandBot.groovy @@ -0,0 +1,196 @@ +package ml.hlaaftana.discordg.util.bot + +import ml.hlaaftana.discordg.objects.API +import ml.hlaaftana.discordg.util.Log + +/** + * A simple bot implementation. + * @author Hlaaftana + */ +class RegexCommandBot { + String name = "DiscordG|CommandBot" + API api + List commands = [] + static def defaultPrefix + boolean acceptOwnCommands = false + private boolean loggedIn = false + + /** + * @param api - The API object this bot should use. + * @param commands - A List of Commands you want to register right off the bat. Empty by default. + */ + RegexCommandBot(API api, List commands=[]){ + this.api = api + this.commands += commands + } + + static def create(List commands=[]){ + return new RegexCommandBot(new API(), commands) + } + + /** + * Adds a command. + * @param command - the command. + */ + def addCommand(Command command){ + commands.add(command) + } + + /** + * Adds a List of Commands. + * @param commands - the list of commands. + */ + def addCommands(List commands){ + this.commands.addAll(commands) + } + + /** + * Logs in with the API before initalizing. You don't have to type your email and password when calling #initalize. + * @param email - the email to log in with. + * @param password - the password to log in with. + */ + def login(String email, String password){ + loggedIn = true + api.login(email, password) + } + + /** + * Starts the bot. You don't have to enter any parameters if you ran #login already. + * @param email - the email to log in with. + * @param password - the password to log in with. + */ + def initialize(String email="", String password=""){ + api.addListener("message create") { Map d -> + for (c in commands){ + for (p in c.prefixes){ + for (a in c.aliases){ + if ((d.message.content + " ").toLowerCase() ==~ p.toLowerCase() + a.toLowerCase() + " .*"){ + try{ + if (acceptOwnCommands){ + c.run(d) + }else if (!(d.message.author.id == api.client.user.id)){ + c.run(d) + } + }catch (ex){ + ex.printStackTrace() + Log.error "Command threw exception", this.name + } + } + } + } + } + } + if (!loggedIn){ + if (email.empty || password.empty) throw new Exception() + api.login(email, password) + } + } + + /** + * A command. + * @author Hlaaftana + */ + static abstract class Command{ + List prefixes = [] + List aliases = [] + + /** + * @param aliasOrAliases - A String or List of Strings of aliases this command will trigger with. + * @param prefixOrPrefixes - A String or List of Strings this command will be triggered by. Note that this is optional, and is CommandBot.defaultPrefix by default. + */ + Command(def aliasOrAliases, def prefixOrPrefixes=RegexCommandBot.defaultPrefix){ + if (aliasOrAliases instanceof List || aliasOrAliases instanceof Object[]){ + aliases.addAll(aliasOrAliases) + }else{ + aliases.add(aliasOrAliases.toString()) + } + if (prefixOrPrefixes instanceof List || prefixOrPrefixes instanceof Object[]){ + prefixes.addAll(prefixOrPrefixes) + }else{ + prefixes.add(prefixOrPrefixes.toString()) + } + } + + // listen + // i hate regex + // and love it + // just don't criticize me for this code + + /** + * Gets the text after the command trigger for this command. + * @param d - the event data. + * @return the arguments as a string. + */ + def args(Map d){ + return (this.allCaptures(d)[0] != null) ? d.message.content.substring(this.allCaptures(d)[0].length() + 1) : "" + } + + def captures(Map d){ + return this.allCaptures(d).with { remove(0); delegate } + } + + def allCaptures(Map d){ + try{ + for (p in prefixes){ + for (a in aliases){ + if ((d.message.content + " ") ==~ (p + a + " .*")){ + def match = (d.message.content =~ p + a).collect{it}[0] + List holyShit = (match instanceof String) ? [match] : match + return holyShit + } + } + } + }catch (ex){ + return [] + } + } + + /** + * Runs the command. + * @param d - the event data. + */ + abstract def run(Map d) + } + + /** + * An implementation of Command with a string response. + * @author Hlaaftana + */ + static class ResponseCommand extends Command{ + String response + + /** + * @param response - a string to respond with to this command.
+ * The rest of the parameters are Command's parameters. + */ + ResponseCommand(String response, def aliasOrAliases, def prefixOrPrefixes=CommandBot.defaultPrefix){ + super(aliasOrAliases, prefixOrPrefixes) + this.response = response + } + + def run(Map d){ + d.sendMessage(response) + } + } + + /** + * An implementation of Command with a closure response. + * @author Hlaaftana + */ + static class ClosureCommand extends Command{ + Closure response + + /** + * @param response - a closure to respond with to this command. Can take one parameter, which is the data of the event. + */ + ClosureCommand(Closure response, def aliasOrAliases, def prefixOrPrefixes=CommandBot.defaultPrefix){ + super(aliasOrAliases, prefixOrPrefixes) + response.delegate = this + this.response = response + } + + def run(Map d){ + response(d) + } + } +} diff --git a/src/main/groovy/ml/hlaaftana/discordg/util/bot/SuffixCommandBot.groovy b/src/main/groovy/ml/hlaaftana/discordg/util/bot/SuffixCommandBot.groovy index f1f559c..05c200d 100644 --- a/src/main/groovy/ml/hlaaftana/discordg/util/bot/SuffixCommandBot.groovy +++ b/src/main/groovy/ml/hlaaftana/discordg/util/bot/SuffixCommandBot.groovy @@ -1,7 +1,6 @@ package ml.hlaaftana.discordg.util.bot import ml.hlaaftana.discordg.objects.API -import ml.hlaaftana.discordg.objects.Event import ml.hlaaftana.discordg.util.Log /** @@ -61,16 +60,16 @@ class SuffixCommandBot { * @param password - the password to log in with. */ def initialize(String email="", String password=""){ - api.addListener("message create") { Event e -> + api.addListener("message create") { Map d -> for (c in commands){ for (p in c.suffixes){ for (a in c.aliases){ - if ((e.data.message.content + " ").toLowerCase().startsWith(a.toLowerCase() + p.toLowerCase() + " ")){ + if ((d.message.content + " ").toLowerCase().startsWith(a.toLowerCase() + p.toLowerCase() + " ")){ try{ if (acceptOwnCommands){ - c.run(e) - }else if (!(e.data.message.author.id == api.client.user.id)){ - c.run(e) + c.run(d) + }else if (!(d.message.author.id == api.client.user.id)){ + c.run(d) } }catch (ex){ ex.printStackTrace() @@ -117,12 +116,12 @@ class SuffixCommandBot { * @param e - an event object. * @return the arguments as a string. */ - def args(Event e){ + def args(Map d){ try{ for (p in suffixes){ for (a in aliases){ - if ((e.data.message.content + " ").toLowerCase().startsWith(a.toLowerCase() + p.toLowerCase() + " ")){ - return e.data.message.content.substring((a + p + " ").length()) + if ((d.message.content + " ").toLowerCase().startsWith(a.toLowerCase() + p.toLowerCase() + " ")){ + return d.message.content.substring((a + p + " ").length()) } } } @@ -135,7 +134,7 @@ class SuffixCommandBot { * Runs the command. * @param e - an event object. */ - abstract def run(Event e) + abstract def run(Map d) } /** @@ -154,8 +153,8 @@ class SuffixCommandBot { this.response = response } - def run(Event e){ - e.data.sendMessage(response) + def run(Map d){ + d.sendMessage(response) } } @@ -167,20 +166,16 @@ class SuffixCommandBot { Closure response /** - * @param response - a closure to respond with to this command. Can take one or two parameters. If it takes one, it has to be an Event object. If it takes two, the first one has to be an Event object, and the second one has to be a Command object.
- * The rest of the parameters are Command's parameters. + * @param response - a closure to respond with to this command. Can take one parameter, which is the data of the event. */ ClosureCommand(Closure response, def aliasOrAliases, def suffixOrSuffixes=SuffixCommandBot.defaultSuffix){ super(aliasOrAliases, suffixOrSuffixes) + response.delegate = this this.response = response } - def run(Event e){ - if (response.maximumNumberOfParameters > 1){ - response(e, this) - }else{ - response(e) - } + def run(Map d){ + response(d) } } }