mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-03-10 16:00:18 +00:00
Allowed Lua scripts to be located by basename (script name only) when resolving /lua unload and related operations, while keeping path-based matching for explicit paths. Ensured /lua load reports failures when script creation fails and added explicit failure feedback to the user. Added unload feedback that distinguishes immediate unloads from deferred unloads, so users see confirmation right away.
1800 lines
56 KiB
C
1800 lines
56 KiB
C
/*
|
|
* Copyright (c) 2015-2016 mniip
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|
* associated documentation files (the "Software"), to deal in the Software without restriction,
|
|
* including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do
|
|
* so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
|
* portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
|
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include <lua.h>
|
|
#include <lauxlib.h>
|
|
#include <lualib.h>
|
|
|
|
#include <glib.h>
|
|
#include <gmodule.h>
|
|
|
|
#ifndef G_OS_WIN32
|
|
#include <pwd.h>
|
|
#endif
|
|
|
|
#include <zoitechat-plugin.h>
|
|
|
|
#define WORD_ARRAY_LEN 32
|
|
|
|
static char plugin_name[] = "Lua";
|
|
static char plugin_description[] = "Lua scripting interface";
|
|
static char plugin_version[16] = "1.3";
|
|
|
|
static char console_tab[] = ">>lua<<";
|
|
static char command_help[] =
|
|
"Usage: /lua load <filename>\n"
|
|
" unload <filename>\n"
|
|
" reload <filename>\n"
|
|
" exec <code>\n"
|
|
" inject <filename> <code>\n"
|
|
" reset\n"
|
|
" list\n"
|
|
" console";
|
|
|
|
static char registry_field[] = "plugin";
|
|
|
|
static zoitechat_plugin *ph;
|
|
|
|
#if LUA_VERSION_NUM < 502
|
|
#define lua_rawlen lua_objlen
|
|
#define luaL_setfuncs(L, r, n) luaL_register(L, NULL, r)
|
|
#endif
|
|
|
|
typedef struct
|
|
{
|
|
zoitechat_hook *hook;
|
|
lua_State *state;
|
|
int ref;
|
|
}
|
|
hook_info;
|
|
|
|
typedef struct
|
|
{
|
|
char *name;
|
|
char *description;
|
|
char *version;
|
|
zoitechat_plugin *handle;
|
|
char *filename;
|
|
lua_State *state;
|
|
GPtrArray *hooks;
|
|
GPtrArray *unload_hooks;
|
|
int traceback;
|
|
int status;
|
|
}
|
|
script_info;
|
|
|
|
#define STATUS_ACTIVE 1
|
|
#define STATUS_DEFERRED_UNLOAD 2
|
|
#define STATUS_DEFERRED_RELOAD 4
|
|
|
|
static void check_deferred(script_info *info);
|
|
|
|
static inline script_info *get_info(lua_State *L)
|
|
{
|
|
script_info *info;
|
|
|
|
lua_getfield(L, LUA_REGISTRYINDEX, registry_field);
|
|
info = lua_touserdata(L, -1);
|
|
lua_pop(L, 1);
|
|
|
|
return info;
|
|
}
|
|
|
|
static int api_zoitechat_register(lua_State *L)
|
|
{
|
|
char const *name, *version, *description;
|
|
script_info *info = get_info(L);
|
|
if(info->name)
|
|
return luaL_error(L, "script is already registered as '%s'", info->name);
|
|
|
|
name = luaL_checkstring(L, 1);
|
|
version = luaL_checkstring(L, 2);
|
|
description = luaL_checkstring(L, 3);
|
|
|
|
info->name = g_strdup(name);
|
|
info->description = g_strdup(description);
|
|
info->version = g_strdup(version);
|
|
info->handle = zoitechat_plugingui_add(ph, info->filename, info->name, info->description, info->version, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int api_zoitechat_command(lua_State *L)
|
|
{
|
|
zoitechat_command(ph, luaL_checkstring(L, 1));
|
|
return 0;
|
|
}
|
|
|
|
static int tostring(lua_State *L, int n)
|
|
{
|
|
luaL_checkany(L, n);
|
|
switch (lua_type(L, n))
|
|
{
|
|
case LUA_TNUMBER:
|
|
lua_pushstring(L, lua_tostring(L, n));
|
|
break;
|
|
case LUA_TSTRING:
|
|
lua_pushvalue(L, n);
|
|
break;
|
|
case LUA_TBOOLEAN:
|
|
lua_pushstring(L, (lua_toboolean(L, n) ? "true" : "false"));
|
|
break;
|
|
case LUA_TNIL:
|
|
lua_pushliteral(L, "nil");
|
|
break;
|
|
default:
|
|
lua_pushfstring(L, "%s: %p", luaL_typename(L, n), lua_topointer(L, n));
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_print(lua_State *L)
|
|
{
|
|
int i, args = lua_gettop(L);
|
|
luaL_Buffer b;
|
|
luaL_buffinit(L, &b);
|
|
for(i = 1; i <= args; i++)
|
|
{
|
|
if(i != 1)
|
|
luaL_addstring(&b, " ");
|
|
tostring(L, i);
|
|
luaL_addvalue(&b);
|
|
}
|
|
luaL_pushresult(&b);
|
|
zoitechat_print(ph, lua_tostring(L, -1));
|
|
return 0;
|
|
}
|
|
|
|
static int api_zoitechat_emit_print(lua_State *L)
|
|
{
|
|
zoitechat_emit_print(ph, luaL_checkstring(L, 1), luaL_optstring(L, 2, NULL), luaL_optstring(L, 3, NULL), luaL_optstring(L, 4, NULL), luaL_optstring(L, 5, NULL), luaL_optstring(L, 6, NULL), NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int api_zoitechat_emit_print_attrs(lua_State *L)
|
|
{
|
|
zoitechat_event_attrs *attrs = *(zoitechat_event_attrs **)luaL_checkudata(L, 1, "attrs");
|
|
zoitechat_emit_print_attrs(ph, attrs, luaL_checkstring(L, 2), luaL_optstring(L, 3, NULL), luaL_optstring(L, 4, NULL), luaL_optstring(L, 5, NULL), luaL_optstring(L, 6, NULL), luaL_optstring(L, 7, NULL), NULL);
|
|
return 0;
|
|
}
|
|
|
|
static int api_zoitechat_send_modes(lua_State *L)
|
|
{
|
|
size_t i, n;
|
|
int modes;
|
|
char const *mode;
|
|
char const **targets;
|
|
|
|
luaL_checktype(L, 1, LUA_TTABLE);
|
|
n = lua_rawlen(L, 1);
|
|
mode = luaL_checkstring(L, 2);
|
|
if(strlen(mode) != 2)
|
|
return luaL_argerror(L, 2, "expected sign followed by a mode letter");
|
|
modes = luaL_optinteger(L, 3, 0);
|
|
targets = g_new(char const *, n);
|
|
|
|
for(i = 0; i < n; i++)
|
|
{
|
|
lua_rawgeti(L, 1, i + 1);
|
|
if(lua_type(L, -1) != LUA_TSTRING)
|
|
{
|
|
g_free(targets);
|
|
return luaL_argerror(L, 1, "expected an array of strings");
|
|
}
|
|
targets[i] = lua_tostring(L, -1);
|
|
lua_pop(L, 1);
|
|
}
|
|
zoitechat_send_modes(ph, targets, n, modes, mode[0], mode[1]);
|
|
g_free(targets);
|
|
return 0;
|
|
}
|
|
|
|
static int api_zoitechat_nickcmp(lua_State *L)
|
|
{
|
|
lua_pushinteger(L, zoitechat_nickcmp(ph, luaL_checkstring(L, 1), luaL_checkstring(L, 2)));
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_strip(lua_State *L)
|
|
{
|
|
size_t len;
|
|
char const *text;
|
|
gboolean leave_colors, leave_attrs;
|
|
char *result;
|
|
|
|
luaL_checktype(L, 1, LUA_TSTRING);
|
|
text = lua_tolstring(L, 1, &len);
|
|
leave_colors = lua_toboolean(L, 2);
|
|
leave_attrs = lua_toboolean(L, 3);
|
|
result = zoitechat_strip(ph, text, len, (leave_colors ? 0 : 1) | (leave_attrs ? 0 : 2));
|
|
if(result)
|
|
{
|
|
lua_pushstring(L, result);
|
|
zoitechat_free(ph, result);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void register_hook(hook_info *hook)
|
|
{
|
|
script_info *info = get_info(hook->state);
|
|
g_ptr_array_add(info->hooks, hook);
|
|
}
|
|
|
|
static void free_hook(hook_info *hook)
|
|
{
|
|
if(hook->state)
|
|
luaL_unref(hook->state, LUA_REGISTRYINDEX, hook->ref);
|
|
if(hook->hook)
|
|
zoitechat_unhook(ph, hook->hook);
|
|
g_free(hook);
|
|
}
|
|
|
|
static int unregister_hook(hook_info *hook)
|
|
{
|
|
script_info *info = get_info(hook->state);
|
|
|
|
if(g_ptr_array_remove_fast(info->hooks, hook))
|
|
return 1;
|
|
|
|
if(g_ptr_array_remove_fast(info->unload_hooks, hook))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int api_command_closure(char *word[], char *word_eol[], void *udata)
|
|
{
|
|
int base, i, ret;
|
|
hook_info *info = udata;
|
|
lua_State *L = info->state;
|
|
script_info *script = get_info(L);
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, script->traceback);
|
|
base = lua_gettop(L);
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
|
|
lua_newtable(L);
|
|
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
|
|
{
|
|
lua_pushstring(L, word[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
lua_newtable(L);
|
|
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
|
|
{
|
|
lua_pushstring(L, word_eol[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
script->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 2, 1, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
lua_pop(L, 2);
|
|
zoitechat_printf(ph, "Lua error in command hook: %s", error ? error : "(non-string error)");
|
|
check_deferred(script);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
ret = lua_tointeger(L, -1);
|
|
lua_pop(L, 2);
|
|
check_deferred(script);
|
|
return ret;
|
|
}
|
|
|
|
static int api_zoitechat_hook_command(lua_State *L)
|
|
{
|
|
hook_info *info, **u;
|
|
char const *command, *help;
|
|
int ref, pri;
|
|
|
|
command = luaL_optstring(L, 1, "");
|
|
lua_pushvalue(L, 2);
|
|
ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
help = luaL_optstring(L, 3, NULL);
|
|
pri = luaL_optinteger(L, 4, HEXCHAT_PRI_NORM);
|
|
info = g_new(hook_info, 1);
|
|
info->state = L;
|
|
info->ref = ref;
|
|
info->hook = zoitechat_hook_command(ph, command, pri, api_command_closure, help, info);
|
|
u = lua_newuserdata(L, sizeof(hook_info *));
|
|
*u = info;
|
|
luaL_newmetatable(L, "hook");
|
|
lua_setmetatable(L, -2);
|
|
register_hook(info);
|
|
return 1;
|
|
}
|
|
|
|
static int api_print_closure(char *word[], void *udata)
|
|
{
|
|
hook_info *info = udata;
|
|
lua_State *L = info->state;
|
|
script_info *script = get_info(L);
|
|
int i, j, base, ret;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, script->traceback);
|
|
base = lua_gettop(L);
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
|
|
|
|
for(j = 31; j >= 1; j--)
|
|
{
|
|
if(*word[j])
|
|
break;
|
|
}
|
|
lua_newtable(L);
|
|
for(i = 1; i <= j; i++)
|
|
{
|
|
lua_pushstring(L, word[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
script->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 1, 1, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
lua_pop(L, 2);
|
|
zoitechat_printf(ph, "Lua error in print hook: %s", error ? error : "(non-string error)");
|
|
check_deferred(script);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
ret = lua_tointeger(L, -1);
|
|
lua_pop(L, 2);
|
|
check_deferred(script);
|
|
return ret;
|
|
}
|
|
|
|
static int api_zoitechat_hook_print(lua_State *L)
|
|
{
|
|
char const *event = luaL_checkstring(L, 1);
|
|
hook_info *info, **u;
|
|
int ref, pri;
|
|
|
|
lua_pushvalue(L, 2);
|
|
ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
pri = luaL_optinteger(L, 3, HEXCHAT_PRI_NORM);
|
|
info = g_new(hook_info, 1);
|
|
info->state = L;
|
|
info->ref = ref;
|
|
info->hook = zoitechat_hook_print(ph, event, pri, api_print_closure, info);
|
|
u = lua_newuserdata(L, sizeof(hook_info *));
|
|
*u = info;
|
|
luaL_newmetatable(L, "hook");
|
|
lua_setmetatable(L, -2);
|
|
register_hook(info);
|
|
return 1;
|
|
}
|
|
|
|
static zoitechat_event_attrs *event_attrs_copy(const zoitechat_event_attrs *attrs)
|
|
{
|
|
zoitechat_event_attrs *copy = zoitechat_event_attrs_create(ph);
|
|
copy->server_time_utc = attrs->server_time_utc;
|
|
return copy;
|
|
}
|
|
|
|
static int api_print_attrs_closure(char *word[], zoitechat_event_attrs *attrs, void *udata)
|
|
{
|
|
hook_info *info = udata;
|
|
lua_State *L = info->state;
|
|
script_info *script = get_info(L);
|
|
int base, i, j, ret;
|
|
zoitechat_event_attrs **u;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, script->traceback);
|
|
base = lua_gettop(L);
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
|
|
for(j = 31; j >= 1; j--)
|
|
{
|
|
if(*word[j])
|
|
break;
|
|
}
|
|
lua_newtable(L);
|
|
for(i = 1; i <= j; i++)
|
|
{
|
|
lua_pushstring(L, word[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
u = lua_newuserdata(L, sizeof(zoitechat_event_attrs *));
|
|
*u = event_attrs_copy(attrs);
|
|
luaL_newmetatable(L, "attrs");
|
|
lua_setmetatable(L, -2);
|
|
script->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 2, 1, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
lua_pop(L, 2);
|
|
zoitechat_printf(ph, "Lua error in print_attrs hook: %s", error ? error : "(non-string error)");
|
|
check_deferred(script);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
ret = lua_tointeger(L, -1);
|
|
lua_pop(L, 2);
|
|
check_deferred(script);
|
|
return ret;
|
|
}
|
|
|
|
static int api_zoitechat_hook_print_attrs(lua_State *L)
|
|
{
|
|
hook_info *info, **u;
|
|
int ref, pri;
|
|
char const *event = luaL_checkstring(L, 1);
|
|
|
|
lua_pushvalue(L, 2);
|
|
ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
pri = luaL_optinteger(L, 3, HEXCHAT_PRI_NORM);
|
|
info = g_new(hook_info, 1);
|
|
info->state = L;
|
|
info->ref = ref;
|
|
info->hook = zoitechat_hook_print_attrs(ph, event, pri, api_print_attrs_closure, info);
|
|
u = lua_newuserdata(L, sizeof(hook_info *));
|
|
*u = info;
|
|
luaL_newmetatable(L, "hook");
|
|
lua_setmetatable(L, -2);
|
|
register_hook(info);
|
|
return 1;
|
|
}
|
|
|
|
static int api_server_closure(char *word[], char *word_eol[], void *udata)
|
|
{
|
|
hook_info *info = udata;
|
|
lua_State *L = info->state;
|
|
script_info *script = get_info(L);
|
|
int base, i, ret;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, script->traceback);
|
|
base = lua_gettop(L);
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
|
|
lua_newtable(L);
|
|
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
|
|
{
|
|
lua_pushstring(L, word[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
lua_newtable(L);
|
|
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
|
|
{
|
|
lua_pushstring(L, word_eol[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
script->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 2, 1, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
lua_pop(L, 2);
|
|
zoitechat_printf(ph, "Lua error in server hook: %s", error ? error : "(non-string error)");
|
|
check_deferred(script);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
ret = lua_tointeger(L, -1);
|
|
lua_pop(L, 2);
|
|
check_deferred(script);
|
|
return ret;
|
|
}
|
|
|
|
static int api_zoitechat_hook_server(lua_State *L)
|
|
{
|
|
char const *command = luaL_optstring(L, 1, "RAW LINE");
|
|
hook_info *info, **u;
|
|
int ref, pri;
|
|
|
|
lua_pushvalue(L, 2);
|
|
ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
pri = luaL_optinteger(L, 3, HEXCHAT_PRI_NORM);
|
|
info = g_new(hook_info, 1);
|
|
info->state = L;
|
|
info->ref = ref;
|
|
info->hook = zoitechat_hook_server(ph, command, pri, api_server_closure, info);
|
|
u = lua_newuserdata(L, sizeof(hook_info *));
|
|
*u = info;
|
|
luaL_newmetatable(L, "hook");
|
|
lua_setmetatable(L, -2);
|
|
register_hook(info);
|
|
return 1;
|
|
}
|
|
|
|
static int api_server_attrs_closure(char *word[], char *word_eol[], zoitechat_event_attrs *attrs, void *udata)
|
|
{
|
|
hook_info *info = udata;
|
|
lua_State *L = info->state;
|
|
script_info *script = get_info(L);
|
|
int base, i, ret;
|
|
zoitechat_event_attrs **u;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, script->traceback);
|
|
base = lua_gettop(L);
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
|
|
lua_newtable(L);
|
|
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
|
|
{
|
|
lua_pushstring(L, word[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
lua_newtable(L);
|
|
for(i = 1; i < WORD_ARRAY_LEN && *word_eol[i]; i++)
|
|
{
|
|
lua_pushstring(L, word_eol[i]);
|
|
lua_rawseti(L, -2, i);
|
|
}
|
|
|
|
u = lua_newuserdata(L, sizeof(zoitechat_event_attrs *));
|
|
*u = event_attrs_copy(attrs);
|
|
luaL_newmetatable(L, "attrs");
|
|
lua_setmetatable(L, -2);
|
|
script->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 3, 1, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
lua_pop(L, 2);
|
|
zoitechat_printf(ph, "Lua error in server_attrs hook: %s", error ? error : "(non-string error)");
|
|
check_deferred(script);
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
ret = lua_tointeger(L, -1);
|
|
lua_pop(L, 2);
|
|
check_deferred(script);
|
|
return ret;
|
|
}
|
|
|
|
static int api_zoitechat_hook_server_attrs(lua_State *L)
|
|
{
|
|
char const *command = luaL_optstring(L, 1, "RAW LINE");
|
|
int ref, pri;
|
|
hook_info *info, **u;
|
|
|
|
lua_pushvalue(L, 2);
|
|
ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
pri = luaL_optinteger(L, 3, HEXCHAT_PRI_NORM);
|
|
info = g_new(hook_info, 1);
|
|
info->state = L;
|
|
info->ref = ref;
|
|
info->hook = zoitechat_hook_server_attrs(ph, command, pri, api_server_attrs_closure, info);
|
|
u = lua_newuserdata(L, sizeof(hook_info *));
|
|
*u = info;
|
|
luaL_newmetatable(L, "hook");
|
|
lua_setmetatable(L, -2);
|
|
register_hook(info);
|
|
return 1;
|
|
}
|
|
|
|
static int api_timer_closure(void *udata)
|
|
{
|
|
hook_info *info = udata;
|
|
lua_State *L = info->state;
|
|
script_info *script = get_info(L);
|
|
int base, ret;
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, script->traceback);
|
|
base = lua_gettop(L);
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->ref);
|
|
script->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 0, 1, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
lua_pop(L, 2);
|
|
zoitechat_printf(ph, "Lua error in timer hook: %s", error ? error : "(non-string error)");
|
|
check_deferred(script);
|
|
return 0;
|
|
}
|
|
ret = lua_toboolean(L, -1);
|
|
lua_pop(L, 2);
|
|
check_deferred(script);
|
|
return ret;
|
|
}
|
|
|
|
static int api_zoitechat_hook_timer(lua_State *L)
|
|
{
|
|
int ref, timeout = luaL_checkinteger (L, 1);
|
|
hook_info *info, **u;
|
|
|
|
lua_pushvalue(L, 2);
|
|
ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
info = g_new(hook_info, 1);
|
|
info->state = L;
|
|
info->ref = ref;
|
|
info->hook = zoitechat_hook_timer(ph, timeout, api_timer_closure, info);
|
|
u = lua_newuserdata(L, sizeof(hook_info *));
|
|
*u = info;
|
|
luaL_newmetatable(L, "hook");
|
|
lua_setmetatable(L, -2);
|
|
register_hook(info);
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_hook_unload(lua_State *L)
|
|
{
|
|
script_info *script;
|
|
hook_info *info, **u;
|
|
int ref;
|
|
|
|
lua_pushvalue(L, 1);
|
|
ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
info = g_new(hook_info, 1);
|
|
info->state = L;
|
|
info->ref = ref;
|
|
info->hook = NULL;
|
|
u = lua_newuserdata(L, sizeof(hook_info *));
|
|
*u = info;
|
|
luaL_newmetatable(L, "hook");
|
|
lua_setmetatable(L, -2);
|
|
script = get_info(info->state);
|
|
|
|
g_ptr_array_add(script->unload_hooks, info);
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_unhook(lua_State *L)
|
|
{
|
|
hook_info **info = (hook_info **)luaL_checkudata(L, 1, "hook");
|
|
if(*info)
|
|
{
|
|
unregister_hook(*info);
|
|
*info = 0;
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
tostring(L, 1);
|
|
return luaL_error(L, "hook %s is already unhooked", lua_tostring(L, -1));
|
|
}
|
|
}
|
|
|
|
static int api_zoitechat_find_context(lua_State *L)
|
|
{
|
|
char const *server = luaL_optstring(L, 1, NULL);
|
|
char const *channel = luaL_optstring(L, 2, NULL);
|
|
zoitechat_context *context = zoitechat_find_context(ph, server, channel);
|
|
if(context)
|
|
{
|
|
zoitechat_context **u = lua_newuserdata(L, sizeof(zoitechat_context *));
|
|
*u = context;
|
|
luaL_newmetatable(L, "context");
|
|
lua_setmetatable(L, -2);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int api_zoitechat_get_context(lua_State *L)
|
|
{
|
|
zoitechat_context *context = zoitechat_get_context(ph);
|
|
zoitechat_context **u = lua_newuserdata(L, sizeof(zoitechat_context *));
|
|
*u = context;
|
|
luaL_newmetatable(L, "context");
|
|
lua_setmetatable(L, -2);
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_set_context(lua_State *L)
|
|
{
|
|
zoitechat_context *context = *(zoitechat_context **)luaL_checkudata(L, 1, "context");
|
|
int success = zoitechat_set_context(ph, context);
|
|
lua_pushboolean(L, success);
|
|
return 1;
|
|
}
|
|
|
|
static int wrap_context_closure(lua_State *L)
|
|
{
|
|
zoitechat_context *old, *context = *(zoitechat_context **)luaL_checkudata(L, 1, "context");
|
|
lua_pushvalue(L, lua_upvalueindex(1));
|
|
lua_replace(L, 1);
|
|
old = zoitechat_get_context(ph);
|
|
if(!zoitechat_set_context(ph, context))
|
|
return luaL_error(L, "could not switch into context");
|
|
lua_call(L, lua_gettop(L) - 1, LUA_MULTRET);
|
|
zoitechat_set_context(ph, old);
|
|
return lua_gettop(L);
|
|
}
|
|
|
|
static inline void wrap_context(lua_State *L, char const *field, lua_CFunction func)
|
|
{
|
|
lua_pushcfunction(L, func);
|
|
lua_pushcclosure(L, wrap_context_closure, 1);
|
|
lua_setfield(L, -2, field);
|
|
}
|
|
|
|
static int api_zoitechat_context_meta_eq(lua_State *L)
|
|
{
|
|
zoitechat_context *this = *(zoitechat_context **)luaL_checkudata(L, 1, "context");
|
|
zoitechat_context *that = *(zoitechat_context **)luaL_checkudata(L, 2, "context");
|
|
lua_pushboolean(L, this == that);
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_get_info(lua_State *L)
|
|
{
|
|
char const *key = luaL_checkstring(L, 1);
|
|
char const *data = zoitechat_get_info(ph, key);
|
|
if(data)
|
|
{
|
|
if(!strcmp(key, "gtkwin_ptr") || !strcmp(key, "win_ptr"))
|
|
lua_pushlightuserdata(L, (void *)data);
|
|
else
|
|
lua_pushstring(L, data);
|
|
return 1;
|
|
}
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_attrs(lua_State *L)
|
|
{
|
|
zoitechat_event_attrs *attrs = zoitechat_event_attrs_create(ph);
|
|
zoitechat_event_attrs **u = lua_newuserdata(L, sizeof(zoitechat_event_attrs *));
|
|
*u = attrs;
|
|
luaL_newmetatable(L, "attrs");
|
|
lua_setmetatable(L, -2);
|
|
return 1;
|
|
}
|
|
|
|
static int api_iterate_closure(lua_State *L)
|
|
{
|
|
zoitechat_list *list = *(zoitechat_list **)luaL_checkudata(L, lua_upvalueindex(1), "list");
|
|
if(zoitechat_list_next(ph, list))
|
|
{
|
|
lua_pushvalue(L, lua_upvalueindex(1));
|
|
return 1;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int api_zoitechat_iterate(lua_State *L)
|
|
{
|
|
char const *name = luaL_checkstring(L, 1);
|
|
zoitechat_list *list = zoitechat_list_get(ph, name);
|
|
if(list)
|
|
{
|
|
zoitechat_list **u = lua_newuserdata(L, sizeof(zoitechat_list *));
|
|
*u = list;
|
|
luaL_newmetatable(L, "list");
|
|
lua_setmetatable(L, -2);
|
|
lua_pushcclosure(L, api_iterate_closure, 1);
|
|
return 1;
|
|
}
|
|
else
|
|
return luaL_argerror(L, 1, "invalid list name");
|
|
}
|
|
|
|
static int api_zoitechat_prefs_meta_index(lua_State *L)
|
|
{
|
|
char const *key = luaL_checkstring(L, 2);
|
|
char const *string;
|
|
int number;
|
|
int ret = zoitechat_get_prefs(ph, key, &string, &number);
|
|
switch(ret)
|
|
{
|
|
case 0:
|
|
lua_pushnil(L);
|
|
return 1;
|
|
case 1:
|
|
lua_pushstring(L, string);
|
|
return 1;
|
|
case 2:
|
|
lua_pushnumber(L, number);
|
|
return 1;
|
|
case 3:
|
|
lua_pushboolean(L, number);
|
|
return 1;
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static int api_zoitechat_prefs_meta_newindex(lua_State *L)
|
|
{
|
|
return luaL_error(L, "zoitechat.prefs is read-only");
|
|
}
|
|
|
|
static inline int list_marshal(lua_State *L, const char *key, zoitechat_list *list)
|
|
{
|
|
char const *str = zoitechat_list_str(ph, list, key);
|
|
int number;
|
|
if(str)
|
|
{
|
|
if(!strcmp(key, "context"))
|
|
{
|
|
zoitechat_context **u = lua_newuserdata(L, sizeof(zoitechat_context *));
|
|
*u = (zoitechat_context *)str;
|
|
luaL_newmetatable(L, "context");
|
|
lua_setmetatable(L, -2);
|
|
return 1;
|
|
}
|
|
lua_pushstring(L, str);
|
|
return 1;
|
|
}
|
|
number = zoitechat_list_int(ph, list, key);
|
|
if(number != -1)
|
|
{
|
|
lua_pushinteger(L, number);
|
|
return 1;
|
|
}
|
|
if (list != NULL)
|
|
{
|
|
time_t tm = zoitechat_list_time(ph, list, key);
|
|
if(tm != -1)
|
|
{
|
|
lua_pushinteger(L, tm);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_props_meta_index(lua_State *L)
|
|
{
|
|
char const *key = luaL_checkstring(L, 2);
|
|
return list_marshal(L, key, NULL);
|
|
}
|
|
|
|
static int api_zoitechat_props_meta_newindex(lua_State *L)
|
|
{
|
|
return luaL_error(L, "zoitechat.props is read-only");
|
|
}
|
|
|
|
static int api_zoitechat_pluginprefs_meta_index(lua_State *L)
|
|
{
|
|
script_info *script = get_info(L);
|
|
const char *key;
|
|
zoitechat_plugin *h;
|
|
char str[512];
|
|
int r;
|
|
|
|
if(!script->name)
|
|
return luaL_error(L, "cannot use zoitechat.pluginprefs before registering with zoitechat.register");
|
|
|
|
key = luaL_checkstring(L, 2);
|
|
h = script->handle;
|
|
r = zoitechat_pluginpref_get_int(h, key);
|
|
if(r != -1)
|
|
{
|
|
lua_pushinteger(L, r);
|
|
return 1;
|
|
}
|
|
if(zoitechat_pluginpref_get_str(h, key, str))
|
|
{
|
|
/* Wasn't actually a failure */
|
|
if (!strcmp(str, "-1"))
|
|
lua_pushinteger(L, r);
|
|
else
|
|
lua_pushstring(L, str);
|
|
return 1;
|
|
}
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
|
|
static int api_zoitechat_pluginprefs_meta_newindex(lua_State *L)
|
|
{
|
|
script_info *script = get_info(L);
|
|
const char *key;
|
|
zoitechat_plugin *h;
|
|
|
|
if(!script->name)
|
|
return luaL_error(L, "cannot use zoitechat.pluginprefs before registering with zoitechat.register");
|
|
|
|
key = luaL_checkstring(L, 2);
|
|
h = script->handle;
|
|
switch(lua_type(L, 3))
|
|
{
|
|
case LUA_TSTRING:
|
|
zoitechat_pluginpref_set_str(h, key, lua_tostring(L, 3));
|
|
return 0;
|
|
case LUA_TNUMBER:
|
|
zoitechat_pluginpref_set_int(h, key, lua_tointeger(L, 3));
|
|
return 0;
|
|
case LUA_TNIL: case LUA_TNONE:
|
|
zoitechat_pluginpref_delete(h, key);
|
|
return 0;
|
|
default:
|
|
return luaL_argerror(L, 3, "expected string, number, or nil");
|
|
}
|
|
}
|
|
|
|
static int api_zoitechat_pluginprefs_meta_pairs_closure(lua_State *L)
|
|
{
|
|
char *dest = lua_touserdata(L, lua_upvalueindex(1));
|
|
zoitechat_plugin *h = get_info(L)->handle;
|
|
|
|
if(dest && *dest)
|
|
{
|
|
int r;
|
|
char str[512];
|
|
char *key = dest;
|
|
|
|
dest = strchr(dest, ',');
|
|
if(dest)
|
|
*(dest++) = 0;
|
|
lua_pushlightuserdata(L, dest);
|
|
lua_replace(L, lua_upvalueindex(1));
|
|
lua_pushstring(L, key);
|
|
r = zoitechat_pluginpref_get_int(h, key);
|
|
if(r != -1)
|
|
{
|
|
lua_pushinteger(L, r);
|
|
return 2;
|
|
}
|
|
if(zoitechat_pluginpref_get_str(h, key, str))
|
|
{
|
|
lua_pushstring(L, str);
|
|
return 2;
|
|
}
|
|
lua_pushnil(L);
|
|
return 2;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int api_zoitechat_pluginprefs_meta_pairs(lua_State *L)
|
|
{
|
|
script_info *script = get_info(L);
|
|
char *dest;
|
|
zoitechat_plugin *h;
|
|
|
|
if(!script->name)
|
|
|
|
return luaL_error(L, "cannot use zoitechat.pluginprefs before registering with zoitechat.register");
|
|
|
|
dest = lua_newuserdata(L, 4096);
|
|
|
|
h = script->handle;
|
|
if(!zoitechat_pluginpref_list(h, dest))
|
|
strcpy(dest, "");
|
|
lua_pushlightuserdata(L, dest);
|
|
lua_pushlightuserdata(L, dest);
|
|
lua_pushcclosure(L, api_zoitechat_pluginprefs_meta_pairs_closure, 2);
|
|
lua_insert(L, -2); // Return the userdata (second return value from pairs),
|
|
// even though it's not used by the closure (first return
|
|
// value from pairs), so that Lua knows not to GC it.
|
|
return 2;
|
|
}
|
|
|
|
static int api_attrs_meta_index(lua_State *L)
|
|
{
|
|
zoitechat_event_attrs *attrs = *(zoitechat_event_attrs **)luaL_checkudata(L, 1, "attrs");
|
|
char const *key = luaL_checkstring(L, 2);
|
|
if(!strcmp(key, "server_time_utc"))
|
|
{
|
|
lua_pushinteger(L, attrs->server_time_utc);
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
lua_pushnil(L);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static int api_attrs_meta_newindex(lua_State *L)
|
|
{
|
|
zoitechat_event_attrs *attrs = *(zoitechat_event_attrs **)luaL_checkudata(L, 1, "attrs");
|
|
char const *key = luaL_checkstring(L, 2);
|
|
if(!strcmp(key, "server_time_utc"))
|
|
{
|
|
attrs->server_time_utc = luaL_checkinteger(L, 3);
|
|
return 0;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
static int api_attrs_meta_gc(lua_State *L)
|
|
{
|
|
zoitechat_event_attrs *attrs = *(zoitechat_event_attrs **)luaL_checkudata(L, 1, "attrs");
|
|
zoitechat_event_attrs_free(ph, attrs);
|
|
return 0;
|
|
}
|
|
|
|
static int api_list_meta_index(lua_State *L)
|
|
{
|
|
zoitechat_list *list = *(zoitechat_list **)luaL_checkudata(L, 1, "list");
|
|
char const *key = luaL_checkstring(L, 2);
|
|
return list_marshal(L, key, list);
|
|
}
|
|
|
|
static int api_list_meta_newindex(lua_State *L)
|
|
{
|
|
return luaL_error(L, "zoitechat.iterate list is read-only");
|
|
}
|
|
|
|
static int api_list_meta_gc(lua_State *L)
|
|
{
|
|
zoitechat_list *list = *(zoitechat_list **)luaL_checkudata(L, 1, "list");
|
|
zoitechat_list_free(ph, list);
|
|
return 0;
|
|
}
|
|
|
|
static luaL_Reg api_zoitechat[] = {
|
|
{"register", api_zoitechat_register},
|
|
{"command", api_zoitechat_command},
|
|
{"print", api_zoitechat_print},
|
|
{"emit_print", api_zoitechat_emit_print},
|
|
{"emit_print_attrs", api_zoitechat_emit_print_attrs},
|
|
{"send_modes", api_zoitechat_send_modes},
|
|
{"nickcmp", api_zoitechat_nickcmp},
|
|
{"strip", api_zoitechat_strip},
|
|
{"get_info", api_zoitechat_get_info},
|
|
{"hook_command", api_zoitechat_hook_command},
|
|
{"hook_print", api_zoitechat_hook_print},
|
|
{"hook_print_attrs", api_zoitechat_hook_print_attrs},
|
|
{"hook_server", api_zoitechat_hook_server},
|
|
{"hook_server_attrs", api_zoitechat_hook_server_attrs},
|
|
{"hook_timer", api_zoitechat_hook_timer},
|
|
{"hook_unload", api_zoitechat_hook_unload},
|
|
{"unhook", api_zoitechat_unhook},
|
|
{"get_context", api_zoitechat_get_context},
|
|
{"find_context", api_zoitechat_find_context},
|
|
{"set_context", api_zoitechat_set_context},
|
|
{"attrs", api_zoitechat_attrs},
|
|
{"iterate", api_zoitechat_iterate},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static luaL_Reg api_zoitechat_props_meta[] = {
|
|
{"__index", api_zoitechat_props_meta_index},
|
|
{"__newindex", api_zoitechat_props_meta_newindex},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static luaL_Reg api_zoitechat_prefs_meta[] = {
|
|
{"__index", api_zoitechat_prefs_meta_index},
|
|
{"__newindex", api_zoitechat_prefs_meta_newindex},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static luaL_Reg api_zoitechat_pluginprefs_meta[] = {
|
|
{"__index", api_zoitechat_pluginprefs_meta_index},
|
|
{"__newindex", api_zoitechat_pluginprefs_meta_newindex},
|
|
{"__pairs", api_zoitechat_pluginprefs_meta_pairs},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static luaL_Reg api_hook_meta_index[] = {
|
|
{"unhook", api_zoitechat_unhook},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static luaL_Reg api_attrs_meta[] = {
|
|
{"__index", api_attrs_meta_index},
|
|
{"__newindex", api_attrs_meta_newindex},
|
|
{"__gc", api_attrs_meta_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static luaL_Reg api_list_meta[] = {
|
|
{"__index", api_list_meta_index},
|
|
{"__newindex", api_list_meta_newindex},
|
|
{"__gc", api_list_meta_gc},
|
|
{NULL, NULL}
|
|
};
|
|
|
|
static int luaopen_zoitechat(lua_State *L)
|
|
{
|
|
lua_newtable(L);
|
|
luaL_setfuncs(L, api_zoitechat, 0);
|
|
|
|
lua_pushinteger(L, HEXCHAT_PRI_HIGHEST); lua_setfield(L, -2, "PRI_HIGHEST");
|
|
lua_pushinteger(L, HEXCHAT_PRI_HIGH); lua_setfield(L, -2, "PRI_HIGH");
|
|
lua_pushinteger(L, HEXCHAT_PRI_NORM); lua_setfield(L, -2, "PRI_NORM");
|
|
lua_pushinteger(L, HEXCHAT_PRI_LOW); lua_setfield(L, -2, "PRI_LOW");
|
|
lua_pushinteger(L, HEXCHAT_PRI_LOWEST); lua_setfield(L, -2, "PRI_LOWEST");
|
|
lua_pushinteger(L, HEXCHAT_EAT_NONE); lua_setfield(L, -2, "EAT_NONE");
|
|
lua_pushinteger(L, HEXCHAT_EAT_HEXCHAT); lua_setfield(L, -2, "EAT_HEXCHAT");
|
|
lua_pushinteger(L, HEXCHAT_EAT_PLUGIN); lua_setfield(L, -2, "EAT_PLUGIN");
|
|
lua_pushinteger(L, HEXCHAT_EAT_ALL); lua_setfield(L, -2, "EAT_ALL");
|
|
|
|
lua_newtable(L);
|
|
lua_newtable(L);
|
|
luaL_setfuncs(L, api_zoitechat_prefs_meta, 0);
|
|
lua_setmetatable(L, -2);
|
|
lua_setfield(L, -2, "prefs");
|
|
|
|
lua_newtable(L);
|
|
lua_newtable(L);
|
|
luaL_setfuncs(L, api_zoitechat_props_meta, 0);
|
|
lua_setmetatable(L, -2);
|
|
lua_setfield(L, -2, "props");
|
|
|
|
lua_newtable(L);
|
|
lua_newtable(L);
|
|
luaL_setfuncs(L, api_zoitechat_pluginprefs_meta, 0);
|
|
lua_setmetatable(L, -2);
|
|
lua_setfield(L, -2, "pluginprefs");
|
|
|
|
luaL_newmetatable(L, "hook");
|
|
lua_newtable(L);
|
|
luaL_setfuncs(L, api_hook_meta_index, 0);
|
|
lua_setfield(L, -2, "__index");
|
|
lua_pop(L, 1);
|
|
|
|
luaL_newmetatable(L, "context");
|
|
lua_newtable(L);
|
|
lua_pushcfunction(L, api_zoitechat_set_context);
|
|
lua_setfield(L, -2, "set");
|
|
wrap_context(L, "find_context", api_zoitechat_find_context);
|
|
wrap_context(L, "print", api_zoitechat_print);
|
|
wrap_context(L, "emit_print", api_zoitechat_emit_print);
|
|
wrap_context(L, "emit_print_attrs", api_zoitechat_emit_print_attrs);
|
|
wrap_context(L, "command", api_zoitechat_command);
|
|
wrap_context(L, "nickcmp", api_zoitechat_nickcmp);
|
|
wrap_context(L, "get_info", api_zoitechat_get_info);
|
|
wrap_context(L, "iterate", api_zoitechat_iterate);
|
|
lua_setfield(L, -2, "__index");
|
|
lua_pushcfunction(L, api_zoitechat_context_meta_eq);
|
|
lua_setfield(L, -2, "__eq");
|
|
lua_pop(L, 1);
|
|
|
|
|
|
luaL_newmetatable(L, "attrs");
|
|
luaL_setfuncs(L, api_attrs_meta, 0);
|
|
lua_pop(L, 1);
|
|
|
|
luaL_newmetatable(L, "list");
|
|
luaL_setfuncs(L, api_list_meta, 0);
|
|
lua_pop(L, 1);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int pairs_closure(lua_State *L)
|
|
{
|
|
lua_settop(L, 1);
|
|
if(luaL_getmetafield(L, 1, "__pairs"))
|
|
{
|
|
lua_insert(L, 1);
|
|
lua_call(L, 1, LUA_MULTRET);
|
|
return lua_gettop(L);
|
|
}
|
|
else
|
|
{
|
|
lua_pushvalue(L, lua_upvalueindex(1));
|
|
lua_insert(L, 1);
|
|
lua_call(L, 1, LUA_MULTRET);
|
|
return lua_gettop(L);
|
|
}
|
|
}
|
|
|
|
static void patch_pairs(lua_State *L)
|
|
{
|
|
lua_getglobal(L, "pairs");
|
|
lua_pushcclosure(L, pairs_closure, 1);
|
|
lua_setglobal(L, "pairs");
|
|
}
|
|
|
|
static void patch_clibs(lua_State *L)
|
|
{
|
|
lua_pushnil(L);
|
|
while(lua_next(L, LUA_REGISTRYINDEX))
|
|
{
|
|
if(lua_type(L, -2) == LUA_TLIGHTUSERDATA && lua_type(L, -1) == LUA_TTABLE)
|
|
{
|
|
lua_setfield(L, LUA_REGISTRYINDEX, "_CLIBS");
|
|
lua_pop(L, 1);
|
|
break;
|
|
}
|
|
lua_pop(L, 1);
|
|
}
|
|
}
|
|
|
|
static GPtrArray *scripts;
|
|
|
|
static char *expand_buffer = NULL;
|
|
static char const *expand_path(char const *path)
|
|
{
|
|
if(g_path_is_absolute(path))
|
|
return path;
|
|
#ifndef G_OS_WIN32
|
|
if(path[0] == '~')
|
|
{
|
|
if(!path[1] || path[1] == '/')
|
|
{
|
|
g_free(expand_buffer);
|
|
expand_buffer = g_build_filename(g_get_home_dir(), path + 1, NULL);
|
|
return expand_buffer;
|
|
}
|
|
else
|
|
{
|
|
char *user = g_strdup(path + 1);
|
|
char *slash_pos = strchr(user, '/');
|
|
struct passwd *pw;
|
|
if(slash_pos)
|
|
*slash_pos = 0;
|
|
pw = getpwnam(user);
|
|
g_free(user);
|
|
if(pw)
|
|
{
|
|
slash_pos = strchr(path, '/');
|
|
if(!slash_pos)
|
|
return pw->pw_dir;
|
|
|
|
g_free(expand_buffer);
|
|
expand_buffer = g_strconcat(pw->pw_dir, slash_pos, NULL);
|
|
return expand_buffer;
|
|
}
|
|
else
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
g_free(expand_buffer);
|
|
expand_buffer = g_build_filename(zoitechat_get_info(ph, "configdir"), "addons", path, NULL);
|
|
return expand_buffer;
|
|
}
|
|
}
|
|
|
|
static inline int is_lua_file(char const *file)
|
|
{
|
|
return g_str_has_suffix(file, ".lua") || g_str_has_suffix(file, ".luac");
|
|
}
|
|
|
|
static void prepare_state(lua_State *L, script_info *info)
|
|
{
|
|
luaL_openlibs(L);
|
|
if(LUA_VERSION_NUM < 502)
|
|
patch_pairs(L);
|
|
if(LUA_VERSION_NUM > 502)
|
|
patch_clibs(L);
|
|
lua_getglobal(L, "debug");
|
|
lua_getfield(L, -1, "traceback");
|
|
info->traceback = luaL_ref(L, LUA_REGISTRYINDEX);
|
|
lua_pop(L, 1);
|
|
lua_pushlightuserdata(L, info);
|
|
lua_setfield(L, LUA_REGISTRYINDEX, registry_field);
|
|
luaopen_zoitechat(L);
|
|
lua_setglobal(L, "zoitechat");
|
|
lua_getglobal(L, "zoitechat");
|
|
lua_getfield(L, -1, "print");
|
|
lua_setglobal(L, "print");
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
static void run_unload_hook(hook_info *hook, lua_State *L)
|
|
{
|
|
int base = lua_gettop(L);
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, hook->ref);
|
|
if(lua_pcall(L, 0, 0, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
zoitechat_printf(ph, "Lua error in unload hook: %s", error ? error : "(non-string error)");
|
|
}
|
|
lua_settop(L, base);
|
|
}
|
|
|
|
static void run_unload_hooks(script_info *info, void *unused)
|
|
{
|
|
lua_State *L = info->state;
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->traceback);
|
|
g_ptr_array_foreach(info->unload_hooks, (GFunc)run_unload_hook, L);
|
|
lua_pop(L, 1);
|
|
}
|
|
|
|
static void destroy_script(script_info *info)
|
|
{
|
|
if (info)
|
|
{
|
|
g_clear_pointer(&info->hooks, g_ptr_array_unref);
|
|
g_clear_pointer(&info->unload_hooks, g_ptr_array_unref);
|
|
g_clear_pointer(&info->state, lua_close);
|
|
if (info->handle)
|
|
zoitechat_plugingui_remove(ph, info->handle);
|
|
g_free(info->filename);
|
|
g_free(info->name);
|
|
g_free(info->description);
|
|
g_free(info->version);
|
|
g_free(info);
|
|
}
|
|
}
|
|
|
|
static script_info *create_script(char const *file)
|
|
{
|
|
int base;
|
|
char *filename_fs;
|
|
lua_State *L;
|
|
script_info *info = g_new0(script_info, 1);
|
|
info->hooks = g_ptr_array_new_with_free_func((GDestroyNotify)free_hook);
|
|
info->unload_hooks = g_ptr_array_new_with_free_func((GDestroyNotify)free_hook);
|
|
info->filename = g_strdup(expand_path(file));
|
|
L = luaL_newstate();
|
|
info->state = L;
|
|
if(!L)
|
|
{
|
|
zoitechat_print(ph, "\00304Could not allocate memory for the script");
|
|
destroy_script(info);
|
|
return NULL;
|
|
}
|
|
prepare_state(L, info);
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->traceback);
|
|
base = lua_gettop(L);
|
|
filename_fs = g_filename_from_utf8(info->filename, -1, NULL, NULL, NULL);
|
|
if(!filename_fs)
|
|
{
|
|
zoitechat_printf(ph, "Invalid filename: %s", info->filename);
|
|
destroy_script(info);
|
|
return NULL;
|
|
}
|
|
if(luaL_loadfile(L, filename_fs))
|
|
{
|
|
g_free(filename_fs);
|
|
zoitechat_printf(ph, "Lua syntax error: %s", luaL_optstring(L, -1, ""));
|
|
destroy_script(info);
|
|
return NULL;
|
|
}
|
|
g_free(filename_fs);
|
|
info->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 0, 0, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
zoitechat_printf(ph, "Lua error: %s", error ? error : "(non-string error)");
|
|
destroy_script(info);
|
|
return NULL;
|
|
}
|
|
lua_pop(L, 1);
|
|
if(!info->name)
|
|
{
|
|
zoitechat_printf(ph, "Lua script didn't register with zoitechat.register");
|
|
destroy_script(info);
|
|
return NULL;
|
|
}
|
|
return info;
|
|
}
|
|
|
|
static script_info *get_script_by_file(char const *filename)
|
|
{
|
|
guint i;
|
|
for(i = 0; i < scripts->len; i++)
|
|
{
|
|
script_info *script = scripts->pdata[i];
|
|
if(!strcmp(script->filename, filename))
|
|
{
|
|
return script;
|
|
}
|
|
if(g_path_is_absolute(filename) || strchr(filename, '/'))
|
|
{
|
|
char const *expanded = expand_path(filename);
|
|
if(!strcmp(script->filename, expanded))
|
|
return script;
|
|
}
|
|
else
|
|
{
|
|
char *basename = g_path_get_basename(script->filename);
|
|
gboolean match = !strcmp(basename, filename);
|
|
g_free(basename);
|
|
if(match)
|
|
return script;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int load_script(char const *file)
|
|
{
|
|
script_info *info = get_script_by_file(file);
|
|
|
|
if (info != NULL)
|
|
{
|
|
zoitechat_print(ph, "Lua script is already loaded");
|
|
return 0;
|
|
}
|
|
|
|
info = create_script(file);
|
|
if (!info)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
g_ptr_array_add(scripts, info);
|
|
check_deferred(info);
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int unload_script(char const *filename)
|
|
{
|
|
script_info *script = get_script_by_file(filename);
|
|
|
|
if (!script)
|
|
return 0;
|
|
|
|
if(script->status & STATUS_ACTIVE)
|
|
script->status |= STATUS_DEFERRED_UNLOAD;
|
|
else
|
|
{
|
|
run_unload_hooks(script, NULL);
|
|
g_ptr_array_remove_fast(scripts, script);
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
static int reload_script(char const *filename)
|
|
{
|
|
script_info *script = get_script_by_file(filename);
|
|
|
|
if (!script)
|
|
return 0;
|
|
|
|
if(script->status & STATUS_ACTIVE)
|
|
{
|
|
script->status |= STATUS_DEFERRED_RELOAD;
|
|
}
|
|
else
|
|
{
|
|
char *filename = g_strdup(script->filename);
|
|
run_unload_hooks(script, NULL);
|
|
g_ptr_array_remove_fast(scripts, script);
|
|
load_script(filename);
|
|
g_free(filename);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void autoload_scripts(void)
|
|
{
|
|
char *path = g_build_filename(zoitechat_get_info(ph, "configdir"), "addons", NULL);
|
|
GDir *dir = g_dir_open(path, 0, NULL);
|
|
if(dir)
|
|
{
|
|
char const *filename;
|
|
while((filename = g_dir_read_name(dir)))
|
|
{
|
|
if(is_lua_file(filename))
|
|
load_script(filename);
|
|
}
|
|
g_dir_close(dir);
|
|
}
|
|
g_free(path);
|
|
}
|
|
|
|
static script_info *interp = NULL;
|
|
static void create_interpreter(void)
|
|
{
|
|
lua_State *L;
|
|
interp = g_new0(script_info, 1);
|
|
interp->hooks = g_ptr_array_new_with_free_func((GDestroyNotify)free_hook);
|
|
interp->unload_hooks = g_ptr_array_new_with_free_func((GDestroyNotify)free_hook);
|
|
interp->name = "lua interpreter";
|
|
interp->description = "";
|
|
interp->version = "";
|
|
interp->handle = ph;
|
|
interp->filename = "";
|
|
L = luaL_newstate();
|
|
interp->state = L;
|
|
if(!L)
|
|
{
|
|
zoitechat_print(ph, "\00304Could not allocate memory for the interpreter");
|
|
g_free(interp);
|
|
interp = NULL;
|
|
return;
|
|
}
|
|
prepare_state(L, interp);
|
|
}
|
|
|
|
static void destroy_interpreter(void)
|
|
{
|
|
if(interp)
|
|
{
|
|
g_clear_pointer(&interp->hooks, g_ptr_array_unref);
|
|
g_clear_pointer(&interp->unload_hooks, g_ptr_array_unref);
|
|
g_clear_pointer(&interp->state, lua_close);
|
|
g_clear_pointer(&interp, g_free);
|
|
}
|
|
}
|
|
|
|
static void inject_string(script_info *info, char const *line)
|
|
{
|
|
lua_State *L = info->state;
|
|
int base, top;
|
|
char *ret_line;
|
|
gboolean force_ret = FALSE;
|
|
|
|
if(line[0] == '=')
|
|
{
|
|
line++;
|
|
force_ret = TRUE;
|
|
}
|
|
ret_line = g_strconcat("return ", line, NULL);
|
|
|
|
lua_rawgeti(L, LUA_REGISTRYINDEX, info->traceback);
|
|
base = lua_gettop(L);
|
|
if(luaL_loadbuffer(L, ret_line, strlen(ret_line), "@interpreter"))
|
|
{
|
|
if(!force_ret)
|
|
lua_pop(L, 1);
|
|
if(force_ret || luaL_loadbuffer(L, line, strlen(line), "@interpreter"))
|
|
{
|
|
zoitechat_printf(ph, "Lua syntax error: %s", luaL_optstring(L, -1, ""));
|
|
lua_pop(L, 2);
|
|
g_free(ret_line);
|
|
return;
|
|
}
|
|
}
|
|
g_free(ret_line);
|
|
info->status |= STATUS_ACTIVE;
|
|
if(lua_pcall(L, 0, LUA_MULTRET, base))
|
|
{
|
|
char const *error = lua_tostring(L, -1);
|
|
lua_pop(L, 2);
|
|
zoitechat_printf(ph, "Lua error: %s", error ? error : "(non-string error)");
|
|
return;
|
|
}
|
|
top = lua_gettop(L);
|
|
if(top > base)
|
|
{
|
|
int i;
|
|
luaL_Buffer b;
|
|
luaL_buffinit(L, &b);
|
|
for(i = base + 1; i <= top; i++)
|
|
{
|
|
if(i != base + 1)
|
|
luaL_addstring(&b, " ");
|
|
tostring(L, i);
|
|
luaL_addvalue(&b);
|
|
}
|
|
luaL_pushresult(&b);
|
|
zoitechat_print(ph, lua_tostring(L, -1));
|
|
lua_pop(L, top - base + 1);
|
|
}
|
|
lua_pop(L, 1);
|
|
check_deferred(info);
|
|
}
|
|
|
|
static int command_load(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
if(is_lua_file(word[2]))
|
|
{
|
|
load_script(word[2]);
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
else
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
static int command_unload(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
if(unload_script(word[2]))
|
|
return HEXCHAT_EAT_ALL;
|
|
else
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
static int command_reload(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
if(reload_script(word[2]))
|
|
return HEXCHAT_EAT_ALL;
|
|
else
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
static int command_console_exec(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
char const *channel = zoitechat_get_info(ph, "channel");
|
|
if(channel && !strcmp(channel, console_tab))
|
|
{
|
|
if(interp)
|
|
{
|
|
zoitechat_printf(ph, "> %s", word_eol[1]);
|
|
inject_string(interp, word_eol[1]);
|
|
}
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
return HEXCHAT_EAT_NONE;
|
|
}
|
|
|
|
static void check_deferred(script_info *info)
|
|
{
|
|
info->status &= ~STATUS_ACTIVE;
|
|
if(info->status & STATUS_DEFERRED_UNLOAD)
|
|
{
|
|
run_unload_hooks(info, NULL);
|
|
g_ptr_array_remove_fast(scripts, info);
|
|
}
|
|
else if(info->status & STATUS_DEFERRED_RELOAD)
|
|
{
|
|
if(info == interp)
|
|
{
|
|
run_unload_hooks(interp, NULL);
|
|
destroy_interpreter();
|
|
create_interpreter();
|
|
}
|
|
else
|
|
{
|
|
char *filename = g_strdup(info->filename);
|
|
run_unload_hooks(info, NULL);
|
|
g_ptr_array_remove_fast(scripts, info);
|
|
load_script(filename);
|
|
g_free(filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int command_lua(char *word[], char *word_eol[], void *userdata)
|
|
{
|
|
if(!strcmp(word[2], "load"))
|
|
{
|
|
if(load_script(word[3]))
|
|
zoitechat_printf(ph, "Loaded Lua script '%s'", word[3]);
|
|
else
|
|
zoitechat_printf(ph, "Failed to load Lua script '%s'", word[3]);
|
|
}
|
|
else if(!strcmp(word[2], "unload"))
|
|
{
|
|
script_info *script = get_script_by_file(word[3]);
|
|
if(script)
|
|
{
|
|
gboolean deferred = script->status & STATUS_ACTIVE;
|
|
unload_script(word[3]);
|
|
if(deferred)
|
|
zoitechat_printf(ph, "Unload scheduled for Lua script '%s'", word[3]);
|
|
else
|
|
zoitechat_printf(ph, "Unloaded Lua script '%s'", word[3]);
|
|
}
|
|
else
|
|
zoitechat_printf(ph, "Could not find a script by the name '%s'", word[3]);
|
|
}
|
|
else if(!strcmp(word[2], "reload"))
|
|
{
|
|
if(!reload_script(word[3]))
|
|
zoitechat_printf(ph, "Could not find a script by the name '%s'", word[3]);
|
|
}
|
|
else if(!strcmp(word[2], "exec"))
|
|
{
|
|
if(interp)
|
|
inject_string(interp, word_eol[3]);
|
|
}
|
|
else if(!strcmp(word[2], "inject"))
|
|
{
|
|
script_info *script = get_script_by_file(word[3]);
|
|
if (script)
|
|
{
|
|
inject_string(script, word_eol[4]);
|
|
}
|
|
else
|
|
{
|
|
zoitechat_printf(ph, "Could not find a script by the name '%s'", word[3]);
|
|
}
|
|
}
|
|
else if(!strcmp(word[2], "reset"))
|
|
{
|
|
if(interp)
|
|
{
|
|
if(interp->status & STATUS_ACTIVE)
|
|
{
|
|
interp->status |= STATUS_DEFERRED_RELOAD;
|
|
}
|
|
else
|
|
{
|
|
run_unload_hooks(interp, NULL);
|
|
destroy_interpreter();
|
|
create_interpreter();
|
|
}
|
|
}
|
|
}
|
|
else if(!strcmp(word[2], "list"))
|
|
{
|
|
guint i;
|
|
zoitechat_print(ph,
|
|
"Name Version Filename Description\n"
|
|
"---- ------- -------- -----------\n");
|
|
for(i = 0; i < scripts->len; i++)
|
|
{
|
|
script_info *info = scripts->pdata[i];
|
|
char *basename = g_path_get_basename(info->filename);
|
|
zoitechat_printf(ph, "%-16s %-8s %-20s %-10s\n", info->name, info->version,
|
|
basename, info->description);
|
|
g_free(basename);
|
|
}
|
|
if(interp)
|
|
zoitechat_printf(ph, "%-16s %-8s %-20s %-10s", interp->name, plugin_version, "", "");
|
|
}
|
|
else if(!strcmp(word[2], "console"))
|
|
{
|
|
zoitechat_commandf(ph, "query %s", console_tab);
|
|
}
|
|
else
|
|
{
|
|
zoitechat_command(ph, "help lua");
|
|
}
|
|
return HEXCHAT_EAT_ALL;
|
|
}
|
|
|
|
/* Reinitialization safegaurd */
|
|
static int initialized = 0;
|
|
|
|
G_MODULE_EXPORT int zoitechat_plugin_init(zoitechat_plugin *plugin_handle, char **name, char **description, char **version, char *arg)
|
|
{
|
|
if(initialized != 0)
|
|
{
|
|
zoitechat_print(plugin_handle, "Lua interface already loaded\n");
|
|
return 0;
|
|
}
|
|
|
|
if (g_str_has_prefix(LUA_VERSION, "Lua "))
|
|
{
|
|
strcat(plugin_version, "/");
|
|
g_strlcat(plugin_version, LUA_VERSION + 4, sizeof(plugin_version));
|
|
}
|
|
|
|
*name = plugin_name;
|
|
*description = plugin_description;
|
|
*version = plugin_version;
|
|
|
|
ph = plugin_handle;
|
|
initialized = 1;
|
|
|
|
zoitechat_hook_command(ph, "", HEXCHAT_PRI_NORM, command_console_exec, NULL, NULL);
|
|
zoitechat_hook_command(ph, "LOAD", HEXCHAT_PRI_NORM, command_load, NULL, NULL);
|
|
zoitechat_hook_command(ph, "UNLOAD", HEXCHAT_PRI_NORM, command_unload, NULL, NULL);
|
|
zoitechat_hook_command(ph, "RELOAD", HEXCHAT_PRI_NORM, command_reload, NULL, NULL);
|
|
zoitechat_hook_command(ph, "lua", HEXCHAT_PRI_NORM, command_lua, command_help, NULL);
|
|
|
|
zoitechat_printf(ph, "%s version %s loaded.\n", plugin_name, plugin_version);
|
|
|
|
scripts = g_ptr_array_new_with_free_func((GDestroyNotify)destroy_script);
|
|
create_interpreter();
|
|
|
|
if(!arg)
|
|
autoload_scripts();
|
|
return 1;
|
|
}
|
|
|
|
G_MODULE_EXPORT int zoitechat_plugin_deinit(zoitechat_plugin *plugin_handle)
|
|
{
|
|
guint i;
|
|
gboolean active = FALSE;
|
|
for(i = 0; i < scripts->len; i++)
|
|
{
|
|
if(((script_info*)scripts->pdata[i])->status & STATUS_ACTIVE)
|
|
{
|
|
active = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
if(interp && interp->status & STATUS_ACTIVE)
|
|
active = TRUE;
|
|
if(active)
|
|
{
|
|
zoitechat_print(ph, "\00304Cannot unload the lua plugin while there are active states");
|
|
return 0;
|
|
}
|
|
if(interp)
|
|
run_unload_hooks(interp, NULL);
|
|
destroy_interpreter();
|
|
g_ptr_array_foreach(scripts, (GFunc)run_unload_hooks, NULL);
|
|
g_clear_pointer(&scripts, g_ptr_array_unref);
|
|
g_clear_pointer(&expand_buffer, g_free);
|
|
return 1;
|
|
}
|