1 module hexchat.plugin;
2 
3 import hexchat.capi;
4 import std.traits : lvalueOf;
5 
6 private __gshared hexchat_plugin* ph; // Plugin handle
7 
8 ///
9 struct PluginInfo
10 {
11 	string name; /// Name of this plugin.
12 	string description; /// Description for this plugin.
13 	string version_; /// Version string for this plugin.
14 }
15 
16 private __gshared PluginInfo pluginInfo;
17 
18 /**
19  * Type of client this plugin should be compatible with.
20  */
21 enum PluginStyle
22 {
23 	hexchat, /// HexChat plugin.
24 	xchat /// XChat plugin.
25 }
26 
27 enum initFuncName(PluginStyle style : PluginStyle.hexchat) = "hexchat_plugin_init";
28 enum deinitFuncName(PluginStyle style : PluginStyle.hexchat) = "hexchat_plugin_deinit";
29 enum initFuncName(PluginStyle style : PluginStyle.xchat) = "xchat_plugin_init";
30 enum deinitFuncName(PluginStyle style : PluginStyle.xchat) = "xchat_plugin_deinit";
31 template initFuncName(PluginStyle) { static assert(false); }
32 template deinitFuncName(PluginStyle) { static assert(false); }
33 
34 // Public undocumented
35 int __initDPlugin(InitFunc)(
36 		InitFunc initFunc,
37 		void* plugin_handle,
38 		immutable(char)** plugin_name,
39 		immutable(char)** plugin_desc,
40 		immutable(char)** plugin_version)
41 	if(is(typeof(initFunc(lvalueOf!PluginInfo))))
42 {
43 	import std.string : fromStringz, toStringz;
44 	import hexchat.capi;
45 
46 	version(Windows) {}
47 	else
48 	{
49 		import core.runtime : Runtime;
50 
51 		if(!Runtime.initialize())
52 			return 0;
53 	}
54 
55 	.ph = cast(hexchat_plugin*)plugin_handle;
56 
57 	if(plugin_name && *plugin_name)
58 		.pluginInfo.name = fromStringz(*plugin_name);
59 
60 	if(plugin_desc && *plugin_desc)
61 		.pluginInfo.description = fromStringz(*plugin_desc);
62 
63 	if(plugin_version && *plugin_version)
64 		.pluginInfo.version_ = fromStringz(*plugin_version);
65 
66 	try
67 	{
68 		initFunc(pluginInfo);
69 	}
70 	catch(Throwable e)
71 	{
72 		auto message = e.toString();
73 		hexchat_printf(ph, `Error initializing plugin "%.*s": %.*s`.ptr, pluginInfo.name.length, pluginInfo.name.ptr, message.length, message.ptr);
74 		return 0;
75 	}
76 
77 	if(pluginInfo.name)
78 		*plugin_name = toStringz(pluginInfo.name);
79 
80 	if(pluginInfo.description)
81 		*plugin_desc = toStringz(pluginInfo.description);
82 
83 	if(pluginInfo.version_)
84 		*plugin_version = toStringz(pluginInfo.version_);
85 
86 	return 1; // Return 1 for success
87 }
88 
89 // Public undocumented
90 int __deinitDPlugin(DeinitFunc)(DeinitFunc deinitFunc, void* ph)
91 	if(is(typeof(deinitFunc())))
92 {
93 	import core.stdc.stdio : fprintf, stderr;
94 	import hexchat.capi;
95 
96 	version(Windows) {} else
97 		import core.runtime : Runtime;
98 
99 	bool deinitSucceeded = true;
100 	try
101 	{
102 		deinitFunc();
103 	}
104 	catch(Throwable e)
105 	{
106 		auto message = e.toString();
107 		fprintf(stderr,`Error initializing plugin "%.*s": %.*s`, pluginInfo.name.length, pluginInfo.name.ptr, message.length, message.ptr);
108 		deinitSucceeded = false;
109 	}
110 
111 	// Return 1 for success
112 	version(Windows)
113 		return deinitSucceeded;
114 	else
115 		return Runtime.terminate() && deinitSucceeded;
116 }
117 
118 
119 /**
120  * Generate entry and exit points for this plugin.
121  *
122  * Params:
123  *    initFunc = plugin initialization function. Must take one parameter of
124  * type $(LREF PluginInfo) by reference. Run when the plugin is loaded by the
125  * IRC client. Set the $(LREF PluginInfo)'s fields to configure those properties
126  * of this plugin.
127  *
128  *    deinitFunc = plugin de-initialization function. Must take no parameters.
129  * Run when then plugin is unloaded by the IRC client. Optional.
130  *
131  *    style = ABI to follow. Use $(D PluginStyle.hexchat) for HexChat plugins
132  * and $(D PluginStyle.xchat) for plugins for other clients. The two ABIs are
133  * $(B not) cross-compatible.
134  */
135 mixin template Plugin(alias initFunc, PluginStyle style = PluginStyle.hexchat)
136 	if(is(typeof(initFunc(lvalueOf!PluginInfo))))
137 {
138 	pragma(mangle, initFuncName!style)
139 	export extern(C) int __initCPlugin(void* plugin_handle,
140 		immutable(char)** plugin_name,
141 		immutable(char)** plugin_desc,
142 		immutable(char)** plugin_version,
143 		char* arg)
144 	{
145 		return __initDPlugin(&initFunc, plugin_handle, plugin_name, plugin_desc, plugin_version);
146 	}
147 }
148 
149 /// Ditto
150 mixin template Plugin(alias initFunc, alias deinitFunc, PluginStyle style = PluginStyle.hexchat)
151 {
152 	mixin Plugin!(initFunc, style);
153 
154 	pragma(mangle, deinitFuncName!style)
155 	export extern(C) int __deinitCPlugin(void* plugin_handle)
156 	{
157 		return __deinitDPlugin(&deinitFunc, plugin_handle);
158 	}
159 }
160 
161 void writefln(FmtArgs...)(const(char)[] fmt, FmtArgs fmtArgs)
162 {
163 	static if(fmtArgs.length != 0)
164 	{
165 		import std.format : format;
166 		fmt = format(fmt, fmtArgs);
167 	}
168 
169 	hexchat_printf(ph, "%.*s".ptr, fmt.length, fmt.ptr);
170 }
171 
172 void commandf(FmtArgs...)(const(char)[] fmt, FmtArgs fmtArgs)
173 {
174 	static if(fmtArgs.length != 0)
175 	{
176 		import std.format : format;
177 		fmt = format(fmt, fmtArgs);
178 	}
179 
180 	hexchat_commandf(ph, "%.*s", fmt.length, fmt.ptr);
181 }
182 
183 string getInfo(in char[] id)
184 {
185 	import std.conv : to;
186 	import std.string : toStringz;
187 
188 	return to!string((hexchat_get_info(ph, toStringz(id))));
189 }
190 
191 void readInfo(in char[] id, void delegate(in char[] info) dg)
192 {
193 	import std.string : fromStringz, toStringz;
194 	dg(fromStringz(hexchat_get_info(ph, toStringz(id))));
195 }
196 
197 struct User
198 {
199 	const(char)[] nick, userName, hostName;
200 }
201 
202 User parseUser(const(char)[] user)
203 {
204 	import std.array : popFront;
205 	import std.string : munch;
206 
207 	auto nick = user.munch("^!");
208 	auto userName = user.munch("^@");
209 
210 	userName.popFront(); // Skip exclamation mark
211 	user.popFront(); // Skip at-mark
212 
213 	return User(nick, userName, user);
214 }
215 
216 /// Event consumption behavior.
217 enum EatMode
218 {
219 	none = HEXCHAT_EAT_NONE, /// Pass it on through.
220 	hexchat = HEXCHAT_EAT_HEXCHAT, /// Don't let xchat see this event.
221 	plugin = HEXCHAT_EAT_PLUGIN, /// Don't let other plugins see this event.
222 	all = HEXCHAT_EAT_HEXCHAT | HEXCHAT_EAT_PLUGIN /// Don't let anything see this event.
223 }
224 
225 ///
226 enum CommandPriority
227 {
228 	highest = HEXCHAT_PRI_HIGHEST, ///
229 	high = HEXCHAT_PRI_HIGH, ///
230 	normal = HEXCHAT_PRI_NORM, ///
231 	low = HEXCHAT_PRI_LOW, ///
232 	lowest = HEXCHAT_PRI_LOWEST ///
233 }
234 
235 private enum PDIWORDS = 32;
236 private alias const(char)[][PDIWORDS] WordBuffer;
237 
238 // TODO: Really inefficent for words_eol
239 private const(char)[][] getWords(const(char)** cwords, ref const(char)[][PDIWORDS] words)
240 {
241 	import std.string : fromStringz;
242 
243 	foreach(i; 1 .. PDIWORDS)
244 	{
245 		auto cword = cwords[i];
246 		if(cword[0])
247 			words[i - 1] = fromStringz(cword);
248 		else
249 			return words[0 .. i - 1];
250 	}
251 	return words[];
252 }
253 
254 private EatMode handleCallback(alias cb, string type, Args...)(Args args)
255 {
256 	try
257 	{
258 		return cb(args);
259 	}
260 	catch(Throwable e)
261 	{
262 		writefln("Error in " ~ type ~ " callback: %s", e.toString());
263 	}
264 	return EatMode.none;
265 }
266 
267 /**
268 * Hook a server message.
269 *
270 * Params:
271 *   type = _type of message to hook
272 *   callback = _callback function or delegate
273 *   priority = priority of this hook. Should be CommandPriority.normal
274 */
275 void hookServer(in char[] type,
276 				 EatMode function(in char[][] words, in char[][] words_eol) callback,
277 				 CommandPriority priority = CommandPriority.normal)
278 {
279 	import std.string : toStringz;
280 
281 	alias typeof(callback) Callback; // Workaround for older compiler versions
282 
283 	extern(C) static int hexchat_serv_cb(const(char)** cwords, const(char)** cwords_eol, void* ud)
284 	{
285 		WordBuffer words_buffer, words_eol_buffer;
286 		auto words = getWords(cwords, words_buffer);
287 		auto words_eol = getWords(cwords_eol, words_eol_buffer);
288 
289 		auto cb = cast(Callback)ud;
290 
291 		return handleCallback!(cb, "server")(words, words_eol);
292 	}
293 
294 	hexchat_hook_server(ph, toStringz(type), priority, &hexchat_serv_cb, callback);
295 }
296 
297 /// Ditto
298 void hookServer(in char[] type,
299 				EatMode delegate(in char[][] words, in char[][] words_eol) callback,
300 				CommandPriority priority = CommandPriority.normal)
301 {
302 	import std.string : toStringz;
303 
304 	static struct CallbackData
305 	{
306 		typeof(callback) cb;
307 	}
308 
309 	extern(C) static int hexchat_serv_cb(const(char)** cwords, const(char)** cwords_eol, void* ud)
310 	{
311 		WordBuffer words_buffer, words_eol_buffer;
312 		auto words = getWords(cwords, words_buffer);
313 		auto words_eol = getWords(cwords_eol, words_eol_buffer);
314 
315 		auto cb = (cast(CallbackData*)ud).cb;
316 
317 		return handleCallback!(cb, "server")(words, words_eol);
318 	}
319 
320 	auto data = new CallbackData;
321 	data.cb = callback;
322 
323 	hexchat_hook_server(ph, toStringz(type), priority, &hexchat_serv_cb, data);
324 }
325 
326 /**
327  * Hook a chat command.
328  *
329  * Params:
330  *   cmd = name of command
331  *   callback = _callback function or delegate
332  *   helpText = instructions for this command, displayed when the $(D /help <cmd>) command is invoked
333  *   priority = priority of this hook. Should be CommandPriority.normal
334  */
335 void hookCommand(in char[] cmd,
336 				 EatMode function(in char[][] words, in char[][] words_eol) callback,
337 				 in char[] helpText = null,
338 				 CommandPriority priority = CommandPriority.normal)
339 {
340 	import std.string : toStringz;
341 
342 	alias typeof(callback) Callback; // Workaround for older compiler versions
343 
344 	extern(C) static int hexchat_cmd_cb(const(char)** cwords, const(char)** cwords_eol, void* ud)
345 	{
346 		WordBuffer words_buffer, words_eol_buffer;
347 		auto words = getWords(cwords, words_buffer);
348 		auto words_eol = getWords(cwords_eol, words_eol_buffer);
349 
350 		auto cb = cast(Callback)ud;
351 
352 		return handleCallback!(cb, "command")(words, words_eol);
353 	}
354 
355 	hexchat_hook_command(ph, toStringz(cmd), priority, &hexchat_cmd_cb, helpText? toStringz(helpText) : null, callback);
356 }
357 
358 /// Ditto
359 void hookCommand(in char[] cmd,
360 				 EatMode delegate(in char[][] words, in char[][] words_eol) callback,
361 				 in char[] helpText = null,
362 				 CommandPriority priority = CommandPriority.normal)
363 {
364 	import std.string : toStringz;
365 
366 	static struct CallbackData
367 	{
368 		typeof(callback) cb;
369 	}
370 
371 	extern(C) static int hexchat_cmd_cb(const(char)** cwords, const(char)** cwords_eol, void* ud)
372 	{
373 		WordBuffer words_buffer, words_eol_buffer;
374 		auto words = getWords(cwords, words_buffer);
375 		auto words_eol = getWords(cwords_eol, words_eol_buffer);
376 
377 		auto cb = (cast(CallbackData*)ud).cb;
378 
379 		return handleCallback!(cb, "command")(words, words_eol);
380 	}
381 
382 	auto data = new CallbackData;
383 	data.cb = callback;
384 
385 	hexchat_hook_command(ph, toStringz(cmd), priority, &hexchat_cmd_cb, helpText? toStringz(helpText) : null, data);
386 }
387 
388 /**
389  * Hook a print event.
390  *
391  * The list of text events can be found in $(D Settings -> Advanced -> Text Events...);
392  * the list at the bottom of the window describes the contents of the $(D words) callback
393  * parameter for a particular event.
394  * Params:
395  *   name = _name of event
396  *   callback = _callback function or delegate
397  *   priority = priority of this hook. Should be CommandPriority.normal
398  */
399 void hookPrint(in char[] name,
400 			   EatMode function(in char[][] words) callback,
401 			   CommandPriority priority = CommandPriority.normal)
402 {
403 	import std.string : toStringz;
404 
405 	alias typeof(callback) Callback; // Workaround for older compiler versions
406 
407 	extern(C) static int hexchat_print_cb(const(char)** cwords, void* ud)
408 	{
409 		WordBuffer words_buffer;
410 		auto words = getWords(cwords, words_buffer);
411 
412 		auto cb = cast(Callback)ud;
413 
414 		return handleCallback!(cb, "print")(words);
415 	}
416 
417 	hexchat_hook_print(ph, toStringz(name), priority, &hexchat_print_cb, callback);
418 }
419 
420 /// Ditto
421 void hookPrint(in char[] name,
422 			   EatMode delegate(in char[][] words) callback,
423 			   CommandPriority priority = CommandPriority.normal)
424 {
425 	import std.string : toStringz;
426 
427 	static struct CallbackData
428 	{
429 		typeof(callback) cb;
430 	}
431 
432 	extern(C) static int hexchat_print_cb(const(char)** cwords, void* ud)
433 	{
434 		WordBuffer words_buffer;
435 		auto words = getWords(cwords, words_buffer);
436 
437 		auto cb = (cast(CallbackData*)ud).cb;
438 
439 		return handleCallback!(cb, "print")(words);
440 	}
441 
442 	auto data = new CallbackData;
443 	data.cb = callback;
444 
445 	hexchat_hook_print(ph, toStringz(name), priority, &hexchat_print_cb, data);
446 }
447