pingex
/
DiscordBot
Archived
1
0
Fork 0

Done #3 (ACL), still need testing though

master
Pingex aka Raphaël 9 years ago
parent 0074f64537
commit 86ab3f8490

1
.gitignore vendored

@ -70,3 +70,4 @@ gradle-app.setting
# gradle/wrapper/gradle-wrapper.properties # gradle/wrapper/gradle-wrapper.properties
config.ini config.ini
permissions.json

@ -29,6 +29,7 @@ dependencies {
compile "org.ini4j:ini4j:0.5.4" compile "org.ini4j:ini4j:0.5.4"
compile "org.slf4j:slf4j-simple:1.7.9" compile "org.slf4j:slf4j-simple:1.7.9"
compile "org.apache.commons:commons-lang3:3.0" compile "org.apache.commons:commons-lang3:3.0"
compile "com.google.code.gson:gson:2.6.2"
} }
jar { jar {

@ -3,6 +3,9 @@
; Will fallback to "!" if the prefix is invalid or empty. ; Will fallback to "!" if the prefix is invalid or empty.
commandPrefix = ! commandPrefix = !
; Bot Owner UID, obtainable using /perm:getMyUID
owner = XXXXXXXXXXXXXXXXXX
[discord] [discord]
; Change next value to true if the account is a bot, thus logins using a token. ; Change next value to true if the account is a bot, thus logins using a token.
; Don't forget to uncomment token then ; Don't forget to uncomment token then
@ -15,3 +18,7 @@ password = samplePassword
; Comma-separated list of modules to load ; Comma-separated list of modules to load
; enable = net.pingex.discordbot.HelloWorldModule, net.pingex.discordbot.AnothaModule, fr.w4rell.discordbot.ThemeSombreModule ; enable = net.pingex.discordbot.HelloWorldModule, net.pingex.discordbot.AnothaModule, fr.w4rell.discordbot.ThemeSombreModule
enable = net.pingex.discordbot.HelloWorldModule enable = net.pingex.discordbot.HelloWorldModule
[permissions]
; The location of the permissions file, defaulting to `permissions.json` if not specified
file = permissions.json

@ -23,4 +23,9 @@ public @interface Command
* Shortened command * Shortened command
*/ */
String shorthand() default ""; String shorthand() default "";
/**
* Default permission for this command
*/
DefaultPermission permission() default DefaultPermission.EVERYONE;
} }

@ -111,20 +111,30 @@ class CommandDispatcher
parsedArray = new Object[foundMethod.getMethod().getParameterCount()]; parsedArray = new Object[foundMethod.getMethod().getParameterCount()];
parsedArray[0] = event; parsedArray[0] = event;
if(foundMethod.getMethod().getParameterCount()-1 == args.size()) // To be redone, maybe ?
Boolean canOverrideRun = PermissionsModule.getInstance().canRun(event.getMessage().getGuild(), event.getMessage().getAuthor(), fullCommand);
if(!foundMethod.getMethod().getAnnotation(Command.class).permission().eval(event))
commandAnswer = "Permission denied.";
if(canOverrideRun != null)
commandAnswer = canOverrideRun ? null : "Permission denied.";
if(commandAnswer == null)
{ {
for(int i=1; i < foundMethod.getMethod().getParameterCount(); i++) if(foundMethod.getMethod().getParameterCount()-1 == args.size())
try {
{ for(int i=1; i < foundMethod.getMethod().getParameterCount(); i++)
parsedArray[i] = parse(foundMethod.getMethod().getParameterTypes()[i], args.get(i-1)); try
} catch (IllegalArgumentException e) {
{ parsedArray[i] = parse(foundMethod.getMethod().getParameterTypes()[i], args.get(i-1));
commandAnswer = "Failed to parse arguments, are they correct ? " + commandList.get("internal:help").invoke(event, fullCommand); } catch (IllegalArgumentException e)
break; {
} commandAnswer = "Failed to parse arguments, are they correct ? " + commandList.get("internal:help").invoke(event, fullCommand);
break;
}
}
else
commandAnswer = "Invalid arguments. " + commandList.get("internal:help").invoke(event, fullCommand);
} }
else
commandAnswer = "Invalid arguments. " + commandList.get("internal:help").invoke(event, fullCommand);
} }
else else
commandAnswer = "Unknown command"; commandAnswer = "Unknown command";

