1887 lines
47 KiB
C
1887 lines
47 KiB
C
/*
|
|
* X-Chat 2.0 LUA Plugin
|
|
*
|
|
* Copyright (c) 2007 Hanno Hecker
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; version 2 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
|
|
*/
|
|
/*
|
|
* $Id: lua.c 91 2007-06-09 18:44:03Z vetinari $
|
|
* $Revision: 91 $
|
|
* $Date: 2007-06-09 20:44:03 +0200 (Szo, 09 jún. 2007) $
|
|
*/
|
|
/*
|
|
* TODO:
|
|
* * compile (was OK)/run on IRIX
|
|
* ? localize error msgs? ... maybe later
|
|
* ? make xchat.print() like print() which does an tostring() on
|
|
* everything it gets?
|
|
* ? add /LUA -s <code>? ... add a new script from cmdline... this state
|
|
* is not removed after the pcall(), but prints a name, which may
|
|
* be used to unload this virtual script. ... no xchat_register(),
|
|
* xchat_init() should be needed
|
|
* ... don't disable xchat.hook_* for this
|
|
* ? timer name per state/script and not per plugin?
|
|
*/
|
|
#define LXC_NAME "Lua"
|
|
#define LXC_DESC "Lua scripting interface"
|
|
#define LXC_VERSION "0.7 (r91)"
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#ifdef _WIN32
|
|
#include <direct.h> /* for getcwd */
|
|
#include "../../src/dirent/dirent-win32.h"
|
|
#include "../../src/common/typedef.h"
|
|
#endif
|
|
|
|
#if !( defined(_WIN32) || defined(LXC_XCHAT_GETTEXT) )
|
|
# include <libintl.h>
|
|
#endif
|
|
|
|
#ifndef PATH_MAX /* hurd */
|
|
# define PATH_MAX 1024
|
|
#endif
|
|
|
|
#include <lua.h>
|
|
#include <lauxlib.h>
|
|
#include <lualib.h>
|
|
|
|
#define lua_pop(L,n) lua_settop(L, -(n)-1)
|
|
|
|
#include "hexchat-plugin.h"
|
|
|
|
static hexchat_plugin *ph; /* plugin handle */
|
|
|
|
#define LXC_STRIP_COLOR 1
|
|
#define LXC_STRIP_ATTR 2
|
|
#define LXC_STRIP_ALL (LXC_STRIP_COLOR|LXC_STRIP_ATTR)
|
|
|
|
/* registered hooks */
|
|
struct lxc_hooks {
|
|
const char *name;
|
|
hexchat_hook *hook;
|
|
struct lxc_hooks *next;
|
|
};
|
|
|
|
/* single linked list of all lua states^Wscripts ;-) */
|
|
struct lxc_States {
|
|
lua_State *state; /* the lua state of the script */
|
|
char file[PATH_MAX+1]; /* the file name of the script */
|
|
struct lxc_hooks *hooks; /* all hooks this script registered */
|
|
void *gui; /* the gui entry in windows->plugins and scripts... */
|
|
struct lxc_States *next;
|
|
};
|
|
|
|
static struct lxc_States *lxc_states = NULL;
|
|
|
|
/* user/script supplied data for a callback */
|
|
struct lxc_userdata {
|
|
int idx; /* table index */
|
|
int type; /* lua type: */
|
|
const char *string; /* only strings, ... */
|
|
double num; /* numbers and booleans are supported */
|
|
struct lxc_userdata *next;
|
|
};
|
|
|
|
/* callback data */
|
|
struct lxc_cbdata {
|
|
lua_State *state;
|
|
const char *func;
|
|
hexchat_hook *hook; /* timer ... */
|
|
struct lxc_userdata *data;
|
|
};
|
|
|
|
static char lxc_event_name[1024] = "\0";
|
|
|
|
static int lxc_run_hook(char *word[], char *word_eol[], void *data);
|
|
static int lxc_run_print(char *word[], void *data);
|
|
static int lxc_run_timer(void *data);
|
|
|
|
static int lxc_hook_command(lua_State *L);
|
|
static int lxc_hook_server(lua_State *L);
|
|
static int lxc_hook_print(lua_State *L);
|
|
static int lxc_event(lua_State *L);
|
|
static int lxc_hook_timer(lua_State *L);
|
|
static int lxc_unhook(lua_State *L);
|
|
|
|
static int lxc_command(lua_State *L);
|
|
static int lxc_print(lua_State *L);
|
|
static int lxc_emit_print(lua_State *L);
|
|
static int lxc_send_modes(lua_State *L);
|
|
static int lxc_find_context(lua_State *L);
|
|
static int lxc_get_context(lua_State *L);
|
|
static int lxc_get_info(lua_State *L);
|
|
static int lxc_get_prefs(lua_State *L);
|
|
static int lxc_set_context(lua_State *L);
|
|
static int lxc_nickcmp(lua_State *L);
|
|
|
|
static int lxc_list_get(lua_State *L);
|
|
static int lxc_list_fields(lua_State *L);
|
|
static int lxc_gettext(lua_State *L);
|
|
|
|
static int lxc_bits(lua_State *L);
|
|
|
|
static luaL_reg lxc_functions[] = {
|
|
{"hook_command", lxc_hook_command },
|
|
/* TODO:
|
|
{"hook_fd", lxc_hook_fd },
|
|
*/
|
|
{"hook_print", lxc_hook_print },
|
|
{"hook_server", lxc_hook_server },
|
|
{"hook_timer", lxc_hook_timer },
|
|
{"unhook", lxc_unhook },
|
|
|
|
{"event", lxc_event },
|
|
|
|
{"command", lxc_command },
|
|
{"print", lxc_print },
|
|
{"emit_print", lxc_emit_print },
|
|
{"send_modes", lxc_send_modes },
|
|
{"find_context", lxc_find_context },
|
|
{"get_context", lxc_get_context },
|
|
{"get_info", lxc_get_info },
|
|
{"get_prefs", lxc_get_prefs },
|
|
{"set_context", lxc_set_context },
|
|
|
|
{"nickcmp", lxc_nickcmp },
|
|
|
|
{"list_get", lxc_list_get },
|
|
{"list_fields", lxc_list_fields },
|
|
|
|
{"gettext", lxc_gettext},
|
|
/* helper function for bit flags */
|
|
{"bits", lxc_bits },
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static struct {
|
|
const char *name;
|
|
long value;
|
|
} lxc_consts[] = {
|
|
{"EAT_NONE", HEXCHAT_EAT_NONE},
|
|
{"EAT_XCHAT", HEXCHAT_EAT_HEXCHAT},
|
|
{"EAT_PLUGIN", HEXCHAT_EAT_PLUGIN},
|
|
{"EAT_ALL", HEXCHAT_EAT_ALL},
|
|
|
|
/* unused until hook_fd is done
|
|
{"FD_READ", HEXCHAT_FD_READ},
|
|
{"FD_WRITE", HEXCHAT_FD_WRITE},
|
|
{"FD_EXCEPTION", HEXCHAT_FD_EXCEPTION},
|
|
{"FD_NOTSOCKET", HEXCHAT_FD_NOTSOCKET},
|
|
*/
|
|
|
|
{"PRI_HIGHEST", HEXCHAT_PRI_HIGHEST},
|
|
{"PRI_HIGH", HEXCHAT_PRI_HIGH},
|
|
{"PRI_NORM", HEXCHAT_PRI_NORM},
|
|
{"PRI_LOW", HEXCHAT_PRI_LOW},
|
|
{"PRI_LOWEST", HEXCHAT_PRI_LOWEST},
|
|
|
|
/* for: clean = xchat.strip(dirty, xchat.STRIP_ALL) */
|
|
{"STRIP_COLOR", LXC_STRIP_COLOR},
|
|
{"STRIP_ATTR", LXC_STRIP_ATTR},
|
|
{"STRIP_ALL", LXC_STRIP_ALL},
|
|
|
|
/* for xchat.commandf("GUI COLOR %d", xchat.TAB_HILIGHT) */
|
|
{"TAB_DEFAULT", 0},
|
|
{"TAB_NEWDATA", 1},
|
|
{"TAB_NEWMSG", 2},
|
|
{"TAB_HILIGHT", 3},
|
|
|
|
{NULL, 0}
|
|
};
|
|
|
|
|
|
#ifdef DEBUG
|
|
static void stackDump (lua_State *L, const char *msg) {
|
|
int i, t;
|
|
int top = lua_gettop(L);
|
|
|
|
fprintf(stderr, "%s\n", msg);
|
|
for (i = 1; i <= top; i++) { /* repeat for each level */
|
|
t = lua_type(L, i);
|
|
switch (t) {
|
|
|
|
case LUA_TSTRING: /* strings */
|
|
fprintf(stderr, "`%s'", lua_tostring(L, i));
|
|
break;
|
|
|
|
case LUA_TBOOLEAN: /* booleans */
|
|
fprintf(stderr, lua_toboolean(L, i) ? "true" : "false");
|
|
break;
|
|
|
|
case LUA_TNUMBER: /* numbers */
|
|
fprintf(stderr, "%g", lua_tonumber(L, i));
|
|
break;
|
|
|
|
default: /* other values */
|
|
fprintf(stderr, "%s", lua_typename(L, t));
|
|
break;
|
|
|
|
}
|
|
fprintf(stderr, " "); /* put a separator */
|
|
}
|
|
fprintf(stderr, "\n"); /* end the listing */
|
|
}
|
|
#endif /* DEBUG */
|
|
|
|
static int lxc__newindex(lua_State *L)
|
|
{
|
|
int i;
|
|
const char *name = lua_tostring(L, 2);
|
|
|
|
luaL_getmetatable(L, "xchat"); /* 4 */
|
|
|
|
lua_pushnil(L); /* 5 */
|
|
while (lua_next(L, 4) != 0) {
|
|
if ((lua_type(L, -2) == LUA_TSTRING)
|
|
&& strcmp("__index", lua_tostring(L, -2)) == 0)
|
|
break; /* now __index is 5, table 6 */
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
lua_pushnil(L);
|
|
while (lua_next(L, 6) != 0) {
|
|
if ((lua_type(L, -2) == LUA_TSTRING)
|
|
&& strcmp(name, lua_tostring(L, -2)) == 0) {
|
|
for (i=0; lxc_consts[i].name; i++) {
|
|
if (strcmp(name, lxc_consts[i].name) == 0) {
|
|
luaL_error(L,
|
|
"`xchat.%s' is a readonly constant", lua_tostring(L, 2));
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
lua_pushvalue(L, 2);
|
|
lua_pushvalue(L, 3);
|
|
lua_rawset(L, 6);
|
|
|
|
lua_settop(L, 1);
|
|
return 0;
|
|
}
|
|
|
|
static int luaopen_xchat(lua_State *L)
|
|
{
|
|
int i;
|
|
/*
|
|
* wrappers for xchat.printf() and xchat.commandf()
|
|
* ... xchat.strip
|
|
*/
|
|
#define LXC_WRAPPERS "function xchat.printf(...)\n" \
|
|
" xchat.print(string.format(unpack(arg)))\n" \
|
|
"end\n" \
|
|
"function xchat.commandf(...)\n" \
|
|
" xchat.command(string.format(unpack(arg)))\n" \
|
|
"end\n" \
|
|
"function xchat.strip(str, flags)\n" \
|
|
" if flags == nil then\n" \
|
|
" flags = xchat.STRIP_ALL\n" \
|
|
" end\n" \
|
|
" local bits = xchat.bits(flags)\n" \
|
|
" if bits[1] then\n" \
|
|
" str = string.gsub(\n" \
|
|
" string.gsub(str, \"\\3%d%d?,%d%d?\", \"\"),\n" \
|
|
" \"\\3%d%d?\", \"\")\n" \
|
|
" end\n" \
|
|
" if bits[2] then\n" \
|
|
" -- bold, beep, reset, reverse, underline\n" \
|
|
" str = string.gsub(str,\n" \
|
|
" \"[\\2\\7\\15\\22\\31]\", \"\")\n" \
|
|
" end\n" \
|
|
" return str\n" \
|
|
"end\n"
|
|
|
|
#if defined(LUA_VERSION_NUM) && (LUA_VERSION_NUM >= 501)
|
|
luaL_register(L, "xchat", lxc_functions);
|
|
(void)luaL_dostring(L, LXC_WRAPPERS);
|
|
#else
|
|
luaL_openlib(L, "xchat", lxc_functions, 0);
|
|
lua_dostring(L, LXC_WRAPPERS);
|
|
#endif
|
|
|
|
luaL_newmetatable(L, "xchat");
|
|
|
|
lua_pushliteral(L, "__index");
|
|
lua_newtable(L);
|
|
|
|
lua_pushstring(L, "ARCH");
|
|
#ifdef _WIN32
|
|
lua_pushstring(L, "Windows");
|
|
#else
|
|
lua_pushstring(L, "Unix");
|
|
#endif
|
|
lua_settable(L, -3); /* add to table __index */
|
|
|
|
for (i=0; lxc_consts[i].name; i++) {
|
|
lua_pushstring(L, lxc_consts[i].name);
|
|
lua_pushnumber(L, lxc_consts[i].value);
|
|
lua_settable(L, -3); /* add to table __index */
|
|
}
|
|
lua_settable(L, -3); /* add to metatable */
|
|
|
|
lua_pushliteral(L, "__newindex");
|
|
lua_pushcfunction(L, lxc__newindex);
|
|
lua_settable(L, -3);
|
|
/*
|
|
lua_pushliteral(L, "__metatable");
|
|
lua_pushstring(L, "nothing to see here, move along");
|
|
lua_settable(L, -3);
|
|
*/
|
|
lua_setmetatable(L, -2);
|
|
lua_pop(L, 1);
|
|
return 1;
|
|
}
|
|
|
|
lua_State *lxc_new_state()
|
|
{
|
|
#if defined(LUA_VERSION_NUM) && (LUA_VERSION_NUM >= 501)
|
|
lua_State *L = luaL_newstate(); /* opens Lua */
|
|
luaL_openlibs(L);
|
|
#else
|
|
lua_State *L = lua_open(); /* opens Lua */
|
|
luaopen_base(L); /* opens the basic library */
|
|
luaopen_table(L); /* opens the table library */
|
|
luaopen_io(L); /* opens the I/O library */
|
|
luaopen_string(L); /* opens the string lib. */
|
|
luaopen_math(L); /* opens the math lib. */
|
|
#endif
|
|
|
|
luaopen_xchat(L);
|
|
return L;
|
|
}
|
|
|
|
static int
|
|
lxc_load_file(const char *script)
|
|
{
|
|
lua_State *L;
|
|
struct lxc_States *state; /* pointer to lua states list */
|
|
struct lxc_States *st; /* pointer to lua states list */
|
|
|
|
L = lxc_new_state();
|
|
state = malloc(sizeof(struct lxc_States));
|
|
if (state == NULL) {
|
|
hexchat_printf(ph, "malloc() failed: %s\n", strerror(errno));
|
|
lua_close(L);
|
|
return 0;
|
|
}
|
|
|
|
state->state = L;
|
|
snprintf(state->file, PATH_MAX, script);
|
|
state->next = NULL;
|
|
state->hooks = NULL;
|
|
state->gui = NULL;
|
|
|
|
if (luaL_loadfile(L, script) || lua_pcall(L, 0, 0, 0)) {
|
|
hexchat_printf(ph, "Lua plugin: error loading script %s",
|
|
lua_tostring(L, -1));
|
|
lua_close(L);
|
|
free(state);
|
|
return 0;
|
|
}
|
|
|
|
if (!lxc_states)
|
|
lxc_states = state;
|
|
else {
|
|
st = lxc_states;
|
|
while (st->next)
|
|
st = st->next;
|
|
st->next = state;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
lxc_autoload_from_path(const char *path)
|
|
{
|
|
DIR *dir;
|
|
struct dirent *ent;
|
|
char *file;
|
|
int len;
|
|
/* hexchat_printf(ph, "loading from %s\n", path); */
|
|
dir = opendir(path);
|
|
if (dir) {
|
|
while ((ent = readdir(dir))) {
|
|
len = strlen(ent->d_name);
|
|
if (len > 4 && strcasecmp(".lua", ent->d_name + len - 4) == 0) {
|
|
file = malloc(len + strlen(path) + 2);
|
|
if (file == NULL) {
|
|
hexchat_printf(ph, "lxc_autoload_from_path(): malloc failed: %s",
|
|
strerror(errno));
|
|
break;
|
|
}
|
|
sprintf(file, "%s" G_DIR_SEPARATOR_S "%s", path, ent->d_name);
|
|
(void)lxc_load_file((const char *)file);
|
|
free(file);
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
}
|
|
|
|
void lxc_unload_script(struct lxc_States *state)
|
|
{
|
|
struct lxc_hooks *hooks, *h;
|
|
struct lxc_cbdata *cb;
|
|
struct lxc_userdata *ud, *u;
|
|
lua_State *L = state->state;
|
|
|
|
lua_pushstring(L, "xchat_unload");
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
if (lua_type(L, -1) == LUA_TFUNCTION) {
|
|
if (lua_pcall(L, 0, 0, 0)) {
|
|
hexchat_printf(ph, "Lua plugin: error while unloading script %s",
|
|
lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
}
|
|
}
|
|
|
|
if (state->gui)
|
|
hexchat_plugingui_remove(ph, state->gui);
|
|
state->gui = NULL;
|
|
|
|
hooks = state->hooks;
|
|
while (hooks) {
|
|
h = hooks;
|
|
hooks = hooks->next;
|
|
|
|
cb = hexchat_unhook(ph, h->hook);
|
|
if (cb) {
|
|
ud = cb->data;
|
|
while (ud) {
|
|
u = ud;
|
|
ud = ud->next;
|
|
free(u);
|
|
}
|
|
free(cb);
|
|
}
|
|
|
|
free(h);
|
|
}
|
|
lua_close(state->state);
|
|
}
|
|
|
|
|
|
static int lxc_cb_load(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
int len;
|
|
struct lxc_States *state;
|
|
lua_State *L;
|
|
const char *name, *desc, *vers;
|
|
const char *xdir = "";
|
|
char *buf;
|
|
char file[PATH_MAX+1];
|
|
struct stat *st;
|
|
|
|
if (word_eol[2][0] == 0)
|
|
return HEXCHAT_EAT_NONE;
|
|
|
|
buf = malloc(PATH_MAX + 1);
|
|
if (!buf) {
|
|
hexchat_printf(ph, "malloc() failed: %s\n", strerror(errno));
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
st = malloc(sizeof(struct stat));
|
|
if (!st) {
|
|
hexchat_printf(ph, "malloc() failed: %s\n", strerror(errno));
|
|
free(buf);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
len = strlen(word[2]);
|
|
if (len > 4 && strcasecmp (".lua", word[2] + len - 4) == 0) {
|
|
if (strrchr(word[2], G_DIR_SEPARATOR) != NULL)
|
|
strncpy(file, word[2], PATH_MAX);
|
|
else
|
|
{
|
|
if (stat(word[2], st) == 0)
|
|
{
|
|
xdir = getcwd (buf, PATH_MAX);
|
|
snprintf (file, PATH_MAX, "%s" G_DIR_SEPARATOR_S "%s", xdir, word[2]);
|
|
}
|
|
else
|
|
{
|
|
xdir = hexchat_get_info (ph, "hexchatdirfs");
|
|
snprintf (file, PATH_MAX, "%s" G_DIR_SEPARATOR_S "addons" G_DIR_SEPARATOR_S "%s", xdir, word[2]);
|
|
}
|
|
}
|
|
|
|
if (lxc_load_file((const char *)file) == 0) {
|
|
free(st);
|
|
free(buf);
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
|
|
state = lxc_states;
|
|
while (state) {
|
|
if (state->next == NULL) {
|
|
L = state->state;
|
|
|
|
lua_pushstring(L, "xchat_register");
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
if (lua_pcall(L, 0, 3, 0)) {
|
|
hexchat_printf(ph, "Lua plugin: error registering script %s",
|
|
lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
free(st);
|
|
free(buf);
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
|
|
name = lua_tostring(L, -3);
|
|
desc = lua_tostring(L, -2);
|
|
vers = lua_tostring(L, -1);
|
|
lua_pop(L, 4); /* func + 3 ret value */
|
|
state->gui = hexchat_plugingui_add(ph, state->file,
|
|
name, desc, vers, NULL
|
|
);
|
|
|
|
lua_pushstring(L, "xchat_init");
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
if (lua_type(L, -1) != LUA_TFUNCTION)
|
|
lua_pop(L, 1);
|
|
else {
|
|
if (lua_pcall(L, 0, 0, 0)) {
|
|
hexchat_printf(ph,
|
|
"Lua plugin: error calling xchat_init() %s",
|
|
lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
}
|
|
}
|
|
free(st);
|
|
free(buf);
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
state = state->next;
|
|
}
|
|
}
|
|
free(st);
|
|
free(buf);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
static int lxc_cb_unload(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
int len;
|
|
struct lxc_States *state;
|
|
struct lxc_States *prev = NULL;
|
|
char *file;
|
|
|
|
if (word_eol[2][0] == 0)
|
|
return HEXCHAT_EAT_NONE;
|
|
|
|
len = strlen(word[2]);
|
|
if (len > 4 && strcasecmp(".lua", word[2] + len - 4) == 0) {
|
|
state = lxc_states;
|
|
while (state) {
|
|
/*
|
|
* state->file is the full or relative path, always with a '/' inside,
|
|
* even if loaded via '/LOAD script.lua'. So strrchr() will never
|
|
* be NULL.
|
|
* ... we just inspect the script name w/o path to see if it's the
|
|
* right one to unload
|
|
*/
|
|
file = strrchr(state->file, '/') + 1;
|
|
if ((strcmp(state->file, word[2]) == 0)
|
|
|| (strcasecmp(file, word[2]) == 0)) {
|
|
lxc_unload_script(state);
|
|
if (prev)
|
|
prev->next = state->next;
|
|
else
|
|
lxc_states = state->next;
|
|
hexchat_printf(ph, "Lua script %s unloaded", file);
|
|
free(state);
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
prev = state;
|
|
state = state->next;
|
|
}
|
|
}
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
static int lxc_cb_lua(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
lua_State *L = lxc_new_state();
|
|
if (word[2][0] == '\0') {
|
|
hexchat_printf(ph, "LUA: Usage: /LUA LUA_CODE... execute LUA_CODE");
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
if (luaL_loadbuffer(L, word_eol[2], strlen(word_eol[2]), "/LUA")) {
|
|
hexchat_printf(ph, "LUA: error loading line %s", lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
#define LXC_HOOK_DISABLE "xchat.hook_command = nil\n" \
|
|
"xchat.hook_server = nil\n" \
|
|
"xchat.hook_print = nil\n" \
|
|
"xchat.hook_timer = nil\n"
|
|
|
|
#if defined(LUA_VERSION_NUM) && (LUA_VERSION_NUM >= 501)
|
|
(void)luaL_dostring(L, LXC_HOOK_DISABLE);
|
|
#else
|
|
lua_dostring(L, LXC_HOOK_DISABLE);
|
|
#endif
|
|
|
|
if (lua_pcall(L, 0, 0, 0)) {
|
|
hexchat_printf(ph, "LUA: error executing line %s", lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
lua_close(L);
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
|
|
int hexchat_plugin_init(hexchat_plugin *plugin_handle,
|
|
char **plugin_name,
|
|
char **plugin_desc,
|
|
char **plugin_version,
|
|
char *arg)
|
|
{
|
|
struct lxc_States *state;
|
|
lua_State *L;
|
|
const char *xdir;
|
|
const char *name, *desc, *vers;
|
|
char *xsubdir;
|
|
/* we need to save this for use with any xchat_* functions */
|
|
ph = plugin_handle;
|
|
|
|
/* tell xchat our info */
|
|
*plugin_name = LXC_NAME;
|
|
*plugin_desc = LXC_DESC;
|
|
*plugin_version = LXC_VERSION;
|
|
|
|
hexchat_hook_command(ph, "LOAD", HEXCHAT_PRI_NORM, lxc_cb_load, NULL, NULL);
|
|
hexchat_hook_command(ph, "UNLOAD", HEXCHAT_PRI_NORM, lxc_cb_unload, NULL, NULL);
|
|
hexchat_hook_command(ph, "LUA", HEXCHAT_PRI_NORM, lxc_cb_lua, "Usage: LUA <code>, executes <code> in a new lua state", NULL);
|
|
|
|
xdir = hexchat_get_info (ph, "hexchatdirfs");
|
|
xsubdir = g_build_filename (xdir, "addons", NULL);
|
|
lxc_autoload_from_path (xsubdir);
|
|
g_free (xsubdir);
|
|
|
|
/* put this here, otherwise it's only displayed when a script is autoloaded upon start */
|
|
hexchat_printf(ph, "Lua interface loaded");
|
|
|
|
if (!lxc_states) /* no scripts loaded */
|
|
return 1;
|
|
|
|
state = lxc_states;
|
|
while (state) {
|
|
L = state->state;
|
|
lua_pushstring(L, "xchat_register");
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
if (lua_pcall(L, 0, 3, 0)) {
|
|
hexchat_printf(ph, "Lua plugin: error registering script %s",
|
|
lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
state = state->next;
|
|
continue;
|
|
}
|
|
|
|
name = lua_tostring(L, -3);
|
|
desc = lua_tostring(L, -2);
|
|
vers = lua_tostring(L, -1);
|
|
lua_pop(L, 4); /* func + 3 ret value */
|
|
state->gui = hexchat_plugingui_add(ph, state->file, name, desc, vers, NULL);
|
|
|
|
lua_pushstring(L, "xchat_init");
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
if (lua_type(L, -1) != LUA_TFUNCTION)
|
|
lua_pop(L, 1);
|
|
else {
|
|
if (lua_pcall(L, 0, 0, 0)) {
|
|
hexchat_printf(ph, "Lua plugin: error calling xchat_init() %s",
|
|
lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
}
|
|
}
|
|
state = state->next;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int hexchat_plugin_deinit(hexchat_plugin *plug_handle)
|
|
{
|
|
struct lxc_States *state, *st;
|
|
|
|
state = lxc_states;
|
|
while (state) {
|
|
lxc_unload_script(state);
|
|
hexchat_printf(ph, "Lua script %s unloaded", state->file);
|
|
st = state;
|
|
state = state->next;
|
|
free(st);
|
|
}
|
|
hexchat_printf(plug_handle, "Lua interface unloaded");
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: func_name(word, word_eol, data)
|
|
* desc: your previously hooked callback function for hook_command() and
|
|
* hook_server(), you must return one of the xchat.EAT_* constants
|
|
* ret: none
|
|
* args:
|
|
* * word (table): the incoming line split into words (max 32)
|
|
* * word_eol (table):
|
|
* for both see
|
|
* http://xchat.org/docs/plugin20.html#word
|
|
* * data (table): the data table you passed to the hook_command() /
|
|
* hook_server() as 5th arg
|
|
*/
|
|
static int lxc_run_hook(char *word[], char *word_eol[], void *data)
|
|
{
|
|
struct lxc_cbdata *cb = data;
|
|
lua_State *L = cb->state;
|
|
struct lxc_userdata *ud = cb->data;
|
|
struct lxc_userdata *u;
|
|
int i;
|
|
lua_pushstring(L, cb->func);
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
|
|
strcpy(lxc_event_name, word[0]);
|
|
lua_newtable(L);
|
|
for (i=1; i<=31 && word[i][0]; i++) {
|
|
lua_pushnumber(L, i);
|
|
lua_pushstring(L, word[i]);
|
|
lua_settable(L, -3);
|
|
}
|
|
|
|
lua_newtable(L);
|
|
for (i=1; i<=31 && word_eol[i][0]; i++) {
|
|
lua_pushnumber(L, i);
|
|
lua_pushstring(L, word_eol[i]);
|
|
lua_settable(L, -3);
|
|
}
|
|
|
|
lua_newtable(L);
|
|
u = ud;
|
|
while (u) {
|
|
lua_pushnumber(L, u->idx);
|
|
switch (u->type) {
|
|
case LUA_TSTRING:
|
|
lua_pushstring(L, u->string);
|
|
break;
|
|
case LUA_TNUMBER:
|
|
lua_pushnumber(L, u->num);
|
|
break;
|
|
case LUA_TBOOLEAN:
|
|
lua_pushboolean(L, (((int)u->num == 0) ? 0 : 1));
|
|
break;
|
|
default: /* LUA_TNIL or others */
|
|
lua_pushnil(L);
|
|
break;
|
|
}
|
|
lua_settable(L, -3);
|
|
u = u->next;
|
|
}
|
|
|
|
if (lua_pcall(L, 3, 1, 0)) {
|
|
hexchat_printf(ph, "failed to call callback for '%s': %s",
|
|
word[1], lua_tostring(L, -1)
|
|
);
|
|
lua_pop(L, 1);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
if (lua_type(L, -1) != LUA_TNUMBER) {
|
|
hexchat_printf(ph, "callback for '%s' did not return number...", word[1]);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
i = (int)lua_tonumber(L, -1);
|
|
lua_pop(L, 1);
|
|
return i;
|
|
}
|
|
|
|
static int lxc_get_userdata(int pos, struct lxc_cbdata *cb)
|
|
{
|
|
struct lxc_userdata *ud, *u;
|
|
lua_State *L = cb->state;
|
|
int i, t;
|
|
|
|
t = lua_type(L, pos);
|
|
if (t == LUA_TNIL)
|
|
return 1;
|
|
if (t != LUA_TTABLE)
|
|
return 0;
|
|
|
|
i = 1;
|
|
while (1) {
|
|
lua_pushnumber(L, i);
|
|
lua_gettable(L, -2);
|
|
|
|
t = lua_type(L, -1);
|
|
if (t == LUA_TNIL) {
|
|
lua_pop(L, 1);
|
|
break;
|
|
}
|
|
|
|
ud = malloc(sizeof(struct lxc_userdata));
|
|
if (!ud) {
|
|
hexchat_printf(ph, "lxc_get_userdata(): failed to malloc: %s",
|
|
strerror(errno));
|
|
if (cb->data != NULL) {
|
|
ud = cb->data;
|
|
while (ud) {
|
|
u = ud;
|
|
ud = ud->next;
|
|
free(u);
|
|
}
|
|
}
|
|
/* free(cb); NO! */
|
|
lua_pushnil(L);
|
|
return 0;
|
|
}
|
|
ud->idx = i;
|
|
ud->next = NULL;
|
|
switch (t) {
|
|
case LUA_TSTRING:
|
|
ud->string = lua_tostring(L, -1);
|
|
ud->type = LUA_TSTRING;
|
|
break;
|
|
case LUA_TNUMBER:
|
|
ud->num = lua_tonumber(L, -1);
|
|
ud->type = LUA_TNUMBER;
|
|
break;
|
|
case LUA_TBOOLEAN:
|
|
ud->num = (double)lua_toboolean(L, -1);
|
|
ud->type = LUA_TBOOLEAN;
|
|
break;
|
|
default:
|
|
ud->type = LUA_TNIL;
|
|
break;
|
|
}
|
|
lua_pop(L, 1);
|
|
|
|
if (cb->data == NULL)
|
|
cb->data = ud;
|
|
else {
|
|
u = cb->data;
|
|
while (u->next)
|
|
u = u->next;
|
|
u->next = ud;
|
|
}
|
|
i++;
|
|
} /* END while (1) */
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.hook_command(name, func_name, prio, help_str, data)
|
|
* desc: Adds a new /command. This allows your program to handle commands
|
|
* entered at the input box. To capture text without a "/" at the start
|
|
* (non-commands), you may hook a special name of "". i.e
|
|
* xchat.hook_command( "", ...)
|
|
* Starting from version 2.6.8, commands hooked that begin with a
|
|
* period ('.') will be hidden in /HELP and /HELP -l.
|
|
* ret: true... or false if something went wrong while registering hook
|
|
* args:
|
|
* * name (string): the name of the new command
|
|
* * func_name (string): the lua function to be called when command is
|
|
* entered
|
|
* * prio (number): use one of the xchat.PRIO_*
|
|
* * help_str (string): help for the new command... use nil for no help
|
|
* * data (table): table with strings, numbers and booleans, which will
|
|
* be passed to func_name as last argument.
|
|
*/
|
|
static int lxc_hook_command(lua_State *L)
|
|
{
|
|
hexchat_hook *hook;
|
|
const char *help, *command, *func;
|
|
double prio;
|
|
struct lxc_hooks *hooks, *h;
|
|
struct lxc_States *st;
|
|
struct lxc_cbdata *cb;
|
|
|
|
|
|
if (lua_gettop(L) < 5) /* expand to five args if necessary */
|
|
lua_settop(L, 5);
|
|
|
|
cb = malloc(sizeof(struct lxc_cbdata));
|
|
if (!cb) {
|
|
hexchat_printf(ph, "lxc_hook_command(): failed to malloc: %s",
|
|
strerror(errno));
|
|
lua_pushboolean(L, 0);
|
|
return 1;
|
|
}
|
|
|
|
cb->state = L;
|
|
cb->data = NULL;
|
|
|
|
command = luaL_checkstring(L, 1);
|
|
func = luaL_checkstring(L, 2);
|
|
cb->func = func;
|
|
cb->hook = NULL;
|
|
|
|
if (lua_type(L, 3) == LUA_TNIL)
|
|
prio = HEXCHAT_PRI_NORM;
|
|
else
|
|
prio = luaL_checknumber(L, 3);
|
|
|
|
if (lua_type(L, 4) == LUA_TSTRING) {
|
|
help = luaL_checkstring(L, 4);
|
|
if (strlen(help) == 0)
|
|
help = NULL;
|
|
}
|
|
else
|
|
help = NULL;
|
|
|
|
if (lxc_get_userdata(5, cb) == 0)
|
|
lua_pushboolean(L, 0);
|
|
else {
|
|
h = malloc(sizeof(struct lxc_hooks));
|
|
if (!h) {
|
|
hexchat_printf(ph, "lxc_hook_command(): failed to malloc: %s",
|
|
strerror(errno));
|
|
lua_pushboolean(L, 0);
|
|
return 1;
|
|
}
|
|
hook = hexchat_hook_command(ph, command, prio, lxc_run_hook, help, cb);
|
|
h->hook = hook;
|
|
h->name = command;
|
|
h->next = NULL;
|
|
st = lxc_states;
|
|
while (st) {
|
|
if (st->state == L) {
|
|
if (!st->hooks)
|
|
st->hooks = h;
|
|
else {
|
|
hooks = st->hooks;
|
|
while (hooks->next)
|
|
hooks = hooks->next;
|
|
hooks->next = h;
|
|
}
|
|
break;
|
|
}
|
|
st = st->next;
|
|
}
|
|
lua_pushboolean(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: func_name(word, data)
|
|
* desc: your previously hooked callback function for hook_print(),
|
|
* you must return one of the xchat.EAT_* constants
|
|
* ret: none
|
|
* args:
|
|
* * word (table): the incoming line split into words (max 32)
|
|
* (see http://xchat.org/docs/plugin20.html#word)
|
|
* * data (table): the data table you passed to the hook_print() /
|
|
* as 4th arg
|
|
*/
|
|
static int lxc_run_print(char *word[], void *data)
|
|
{
|
|
struct lxc_cbdata *cb = data;
|
|
lua_State *L = cb->state;
|
|
int i;
|
|
|
|
lua_pushstring(L, cb->func);
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
|
|
strcpy(lxc_event_name, word[0]);
|
|
lua_newtable(L);
|
|
for (i=1; i<=31 && word[i][0]; i++) {
|
|
lua_pushnumber(L, i);
|
|
lua_pushstring(L, word[i]);
|
|
lua_settable(L, -3);
|
|
}
|
|
|
|
if (lua_pcall(L, 1, 1, 0)) {
|
|
hexchat_printf(ph, "failed to call callback for '%s': %s",
|
|
word[1], lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
return 0;
|
|
}
|
|
|
|
if (lua_type(L, -1) != LUA_TNUMBER) {
|
|
hexchat_printf(ph, "callback for '%s' didn't return number...", word[1]);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
i = (int)lua_tonumber(L, -1);
|
|
lua_pop(L, 1);
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.hook_print(name, func_name, prio, data)
|
|
* desc: Registers a function to trap any print events. The event names may
|
|
* be any available in the "Advanced > Text Events" window. There are
|
|
* also some extra "special" events you may hook using this function,
|
|
* see: http://xchat.org/docs/plugin20.html#hexchat_hook_print
|
|
* ret: true... or false if something went wrong while registering hook
|
|
* args:
|
|
* * name (string): the name of the new command
|
|
* * prio (number): use one of the xchat.PRIO_*
|
|
* * func_name (string): the lua function to be called when command is
|
|
* entered
|
|
* * data (table): table with strings, numbers and booleans, which will
|
|
* be passed to func_name as last argument.
|
|
*/
|
|
static int lxc_hook_print(lua_State *L)
|
|
{
|
|
hexchat_hook *hook;
|
|
struct lxc_hooks *hooks, *h;
|
|
struct lxc_States *st;
|
|
struct lxc_cbdata *cb = malloc(sizeof(struct lxc_cbdata));
|
|
const char *name, *func;
|
|
double prio;
|
|
|
|
if (!cb) {
|
|
luaL_error(L, "lxc_hook_print(): failed to malloc: %s", strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
if (lua_gettop(L) < 4) /* expand to 4 args if necessary */
|
|
lua_settop(L, 4);
|
|
|
|
name = luaL_checkstring(L, 1);
|
|
func = luaL_checkstring(L, 2);
|
|
if (lua_type(L, 3) == LUA_TNIL)
|
|
prio = HEXCHAT_PRI_NORM;
|
|
else
|
|
prio = luaL_checknumber(L, 3);
|
|
|
|
cb->state = L;
|
|
cb->func = func;
|
|
cb->data = NULL;
|
|
cb->hook = NULL;
|
|
|
|
if (lxc_get_userdata(4, cb) == 0)
|
|
lua_pushboolean(L, 0);
|
|
else {
|
|
h = malloc(sizeof(struct lxc_hooks));
|
|
if (!h) {
|
|
hexchat_printf(ph, "lxc_hook_print(): failed to malloc: %s",
|
|
strerror(errno));
|
|
lua_pushboolean(L, 0);
|
|
return 1;
|
|
}
|
|
hook = hexchat_hook_print(ph, name, prio, lxc_run_print, cb);
|
|
h->hook = hook;
|
|
h->name = name;
|
|
h->next = NULL;
|
|
st = lxc_states;
|
|
while (st) {
|
|
if (st->state == L) {
|
|
if (!st->hooks)
|
|
st->hooks = h;
|
|
else {
|
|
hooks = st->hooks;
|
|
while (hooks->next)
|
|
hooks = hooks->next;
|
|
hooks->next = h;
|
|
}
|
|
break;
|
|
}
|
|
st = st->next;
|
|
}
|
|
lua_pushboolean(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.hook_server(name, func_name, prio, data)
|
|
* desc: Registers a function to be called when a certain server event
|
|
* occurs. You can use this to trap PRIVMSG, NOTICE, PART, a server
|
|
* numeric etc... If you want to hook every line that comes from the
|
|
* IRC server, you may use the special name of "RAW LINE".
|
|
* ret: true... or false if something went wrong while registering
|
|
* args:
|
|
* * name (string): the event name / numeric (yes, also as a string)
|
|
* * prio (number): one of the xchat.PRIO_* constants
|
|
* * func_name (string): the function to be called, when the event
|
|
* happens
|
|
* * data (table)... see xchat.hook_command()
|
|
*/
|
|
static int lxc_hook_server(lua_State *L)
|
|
{
|
|
hexchat_hook *hook;
|
|
struct lxc_hooks *hooks, *h;
|
|
struct lxc_States *st;
|
|
const char *name, *func;
|
|
double prio;
|
|
|
|
struct lxc_cbdata *cb = malloc(sizeof(struct lxc_cbdata));
|
|
if (!cb) {
|
|
hexchat_printf(ph, "lxc_hook_server(): failed to malloc: %s",
|
|
strerror(errno));
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
if (lua_gettop(L) < 4) /* expand to 4 args if necessary */
|
|
lua_settop(L, 4);
|
|
|
|
name = luaL_checkstring(L, 1);
|
|
func = luaL_checkstring(L, 2);
|
|
if (lua_type(L, 3) == LUA_TNIL)
|
|
prio = HEXCHAT_PRI_NORM;
|
|
else
|
|
prio = luaL_checknumber(L, 3);
|
|
|
|
cb->state = L;
|
|
cb->func = func;
|
|
cb->data = NULL;
|
|
cb->hook = NULL;
|
|
|
|
if (lxc_get_userdata(4, cb) == 0)
|
|
lua_pushboolean(L, 0);
|
|
else {
|
|
h = malloc(sizeof(struct lxc_hooks));
|
|
if (!h) {
|
|
hexchat_printf(ph, "lxc_hook_server(): failed to malloc: %s",
|
|
strerror(errno));
|
|
lua_pushboolean(L, 0);
|
|
return 1;
|
|
}
|
|
hook = hexchat_hook_server(ph, name, prio, lxc_run_hook, cb);
|
|
h->hook = hook;
|
|
h->name = name;
|
|
h->next = NULL;
|
|
st = lxc_states;
|
|
while (st) {
|
|
if (st->state == L) {
|
|
if (!st->hooks)
|
|
st->hooks = h;
|
|
else {
|
|
hooks = st->hooks;
|
|
while (hooks->next)
|
|
hooks = hooks->next;
|
|
hooks->next = h;
|
|
}
|
|
break;
|
|
}
|
|
st = st->next;
|
|
}
|
|
lua_pushboolean(L, 1);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.hook_timer(timeout, func_name, data)
|
|
* desc: Registers a function to be called every "timeout" milliseconds.
|
|
* ret: true (or false on error while registering)
|
|
* args:
|
|
* * timeout (number): Timeout in milliseconds (1000 is 1 second).
|
|
* * func_name (string): Callback function. This will be called
|
|
* every "timeout" milliseconds.
|
|
* * data (table): see xchat.hook_command()
|
|
*/
|
|
|
|
static unsigned long long lxc_timer_count = 0;
|
|
|
|
static int lxc_hook_timer(lua_State *L)
|
|
{
|
|
hexchat_hook *hook;
|
|
struct lxc_hooks *hooks, *h;
|
|
struct lxc_States *st;
|
|
double timeout;
|
|
const char *func;
|
|
char name[32];
|
|
|
|
struct lxc_cbdata *cb = malloc(sizeof(struct lxc_cbdata));
|
|
if (!cb) {
|
|
luaL_error(L, "lxc_hook_timer(): failed to malloc: %s", strerror(errno));
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
if (lua_gettop(L) < 3) /* expand to 3 args if necessary */
|
|
lua_settop(L, 3);
|
|
|
|
timeout = luaL_checknumber(L, 1);
|
|
func = luaL_checkstring(L, 2);
|
|
|
|
cb->state = L;
|
|
cb->func = func;
|
|
cb->data = NULL;
|
|
|
|
if (lxc_get_userdata(3, cb) == 0)
|
|
lua_pushnil(L);
|
|
else {
|
|
h = malloc(sizeof(struct lxc_hooks));
|
|
if (!h) {
|
|
luaL_error(L, "lxc_hook_timer(): failed to malloc: %s",
|
|
strerror(errno));
|
|
return 0;
|
|
}
|
|
hook = hexchat_hook_timer(ph, timeout, lxc_run_timer, cb);
|
|
cb->hook = hook;
|
|
h->hook = hook;
|
|
h->next = NULL;
|
|
snprintf(name, 31, "timer%llu", lxc_timer_count++);
|
|
h->name = name;
|
|
lua_pushstring(L, name);
|
|
|
|
st = lxc_states;
|
|
while (st) {
|
|
if (st->state == L) {
|
|
if (!st->hooks)
|
|
st->hooks = h;
|
|
else {
|
|
hooks = st->hooks;
|
|
while (hooks->next)
|
|
hooks = hooks->next;
|
|
hooks->next = h;
|
|
}
|
|
break;
|
|
}
|
|
st = st->next;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void lxc_unhook_timer(lua_State *L, hexchat_hook *hook)
|
|
{
|
|
struct lxc_States *state;
|
|
struct lxc_hooks *hooks, *h, *prev_hook;
|
|
struct lxc_cbdata *cb;
|
|
struct lxc_userdata *ud, *u;
|
|
|
|
prev_hook = NULL;
|
|
state = lxc_states;
|
|
while (state) {
|
|
if (state->state == L) {
|
|
hooks = state->hooks;
|
|
while (hooks) {
|
|
if (hooks->hook == hook) {
|
|
h = hooks;
|
|
if (prev_hook)
|
|
prev_hook->next = hooks->next;
|
|
else
|
|
state->hooks = hooks->next;
|
|
|
|
cb = hexchat_unhook(ph, h->hook);
|
|
if (cb) {
|
|
ud = cb->data;
|
|
while (ud) {
|
|
u = ud;
|
|
ud = ud->next;
|
|
free(u);
|
|
}
|
|
free(cb);
|
|
}
|
|
|
|
free(h);
|
|
return;
|
|
}
|
|
prev_hook = hooks;
|
|
hooks = hooks->next;
|
|
}
|
|
break;
|
|
}
|
|
state = state->next;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* lua: func_name(data)
|
|
* desc: the callback function for the registered timer hook, return
|
|
* true to keep this timer going, false to stop it
|
|
* ret: none
|
|
* args:
|
|
* * data (table): the table you gave the hook_timer() as last
|
|
* argument
|
|
*/
|
|
static int lxc_run_timer(void *data)
|
|
{
|
|
int ret;
|
|
struct lxc_cbdata *cb = data;
|
|
hexchat_hook *hook = cb->hook;
|
|
lua_State *L = cb->state;
|
|
|
|
lua_pushstring(L, cb->func);
|
|
lua_gettable(L, LUA_GLOBALSINDEX);
|
|
|
|
if (lua_pcall(L, 0, 1, 0)) {
|
|
hexchat_printf(ph, "failed to call timer callback for '%s': %s",
|
|
cb->func, lua_tostring(L, -1));
|
|
lua_pop(L, 1);
|
|
lxc_unhook_timer(L, hook);
|
|
return 0;
|
|
}
|
|
|
|
if (lua_type(L, -1) != LUA_TBOOLEAN) {
|
|
hexchat_printf(ph,
|
|
"timer callback for '%s' didn't return a boolean", cb->func);
|
|
lua_pop(L, 1);
|
|
lxc_unhook_timer(L, hook);
|
|
return 0;
|
|
}
|
|
|
|
ret = (lua_toboolean(L, -1) == 0) ? 0 : 1;
|
|
lua_pop(L, 1);
|
|
|
|
if (ret == 0)
|
|
lxc_unhook_timer(L, hook);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.unhook(name)
|
|
* desc: unhooks a previously hooked hook
|
|
* ret: true if the hook existed, else false..
|
|
* args:
|
|
* * name (string): name of a registered hook (e.g. with
|
|
* xchat.hook_command("whois", ... ) you would unhook "whois"
|
|
* ... see timer warnings... there's currently just one "timer"
|
|
* to unhook
|
|
*/
|
|
static int lxc_unhook(lua_State *L)
|
|
{
|
|
struct lxc_States *state;
|
|
struct lxc_hooks *hooks, *h, *prev_hook;
|
|
struct lxc_cbdata *cb;
|
|
struct lxc_userdata *ud, *u;
|
|
int done = 0;
|
|
const char *name = luaL_checkstring(L, 1);
|
|
|
|
prev_hook = NULL;
|
|
state = lxc_states;
|
|
while (state) {
|
|
if (state->state == L) {
|
|
hooks = state->hooks;
|
|
while (hooks) {
|
|
if (strcasecmp(hooks->name, name) == 0) {
|
|
h = hooks;
|
|
if (prev_hook)
|
|
prev_hook->next = hooks->next;
|
|
else
|
|
state->hooks = hooks->next;
|
|
|
|
cb = hexchat_unhook(ph, h->hook);
|
|
if (cb) {
|
|
ud = cb->data;
|
|
while (ud) {
|
|
u = ud;
|
|
ud = ud->next;
|
|
free(u);
|
|
}
|
|
free(cb);
|
|
}
|
|
|
|
free(h);
|
|
done = 1;
|
|
break;
|
|
}
|
|
prev_hook = hooks;
|
|
hooks = hooks->next;
|
|
}
|
|
break;
|
|
}
|
|
state = state->next;
|
|
}
|
|
lua_pushboolean(L, done);
|
|
return 1;
|
|
}
|
|
|
|
static int lxc_event(lua_State *L)
|
|
{
|
|
lua_pushstring(L, lxc_event_name);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.command(command)
|
|
* desc: executes a command as if it were typed in xchat's input box.
|
|
* ret: none
|
|
* args:
|
|
* * command (string): command to execute, without the forward slash "/".
|
|
*/
|
|
static int lxc_command(lua_State *L)
|
|
{
|
|
const char *command = luaL_checkstring(L, 1);
|
|
hexchat_command(ph, command);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.print(text)
|
|
* desc: Prints some text to the current tab/window.
|
|
* ret: none
|
|
* args:
|
|
* * text (string): the text to print
|
|
*/
|
|
static int lxc_print(lua_State *L)
|
|
{
|
|
const char *txt = luaL_checkstring(L, 1);
|
|
// FIXME? const char *txt = lua_tostring(L, 1);
|
|
hexchat_print(ph, txt);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.emit_print(event, text, [text2, ...])
|
|
* desc: Generates a print event. This can be any event found in the
|
|
* Preferences > Advanced > Text Events window. The vararg parameter
|
|
* list MUST be no longer than four (4) parameters.
|
|
* Special care should be taken when calling this function inside a
|
|
* print callback (from xchat.hook_print()), as not to cause endless
|
|
* recursion.
|
|
* ret: true on success, false on error
|
|
* args:
|
|
* * event (string): the event name from the references > Advanced >
|
|
* Text Events window
|
|
* * text (string)
|
|
* text2 (string)
|
|
* ... (string(s)):
|
|
* parameters for the given event
|
|
*/
|
|
static int lxc_emit_print(lua_State *L)
|
|
{
|
|
|
|
int n = lua_gettop(L);
|
|
const char *text[5];
|
|
const char *event;
|
|
int i = 2;
|
|
|
|
if (n > 6)
|
|
luaL_error(L, "too many arguments to xchat.emit_print()");
|
|
|
|
event = luaL_checkstring(L, 1);
|
|
while (i <= n) {
|
|
text[i-2] = luaL_checkstring(L, i);
|
|
i++;
|
|
}
|
|
switch (n-1) {
|
|
case 0:
|
|
i = hexchat_emit_print(ph, event, NULL);
|
|
break;
|
|
case 1:
|
|
i = hexchat_emit_print(ph, event, text[0], NULL);
|
|
break;
|
|
case 2:
|
|
i = hexchat_emit_print(ph, event, text[0], text[1], NULL);
|
|
break;
|
|
case 3:
|
|
i = hexchat_emit_print(ph, event, text[0], text[1], text[2], NULL);
|
|
break;
|
|
case 4:
|
|
i = hexchat_emit_print(ph, event, text[0], text[1], text[2], text[3], NULL);
|
|
break;
|
|
}
|
|
lua_pushboolean(L, (i == 0) ? 0 : 1);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.send_modes(targets, sign, mode [, modes_per_line])
|
|
* desc: Sends a number of channel mode changes to the current channel.
|
|
* For example, you can Op a whole group of people in one go. It may
|
|
* send multiple MODE lines if the request doesn't fit on one. Pass 0
|
|
* for modes_per_line to use the current server's maximum possible.
|
|
* This function should only be called while in a channel context.
|
|
* ret: none
|
|
* args:
|
|
* * targets (table): list of names
|
|
* * sign (string): mode sign, i.e. "+" or "-", only the first char of
|
|
* this is used (currently unchecked if it's really "+" or "-")
|
|
* * mode (string): mode char, i.e. "o" for opping, only the first
|
|
* char of this is used (currently unchecked, what char)
|
|
* * modes_per_line (number): [optional] number of modes per line
|
|
*/
|
|
static int lxc_send_modes(lua_State *L)
|
|
{
|
|
int i = 1;
|
|
const char *name, *mode, *sign;
|
|
const char *targets[4096];
|
|
int num = 0; /* modes per line */
|
|
|
|
if (!lua_istable(L, 1)) {
|
|
luaL_error(L,
|
|
"xchat.send_modes(): first argument is not a table: %s",
|
|
lua_typename(L, lua_type(L, 1)));
|
|
return 0;
|
|
}
|
|
|
|
while (1) {
|
|
lua_pushnumber(L, i); /* push index on stack */
|
|
lua_gettable(L, 1); /* and get the element @ index */
|
|
if (lua_isnil(L, -1)) { /* end of table */
|
|
lua_pop(L, 1);
|
|
break;
|
|
}
|
|
|
|
if (lua_type(L, -1) != LUA_TSTRING) { /* oops, something wrong */
|
|
luaL_error(L,
|
|
"lua: xchat.send_modes(): table element #%d not a string: %s",
|
|
i, lua_typename(L, lua_type(L, -1)));
|
|
lua_pop(L, 1);
|
|
return 0;
|
|
}
|
|
|
|
name = lua_tostring(L, -1);
|
|
if (name == NULL) { /* this should not happen, but... */
|
|
lua_pop(L, 1);
|
|
break;
|
|
}
|
|
|
|
targets[i-1] = name;
|
|
lua_pop(L, 1); /* take index from stack */
|
|
++i;
|
|
}
|
|
|
|
sign = luaL_checkstring(L, 2);
|
|
if (sign[0] == '\0' || sign[1] != '\0') {
|
|
luaL_error(L, "argument #2 (mode sign) does not have length 1");
|
|
return 0;
|
|
}
|
|
if ((sign[0] != '+') && (sign[0] != '-')) {
|
|
luaL_error(L, "argument #2 (mode sign) is not '+' or '-'");
|
|
return 0;
|
|
}
|
|
|
|
mode = luaL_checkstring(L, 3);
|
|
if (mode[0] == '\0' || mode[1] != '\0') {
|
|
luaL_error(L, "argument #3 (mode char) does not have length 1");
|
|
return 0;
|
|
}
|
|
if (!isalpha((int)mode[0]) || !isascii((int)mode[0])) {
|
|
luaL_error(L, "argument #3 is not a valid mode character");
|
|
return 0;
|
|
}
|
|
|
|
if (lua_gettop(L) == 4)
|
|
num = luaL_checknumber(L, 4);
|
|
|
|
hexchat_send_modes(ph, targets, i-1, num, sign[0], mode[0]);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.find_context(srv, chan)
|
|
* desc: Finds a context based on a channel and servername. If servname is nil,
|
|
* it finds any channel (or query) by the given name. If channel is nil,
|
|
* it finds the front-most tab/window of the given servname. If nil is
|
|
* given for both arguments, the currently focused tab/window will be
|
|
* returned.
|
|
* Changed in 2.6.1. If servname is nil, it finds the channel (or query)
|
|
* by the given name in the same server group as the current context.
|
|
* If that doesn't exists then find any by the given name.
|
|
* ret: context number (DON'T modify)
|
|
* args:
|
|
* * srv (string or nil): server name
|
|
* * chan (string or nil): channel / query name
|
|
*/
|
|
static int lxc_find_context(lua_State *L)
|
|
{
|
|
const char *srv, *chan;
|
|
long ctx;
|
|
hexchat_context *ptr;
|
|
|
|
if (lua_type(L, 1) == LUA_TSTRING) {
|
|
srv = lua_tostring(L, 1);
|
|
if (srv[0] == '\0')
|
|
srv = NULL;
|
|
}
|
|
else
|
|
srv = NULL;
|
|
|
|
if (lua_type(L, 2) == LUA_TSTRING) {
|
|
chan = lua_tostring(L, 2);
|
|
if (chan[0] == '\0')
|
|
chan = NULL;
|
|
}
|
|
else
|
|
chan = NULL;
|
|
|
|
ptr = hexchat_find_context(ph, srv, chan);
|
|
ctx = (long)ptr;
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "find_context(): %#lx\n", (long)ptr);
|
|
#endif
|
|
lua_pushnumber(L, (double)ctx);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.get_context()
|
|
* desc: Returns the current context for your plugin. You can use this later
|
|
* with hexchat_set_context.
|
|
* ret: context number ... DON'T modifiy
|
|
* args: none
|
|
*/
|
|
static int lxc_get_context(lua_State *L)
|
|
{
|
|
long ptr;
|
|
hexchat_context *ctx = hexchat_get_context(ph);
|
|
ptr = (long)ctx;
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "get_context(): %#lx\n", ptr);
|
|
#endif
|
|
lua_pushnumber(L, (double)ptr);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.get_info(id)
|
|
* desc: Returns information based on your current context.
|
|
* ret: the requested string or nil on error
|
|
* args:
|
|
* * id (string): the wanted information
|
|
*/
|
|
static int lxc_get_info(lua_State *L)
|
|
{
|
|
const char *id = luaL_checkstring(L, 1);
|
|
const char *value = hexchat_get_info(ph, id);
|
|
if (value == NULL)
|
|
lua_pushnil(L);
|
|
else
|
|
lua_pushstring(L, value);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: hexchat.get_prefs(name)
|
|
* desc: Provides xchat's setting information (that which is available
|
|
* through the /set command). A few extra bits of information are
|
|
* available that don't appear in the /set list, currently they are:
|
|
* * state_cursor: Current input-box cursor position (characters,
|
|
* not bytes). Since 2.4.2.
|
|
* *id: Unique server id. Since 2.6.1.
|
|
* ret: returns the string/number/boolean for the given config var
|
|
* or nil on error
|
|
* args:
|
|
* * name (string): the wanted setting's name
|
|
*/
|
|
static int lxc_get_prefs(lua_State *L)
|
|
{
|
|
int i;
|
|
const char *str;
|
|
const char *name = luaL_checkstring(L, 1);
|
|
/*
|
|
* luckily we can store anything in a lua var... this makes the
|
|
* xchat lua api more user friendly ;-)
|
|
*/
|
|
switch (hexchat_get_prefs(ph, name, &str, &i)) {
|
|
case 0: /* request failed */
|
|
lua_pushnil(L);
|
|
break;
|
|
case 1:
|
|
lua_pushstring(L, str);
|
|
break;
|
|
case 2:
|
|
lua_pushnumber(L, (double)i);
|
|
break;
|
|
case 3:
|
|
lua_pushboolean(L, i);
|
|
break;
|
|
default: /* doesn't happen if xchat's C-API doesn't change ;-) */
|
|
lua_pushnil(L);
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.set_context(ctx)
|
|
* desc: Changes your current context to the one given.
|
|
* ret: true or false
|
|
* args:
|
|
* * ctx (number): the context (e.g. from xchat.get_context())
|
|
*/
|
|
static int lxc_set_context(lua_State *L)
|
|
{
|
|
double ctx = luaL_checknumber(L, 1);
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "set_context(): %#lx\n", (long)ctx);
|
|
#endif
|
|
hexchat_context *xc = (void *)(long)ctx;
|
|
lua_pushboolean(L, hexchat_set_context(ph, xc));
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.nickcmp(name1, name2)
|
|
* desc: Performs a nick name comparision, based on the current server
|
|
* connection. This might be a RFC1459 compliant string compare, or
|
|
* plain ascii (in the case of DALNet). Use this to compare channels
|
|
* and nicknames. The function works the same way as strcasecmp.
|
|
* ret: number ess than, equal to, or greater than zero if name1 is found,
|
|
* respectively, to be less than, to match, or be greater than name2.
|
|
* args:
|
|
* * name1 (string): nick or channel name
|
|
* * name2 (string): nick or channel name
|
|
*/
|
|
static int lxc_nickcmp(lua_State *L)
|
|
{
|
|
const char *n1 = luaL_checkstring(L, 1);
|
|
const char *n2 = luaL_checkstring(L, 2);
|
|
lua_pushnumber(L, (double)hexchat_nickcmp(ph, n1, n2));
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.list_get(name)
|
|
* desc: http://xchat.org/docs/plugin20.html#lists :)
|
|
* time_t values are stored as number => e.g.
|
|
* os.date("%Y-%m-%d, %H:%M:%S", time_t_value)
|
|
* pointers (channel -> context) as number... untested if this works
|
|
* ret: table with tables with all keys=Name, value=(Type)... or nil on error
|
|
* args:
|
|
* * name (string): the wanted list
|
|
*/
|
|
static int lxc_list_get(lua_State *L)
|
|
{
|
|
const char *name = luaL_checkstring(L, 1);
|
|
int i; /* item index */
|
|
int l; /* list index */
|
|
const char *str;
|
|
double num;
|
|
time_t date;
|
|
long ptr;
|
|
const char *const *fields = hexchat_list_fields(ph, name);
|
|
hexchat_list *list = hexchat_list_get(ph, name);
|
|
|
|
if (!list) {
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
lua_newtable(L);
|
|
/* this is like the perl plugin does it ;-) */
|
|
l = 1;
|
|
while (hexchat_list_next(ph, list)) {
|
|
i = 0;
|
|
lua_pushnumber(L, l);
|
|
lua_newtable(L);
|
|
while (fields[i] != NULL) {
|
|
switch (fields[i][0]) {
|
|
case 's':
|
|
str = hexchat_list_str(ph, list, fields [i] + 1);
|
|
lua_pushstring(L, fields[i]+1);
|
|
if (str != NULL)
|
|
lua_pushstring(L, str);
|
|
else
|
|
lua_pushnil(L);
|
|
lua_settable(L, -3);
|
|
break;
|
|
case 'p':
|
|
ptr = (long)hexchat_list_str(ph, list, fields [i] + 1);
|
|
num = (double)ptr;
|
|
lua_pushstring(L, fields[i]+1);
|
|
lua_pushnumber(L, num);
|
|
lua_settable(L, -3);
|
|
break;
|
|
case 'i':
|
|
num = (double)hexchat_list_int(ph, list, fields[i] + 1);
|
|
lua_pushstring(L, fields[i]+1);
|
|
lua_pushnumber(L, num);
|
|
lua_settable(L, -3);
|
|
break;
|
|
case 't':
|
|
date = hexchat_list_time(ph, list, fields[i] + 1);
|
|
lua_pushstring(L, fields[i]+1);
|
|
lua_pushnumber(L, (double)date);
|
|
lua_settable(L, -3);
|
|
break;
|
|
}
|
|
i++;
|
|
}
|
|
lua_settable(L, -3);
|
|
l++;
|
|
}
|
|
hexchat_list_free(ph, list);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.list_fields(name)
|
|
* desc: returns the possible keys for name as table
|
|
* ret: table ;->
|
|
* args:
|
|
* * name (string): name of the wanted list ("channels", "dcc",
|
|
* "ignore", "notify", "users")
|
|
*/
|
|
static int lxc_list_fields(lua_State *L)
|
|
{
|
|
const char *name = luaL_checkstring(L, 1);
|
|
const char *const *fields = hexchat_list_fields(ph, name);
|
|
int i;
|
|
|
|
lua_newtable(L);
|
|
i = 0;
|
|
while (fields[i] != NULL) {
|
|
lua_pushnumber(L, i);
|
|
/* first char is the type ... */
|
|
lua_pushstring(L, fields[i]+1);
|
|
lua_settable(L, -3);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.gettext(str)
|
|
* desc:
|
|
*/
|
|
static int lxc_gettext(lua_State *L)
|
|
{
|
|
#if defined(_WIN32) || defined(LXC_XCHAT_GETTEXT)
|
|
lua_pushstring(L, hexchat_gettext(ph, luaL_checkstring(L, 1)));
|
|
#else
|
|
const char *dom;
|
|
const char *msgid = luaL_checkstring(L, 1);
|
|
if (lua_type(L,2) == LUA_TSTRING)
|
|
dom = lua_tostring(L, 2);
|
|
else
|
|
dom = "xchat";
|
|
lua_pushstring(L, dgettext(dom, msgid));
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* lua: xchat.bits(flags)
|
|
* desc: returns a table of booleans if the bit at index (err... index-1) is
|
|
* set
|
|
* ret: table of booleans
|
|
* args:
|
|
* * flags (number)
|
|
*/
|
|
static int lxc_bits(lua_State *L)
|
|
{
|
|
int flags = luaL_checknumber(L, 1);
|
|
int i;
|
|
lua_pop(L, 1);
|
|
lua_newtable(L);
|
|
for (i=0; i<16; i++) { /* at time of writing, the highest index was 9 ... */
|
|
lua_pushnumber(L, i+1);
|
|
lua_pushboolean(L, ((1<<i) & flags) == 0 ? 0 : 1);
|
|
lua_settable(L, -3);
|
|
}
|
|
return 1;
|
|
}
|
|
/*
|
|
* vim: ts=3 noexpandtab
|
|
*/
|