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 }