@ -0,0 +1,56 @@
package net.pingex.discordbot;
import sx.blah.discord.handle.impl.events.MessageReceivedEvent;
import java.util.function.Predicate;
/**
* Enum describing the default behavior of a command when no specific permission override is given.
* @version 0.1-dev
* @author Raphael "Pingex" NAAS
*/
public enum DefaultPermission
{
/**
* Everyone can call the command
*/
EVERYONE(e -> true),
/**
* Only the owner of the guild where command was run can run the command
*/
GUILD_OWNER(e -> e.getMessage().getGuild().getOwnerID().equals(e.getMessage().getAuthor().getID())),
/**
* Only the bot owner can run the command. Bot owner can be defined in the configuration file
*/
BOT_OWNER(e -> e.getMessage().getAuthor().getID().equals(Configuration.getValue("general", "owner"))),
/**
* Nobody can run the command
*/
NONE(e -> false);
/**
* The predicate for each enum value defined above
*/
private Predicate<MessageReceivedEvent> predicate;
/**
* Quick constructor, no further explanation needed
* @param p Input Predicate
*/
DefaultPermission(Predicate<MessageReceivedEvent> p)
{
predicate = p;
}
/**
* Evaluates the input MRE against the predicate of the said enum value
* @param e The input event, containing all needed methods to evaluate the predicate
* @return What Predicate.test() returns
*/
boolean eval(MessageReceivedEvent e)
{
return predicate.test(e);
}
}

