# -*- coding: utf-8 -*-# MIT License## Copyright (c) 2017-2019 Matthias Adamczyk# Copyright (c) 2019 Marco Trevisan## 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."""Automatically hide read buffers and unhide them on new activity.Requires WeeChat version 1.0 or higher.Configuration: plugins.var.python.buffer_autohide.hide_inactive: Hide inactive buffers (default: "off") plugins.var.python.buffer_autohide.hide_private: Hide private buffers (default: "off") plugins.var.python.buffer_autohide.unhide_low: Unhide a buffer when a low priority message (like JOIN, PART, etc.) has been received (default: "off"), plugins.var.python.buffer_autohide.exemptions: An enumeration of buffers that should not become hidden (default: "") plugins.var.python.buffer_autohide.keep_open: Keep a buffer open for a short amount of time (default: "off") plugins.var.python.buffer_autohide.keep_open_timeout: Timeout in milliseconds for how long a selected buffer should be kept around (default: "60 * 1000")History:2017-03-19: Matthias Adamczyk <mail@notmatti.me> version 0.1: Initial release2018-06-28: yeled <yeled@github.com> version 0.2: Only skip irc.servers2018-12-07: Matthias Adamczyk <mail@notmatti.me> version 0.3: Add a functionality to define exemptions for certain buffers2018-12-07: Marco Trevisan <mail@3v1n0.net> version 0.4: Keep buffers active for a given time before hide them again if they should2019-01-31: Trygve Aaberge <trygveaa@gmail.com> version 0.5: Support buffers from plugins other than IRC as wellhttps://github.com/notmatti/buffer_autohide"""from__future__importprint_functionimportastimportoperatorasopimport_ok=Truetry:importweechatfromweechatimportWEECHAT_RC_OKexceptImportError:print("Script must be run under weechat. https://weechat.org")import_ok=FalseSCRIPT_NAME="buffer_autohide"SCRIPT_AUTHOR="Matthias Adamczyk <mail@notmatti.me>"SCRIPT_VERSION="0.5"SCRIPT_LICENSE="MIT"SCRIPT_DESC="Automatically hide read buffers and unhide them on new activity"SCRIPT_COMMAND=SCRIPT_NAMEDELIMITER="|@|"MINIMUM_BUFFER_LIFE=500# How many ms are enough to consider a buffer validKEEP_ALIVE_TIMEOUT=60*1000# How long a selected buffer should be kept aroundCURRENT_BUFFER="0x0"# pointer string representationCURRENT_BUFFER_TIMER_HOOK=None# Timeout hook referenceKEEP_ALIVE_BUFFERS={}# {pointer_string_rep: timeout_hook}defconfig_init():"""Add configuration options to weechat."""globalKEEP_ALIVE_TIMEOUTconfig={"hide_inactive":("off","Hide inactive buffers"),"hide_private":("off","Hide private buffers"),"unhide_low":("off","Unhide a buffer when a low priority message (like JOIN, PART, etc.) has been received"),"exemptions":("","An enumeration of buffers that should not get hidden"),"keep_open":("off","Keep a buffer open for a short amount of time"),"keep_open_timeout":("60 * 1000","Timeout in milliseconds for how long a selected buffer should be kept around"),}foroption,default_valueinconfig.items():ifweechat.config_get_plugin(option)=="":weechat.config_set_plugin(option,default_value[0])weechat.config_set_desc_plugin(option,'{} (default: "{}")'.format(default_value[1],default_value[0]))weechat.hook_config("plugins.var.python.buffer_autohide.keep_open_timeout","timeout_config_changed_cb","")ifweechat.config_is_set_plugin("keep_open_timeout"):KEEP_ALIVE_TIMEOUT=eval_expr(weechat.config_get_plugin("keep_open_timeout"))defeval_expr(expr):"""Evaluate a mathematical expression. >>> eval_expr('2 * 6') 12 """defevaluate(node):# supported operatorsoperators={ast.Add:op.add,ast.Sub:op.sub,ast.Mult:op.mul,ast.Div:op.truediv,ast.Pow:op.pow,ast.BitXor:op.xor,ast.USub:op.neg}ifisinstance(node,ast.Num):# <number>returnnode.nelifisinstance(node,ast.BinOp):# <left> <operator> <right>returnoperators[type(node.op)](evaluate(node.left),evaluate(node.right))elifisinstance(node,ast.UnaryOp):# <operator> <operand> e.g., -1returnoperators[type(node.op)](evaluate(node.operand))else:raiseTypeError(node)returnevaluate(ast.parse(expr,mode='eval').body)deftimeout_config_changed_cb(data,option,value):"""Set the new keep_alive timeout upon change of the corresponding value in plugins.conf."""globalKEEP_ALIVE_TIMEOUTKEEP_ALIVE_TIMEOUT=eval_expr(value)returnWEECHAT_RC_OKdefhotlist_dict():"""Return the contents of the hotlist as a dictionary. The returned dictionary has the following structure: >>> hotlist = { ... "0x0": { # string representation of the buffer pointer ... "count_low": 0, ... "count_message": 0, ... "count_private": 0, ... "count_highlight": 0, ... } ... } """hotlist={}infolist=weechat.infolist_get("hotlist","","")whileweechat.infolist_next(infolist):buffer_pointer=weechat.infolist_pointer(infolist,"buffer_pointer")hotlist[buffer_pointer]={}hotlist[buffer_pointer]["count_low"]=weechat.infolist_integer(infolist,"count_00")hotlist[buffer_pointer]["count_message"]=weechat.infolist_integer(infolist,"count_01")hotlist[buffer_pointer]["count_private"]=weechat.infolist_integer(infolist,"count_02")hotlist[buffer_pointer]["count_highlight"]=weechat.infolist_integer(infolist,"count_03")weechat.infolist_free(infolist)returnhotlistdefon_temporary_active_buffer_timeout(buffer,remaining_calls):remove_keep_alive(buffer)maybe_hide_buffer(buffer)returnweechat.WEECHAT_RC_OKdefkeep_alive_buffer(buffer):remove_keep_alive(buffer)ifbuffer_is_hidable(buffer):KEEP_ALIVE_BUFFERS[buffer]=weechat.hook_timer(KEEP_ALIVE_TIMEOUT,0,1,"on_temporary_active_buffer_timeout",buffer)defremove_keep_alive(buffer):globalKEEP_ALIVE_BUFFERSifbufferinKEEP_ALIVE_BUFFERS.keys():weechat.unhook(KEEP_ALIVE_BUFFERS.pop(buffer))defswitch_current_buffer():"""Save current buffer and ensure that it's visible, then if the buffer is elegible to be hidden, we add it to the list of the buffers to be hidden after a delay """globalCURRENT_BUFFERglobalCURRENT_BUFFER_TIMER_HOOKprevious_buffer=CURRENT_BUFFERCURRENT_BUFFER=weechat.current_buffer()ifprevious_buffer==CURRENT_BUFFER:returnifweechat.buffer_get_integer(CURRENT_BUFFER,"hidden")==1:weechat.buffer_set(CURRENT_BUFFER,"hidden","0")ifweechat.config_get_plugin("keep_open")!="off":ifCURRENT_BUFFER_TIMER_HOOKisnotNone:weechat.unhook(CURRENT_BUFFER_TIMER_HOOK)CURRENT_BUFFER_TIMER_HOOK=Nonemaybe_hide_buffer(previous_buffer)else:keep_alive_buffer(previous_buffer)CURRENT_BUFFER_TIMER_HOOK=weechat.hook_timer(MINIMUM_BUFFER_LIFE,0,1,"on_current_buffer_is_still_active_timeout","")else:maybe_hide_buffer(previous_buffer)defon_current_buffer_is_still_active_timeout(pointer,remaining_calls):globalCURRENT_BUFFER_TIMER_HOOKglobalKEEP_ALIVE_BUFFERSCURRENT_BUFFER_TIMER_HOOK=Noneremove_keep_alive(CURRENT_BUFFER)returnweechat.WEECHAT_RC_OKdefswitch_buffer_cb(data,signal,signal_data):""" :param data: Pointer :param signal: Signal sent by Weechat :param signal_data: Data sent with signal :returns: callback return value expected by Weechat. """switch_current_buffer()returnWEECHAT_RC_OKdefbuffer_is_hidable(buffer):"""Check if passed buffer can be hidden. If configuration option ``hide_private`` is enabled, private buffers will become hidden as well. If the previous buffer name matches any of the exemptions defined in ``exemptions``, it will not become hidden. :param buffer: Buffer string representation """ifbuffer==weechat.current_buffer():returnFalseifbufferinKEEP_ALIVE_BUFFERS.keys():returnFalsefull_name=weechat.buffer_get_string(buffer,"full_name")iffull_name.startswith("irc.server"):returnFalsebuffer_type=weechat.buffer_get_string(buffer,'localvar_type')if(buffer_type=="private"andweechat.config_get_plugin("hide_private")=="off"):returnFalseifweechat.config_get_plugin("hide_inactive")=="off":nicks_count=weechat.buffer_get_integer(buffer,'nicklist_nicks_count')ifnicks_count==0:returnFalseforentryinlist_exemptions():ifentryinfull_name:returnFalsereturnTruedefmaybe_hide_buffer(buffer):"""Hide a buffer if all the conditions are met"""ifbuffer_is_hidable(buffer):weechat.buffer_set(buffer,"hidden","1")defunhide_buffer_cb(data,signal,signal_data):"""Unhide a buffer on new activity. This callback unhides a buffer in which a new message has been received. If configuration option ``unhide_low`` is enabled, buffers with only low priority messages (like JOIN, PART, etc.) will be unhidden as well. :param data: Pointer :param signal: Signal sent by Weechat :param signal_data: Data sent with signal :returns: Callback return value expected by Weechat. """hotlist=hotlist_dict()line_data=weechat.hdata_pointer(weechat.hdata_get('line'),signal_data,'data')buffer=weechat.hdata_pointer(weechat.hdata_get('line_data'),line_data,'buffer')ifnotbufferinhotlist.keys():# just some background noisereturnWEECHAT_RC_OKif(weechat.config_get_plugin("unhide_low")=="on"andhotlist[buffer]["count_low"]>0orhotlist[buffer]["count_message"]>0orhotlist[buffer]["count_private"]>0orhotlist[buffer]["count_highlight"]>0):remove_keep_alive(buffer)weechat.buffer_set(buffer,"hidden","0")returnWEECHAT_RC_OKdeflist_exemptions():"""Return a list of exemption defined in ``exemptions``. :returns: A list of defined exemptions. """return[xforxinweechat.config_get_plugin("exemptions").split(DELIMITER)ifx!=""]defadd_to_exemptions(entry):"""Add an entry to the list of exemptions. An entry can be either a #channel or server_name.#channel :param entry: The entry to add. :returns: the new list of entries. The return value is only used for unit testing. """entries=list_exemptions()entries.append(entry)weechat.config_set_plugin("exemptions",DELIMITER.join(entries))weechat.prnt("","[{}] add: {} added to exemptions.".format(SCRIPT_COMMAND,entry))returnentriesdefdel_from_exemptions(entry):"""Remove an entry from the list of defined exemptions. :param entry: The entry to delete, which can be specified by the position in the list or by the name itself. :returns: the new list of entries. The return value is only used for unit testing. """entries=list_exemptions()try:# by indextry:index=int(entry)-1ifindex<0:raiseIndexErrorentry=entries.pop(index)exceptIndexError:weechat.prnt("","[{}] del: Index out of range".format(SCRIPT_COMMAND))returnentriesexceptValueError:try:# by nameentries.remove(entry)weechat.config_set_plugin("exemptions",DELIMITER.join(entries))exceptValueError:weechat.prnt("","[{}] del: Could not find {}".format(SCRIPT_COMMAND,entry))returnentriesweechat.config_set_plugin("exemptions",DELIMITER.join(entries))weechat.prnt("","[{}] del: Removed {} from exemptions.".format(SCRIPT_COMMAND,entry))returnentriesdefprint_exemptions():"""Print all exemptions defined in ``exemptions``"""entries=list_exemptions()ifentries:count=1forentryinentries:weechat.prnt("","[{}] {}: {}".format(SCRIPT_COMMAND,count,entry))count+=1else:weechat.prnt("","[{}] list: No exemptions defined so far.".format(SCRIPT_COMMAND))defcommand_cb(data,buffer,args):"""Weechat callback for parsing and executing the given command. :returns: Callback return value expected by Weechat. """list_args=args.split(" ")iflist_args[0]notin["add","del","list"]:weechat.prnt("","[{0}] bad option while using /{0} command, try '/help {0}' for more info".format(SCRIPT_COMMAND))eliflist_args[0]=="add":iflen(list_args)==2:add_to_exemptions(list_args[1])eliflist_args[0]=="del":iflen(list_args)==2:del_from_exemptions(list_args[1])eliflist_args[0]=="list":print_exemptions()else:weechat.command("","/help "+SCRIPT_COMMAND)returnWEECHAT_RC_OKif(__name__=='__main__'andimport_okandweechat.register(SCRIPT_NAME,SCRIPT_AUTHOR,SCRIPT_VERSION,SCRIPT_LICENSE,SCRIPT_DESC,'','')):weechat_version=weechat.info_get("version_number","")or0ifint(weechat_version)>=0x01000000:config_init()CURRENT_BUFFER=weechat.current_buffer()weechat.hook_signal("buffer_switch","switch_buffer_cb","")weechat.hook_signal("buffer_line_added","unhide_buffer_cb","")weechat.hook_command(SCRIPT_NAME,SCRIPT_DESC,"add $buffer_name | del { $buffer_name | $list_position } | list"," add : Add $buffer_name to the list of exemptions\n"" $buffer_name can be either #channel or server_name.#channel\n"" del : Delete $buffer_name from the list of exemptions\n"" list : Return a list of all buffers that should not become hidden.","add|del|list","command_cb","")else:weechat.prnt("","{}{} requires WeeChat version 1.0 or higher".format(weechat.prefix('error'),SCRIPT_NAME))