Ejemplo n.º 1
0
def matrix_create_room_buffer(server, room_id):
    # type: (MatrixServer, str) -> None
    buf = W.buffer_new(room_id, "room_input_cb", server.name, "room_close_cb",
                       server.name)

    W.buffer_set(buf, "localvar_set_type", 'channel')
    W.buffer_set(buf, "type", 'formatted')

    W.buffer_set(buf, "localvar_set_channel", room_id)

    W.buffer_set(buf, "localvar_set_nick", server.user)

    W.buffer_set(buf, "localvar_set_server", server.name)

    short_name = strip_matrix_server(room_id)
    W.buffer_set(buf, "short_name", short_name)

    W.nicklist_add_group(buf, '', "000|o", "weechat.color.nicklist_group", 1)
    W.nicklist_add_group(buf, '', "001|h", "weechat.color.nicklist_group", 1)
    W.nicklist_add_group(buf, '', "002|v", "weechat.color.nicklist_group", 1)
    W.nicklist_add_group(buf, '', "999|...", "weechat.color.nicklist_group", 1)

    W.buffer_set(buf, "nicklist", "1")
    W.buffer_set(buf, "nicklist_display_groups", "0")

    # TODO make this configurable
    W.buffer_set(buf, "highlight_tags_restrict", "matrix_message")

    server.buffers[room_id] = buf
    server.rooms[room_id] = MatrixRoom(room_id)
Ejemplo n.º 2
0
def room_close_cb(data, buffer):
    W.prnt("",
           "Buffer '%s' will be closed!" % W.buffer_get_string(buffer, "name"))
    return W.WEECHAT_RC_OK
Ejemplo n.º 3
0
def hook_page_up():
    OPTIONS.page_up_hook = W.hook_command_run('/window page_up',
                                              'matrix_command_pgup_cb', '')
Ejemplo n.º 4
0
def matrix_debug_completion_cb(data, completion_item, buffer, completion):
    for debug_type in ["messaging", "network", "timing"]:
        W.hook_completion_list_add(completion, debug_type, 0,
                                   W.WEECHAT_LIST_POS_SORT)
    return W.WEECHAT_RC_OK
Ejemplo n.º 5
0
def receive_cb(server_name, file_descriptor):
    server = SERVERS[server_name]

    while True:
        try:
            data = server.socket.recv(4096)
        except ssl.SSLWantReadError:
            break
        except socket.error as error:
            errno = "error" + str(error.errno) + " " if error.errno else ""
            str_error = error.strerror if error.strerror else "Unknown error"
            str_error = errno + str_error

            message = ("{prefix}Error while reading from "
                       "socket: {error}").format(prefix=W.prefix("network"),
                                                 error=str_error)

            server_buffer_prnt(server, message)

            server_buffer_prnt(
                server,
                ("{prefix}matrix: disconnecting from server...").format(
                    prefix=W.prefix("network")))

            server.disconnect()

            return W.WEECHAT_RC_OK

        if not data:
            server_buffer_prnt(
                server,
                "{prefix}matrix: Error while reading from socket".format(
                    prefix=W.prefix("network")))
            server_buffer_prnt(
                server,
                ("{prefix}matrix: disconnecting from server...").format(
                    prefix=W.prefix("network")))

            server.disconnect()
            break

        try:
            server.client.receive(data)
        except (RemoteTransportError, RemoteProtocolError) as e:
            server.error(str(e))
            server.disconnect()
            break

        response = server.client.next_response()

        # Check if we need to send some data back
        data_to_send = server.client.data_to_send()

        if data_to_send:
            server.send(data_to_send)

        if response:
            server.handle_response(response)
            break

    return W.WEECHAT_RC_OK
Ejemplo n.º 6
0
 def add_user(completion, user):
     W.hook_completion_list_add(completion, user, 0,
                                W.WEECHAT_LIST_POS_SORT)
