= WeeChat scripting guide :author: Sébastien Helleu :email: flashcode@flashtux.org :lang: en :toc: left :toclevels: 3 :sectnums: :docinfo1: This manual documents WeeChat chat client, it is part of WeeChat. Latest version of this document can be found on this page: https://weechat.org/doc [[introduction]] == Introduction WeeChat (Wee Enhanced Environment for Chat) is a free chat client, fast and light, designed for many operating systems. This manual documents way to write scripts for WeeChat, using one of supported script languages: * Python * Perl * Ruby * Lua * Tcl * Guile (Scheme) * Javascript * PHP [NOTE] Almost all examples in this doc are written in Python, but API is the same for other languages. [[scripts_in_weechat]] == Scripts in WeeChat [[languages_specificities]] === Languages specificities ==== Python * You have to `import weechat`. * Functions `+print*+` are called `+prnt*+` in python (because _print_ is reserved keyword). * Functions are called with `weechat.xxx(arg1, arg2, ...)`. ==== Perl * Functions are called with `weechat::xxx(arg1, arg2, ...);`. ==== Ruby * You have to define _weechat_init_ and call _register_ inside. * Functions are called with `Weechat.xxx(arg1, arg2, ...)`. * Due to a limitation of Ruby (15 arguments max by function), the function `Weechat.config_new_option` receives the callbacks in an array of 6 strings (3 callbacks + 3 data strings), so a call to this function looks like: [source,ruby] ---- Weechat.config_new_option(config, section, "name", "string", "description of option", "", 0, 0, "value", "value", 0, ["check_cb", "", "change_cb", "", "delete_cb", ""]) ---- ==== Lua * Functions are called with `weechat.xxx(arg1, arg2, ...)`. ==== Tcl * Functions are called with `weechat::xxx arg1 arg2 ...`. ==== Guile (Scheme) * Functions are called with `(weechat:xxx arg1 arg2 ...)`. * Following functions take one list of arguments (instead of many arguments for other functions), because number of arguments exceed number of allowed arguments in Guile: ** config_new_section ** config_new_option ** bar_new ==== Javascript * Functions are called with `weechat.xxx(arg1, arg2, ...);`. ==== PHP * Functions are called with `weechat_xxx(arg1, arg2, ...);`. [[register_function]] === Register function All WeeChat scripts must "register" themselves to WeeChat, and this must be first WeeChat function called in script. Prototype: [source,python] ---- weechat.register(name, author, version, license, description, shutdown_function, charset) ---- Arguments: * _name_: string, internal name of script * _author_: string, author name * _version_: string, script version * _license_: string, script license * _description_: string, short description of script * _shutdown_function_: string, name of function called when script is unloaded (can be empty string) * _charset_: string, script charset (if your script is UTF-8, you can use blank value here, because UTF-8 is default charset) Example of script, for each language: * Python: [source,python] ---- import weechat weechat.register("test_python", "FlashCode", "1.0", "GPL3", "Test script", "", "") weechat.prnt("", "Hello, from python script!") ---- * Perl: [source,perl] ---- weechat::register("test_perl", "FlashCode", "1.0", "GPL3", "Test script", "", ""); weechat::print("", "Hello, from perl script!"); ---- * Ruby: [source,ruby] ---- def weechat_init Weechat.register("test_ruby", "FlashCode", "1.0", "GPL3", "Test script", "", "") Weechat.print("", "Hello, from ruby script!") return Weechat::WEECHAT_RC_OK end ---- * Lua: [source,lua] ---- weechat.register("test_lua", "FlashCode", "1.0", "GPL3", "Test script", "", "") weechat.print("", "Hello, from lua script!") ---- * Tcl: [source,tcl] ---- weechat::register "test_tcl" "FlashCode" "1.0" "GPL3" "Test script" "" "" weechat::print "" "Hello, from tcl script!" ---- * Guile (Scheme): [source,lisp] ---- (weechat:register "test_scheme" "FlashCode" "1.0" "GPL3" "Test script" "" "") (weechat:print "" "Hello, from scheme script!") ---- * Javascript: [source,javascript] ---- weechat.register("test_js", "FlashCode", "1.0", "GPL3", "Test script", "", ""); weechat.print("", "Hello, from javascript script!"); ---- * PHP: [source,php] ---- >). Examples: [source,python] ---- # display "hello" on core buffer weechat.prnt("", "hello") # display "hello" on core buffer, but do not write it to log file # (version >= 0.3.3 only) weechat.prnt_date_tags("", 0, "no_log", "hello") # display prefix "==>" and message "hello" on current buffer # (prefix and message must be separated by tab) weechat.prnt(weechat.current_buffer(), "==>\thello") # display error message on core buffer (with error prefix) weechat.prnt("", "%swrong arguments" % weechat.prefix("error")) # display message with color on core buffer weechat.prnt("", "text %syellow on blue" % weechat.color("yellow,blue")) # search buffer and display message # (full name of buffer is plugin.name, for example: "irc.freenode.#weechat") buffer = weechat.buffer_search("irc", "freenode.#weechat") weechat.prnt(buffer, "message on #weechat channel") # other solution to find an IRC buffer (better) # (note that server and channel are separated by a comma) buffer = weechat.info_get("irc_buffer", "freenode,#weechat") weechat.prnt(buffer, "message on #weechat channel") ---- [NOTE] Print function is called `print` in Perl/Ruby/Lua/Tcl/Guile/Javascript and `prnt` in Python. [[buffers_send_text]] ==== Send text to buffer You can send text or command to a buffer. This is exactly like if you type text on command line and press [Enter]. Examples: [source,python] ---- # execute command "/help" on current buffer (result is on core buffer) weechat.command("", "/help") # send "hello" to #weechat IRC channel (users on channel will see message) buffer = weechat.info_get("irc_buffer", "freenode,#weechat") weechat.command(buffer, "hello") ---- [[buffers_new]] ==== Create new buffer You can create a new buffer in your script, then use it for displaying messages. Two callbacks can be called (they are optional): one for input data (when you type some text and press [Enter] on buffer), the other is called when buffer is closed (for example by `/buffer close`). Example: [source,python] ---- # callback for data received in input def buffer_input_cb(data, buffer, input_data): # ... return weechat.WEECHAT_RC_OK # callback called when buffer is closed def buffer_close_cb(data, buffer): # ... return weechat.WEECHAT_RC_OK # create buffer buffer = weechat.buffer_new("mybuffer", "buffer_input_cb", "", "buffer_close_cb", "") # set title weechat.buffer_set(buffer, "title", "This is title for my buffer.") # disable logging, by setting local variable "no_log" to "1" weechat.buffer_set(buffer, "localvar_set_no_log", "1") ---- [[buffers_properties]] ==== Buffer properties You can read buffer properties, as string, integer or pointer. Examples: [source,python] ---- buffer = weechat.current_buffer() number = weechat.buffer_get_integer(buffer, "number") name = weechat.buffer_get_string(buffer, "name") short_name = weechat.buffer_get_string(buffer, "short_name") ---- It is possible to add, read or delete local variables in buffer: [source,python] ---- # add local variable weechat.buffer_set(buffer, "localvar_set_myvar", "my_value") # read local variable myvar = weechat.buffer_get_string(buffer, "localvar_myvar") # delete local variable weechat.buffer_set(buffer, "localvar_del_myvar", "") ---- To see local variables of a buffer, do this command in WeeChat: ---- /buffer localvar ---- [[hooks]] === Hooks [[hook_command]] ==== Add new command Add a custom command with `hook_command`. You can use a custom completion template to complete arguments of your command. Example: [source,python] ---- def my_command_cb(data, buffer, args): # ... return weechat.WEECHAT_RC_OK hook = weechat.hook_command("myfilter", "description of myfilter", "[list] | [enable|disable|toggle [name]] | [add name plugin.buffer tags regex] | [del name|-all]", "description of arguments...", "list" " || enable %(filters_names)" " || disable %(filters_names)" " || toggle %(filters_names)" " || add %(filters_names) %(buffers_plugins_names)|*" " || del %(filters_names)|-all", "my_command_cb", "") ---- And then in WeeChat: ---- /help myfilter /myfilter arguments... ---- [[hook_timer]] ==== Add a timer Add a timer with `hook_timer`. Example: [source,python] ---- def timer_cb(data, remaining_calls): # ... return weechat.WEECHAT_RC_OK # timer called each minute when second is 00 weechat.hook_timer(60 * 1000, 60, 0, "timer_cb", "") ---- [[hook_process]] ==== Run a background process You can run a background process with `hook_process`. Your callback will be called when data is ready. It may be called many times. For the last call to your callback, _rc_ is set to 0 or positive value, it's return code of command. Example: [source,python] ---- process_output = "" def my_process_cb(data, command, rc, out, err): global process_output if out != "": process_output += out if int(rc) >= 0: weechat.prnt("", process_output) return weechat.WEECHAT_RC_OK weechat.hook_process("/bin/ls -l /etc", 10 * 1000, "my_process_cb", "") ---- [[url_transfer]] ==== URL transfer _New in version 0.3.7._ To download URL (or post to URL), you have to use function `hook_process`, or `hook_process_hashtable` if you need to set options for URL transfer. Example of URL transfer without option: the HTML page will be received as "out" in callback (standard output of process): [source,python] ---- # Display current stable version of WeeChat. weechat_version = "" def weechat_process_cb(data, command, rc, out, err): global weechat_version if out != "": weechat_version += out if int(rc) >= 0: weechat.prnt("", "Current WeeChat stable is: %s" % weechat_version) return weechat.WEECHAT_RC_OK weechat.hook_process("url:https://weechat.org/dev/info/stable/", 30 * 1000, "weechat_process_cb", "") ---- [TIP] All infos available about WeeChat are on page https://weechat.org/dev/info Example of URL transfer with an option: download latest WeeChat development package in file _/tmp/weechat-devel.tar.gz_: [source,python] ---- def my_process_cb(data, command, rc, out, err): if int(rc) >= 0: weechat.prnt("", "End of transfer (rc=%s)" % rc) return weechat.WEECHAT_RC_OK weechat.hook_process_hashtable("url:https://weechat.org/files/src/weechat-devel.tar.gz", {"file_out": "/tmp/weechat-devel.tar.gz"}, 30 * 1000, "my_process_cb", "") ---- For more information about URL transfer and available options, see functions `hook_process` and `hook_process_hashtable` in link:weechat_plugin_api.en.html#_hook_process[WeeChat plugin API reference]. [[config_options]] === Config / options [[config_options_set_script]] ==== Set options for script Function `config_is_set_plugin` is used to check if an option is set or not, and `config_set_plugin` to set option. Example: [source,python] ---- script_options = { "option1" : "value1", "option2" : "value2", "option3" : "value3", } for option, default_value in script_options.items(): if not weechat.config_is_set_plugin(option): weechat.config_set_plugin(option, default_value) ---- [[config_options_detect_changes]] ==== Detect changes You must use `hook_config` to be notified if user changes some script options. Example: [source,python] ---- SCRIPT_NAME = "myscript" # ... def config_cb(data, option, value): """Callback called when a script option is changed.""" # for example, read all script options to script variables... # ... return weechat.WEECHAT_RC_OK # ... weechat.hook_config("plugins.var.python." + SCRIPT_NAME + ".*", "config_cb", "") # for other languages, change "python" with your language (perl/ruby/lua/tcl/guile/javascript) ---- [[config_options_weechat]] ==== Read WeeChat options Function `config_get` returns pointer to option. Then, depending on option type, you must call `config_string`, `config_boolean`, `config_integer` or `config_color`. [source,python] ---- # string weechat.prnt("", "value of option weechat.look.item_time_format is: %s" % (weechat.config_string(weechat.config_get("weechat.look.item_time_format")))) # boolean weechat.prnt("", "value of option weechat.look.day_change is: %d" % (weechat.config_boolean(weechat.config_get("weechat.look.day_change")))) # integer weechat.prnt("", "value of option weechat.look.scroll_page_percent is: %d" % (weechat.config_integer(weechat.config_get("weechat.look.scroll_page_percent")))) # color weechat.prnt("", "value of option weechat.color.chat_delimiters is: %s" % (weechat.config_color(weechat.config_get("weechat.color.chat_delimiters")))) ---- [[irc]] === IRC [[irc_catch_messages]] ==== Catch messages IRC plugin sends two signals for a message received (`xxx` is IRC internal server name, `yyy` is IRC command name like JOIN, QUIT, PRIVMSG, 301, ..): xxxx,irc_in_yyy:: signal sent before processing message xxx,irc_in2_yyy:: signal sent after processing message [source,python] ---- def join_cb(data, signal, signal_data): # signal is for example: "freenode,irc_in2_join" # signal_data is IRC message, for example: ":nick!user@host JOIN :#channel" server = signal.split(",")[0] msg = weechat.info_get_hashtable("irc_message_parse", {"message": signal_data}) buffer = weechat.info_get("irc_buffer", "%s,%s" % (server, msg["channel"])) if buffer: weechat.prnt(buffer, "%s (%s) has joined this channel!" % (msg["nick"], msg["host"])) return weechat.WEECHAT_RC_OK # it is useful here to use "*" as server, to catch JOIN messages on all IRC # servers weechat.hook_signal("*,irc_in2_join", "join_cb", "") ---- [[irc_modify_messages]] ==== Modify messages IRC plugin sends a "modifier" called "irc_in_xxx" ("xxx" is IRC command) for a message received, so that you can modify it. [source,python] ---- def modifier_cb(data, modifier, modifier_data, string): # add server name to all messages received # (OK that's not very useful, but that's just an example!) return "%s %s" % (string, modifier_data) weechat.hook_modifier("irc_in_privmsg", "modifier_cb", "") ---- [WARNING] A malformed message could crash WeeChat or cause severe problems! [[irc_message_parse]] ==== Parse message _New in version 0.3.4._ You can parse an IRC message with info_hashtable called "irc_message_parse". The result is a hashtable with following keys (the example values are built with this message: `@time=2015-06-27T16:40:35.000Z :nick!user@host PRIVMSG #weechat :hello!`): [width="100%",cols="1,^2,10,8",options="header"] |=== | Key | WeeChat version | Description | Example | tags | ≥ 0.4.0 | The tags in message (can be empty). | `time=2015-06-27T16:40:35.000Z` | message_without_tags | ≥ 0.4.0 | The message without the tags (the same as message if there are no tags). | `:nick!user@host PRIVMSG #weechat :hello!` | nick | ≥ 0.3.4 | The origin nick. | `nick` | host | ≥ 0.3.4 | The origin host (includes the nick). | `nick!user@host` | command | ≥ 0.3.4 | The command (_PRIVMSG_, _NOTICE_, ...). | `PRIVMSG` | channel | ≥ 0.3.4 | The target channel. | `#weechat` | arguments | ≥ 0.3.4 | The command arguments (includes the channel). | `#weechat :hello!` | text | ≥ 1.3 | The text (for example user message). | `hello!` | pos_command | ≥ 1.3 | The index of _command_ in message ("-1" if _command_ was not found). | `47` | pos_arguments | ≥ 1.3 | The index of _arguments_ in message ("-1" if _arguments_ was not found). | `55` | pos_channel | ≥ 1.3 | The index of _channel_ in message ("-1" if _channel_ was not found). | `55` | pos_text | ≥ 1.3 | The index of _text_ in message ("-1" if _text_ was not found). | `65` |=== [source,python] ---- dict = weechat.info_get_hashtable( "irc_message_parse", {"message": "@time=2015-06-27T16:40:35.000Z :nick!user@host PRIVMSG #weechat :hello!"}) # dict == { # "tags": "time=2015-06-27T16:40:35.000Z", # "message_without_tags": ":nick!user@host PRIVMSG #weechat :hello!", # "nick": "nick", # "host": "nick!user@host", # "command": "PRIVMSG", # "channel": "#weechat", # "arguments": "#weechat :hello!", # "text": "hello!", # "pos_command": "47", # "pos_arguments": "55", # "pos_channel": "55", # "pos_text": "65", # } ---- [[infos]] === Infos [[infos_weechat_version]] ==== WeeChat version The best way to check version is to ask "version_number" and make integer comparison with hexadecimal version number. Example: [source,python] ---- version = weechat.info_get("version_number", "") or 0 if int(version) >= 0x00030200: weechat.prnt("", "This is WeeChat 0.3.2 or newer") else: weechat.prnt("", "This is WeeChat 0.3.1 or older") ---- [NOTE] Versions ≤ 0.3.1.1 return empty string for _info_get("version_number")_ so you must check that value returned is *not* empty. To get version as string: [source,python] ---- # this will display for example "Version 0.3.2" weechat.prnt("", "Version %s" % weechat.info_get("version", "")) ---- [[infos_other]] ==== Other infos [source,python] ---- # WeeChat home directory, for example: "/home/xxxx/.weechat" weechat.prnt("", "WeeChat home dir: %s" % weechat.info_get("weechat_dir", "")) # keyboard inactivity weechat.prnt("", "Inactivity since %s seconds" % weechat.info_get("inactivity", "")) ---- [[infolists]] === Infolists [[infolists_read]] ==== Read an infolist You can read infolist built by WeeChat or other plugins. Example: [source,python] ---- # read infolist "buffer", to get list of buffers infolist = weechat.infolist_get("buffer", "", "") if infolist: while weechat.infolist_next(infolist): name = weechat.infolist_string(infolist, "name") weechat.prnt("", "buffer: %s" % name) weechat.infolist_free(infolist) ---- [IMPORTANT] Don't forget to call `infolist_free` to free memory used by infolist, because WeeChat will not automatically free memory.