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