1 /** 2 Copyright: Copyright (c) 2015-2017 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 module voxelman.command.plugin; 7 8 import netlib; 9 import pluginlib; 10 public import netlib : SessionId; 11 public import std.getopt; 12 import voxelman.log; 13 import std..string : format; 14 15 struct CommandParams 16 { 17 string rawArgs; // without command name 18 auto rawStrippedArgs() @property 19 { 20 import std..string : strip; 21 return rawArgs.strip; 22 } 23 string[] args; // first arg is command name. Use with getopt. 24 SessionId source; 25 } 26 27 // On client side source == 0 28 // On server if command is issued locally source == 0 29 // First argument is command name (useful for std.getopt) 30 alias CommandHandler = void delegate(CommandParams params); 31 32 enum ExecStatus 33 { 34 success, 35 notRegistered, 36 //invalidArgs, 37 error 38 } 39 40 struct ExecResult 41 { 42 string[] args; 43 ExecStatus status; 44 string error; 45 } 46 47 final class CommandPluginClient : IPlugin 48 { 49 mixin CommandPluginCommon; 50 } 51 52 final class CommandPluginServer : IPlugin 53 { 54 mixin CommandPluginCommon; 55 mixin CommandPluginServerImpl; 56 } 57 58 mixin template CommandPluginCommon() 59 { 60 // IPlugin stuff 61 mixin IdAndSemverFrom!"voxelman.command.plugininfo"; 62 63 CommandHandler[string] handlers; 64 65 void registerCommand(string commandName, CommandHandler handler) 66 { 67 import std.algorithm : splitter; 68 foreach(comAlias; commandName.splitter('|')) 69 { 70 assert(comAlias !in handlers, comAlias ~ " command is already registered"); 71 handlers[comAlias] = handler; 72 } 73 } 74 75 ExecResult execute(const(char)[] input, SessionId source = SessionId(0)) 76 { 77 import std.regex : ctRegex, splitter; 78 import std..string : strip; 79 import std.array : array; 80 81 string stripped = cast(string)input.strip; 82 string[] args = splitter(stripped, ctRegex!`\s+`).array; 83 84 if (args.length == 0) 85 return ExecResult(args, ExecStatus.notRegistered); 86 87 string comName = args[0]; 88 string rawArgs = stripped[args[0].length..$]; 89 90 if (auto handler = handlers.get(cast(string)comName, null)) 91 { 92 try 93 { 94 handler(CommandParams(rawArgs, args, source)); 95 } 96 catch(Exception e) 97 { 98 return ExecResult(args, ExecStatus.error, e.msg); 99 } 100 } 101 else 102 { 103 return ExecResult(args, ExecStatus.notRegistered); 104 } 105 106 return ExecResult(args, ExecStatus.success); 107 } 108 } 109 110 mixin template CommandPluginServerImpl() 111 { 112 import voxelman.net.plugin : NetServerPlugin; 113 import voxelman.core.packets : CommandPacket; 114 import voxelman.net.packets : MessagePacket; 115 import voxelman.session.server; 116 117 NetServerPlugin connection; 118 ClientManager clientMan; 119 120 override void init(IPluginManager pluginman) 121 { 122 connection = pluginman.getPlugin!NetServerPlugin; 123 connection.registerPacketHandler!CommandPacket(&handleCommandPacket); 124 clientMan = pluginman.getPlugin!ClientManager; 125 } 126 127 void handleCommandPacket(ubyte[] packetData, SessionId sessionId) 128 { 129 if (sessionId != 0) // not server 130 { 131 if (!clientMan.isLoggedIn(sessionId)) 132 { 133 connection.sendTo(sessionId, MessagePacket("Log in to use commands")); 134 return; 135 } 136 } 137 auto packet = unpackPacket!CommandPacket(packetData); 138 139 ExecResult res = execute(packet.command, sessionId); 140 141 if (res.status == ExecStatus.notRegistered) 142 connection.sendTo(sessionId, MessagePacket(format("Unknown command '%s'", packet.command))); 143 else if (res.status == ExecStatus.error) 144 connection.sendTo(sessionId, 145 MessagePacket(format("Error executing command '%s': %s", packet.command, res.error))); 146 } 147 }