Ejemplo n.º 7
0
def init_completion():
    W.hook_completion(
        "matrix_server_commands",
        "Matrix server completion",
        "matrix_server_command_completion_cb",
        "",
    )

    W.hook_completion(
        "matrix_servers",
        "Matrix server completion",
        "matrix_server_completion_cb",
        "",
    )

    W.hook_completion(
        "matrix_commands",
        "Matrix command completion",
        "matrix_command_completion_cb",
        "",
    )

    W.hook_completion(
        "matrix_messages",
        "Matrix message completion",
        "matrix_message_completion_cb",
        "",
    )

    W.hook_completion(
        "matrix_debug_types",
        "Matrix debugging type completion",
        "matrix_debug_completion_cb",
        "",
    )

    W.hook_completion(
        "olm_user_ids",
        "Matrix olm user id completion",
        "matrix_olm_user_completion_cb",
        "",
    )

    W.hook_completion(
        "olm_devices",
        "Matrix olm device id completion",
        "matrix_olm_device_completion_cb",
        "",
    )

    W.hook_completion(
        "matrix_users",
        "Matrix user id completion",
        "matrix_user_completion_cb",
        "",
    )

    W.hook_completion(
        "matrix_own_devices",
        "Matrix own devices completion",
        "matrix_own_devices_completion_cb",
        "",
    )

    W.hook_completion(
        "matrix_rooms",
        "Matrix room name completion",
        "matrix_room_completion_cb",
        "",
    )
Ejemplo n.º 8
0
 def str_evaluate_getter(self):
     return W.string_eval_expression(
         W.config_string(self._option_ptrs[name]), {}, {}, {})
Ejemplo n.º 9
0
 def int_getter(self):
     if cast_func:
         return cast_func(W.config_integer(self._option_ptrs[name]))
     return W.config_integer(self._option_ptrs[name])
Ejemplo n.º 10
0
 def bool_getter(self):
     return bool(W.config_boolean(self._option_ptrs[name]))
Ejemplo n.º 11
0
 def str_getter(self):
     if cast_func:
         return cast_func(W.config_string(self._option_ptrs[name]))
     return W.config_string(self._option_ptrs[name])
Ejemplo n.º 12
0
 def free(self):
     W.config_section_free_options(self._ptr)
     W.config_section_free(self._ptr)
Ejemplo n.º 13
0
    def buffer_sort_messages(buff):
        lines = []

        own_lines = W.hdata_pointer(W.hdata_get('buffer'), buff, 'own_lines')

        if own_lines:
            hdata_line = W.hdata_get('line')
            hdata_line_data = W.hdata_get('line_data')
            line = W.hdata_pointer(
                W.hdata_get('lines'), own_lines, 'first_line')

            while line:
                data = W.hdata_pointer(hdata_line, line, 'data')

                line_data = {}

                if data:
                    date = W.hdata_time(hdata_line_data, data, 'date')
                    print_date = W.hdata_time(hdata_line_data, data,
                                              'date_printed')
                    tags = tags_from_line_data(data)
                    prefix = W.hdata_string(hdata_line_data, data, 'prefix')
                    message = W.hdata_string(hdata_line_data, data, 'message')
                    highlight = W.hdata_char(hdata_line_data, data, "highlight")

                    line_data = {
                        'date': date,
                        'date_printed': print_date,
                        'tags_array': ','.join(tags),
                        'prefix': prefix,
                        'message': message,
                        'highlight': highlight
                    }

                    lines.append(line_data)

                line = W.hdata_move(hdata_line, line, 1)

            sorted_lines = sorted(lines, key=itemgetter('date'))
            lines = []

            # We need to convert the dates to a string for hdata_update(), this
            # will reverse the list at the same time
            while sorted_lines:
                line = sorted_lines.pop()
                new_line = {k: str(v) for k, v in line.items()}
                lines.append(new_line)

            MatrixBacklogEvent.update_buffer_lines(lines, own_lines)
Ejemplo n.º 14
0
def init_bar_items():
    W.bar_item_new("(extra)buffer_plugin", "matrix_bar_item_plugin", "")
    W.bar_item_new("(extra)buffer_name", "matrix_bar_item_name", "")
    W.bar_item_new("(extra)lag", "matrix_bar_item_lag", "")
    W.bar_item_new("(extra)buffer_modes", "matrix_bar_item_buffer_modes", "")
Ejemplo n.º 15
0
def server_buffer_prnt(server, string):
    # type: (MatrixServer, str) -> None
    assert server.server_buffer
    buffer = server.server_buffer
    now = int(time.time())
    W.prnt_date_tags(buffer, now, "", string)
