more work on lua module, now able to load/unload scripts
This commit is contained in:
parent
5626a7844b
commit
b1a4865852
@ -4,10 +4,12 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
int block = 0;
|
||||||
|
|
||||||
void lua_init_events()
|
void lua_init_events()
|
||||||
{
|
{
|
||||||
lua_register(lua.L, "add_handler", lua_add_handler);
|
lua_register(lua.L, "_add_handler", lua_add_handler);
|
||||||
lua_register(lua.L, "del_handler", lua_del_handler);
|
lua_register(lua.L, "_del_handler", lua_del_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
int lua_add_handler(lua_State *L)
|
int lua_add_handler(lua_State *L)
|
||||||
@ -15,6 +17,12 @@ int lua_add_handler(lua_State *L)
|
|||||||
char *event;
|
char *event;
|
||||||
int lreg;
|
int lreg;
|
||||||
|
|
||||||
|
if (block)
|
||||||
|
{
|
||||||
|
// return error
|
||||||
|
return !LUA_OK;
|
||||||
|
}
|
||||||
|
|
||||||
if (lua_gettop(L) < 2)
|
if (lua_gettop(L) < 2)
|
||||||
{
|
{
|
||||||
printf("Error: add_handler requires 2 arguments\n");
|
printf("Error: add_handler requires 2 arguments\n");
|
||||||
@ -118,6 +126,8 @@ void lua_fire_handlers(char *event, ...)
|
|||||||
int i;
|
int i;
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
|
printf("lua_fire_handlers: %s\n", event);
|
||||||
|
|
||||||
char *user, *host, *chan, *text;
|
char *user, *host, *chan, *text;
|
||||||
|
|
||||||
for (i = 0; i < lua.event_count; i++)
|
for (i = 0; i < lua.event_count; i++)
|
||||||
@ -137,6 +147,7 @@ void lua_fire_handlers(char *event, ...)
|
|||||||
text = va_arg(args, char *);
|
text = va_arg(args, char *);
|
||||||
|
|
||||||
printf("dbug: %s %s %s %s\n", user, host, chan, text);
|
printf("dbug: %s %s %s %s\n", user, host, chan, text);
|
||||||
|
printf("dbug: %s %d %d\n", lua.events[i].event, lua.events[i].lreg, lua.event_count);
|
||||||
|
|
||||||
lua_callfunc(lua.events[i].lreg, 4, user, host, chan, text);
|
lua_callfunc(lua.events[i].lreg, 4, user, host, chan, text);
|
||||||
}
|
}
|
||||||
|
41
mods/lua/init.lua
Executable file
41
mods/lua/init.lua
Executable file
@ -0,0 +1,41 @@
|
|||||||
|
-- init.lua: xbot runtime script
|
||||||
|
-- Do not modify this file unless you know what you are doing.
|
||||||
|
--
|
||||||
|
-- Written by Aaron Blakely
|
||||||
|
|
||||||
|
local handlerstore = {}
|
||||||
|
|
||||||
|
function load() end
|
||||||
|
function unload() end
|
||||||
|
|
||||||
|
function add_handler(type, func)
|
||||||
|
local ret = _add_handler(type, func)
|
||||||
|
|
||||||
|
local callfilename = debug.getinfo(2, "S").source
|
||||||
|
|
||||||
|
if ret then
|
||||||
|
table.insert(handlerstore, {type, func, ret, callfilename})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function del_handler(type, func)
|
||||||
|
local callfilename = debug.getinfo(2, "S").source
|
||||||
|
|
||||||
|
-- loop through the handlerstore and remove the handler
|
||||||
|
for i, v in ipairs(handlerstore) do
|
||||||
|
|
||||||
|
print("called with type: " .. type .. "callfilename: " .. callfilename)
|
||||||
|
print("dbug: type: " .. v[1] .. " func: " .. v[3] .. " callfilename: " .. v[4])
|
||||||
|
if v[2] == func then
|
||||||
|
print("dbug: ref found")
|
||||||
|
end
|
||||||
|
|
||||||
|
if v[1] == type and v[2] == func and v[4] == callfilename then
|
||||||
|
_del_handler(type, v[3])
|
||||||
|
table.remove(handlerstore, i)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
print("Handler not found")
|
||||||
|
end
|
217
mods/lua/lua.c
217
mods/lua/lua.c
@ -15,21 +15,134 @@
|
|||||||
struct lua_interp lua;
|
struct lua_interp lua;
|
||||||
struct irc_conn *instance;
|
struct irc_conn *instance;
|
||||||
|
|
||||||
|
char *scriptsfile = "./mods/lua/scripts";
|
||||||
|
|
||||||
|
int append_script(char *fname)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char *buf = (char *)malloc(sizeof(char *) * 500);
|
||||||
|
|
||||||
|
// check if the file is already in the list
|
||||||
|
struct script_list list = get_scripts();
|
||||||
|
|
||||||
|
for (int i = 0; i < list.count; i++)
|
||||||
|
{
|
||||||
|
if (strcmp(list.scripts[i], fname) == 0)
|
||||||
|
{
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((fp = fopen(scriptsfile, "a")) == NULL)
|
||||||
|
{
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sprintf(buf, "%s\n", fname);
|
||||||
|
fputs(buf, fp);
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remove_script(char *fname)
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char *buf = (char *)malloc(sizeof(char *) * 500);
|
||||||
|
char *tmpfile = "./mods/lua/scripts.tmp";
|
||||||
|
|
||||||
|
if ((fp = fopen(scriptsfile, "r")) == NULL)
|
||||||
|
{
|
||||||
|
free(buf);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
FILE *tmp = fopen(tmpfile, "w");
|
||||||
|
|
||||||
|
while (fgets(buf, 500, fp) != NULL)
|
||||||
|
{
|
||||||
|
if (strcmp(buf, fname) != 0)
|
||||||
|
{
|
||||||
|
fputs(buf, tmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(fp);
|
||||||
|
fclose(tmp);
|
||||||
|
|
||||||
|
remove(scriptsfile);
|
||||||
|
rename(tmpfile, scriptsfile);
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct script_list get_scripts()
|
||||||
|
{
|
||||||
|
FILE *fp;
|
||||||
|
char *buf = (char *)malloc(sizeof(char *) * 500);
|
||||||
|
struct script_list list;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if ((fp = fopen(scriptsfile, "r")) == NULL)
|
||||||
|
{
|
||||||
|
free(buf);
|
||||||
|
|
||||||
|
list.count = 0;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (fgets(buf, 500, fp) != NULL)
|
||||||
|
{
|
||||||
|
list.scripts[i] = (char *)malloc(sizeof(char *) * 150);
|
||||||
|
strlcpy(list.scripts[i], buf, 150);
|
||||||
|
|
||||||
|
// remove newline
|
||||||
|
list.scripts[i][strlen(list.scripts[i]) - 1] = '\0';
|
||||||
|
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.count = i;
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
MY_API void lua_eval(struct irc_conn *bot, char *user, char *host, char *chan, const char *text)
|
MY_API void lua_eval(struct irc_conn *bot, char *user, char *host, char *chan, const char *text)
|
||||||
{
|
{
|
||||||
|
int res;
|
||||||
|
|
||||||
|
block = 1;
|
||||||
printf("lua eval called with %s\n", text);
|
printf("lua eval called with %s\n", text);
|
||||||
if (strstr(text, "!lua") != NULL)
|
if (strstr(text, "!lua") != NULL)
|
||||||
{
|
{
|
||||||
// skip the command
|
|
||||||
text = skip(text, ' ');
|
text = skip(text, ' ');
|
||||||
printf("lua: %s\n", text);
|
printf("lua: %s\n", text);
|
||||||
|
|
||||||
if (luaL_dostring(lua.L, text))
|
res = luaL_loadstring(lua.L, text);
|
||||||
|
|
||||||
|
if (res == LUA_OK)
|
||||||
|
{
|
||||||
|
res = lua_pcall(lua.L, 0, 0, 0);
|
||||||
|
if (res != LUA_OK)
|
||||||
|
{
|
||||||
|
irc_privmsg(bot, chan, "Error: %s", lua_tostring(lua.L, -1));
|
||||||
|
lua_pop(lua.L, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
irc_privmsg(bot, chan, "Error: %s", lua_tostring(lua.L, -1));
|
irc_privmsg(bot, chan, "Error: %s", lua_tostring(lua.L, -1));
|
||||||
|
lua_pop(lua.L, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
block = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_API void lua_load_script(struct irc_conn *bot, char *user, char *host, char *chan, const char *text)
|
MY_API void lua_load_script(struct irc_conn *bot, char *user, char *host, char *chan, const char *text)
|
||||||
@ -47,17 +160,34 @@ MY_API void lua_load_script(struct irc_conn *bot, char *user, char *host, char *
|
|||||||
|
|
||||||
if (luaL_loadfile(lua.L, buf) != LUA_OK)
|
if (luaL_loadfile(lua.L, buf) != LUA_OK)
|
||||||
{
|
{
|
||||||
irc_privmsg(bot, chan, "Error loading lua script: %s", lua_tostring(lua.L, -1));
|
if (!strcmp(chan, "-stdio-"))
|
||||||
|
{
|
||||||
|
printf("Error loading lua script: %s\n", lua_tostring(lua.L, -1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
irc_privmsg(bot, chan, "Error loading lua script: %s", lua_tostring(lua.L, -1));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
name = basename(buf);
|
|
||||||
|
|
||||||
|
name = basename(buf);
|
||||||
|
append_script(text);
|
||||||
|
|
||||||
// execute the script
|
// execute the script
|
||||||
if (lua_pcall(lua.L, 0, 0, 0) != LUA_OK)
|
if (lua_pcall(lua.L, 0, 0, 0) != LUA_OK)
|
||||||
{
|
{
|
||||||
irc_privmsg(bot, chan, "Error executing lua script: %s", lua_tostring(lua.L, -1));
|
if (!strcmp(chan, "-stdio-"))
|
||||||
|
{
|
||||||
|
printf("Error executing lua script: %s\n", lua_tostring(lua.L, -1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
irc_privmsg(bot, chan, "Error executing lua script: %s", lua_tostring(lua.L, -1));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +202,15 @@ MY_API void lua_load_script(struct irc_conn *bot, char *user, char *host, char *
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
lua.scripts[lua.script_count].unload = -1;
|
lua.scripts[lua.script_count].unload = -1;
|
||||||
irc_privmsg(bot, chan, "No unload() function in %s", name);
|
|
||||||
|
if (!strcmp(chan, "-stdio-"))
|
||||||
|
{
|
||||||
|
printf("No unload() function in %s\n", name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
irc_privmsg(bot, chan, "No unload() function in %s", name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// call the load function if it exists
|
// call the load function if it exists
|
||||||
@ -82,18 +220,39 @@ MY_API void lua_load_script(struct irc_conn *bot, char *user, char *host, char *
|
|||||||
{
|
{
|
||||||
if (lua_pcall(lua.L, 0, 0, 0) != LUA_OK)
|
if (lua_pcall(lua.L, 0, 0, 0) != LUA_OK)
|
||||||
{
|
{
|
||||||
irc_privmsg(bot, chan, "Error calling load() in %s: %s", buf, lua_tostring(lua.L, -1));
|
if (!strcmp(chan, "-stdio-"))
|
||||||
|
{
|
||||||
|
printf("Error calling load() in %s: %s\n", buf, lua_tostring(lua.L, -1));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
irc_privmsg(bot, chan, "Error calling load() in %s: %s", buf, lua_tostring(lua.L, -1));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sprintf(buf, "Loaded %s", name);
|
|
||||||
lua.script_count++;
|
lua.script_count++;
|
||||||
|
|
||||||
irc_privmsg(bot, chan, buf);
|
if (!strcmp(chan, "-stdio-"))
|
||||||
|
{
|
||||||
|
printf("Loaded %s\n", name);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
irc_privmsg(bot, chan, "Loaded %s", name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
irc_privmsg(bot, chan, "Error: No load() function in %s", buf);
|
if (!strcmp(chan, "-stdio-"))
|
||||||
|
{
|
||||||
|
printf("Error: No load() function in %s\n", buf);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
irc_privmsg(bot, chan, "Error: No load() function in %s", buf);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,9 +287,17 @@ MY_API void lua_unload_script(struct irc_conn *bot, char *user, char *host, char
|
|||||||
luaL_unref(lua.L, LUA_REGISTRYINDEX, lua.scripts[i].unload);
|
luaL_unref(lua.L, LUA_REGISTRYINDEX, lua.scripts[i].unload);
|
||||||
lua.scripts[i].unload = -1;
|
lua.scripts[i].unload = -1;
|
||||||
|
|
||||||
|
remove_script(text);
|
||||||
|
|
||||||
sprintf(buf, "Unloaded %s", text);
|
sprintf(buf, "Unloaded %s", text);
|
||||||
irc_privmsg(bot, chan, buf);
|
irc_privmsg(bot, chan, buf);
|
||||||
|
|
||||||
|
while (i < lua.script_count)
|
||||||
|
{
|
||||||
|
lua.scripts[i] = lua.scripts[i + 1];
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
lua.script_count--;
|
lua.script_count--;
|
||||||
|
|
||||||
free(buf);
|
free(buf);
|
||||||
@ -153,6 +320,8 @@ void lua_setvar(char *name, char *value)
|
|||||||
|
|
||||||
MY_API void mod_init()
|
MY_API void mod_init()
|
||||||
{
|
{
|
||||||
|
char *buf = (char *)malloc(sizeof(char *) * 500);
|
||||||
|
struct script_list list = get_scripts();
|
||||||
instance = get_bot();
|
instance = get_bot();
|
||||||
|
|
||||||
lua.scripts = calloc(512, sizeof(struct lua_script));
|
lua.scripts = calloc(512, sizeof(struct lua_script));
|
||||||
@ -166,16 +335,42 @@ MY_API void mod_init()
|
|||||||
lua_init_wrappers();
|
lua_init_wrappers();
|
||||||
lua_init_events();
|
lua_init_events();
|
||||||
|
|
||||||
register_module("lua", "Aaron Blakely", "v0.1", "Lua module");
|
|
||||||
lua_init_handlers();
|
lua_init_handlers();
|
||||||
|
register_module("lua", "Aaron Blakely", "v0.1", "Lua module");
|
||||||
|
|
||||||
|
// load init.lua
|
||||||
|
if (luaL_loadfile(lua.L, "./mods/lua/init.lua") != LUA_OK)
|
||||||
|
{
|
||||||
|
printf("Error loading init.lua: %s\n", lua_tostring(lua.L, -1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_pcall(lua.L, 0, 0, 0) != LUA_OK)
|
||||||
|
{
|
||||||
|
printf("Error executing init.lua: %s\n", lua_tostring(lua.L, -1));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < list.count; i++)
|
||||||
|
{
|
||||||
|
printf("Loading %s\n", list.scripts[i]);
|
||||||
|
|
||||||
|
sprintf(buf, "!load %s", list.scripts[i]);
|
||||||
|
|
||||||
|
lua_load_script(instance, "lua", "localhost", "-stdio-", buf);
|
||||||
|
}
|
||||||
|
|
||||||
printf("Lua module loaded\n");
|
printf("Lua module loaded\n");
|
||||||
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
MY_API void mod_unload()
|
MY_API void mod_unload()
|
||||||
{
|
{
|
||||||
lua_close(lua.L);
|
lua_close(lua.L);
|
||||||
|
|
||||||
|
free(lua.scripts);
|
||||||
|
free(lua.events);
|
||||||
|
|
||||||
unregister_module("lua");
|
unregister_module("lua");
|
||||||
del_handler(PRIVMSG_CHAN, lua_eval);
|
del_handler(PRIVMSG_CHAN, lua_eval);
|
||||||
printf("Lua module unloaded\n");
|
printf("Lua module unloaded\n");
|
||||||
|
@ -34,6 +34,13 @@ struct lua_interp
|
|||||||
struct lua_event *events;
|
struct lua_event *events;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct script_list
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
char *scripts[512];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int block;
|
||||||
|
|
||||||
extern struct lua_interp lua;
|
extern struct lua_interp lua;
|
||||||
extern struct irc_conn *instance;
|
extern struct irc_conn *instance;
|
||||||
@ -70,6 +77,10 @@ MY_API void quit_handler(struct irc_conn *bot, char *user, char *host, const cha
|
|||||||
|
|
||||||
// lua.c
|
// lua.c
|
||||||
void lua_setvar(char *name, char *value);
|
void lua_setvar(char *name, char *value);
|
||||||
|
int append_script(char *fname);
|
||||||
|
int remove_script(char *fname);
|
||||||
|
struct script_list get_scripts();
|
||||||
|
|
||||||
MY_API void lua_eval(struct irc_conn *bot, char *user, char *host, char *chan, const char *text);
|
MY_API void lua_eval(struct irc_conn *bot, char *user, char *host, char *chan, const char *text);
|
||||||
MY_API void lua_load_script(struct irc_conn *bot, char *user, char *host, char *chan, const char *text);
|
MY_API void lua_load_script(struct irc_conn *bot, char *user, char *host, char *chan, const char *text);
|
||||||
MY_API void lua_unload_script(struct irc_conn *bot, char *user, char *host, char *chan, const char *text);
|
MY_API void lua_unload_script(struct irc_conn *bot, char *user, char *host, char *chan, const char *text);
|
||||||
|
2
mods/lua/scripts.tmp
Executable file
2
mods/lua/scripts.tmp
Executable file
@ -0,0 +1,2 @@
|
|||||||
|
test.lua
|
||||||
|
hello.lua
|
@ -3,20 +3,18 @@ VERSION = "v0.5"
|
|||||||
AUTHOR = "Aaron Blakely"
|
AUTHOR = "Aaron Blakely"
|
||||||
DESCRIPTION = "A simple hello world script for xbot"
|
DESCRIPTION = "A simple hello world script for xbot"
|
||||||
|
|
||||||
local handlers = {}
|
|
||||||
|
|
||||||
function hi(nick, host, chan, text)
|
function hi(nick, host, chan, text)
|
||||||
privmsg(chan, "Hello, " .. nick .. "!")
|
privmsg(chan, "Hello, " .. nick .. " from hello.lua!!!!!")
|
||||||
end
|
end
|
||||||
|
|
||||||
function load()
|
function load()
|
||||||
-- register_script(NAME, VERSION, AUTHOR, DESCRIPTION)
|
-- register_script(NAME, VERSION, AUTHOR, DESCRIPTION)
|
||||||
|
|
||||||
table.insert(handlers, {PRIVMSG_CHAN, add_handler(PRIVMSG_CHAN, hi)})
|
add_handler(PRIVMSG_CHAN, hi)
|
||||||
end
|
end
|
||||||
|
|
||||||
function unload()
|
function unload()
|
||||||
for i, v in ipairs(handlers) do
|
-- unregister_script(NAME)
|
||||||
del_handler(v[1], v[2])
|
|
||||||
end
|
del_handler(PRIVMSG_CHAN, hi)
|
||||||
end
|
end
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
NAME = "hello"
|
local NAME = "hello"
|
||||||
VERSION = "v0.5"
|
local VERSION = "v0.5"
|
||||||
AUTHOR = "Aaron Blakely"
|
local AUTHOR = "Aaron Blakely"
|
||||||
DESCRIPTION = "A simple hello world script for xbot"
|
local DESCRIPTION = "A simple hello world script for xbot"
|
||||||
|
|
||||||
local handlers = {}
|
function test(nick, host, chan, text)
|
||||||
|
-- check if text contains "hello"
|
||||||
|
|
||||||
function hi(nick, host, chan, text)
|
|
||||||
privmsg(chan, "Hello, " .. nick .. " from test.lua!")
|
if string.find(text, "hello") then
|
||||||
|
privmsg(chan, "Hello, " .. nick .. " from test.lua!!!")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function load()
|
function load()
|
||||||
-- register_script(NAME, VERSION, AUTHOR, DESCRIPTION)
|
-- register_script(NAME, VERSION, AUTHOR, DESCRIPTION)
|
||||||
|
|
||||||
table.insert(handlers, {PRIVMSG_CHAN, add_handler(PRIVMSG_CHAN, hi)})
|
add_handler(PRIVMSG_CHAN, test)
|
||||||
end
|
end
|
||||||
|
|
||||||
function unload()
|
function unload()
|
||||||
privmsg("#lobby", "Unloading test.lua")
|
-- unregister_script(NAME)
|
||||||
for i, v in ipairs(handlers) do
|
|
||||||
del_handler(v[1], v[2])
|
del_handler(PRIVMSG_CHAN, test)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user