@ -45,6 +45,7 @@ class DiscordBot
// Hardcoded internal modules // Hardcoded internal modules
new InternalCommandsModule(client); new InternalCommandsModule(client);
new PermissionsModule(client);
if(!Configuration.exists("plugins", "enable") || Configuration.getValue("plugins", "enable").isEmpty()) if(!Configuration.exists("plugins", "enable") || Configuration.getValue("plugins", "enable").isEmpty())
{ {

@ -0,0 +1,360 @@
package net.pingex.discordbot;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import net.pingex.discordbot.json.permissions.Guild;
import sx.blah.discord.api.IDiscordClient;
import sx.blah.discord.handle.impl.events.MessageReceivedEvent;
import sx.blah.discord.handle.obj.IGuild;
import sx.blah.discord.handle.obj.IRole;
import sx.blah.discord.handle.obj.IUser;
import sx.blah.discord.util.DiscordException;
import sx.blah.discord.util.HTTP429Exception;
import sx.blah.discord.util.MissingPermissionsException;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
/**
* Permissions modules.
* @version 0.1-dev
* @author Raphael "Pingex" NAAS
*/
@Controllable(name="perm")
class PermissionsModule extends AbstractModule
{
/**
* Main permissions table
*/
private HashMap<String, Guild> permissions;
/**
* Gson instance for json (de)serialisation
*/
private static Gson gson = new GsonBuilder().setPrettyPrinting().create();
/**
* Current instance of this module, for easier access
*/
private static PermissionsModule INSTANCE = null;
/**
* Get the current instance of this module
* @return This instance
*/
public static PermissionsModule getInstance()
{
return INSTANCE;
}
/**
* Constructor doing all the basic stuff, like registering as a Listener to Discord, getting a logger, etc.
* @param client Discord Client instance used to register self.
*/
public PermissionsModule(IDiscordClient client)
{
super(client);
INSTANCE = this;
if(!Configuration.exists("permissions", "file") || Configuration.getValue("permissions", "file").isEmpty())
{
logger.warning("Permissions file location not specified, defaulting to `permissions.json`");
Configuration.setValue("permissions", "file", "permissions.json");
}
reloadPermissions();
}
/**
* Gives the UID of the User passed as arguments
* @param name The current name of the user
* @param discriminator His discriminator
* @return The ID of the target user
*/
@Command
public String getUID(MessageReceivedEvent event, String name, String discriminator)
{
for(IUser i : event.getMessage().getGuild().getUsers())
if(i.getName().equals(name) && i.getDiscriminator().equals(discriminator))
return i.getID();
return "User not found";
}
/**
* Gets caller's UID
* @return Caller's UID
*/
@Command
public String getMyUID(MessageReceivedEvent event)
{
return event.getMessage().getAuthor().getID();
}
/**
* Give GID of the named role
* @param name The role current name
* @return The GID of the role
*/
@Command
public String getGID(MessageReceivedEvent event, String name)
{
for(IRole i : event.getMessage().getGuild().getRoles())
if(i.getName().equals(name))
return i.getID();
return "Role not found";
}
/**
* Dump all Roles and their GID
* Note: this commands answers the dump via PM to avoid spam and the @everyone role to be publicly displayed thus notify everyone in the guild.
* @return The whole dump, formatted as a displayable String
*/
@Command
public String dumpGID(MessageReceivedEvent event)
{
StringBuffer buffer = new StringBuffer("Dumping Roles for Guild ").append(event.getMessage().getGuild().getName()).append("\n");
for(IRole i : event.getMessage().getGuild().getRoles())
buffer.append(i.getName()).append("\t").append(i.getID()).append("\n");
// PM the IDs
// TODO: Remove when PM answers are implemented
try
{
client.getOrCreatePMChannel(event.getMessage().getAuthor()).sendMessage(buffer.toString());
} catch (MissingPermissionsException | HTTP429Exception | DiscordException e)
{
logger.warning("Fail to send the result");
e.printStackTrace();
}
return "Check your PM for the command result";
}
/**
* Dump all Users and their UID
* Note: this commands answers the dump via PM to avoid spam.
* @return The whole dump, formatted as a displayable String
*/
@Command
public String dumpUID(MessageReceivedEvent event)
{
StringBuffer buffer = new StringBuffer("Dumping all users for Guild ").append(event.getMessage().getGuild().getName()).append("\n");
for(IUser i : event.getMessage().getGuild().getUsers())
buffer.append(i.getName()).append("#").append(i.getDiscriminator()).append("\t").append(i.getID()).append("\n");
// PM the IDs
// TODO: Remove when PM answers are implemented
try
{
client.getOrCreatePMChannel(event.getMessage().getAuthor()).sendMessage(buffer.toString());
} catch (MissingPermissionsException | HTTP429Exception | DiscordException e)
{
logger.warning("Fail to send the result");
e.printStackTrace();
}
return "Check your PM for the command result";
}
/**
* Reload permissions from file
* @return Nothing
*/
@Command
public String reload(MessageReceivedEvent event)
{
reloadPermissions();
return null;
}
/**
* Save permissions to file
* @return Nothing
*/
@Command
public String save(MessageReceivedEvent event)
{
writePermissions();
return null;
}
/**
* Sets an user permission
* @param command The target *full* command, shorthands doesn't work
* @param user The user's UID
* @param target `true` to allow execution of the command, `false` to deny it, `null` to remove the rule
* @return
*/
@Command
public String setUser(MessageReceivedEvent event, String command, String user, Boolean target)
{
if(!permissions.containsKey(event.getMessage().getGuild().getID()))
permissions.put(event.getMessage().getGuild().getID(), new Guild(new HashMap<>()));
Guild g = permissions.get(event.getMessage().getGuild().getID());
if(!g.commands.containsKey(command))
g.commands.put(command, new net.pingex.discordbot.json.permissions.Command(new HashMap<>(), new HashMap<>()));
net.pingex.discordbot.json.permissions.Command c = g.commands.get(command);
if(target == null) c.users.remove(user);
else c.users.put(user, target);
return "OK";
}
/**
* Sets a role permission
* @param command The target *full* command, shorthands doesn't work
* @param group The role's GID
* @param target `true` to allow execution of the command, `false` to deny it, `null` to remove the rule
* @return
*/
@Command
public String setRole(MessageReceivedEvent event, String command, String group, Boolean target)
{
if(!permissions.containsKey(event.getMessage().getGuild().getID()))
permissions.put(event.getMessage().getGuild().getID(), new Guild(new HashMap<>()));
Guild g = permissions.get(event.getMessage().getGuild().getID());
if(!g.commands.containsKey(command))
g.commands.put(command, new net.pingex.discordbot.json.permissions.Command(new HashMap<>(), new HashMap<>()));
net.pingex.discordbot.json.permissions.Command c = g.commands.get(command);
if(target == null) c.roles.remove(group);
else c.roles.put(group, target);
return "OK";
}
/**
* Dump all permissions defined for the current guild
* @return Displayable String of the permissions
*/
@Command(shorthand = "dumpPerm")
public String dumpPermissions(MessageReceivedEvent event)
{
StringBuffer buffer = new StringBuffer("Dumping all permissions for Guild ").append(event.getMessage().getGuild().getName()).append("\n\n```");
if(!permissions.containsKey(event.getMessage().getGuild().getID()))
return "No permissions set for Guild";
Guild g = permissions.get(event.getMessage().getGuild().getID());
for(Map.Entry<String, net.pingex.discordbot.json.permissions.Command> i : g.commands.entrySet())
{
buffer.append("========== COMMAND ").append(i.getKey()).append(" ==========\n");
// USERS
buffer.append("----- Users -----\n");
for(Map.Entry<String, Boolean> j : i.getValue().users.entrySet())
{
buffer.append(j.getKey()).append("\t").append(j.getValue()).append("\n");
}
buffer.append("\n");
// GROUPS
buffer.append("----- Roles -----\n");
for(Map.Entry<String, Boolean> j : i.getValue().roles.entrySet())
{
buffer.append(j.getKey()).append("\t").append(j.getValue()).append("\n");
}
buffer.append("\n\n");
}
buffer.append("```");
return buffer.toString();
}
/**
* Says if the permissions overrider will allow the target user to run the target command
* @param user User name
* @param discriminator His discriminator
* @param command The target command
* @return `YES` if he can run it, `NO` if he can't, `N/A` if the decision relies to the default behavior
*/
@Command
public String canRun(MessageReceivedEvent event, String user, String discriminator, String command)
{
IUser target = null;
for(IUser i : event.getMessage().getGuild().getUsers())
if(i.getName().equals(user) && i.getDiscriminator().equals(discriminator))
{
target = i;
break;
}
if(target == null)
return "User not found in this Guild.";
Boolean toReturn = canRun(event.getMessage().getGuild(), target, command);
return toReturn == null ? "N/A" : (toReturn ? "YES" : "NO");
}
/**
* Says if the permissions overrider will allow the target user to run the target command. To be run internally
* @param guild The current guild
* @param user The target user
* @param command The target command
* @return `true` is he can run the command, `false` if he can't, `null` if the overrider has nothing to say
*/
public Boolean canRun(IGuild guild, IUser user, String command)
{
// Return null if command or guild aren't mentioned in the permissions file
if(!permissions.containsKey(guild.getID()) || !permissions.get(guild.getID()).commands.containsKey(command)) return null;
net.pingex.discordbot.json.permissions.Command c = permissions.get(guild.getID()).commands.get(command);
// User matching
if(c.users.containsKey(user.getID()))
return c.users.get(user.getID());
// Role matching
for(IRole i : user.getRolesForGuild(guild))
{
if(c.roles.containsKey(i.getID()))
return c.roles.get(i.getID());
}
return null;
}
/**
* Reloads the permissions file. Discards the current permissions cache
*/
private void reloadPermissions()
{
logger.info("Reloading permissions");
try
{
FileReader reader = new FileReader(Configuration.getValue("permissions", "file"));
permissions = gson.fromJson(reader, new TypeToken<HashMap<String, Guild>>(){}.getType());
reader.close();
}
catch (FileNotFoundException e)
{
logger.info("File doesn't exist, using default permissions");
permissions = new HashMap<>();
}
catch (IOException e)
{
logger.severe("I/O error while reading permissions file");
e.printStackTrace();
}
}
/**
* Write the current HashMap to file.
*/
private void writePermissions()
{
logger.info("Writing permissions");
try
{
FileWriter writer = new FileWriter(Configuration.getValue("permissions", "file"));
writer.write(gson.toJson(permissions));
writer.close();
}
catch (IOException e)
{
logger.severe("I/O error while writing permissions file");
e.printStackTrace();
}
}
}

@ -0,0 +1,25 @@
package net.pingex.discordbot.json.permissions;
import java.util.HashMap;
/**
* A command's permissions list
*/
public class Command
{
/**
* Users
*/
public HashMap<String, Boolean> users;
/**
* Roles
*/
public HashMap<String, Boolean> roles;
public Command(HashMap<String, Boolean> users, HashMap<String, Boolean> roles)
{
this.users = users;
this.roles = roles;
}
}

@ -0,0 +1,19 @@
package net.pingex.discordbot.json.permissions;
import java.util.HashMap;
/**
* Json representation of a guild, contains an HashMap of commands
*/
public class Guild
{
/**
* The list of commands which have permission overrides defined.
*/
public HashMap<String, Command> commands;
public Guild(HashMap<String, Command> commands)
{
this.commands = commands;
}
}