Ejemplo n.º 16
0
    def __init__(self):
        self.debug_buffer = ""
        self.upload_buffer = ""
        self.debug_category = "all"
        self.page_up_hook = None

        look_options = [
            Option(
                "redactions",
                "integer",
                "strikethrough|notice|delete",
                0,
                0,
                "strikethrough",
                ("Only notice redactions, strike through or delete "
                 "redacted messages"),
                RedactType,
            ),
            Option(
                "server_buffer",
                "integer",
                "merge_with_core|merge_without_core|independent",
                0,
                0,
                "merge_with_core",
                "Merge server buffers",
                ServerBufferType,
                config_server_buffer_cb,
            ),
            Option(
                "max_typing_notice_item_length",
                "integer",
                "",
                10,
                1000,
                "50",
                ("Limit the length of the typing notice bar item."),
            ),
            Option(
                "bar_item_typing_notice_prefix",
                "string",
                "",
                0,
                0,
                "Typing: ",
                ("Prefix for the typing notice bar item."),
            ),
            Option(
                "encryption_warning_sign",
                "string",
                "",
                0,
                0,
                "⚠️ ",
                ("A sign that is used to signal trust issues in encrypted "
                 "rooms (note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "busy_sign",
                "string",
                "",
                0,
                0,
                "⏳",
                ("A sign that is used to signal that the client is busy e.g. "
                 "when the room backlog is fetching"
                 " (note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "encrypted_room_sign",
                "string",
                "",
                0,
                0,
                "🔐",
                ("A sign that is used to show that the current room is "
                 "encrypted "
                 "(note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "disconnect_sign",
                "string",
                "",
                0,
                0,
                "❌",
                ("A sign that is used to show that the server is disconnected "
                 "(note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "pygments_style",
                "string",
                "",
                0,
                0,
                "native",
                "Pygments style to use for highlighting source code blocks",
            ),
            Option(
                "code_blocks",
                "boolean",
                "",
                0,
                0,
                "on",
                ("Display preformatted code blocks as rectangular areas by "
                 "padding them with whitespace up to the length of the longest"
                 " line (with optional margin)"),
            ),
            Option(
                "code_block_margin",
                "integer",
                "",
                0,
                100,
                "2",
                ("Number of spaces to add as a margin around around a code "
                 "block"),
            ),
        ]

        network_options = [
            Option(
                "max_initial_sync_events",
                "integer",
                "",
                1,
                10000,
                "30",
                ("How many events to fetch during the initial sync"),
            ),
            Option(
                "max_backlog_sync_events",
                "integer",
                "",
                1,
                100,
                "10",
                ("How many events to fetch during backlog fetching"),
            ),
            Option(
                "fetch_backlog_on_pgup",
                "boolean",
                "",
                0,
                0,
                "on",
                ("Fetch messages in the backlog on a window page up event"),
                None,
                config_pgup_cb,
            ),
            Option(
                "debug_level",
                "integer",
                "error|warn|info|debug",
                0,
                0,
                "error",
                "Enable network protocol debugging.",
                level_to_logbook,
                config_log_level_cb,
            ),
            Option(
                "debug_category",
                "integer",
                "all|http|client|events|responses|encryption",
                0,
                0,
                "all",
                "Debugging category",
                logbook_category,
                config_log_category_cb,
            ),
            Option(
                "debug_buffer",
                "boolean",
                "",
                0,
                0,
                "off",
                ("Use a separate buffer for debug logs."),
            ),
            Option(
                "lazy_load_room_users",
                "boolean",
                "",
                0,
                0,
                "off",
                ("If on, room users won't be loaded in the background "
                 "proactively, they will be loaded when the user switches to "
                 "the room buffer. This only affects non-encrypted rooms."),
            ),
            Option(
                "max_nicklist_users",
                "integer",
                "",
                100,
                20000,
                "5000",
                ("Limit the number of users that are added to the nicklist. "
                 "Active users and users with a higher power level are always."
                 " Inactive users will be removed from the nicklist after a "
                 "day of inactivity."),
            ),
            Option(
                "lag_reconnect",
                "integer",
                "",
                5,
                604800,
                "90",
                ("Reconnect to the server if the lag is greater than this "
                 "value (in seconds)"),
            ),
            Option(
                "print_unconfirmed_messages",
                "boolean",
                "",
                0,
                0,
                "on",
                ("If off, messages are only printed after the server confirms "
                 "their receival. If on, messages are immediately printed but "
                 "colored differently until receival is confirmed."),
            ),
            Option(
                "lag_min_show",
                "integer",
                "",
                1,
                604800,
                "500",
                ("minimum lag to show (in milliseconds)"),
            ),
            Option(
                "typing_notice_conditions",
                "string",
                "",
                0,
                0,
                "${typing_enabled}",
                ("conditions to send typing notifications (note: content is "
                 "evaluated, see /help eval); besides the buffer and window "
                 "variables the typing_enabled variable is also expanded; "
                 "the typing_enabled variable can be manipulated with the "
                 "/room command, see /help room"),
            ),
            Option(
                "read_markers_conditions",
                "string",
                "",
                0,
                0,
                "${markers_enabled}",
                ("conditions to send read markers (note: content is "
                 "evaluated, see /help eval); besides the buffer and window "
                 "variables the markers_enabled variable is also expanded; "
                 "the markers_enabled variable can be manipulated with the "
                 "/room command, see /help room"),
            ),
        ]

        color_options = [
            Option(
                "quote_fg",
                "color",
                "",
                0,
                0,
                "lightgreen",
                "Foreground color for matrix style blockquotes",
            ),
            Option(
                "quote_bg",
                "color",
                "",
                0,
                0,
                "default",
                "Background counterpart of quote_fg",
            ),
            Option(
                "error_message_fg",
                "color",
                "",
                0,
                0,
                "darkgray",
                ("Foreground color for error messages that appear inside a "
                 "room buffer (e.g. when a message errors out when sending or "
                 "when a message is redacted)"),
            ),
            Option(
                "error_message_bg",
                "color",
                "",
                0,
                0,
                "default",
                "Background counterpart of error_message_fg.",
            ),
            Option(
                "unconfirmed_message_fg",
                "color",
                "",
                0,
                0,
                "darkgray",
                ("Foreground color for messages that are printed out but the "
                 "server hasn't confirmed the that he received them."),
            ),
            Option("unconfirmed_message_bg", "color", "", 0, 0, "default",
                   "Background counterpart of unconfirmed_message_fg."),
            Option(
                "untagged_code_fg",
                "color",
                "",
                0,
                0,
                "blue",
                ("Foreground color for code without a language specifier. "
                 "Also used for `inline code`."),
            ),
            Option(
                "untagged_code_bg",
                "color",
                "",
                0,
                0,
                "default",
                "Background counterpart of untagged_code_fg",
            ),
        ]

        sections = [
            ("network", network_options),
            ("look", look_options),
            ("color", color_options),
        ]

        super().__init__(sections)

        # The server section is essentially a section with subsections and no
        # options, handle that case independently.
        W.config_new_section(
            self._ptr,
            "server",
            0,
            0,
            "matrix_config_server_read_cb",
            "",
            "matrix_config_server_write_cb",
            "",
            "",
            "",
            "",
            "",
            "",
            "",
        )
Ejemplo n.º 17
0
def server_buffer_merge(buffer):
    if OPTIONS.look_server_buf == ServerBufferType.MERGE_CORE:
        num = W.buffer_get_integer(W.buffer_search_main(), "number")
        W.buffer_unmerge(buffer, num + 1)
        W.buffer_merge(buffer, W.buffer_search_main())
    elif OPTIONS.look_server_buf == ServerBufferType.MERGE:
        if SERVERS:
            first = None
            for server in SERVERS.values():
                if server.server_buffer:
                    first = server.server_buffer
                    break
            if first:
                num = W.buffer_get_integer(W.buffer_search_main(), "number")
                W.buffer_unmerge(buffer, num + 1)
                if buffer is not first:
                    W.buffer_merge(buffer, first)
    else:
        num = W.buffer_get_integer(W.buffer_search_main(), "number")
        W.buffer_unmerge(buffer, num + 1)
Ejemplo n.º 18
0
 def free(self):
     section_ptr = W.config_search_section(self._ptr, "server")
     W.config_section_free(section_ptr)
     super().free()
Ejemplo n.º 19
0
def add_servers_to_completion(completion):
    for server_name in SERVERS:
        W.hook_completion_list_add(completion, server_name, 0,
                                   W.WEECHAT_LIST_POS_SORT)
Ejemplo n.º 20
0
 def _get_session_path(self):
     home_dir = W.info_get('weechat_dir', '')
     return os.path.join(home_dir, "matrix", self.name)
Ejemplo n.º 21
0
 def complete_commands():
     for command in commands:
         W.hook_completion_list_add(completion, command, 0,
                                    W.WEECHAT_LIST_POS_SORT)
Ejemplo n.º 22
0
    def connect(self):
        # type: (MatrixServer) -> int
        if not self.address or not self.port:
            message = "{prefix}Server address or port not set".format(
                prefix=W.prefix("error"))
            W.prnt("", message)
            return False

        if not self.user or not self.password:
            message = "{prefix}User or password not set".format(
                prefix=W.prefix("error"))
            W.prnt("", message)
            return False

        if self.connected:
            return True

        if not self.server_buffer:
            create_server_buffer(self)

        if not self.timer_hook:
            self.timer_hook = W.hook_timer(1 * 1000, 0, 0, "matrix_timer_cb",
                                           self.name)

        ssl_message = " (SSL)" if self.ssl_context.check_hostname else ""

        message = ("{prefix}matrix: Connecting to "
                   "{server}:{port}{ssl}...").format(
                       prefix=W.prefix("network"),
                       server=self.address,
                       port=self.port,
                       ssl=ssl_message)

        W.prnt(self.server_buffer, message)

        W.hook_connect(self.proxy if self.proxy else "", self.address,
                       self.port, 1, 0, "", "connect_cb", self.name)

        return True
Ejemplo n.º 23
0
        if command in buffer.short_name:
            displayed = W.current_buffer() == buffer._ptr

            if displayed:
                continue

            W.buffer_set(buffer._ptr, 'display', '1')
            return W.WEECHAT_RC_OK_EAT

    return W.WEECHAT_RC_OK


if __name__ == "__main__":
    if W.register(WEECHAT_SCRIPT_NAME, WEECHAT_SCRIPT_AUTHOR,
                  WEECHAT_SCRIPT_VERSION, WEECHAT_SCRIPT_LICENSE,
                  WEECHAT_SCRIPT_DESCRIPTION, 'matrix_unload_cb', ''):

        if not W.mkdir_home("matrix", 0o700):
            message = ("{prefix}matrix: Error creating session "
                       "directory").format(prefix=W.prefix("error"))
            W.prnt("", message)

        handler = WeechatHandler()
        handler.format_string = "{record.channel}: {record.message}"
        handler.push_application()

        # TODO if this fails we should abort and unload the script.
        G.CONFIG = MatrixConfig()
        G.CONFIG.read()
Ejemplo n.º 24
0
 def _create_session_dir(self):
     path = os.path.join("matrix", self.name)
     if not W.mkdir_home(path, 0o700):
         message = ("{prefix}matrix: Error creating server session "
                    "directory").format(prefix=W.prefix("error"))
         W.prnt("", message)
Ejemplo n.º 25
0
def sso_login_cb(server_name, command, return_code, out, err):
    try:
        server = SERVERS[server_name]
    except KeyError:
        message = (
            "{}{}: SSO callback ran, but no server for it was found.").format(
                W.prefix("error"), SCRIPT_NAME)
        W.prnt("", message)

    if return_code == W.WEECHAT_HOOK_PROCESS_ERROR:
        server.error("Error while running the matrix_sso_helper. Please "
                     "make sure that the helper script is executable and can "
                     "be found in your PATH.")
        server.sso_hook = None
        server.disconnect()
        return W.WEECHAT_RC_OK

    # The child process exited mark the hook as done.
    if return_code == 0:
        server.sso_hook = None

    if err != "":
        W.prnt("", "stderr: %s" % err)

    if out == "":
        return W.WEECHAT_RC_OK

    try:
        ret = json.loads(out)
        msgtype = ret.get("type")

        if msgtype == "redirectUrl":
            redirect_url = "http://{}:{}".format(ret["host"], ret["port"])

            login_url = (
                "{}/_matrix/client/r0/login/sso/redirect?redirectUrl={}"
            ).format(server.homeserver.geturl(), redirect_url)

            server.info_highlight(
                "The server requested a single sign-on, please open "
                "this URL in your browser. Note that the "
                "browser needs to run on the same host as Weechat.")
            server.info_highlight(login_url)

            message = {"server": server.name, "url": login_url}
            W.hook_hsignal_send("matrix_sso_login", message)

        elif msgtype == "token":
            token = ret["loginToken"]
            server.login(token=token)

        elif msgtype == "error":
            server.error("Error in the SSO helper {}".format(ret["message"]))

        else:
            server.error("Unknown SSO login message received from child "
                         "process.")

    except JSONDecodeError:
        server.error(
            "Error decoding SSO login message from child process: {}".format(
                out))

    return W.WEECHAT_RC_OK
Ejemplo n.º 26
0
def color_for_tags(color):
    if color == "weechat.color.chat_nick_self":
        option = W.config_get(color)
        return W.config_string(option)
    return color
Ejemplo n.º 27
0
def server_buffer_cb(server_name, buffer, input_data):
    message = ("{}{}: this buffer is not a room buffer!").format(
        W.prefix("error"), SCRIPT_NAME)
    W.prnt(buffer, message)
    return W.WEECHAT_RC_OK
Ejemplo n.º 28
0
def prnt_debug(debug_type, server, message):
    if debug_type in OPTIONS.debug:
        W.prnt(server.server_buffer, message)
Ejemplo n.º 29
0
def hook_commands():
    W.hook_command(
        # Command name and short description
        'matrix',
        'Matrix chat protocol command',
        # Synopsis
        ('server add <server-name> <hostname>[:<port>] ||'
         'server delete|list|listfull <server-name> ||'
         'connect <server-name> ||'
         'disconnect <server-name> ||'
         'reconnect <server-name> ||'
         'debug <debug-type> ||'
         'help <matrix-command>'),
        # Description
        ('    server: list, add, or remove Matrix servers\n'
         '   connect: connect to Matrix servers\n'
         'disconnect: disconnect from one or all Matrix servers\n'
         ' reconnect: reconnect to server(s)\n\n'
         '      help: show detailed command help\n\n'
         '     debug: enable or disable debugging\n\n'
         'Use /matrix help [command] to find out more.\n'),
        # Completions
        ('server %(matrix_server_commands)|%* ||'
         'connect %(matrix_servers) ||'
         'disconnect %(matrix_servers) ||'
         'reconnect %(matrix_servers) ||'
         'debug %(matrix_debug_types) ||'
         'help %(matrix_commands)'),
        # Function name
        'matrix_command_cb',
        '')

    W.hook_command(
        # Command name and short description
        'redact',
        'redact messages',
        # Synopsis
        ('<message-number>[:"<message-part>"] [<reason>]'),
        # Description
        ("message-number: number of message to redact (starting from 1 for\n"
         "                the last message received, counting up)\n"
         "  message-part: an initial part of the message (ignored, only used\n"
         "                as visual feedback when using completion)\n"
         "        reason: the redaction reason\n"),
        # Completions
        ('%(matrix_messages)'),
        # Function name
        'matrix_redact_command_cb',
        '')

    W.hook_command(
        # Command name and short description
        "me",
        "send an emote message to the current room",
        # Synopsis
        ("<message>"),
        # Description
        ("message: message to send"),
        # Completions
        "",
        # Function name
        "matrix_me_command_cb",
        "")

    W.hook_command_run('/topic', 'matrix_command_topic_cb', '')
    W.hook_command_run('/buffer clear', 'matrix_command_buf_clear_cb', '')
    W.hook_command_run('/join', 'matrix_command_join_cb', '')
    W.hook_command_run('/part', 'matrix_command_part_cb', '')
    W.hook_command_run('/invite', 'matrix_command_invite_cb', '')
    W.hook_command_run('/kick', 'matrix_command_kick_cb', '')

    if OPTIONS.enable_backlog:
        hook_page_up()
Ejemplo n.º 30
0
    def __init__(self):
        self.debug_buffer = ""
        self.upload_buffer = ""
        self.debug_category = "all"
        self.page_up_hook = None
        self.human_buffer_names = None

        look_options = [
            Option(
                "redactions",
                "integer",
                "strikethrough|notice|delete",
                0,
                0,
                "strikethrough",
                ("Only notice redactions, strike through or delete "
                 "redacted messages"),
                RedactType,
            ),
            Option(
                "server_buffer",
                "integer",
                "merge_with_core|merge_without_core|independent",
                0,
                0,
                "merge_with_core",
                "Merge server buffers",
                ServerBufferType,
                config_server_buffer_cb,
            ),
            Option(
                "new_channel_position",
                "integer",
                "none|next|near_server",
                min(NewChannelPosition),
                max(NewChannelPosition),
                "none",
                "force position of new channel in list of buffers "
                "(none = default position (should be last buffer), "
                "next = current buffer + 1, near_server = after last "
                "channel/pv of server)",
                NewChannelPosition,
            ),
            Option(
                "max_typing_notice_item_length",
                "integer",
                "",
                10,
                1000,
                "50",
                ("Limit the length of the typing notice bar item."),
            ),
            Option(
                "bar_item_typing_notice_prefix",
                "string",
                "",
                0,
                0,
                "Typing: ",
                ("Prefix for the typing notice bar item."),
            ),
            Option(
                "encryption_warning_sign",
                "string",
                "",
                0,
                0,
                "⚠️ ",
                ("A sign that is used to signal trust issues in encrypted "
                 "rooms (note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "busy_sign",
                "string",
                "",
                0,
                0,
                "⏳",
                ("A sign that is used to signal that the client is busy e.g. "
                 "when the room backlog is fetching"
                 " (note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "encrypted_room_sign",
                "string",
                "",
                0,
                0,
                "🔐",
                ("A sign that is used to show that the current room is "
                 "encrypted "
                 "(note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "disconnect_sign",
                "string",
                "",
                0,
                0,
                "❌",
                ("A sign that is used to show that the server is disconnected "
                 "(note: content is evaluated, see /help eval)"),
                eval_cast,
            ),
            Option(
                "pygments_style",
                "string",
                "",
                0,
                0,
                "native",
                "Pygments style to use for highlighting source code blocks",
            ),
            Option(
                "code_blocks",
                "boolean",
                "",
                0,
                0,
                "on",
                ("Display preformatted code blocks as rectangular areas by "
                 "padding them with whitespace up to the length of the longest"
                 " line (with optional margin)"),
            ),
            Option(
                "code_block_margin",
                "integer",
                "",
                0,
                100,
                "2",
                ("Number of spaces to add as a margin around around a code "
                 "block"),
            ),
            Option(
                "quote_wrap",
                "integer",
                "",
                -1,
                1000,
                "67",
                ("After how many characters to soft-wrap lines in a quote "
                 "block (reply message). Set to -1 to disable soft-wrapping."),
            ),
            Option(
                "human_buffer_names",
                "boolean",
                "",
                0,
                0,
                "off",
                ("If turned on the buffer name will consist of the server "
                 "name and the room name instead of the Matrix room ID. Note, "
                 "this requires a change to the logger.file.mask setting "
                 "since conflicts can happen otherwise "
                 "(requires a script reload)."),
            ),
        ]

        network_options = [
            Option(
                "max_initial_sync_events",
                "integer",
                "",
                1,
                10000,
                "30",
                ("How many events to fetch during the initial sync"),
            ),
            Option(
                "max_backlog_sync_events",
                "integer",
                "",
                1,
                100,
                "10",
                ("How many events to fetch during backlog fetching"),
            ),
            Option(
                "fetch_backlog_on_pgup",
                "boolean",
                "",
                0,
                0,
                "on",
                ("Fetch messages in the backlog on a window page up event"),
                None,
                config_pgup_cb,
            ),
            Option(
                "debug_level",
                "integer",
                "error|warn|info|debug",
                0,
                0,
                "error",
                "Enable network protocol debugging.",
                level_to_logbook,
                config_log_level_cb,
            ),
            Option(
                "debug_category",
                "integer",
                "all|http|client|events|responses|encryption",
                0,
                0,
                "all",
                "Debugging category",
                logbook_category,
                config_log_category_cb,
            ),
            Option(
                "debug_buffer",
                "boolean",
                "",
                0,
                0,
                "off",
                ("Use a separate buffer for debug logs."),
            ),
            Option(
                "lazy_load_room_users",
                "boolean",
                "",
                0,
                0,
                "off",
                ("If on, room users won't be loaded in the background "
                 "proactively, they will be loaded when the user switches to "
                 "the room buffer. This only affects non-encrypted rooms."),
            ),
            Option(
                "max_nicklist_users",
                "integer",
                "",
                100,
                20000,
                "5000",
                ("Limit the number of users that are added to the nicklist. "
                 "Active users and users with a higher power level are always."
                 " Inactive users will be removed from the nicklist after a "
                 "day of inactivity."),
            ),
            Option(
                "lag_reconnect",
                "integer",
                "",
                5,
                604800,
                "90",
                ("Reconnect to the server if the lag is greater than this "
                 "value (in seconds)"),
            ),
            Option(
                "autoreconnect_delay_growing",
                "integer",
                "",
                1,
                100,
                "2",
                ("growing factor for autoreconnect delay to server "
                 "(1 = always same delay, 2 = delay*2 for each retry, etc.)"),
            ),
            Option(
                "autoreconnect_delay_max",
                "integer",
                "",
                0,
                604800,
                "600",
                ("maximum autoreconnect delay to server "
                 "(in seconds, 0 = no maximum)"),
            ),
            Option(
                "print_unconfirmed_messages",
                "boolean",
                "",
                0,
                0,
                "on",
                ("If off, messages are only printed after the server confirms "
                 "their receival. If on, messages are immediately printed but "
                 "colored differently until receival is confirmed."),
            ),
            Option(
                "lag_min_show",
                "integer",
                "",
                1,
                604800,
                "500",
                ("minimum lag to show (in milliseconds)"),
            ),
            Option(
                "typing_notice_conditions",
                "string",
                "",
                0,
                0,
                "${typing_enabled}",
                ("conditions to send typing notifications (note: content is "
                 "evaluated, see /help eval); besides the buffer and window "
                 "variables the typing_enabled variable is also expanded; "
                 "the typing_enabled variable can be manipulated with the "
                 "/room command, see /help room"),
            ),
            Option(
                "read_markers_conditions",
                "string",
                "",
                0,
                0,
                "${markers_enabled}",
                ("conditions to send read markers (note: content is "
                 "evaluated, see /help eval); besides the buffer and window "
                 "variables the markers_enabled variable is also expanded; "
                 "the markers_enabled variable can be manipulated with the "
                 "/room command, see /help room"),
            ),
            Option(
                "resending_ignores_devices",
                "boolean",
                "",
                0,
                0,
                "on",
                ("If on resending the same message to a room that contains "
                 "unverified devices will mark the devices as ignored and "
                 "continue sending the message. If off resending the message "
                 "will again fail and devices need to be marked as verified "
                 "one by one or the /send-anyways command needs to be used to "
                 "ignore them."),
            ),
        ]

        color_options = [
            Option(
                "quote_fg",
                "color",
                "",
                0,
                0,
                "lightgreen",
                "Foreground color for matrix style blockquotes",
            ),
            Option(
                "quote_bg",
                "color",
                "",
                0,
                0,
                "default",
                "Background counterpart of quote_fg",
            ),
            Option(
                "error_message_fg",
                "color",
                "",
                0,
                0,
                "darkgray",
                ("Foreground color for error messages that appear inside a "
                 "room buffer (e.g. when a message errors out when sending or "
                 "when a message is redacted)"),
            ),
            Option(
                "error_message_bg",
                "color",
                "",
                0,
                0,
                "default",
                "Background counterpart of error_message_fg.",
            ),
            Option(
                "unconfirmed_message_fg",
                "color",
                "",
                0,
                0,
                "darkgray",
                ("Foreground color for messages that are printed out but the "
                 "server hasn't confirmed the that he received them."),
            ),
            Option("unconfirmed_message_bg", "color", "", 0, 0, "default",
                   "Background counterpart of unconfirmed_message_fg."),
            Option(
                "untagged_code_fg",
                "color",
                "",
                0,
                0,
                "blue",
                ("Foreground color for code without a language specifier. "
                 "Also used for `inline code`."),
            ),
            Option(
                "untagged_code_bg",
                "color",
                "",
                0,
                0,
                "default",
                "Background counterpart of untagged_code_fg",
            ),
            Option(
                "nick_prefixes",
                "string",
                "",
                0,
                0,
                "admin=lightgreen;mod=lightgreen;power=yellow",
                ('Colors for nick prefixes indicating power level. '
                 'Format is "admin:color1;mod:color2;power:color3", '
                 'where "admin" stands for admins (power level = 100), '
                 '"mod" stands for moderators (power level >= 50) and '
                 '"power" for any other power user (power level > 0). '
                 'Requires restart to apply changes.'),
                parse_nick_prefix_colors,
            ),
        ]

        sections = [
            ("network", network_options),
            ("look", look_options),
            ("color", color_options),
        ]

        super().__init__(sections)

        # The server section is essentially a section with subsections and no
        # options, handle that case independently.
        W.config_new_section(
            self._ptr,
            "server",
            0,
            0,
            "matrix_config_server_read_cb",
            "",
            "matrix_config_server_write_cb",
            "",
            "",
            "",
            "",
            "",
            "",
            "",
        )