# -*- coding: utf-8 -*- # # The MIT License (MIT) # # Copyright (c) 2015 - 2016 Jos Ahrens # # 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. # # --- # # This script compares channels with a different user via WHOIS and returns which # channels you share. The script comes with a command that can be used to force # comparisons (/chancomp ). Depending on your settings, the script will also # function during normal /WHOIS requests and alternatively offer extra messages # when the verbose setting is turned on. # # Script options: # compare_only_on_command (default: off, options: on, off) # Require usage of /chancomp to do comparisons, and do not perform comparisons on # normal /WHOIS requests. # # ignored_servers (default: "", expects a comma separated string of servers to ignore.) # The script does not do comparisons on ignored servers. This setting expects # a comma separated list of servers (i.e.: "freenode,notfreenode") - ignored # servers will stop /chancomp from functioning and will not display comparisons # in your /WHOIS data. # # output_priority (default: smart, options: smart, shared, not_shared) # In order to not display too much information to consume, the output_priority # option allows you to either set a constant value to be displayed. `shared` will # only ever list comparisons of channels you both share, `not_shared` will do the # opposite: only list channels you do not share. The `smart` setting looks at # how many channels there are, and list the channels with the fewest amount of channels. # # sorting (default: none, options: none, alpha, alpha_ignore_case) # In order to make it easier to do comparisons, sorting options are allowed # so that you roughly can know where to look. The none option does not ensure # any sorting at all, alpha sorts alphabetically, and alpha_ignore_case sorts # alphabetically but makes no distinguishment between 'a' or 'A' # # verbose_output (default: on, options: on, off) # WHOIS comparisons always print something when verbose_output is on and an # unique case is occurring. For example, if you and the target are in the same channels, # verbose_output will allow "All channels are shared" to be printed, as opposed to # listing all channels. When the setting is off, nothing gets printed. # # This script functions on WeeChat 1.3 and above, supporting both Python 2 and Python 3. # It probably runs on earlier versions of WeeChat, but has not been tested. # # History: # version 1.0 - 2015-10-29 # Script creation # version 1.1 - 2015-12-16 # Honour irc.look.msgbuffer_fallback # # TODOs: # - Possibly support a verbose output of "sharing all their channels" # when you and that user share all channels they are in. Currently # the script only looks for all channels you're in, and when that # is equal to the amount of channels they're in, it confirms all # channels are shared. # In addition, if the user is none, print "the target is in no channels" try: import weechat as w import_ok = True except ImportError: print("This script must be run under WeeChat") print("Get WeeChat now at: https://weechat.org/") import_ok = False import re SCRIPT_NAME = "chancomp" SCRIPT_AUTHOR = "Zarthus " SCRIPT_VERSION = "1.1" SCRIPT_LICENSE = "MIT" SCRIPT_DESC = "List shared channels with user on command or WHOIS" SCRIPT_COMMAND = "chancomp" _force_comparison = False # Global variable set to True whenever a manual channel comparison is requested _shared_channels = {"shared": [], "not_shared": []} # Commands and callbacks def cmd_chancomp(data, buffer, target): """Trigger for /chancomp """ global _force_comparison _force_comparison = True w.command("", "/WHOIS {}".format(target)) return w.WEECHAT_RC_OK def whois_callback_chan(data, signal, signal_data): """When the WHOIS channel numeric comes by""" server = signal.split(",")[0] if not should_compare_channels(server): return w.WEECHAT_RC_OK chanlist = filter_channels_by_server(retrieve_channels(server), server) parsed = w.info_get_hashtable("irc_message_parse", {"message": signal_data}) chanlist_target = parsed["text"].strip().split(":")[1] whois_regex = create_whois_regex_from_isupport(server) chanlist_target = whois_regex.sub(r"\1", chanlist_target).split(" ") global _shared_channels comparison = compare_channels(chanlist, chanlist_target) _shared_channels["shared"] += comparison["shared"] _shared_channels["not_shared"] += comparison["not_shared"] return w.WEECHAT_RC_OK def whois_callback_afterchan(data, signal, signal_data): """When the WHOIS server info numeric comes by""" server = signal.split(",")[0] if not should_compare_channels(server): return w.WEECHAT_RC_OK nick = w.info_get_hashtable("irc_message_parse", {"message": signal_data})["arguments"].split(" ")[1] print_shared_list(server, nick) global _force_comparison if _force_comparison: # This was a forced comparison request. _force_comparison = False return w.WEECHAT_RC_OK # Utility functions def compare_channels(chanlist, chanlist_target): """Return a list of channels that are in both lists""" shared_channels = [] not_shared_channels = [] for chan in chanlist: if chan in chanlist_target: shared_channels.append(chan) else: not_shared_channels.append(chan) return {"shared": shared_channels, "not_shared": not_shared_channels} def format_shared_channels(channels, current_channels): """Take a list of channels and format it into a displayable string""" count_shared = len(channels["shared"]) count_total = len(current_channels) verbose = w.config_get_plugin("verbose_output") in ["true", "on"] output_priority = w.config_get_plugin("output_priority") if count_total == count_shared: if verbose: return "All channels are shared" return None if not count_shared: if verbose: return "No channels are shared" return None if output_priority == "not_shared" or (output_priority == "smart" and count_shared > count_total / 2): output = ", ".join(sort_output(channels["not_shared"])) append = ", not sharing" else: output = ", ".join(sort_output(channels["shared"])) append = "" return "Sharing {}/{} channels{}: {}".format(count_shared, count_total, append, output) def sort_output(data): """Sort WHOIS output by user specification, default is no sorting at all""" sorting = w.config_get_plugin("sorting") sortings = ["alpha", "alpha_ignore_case"] if sorting in sortings: if sorting == "alpha": data = sorted(data) if sorting == "alpha_ignore_case": data = sorted(data, key=str.lower) return data def filter_channels_by_server(channels, server): """Remove channels that are on the designated server""" chans = [] for channel in channels: sv, chan = channel.split(".", 1) if sv == server: chans.append(chan) return chans def retrieve_channels(server): """Get a list of the channels the user is in""" isupport_chantypes = w.info_get("irc_server_isupport_value", "{},CHANTYPES".format(server)) ischan_regex = re.compile("^{}\.[{}]".format(re.escape(server), re.escape(isupport_chantypes))) chans = [] infolist = w.infolist_get("buffer", "", "") while w.infolist_next(infolist): bname = w.infolist_string(infolist, "name") if ischan_regex.match(bname): chans.append(bname) w.infolist_free(infolist) return chans def find_target_buffer(server, nick): """Return the buffer the user most likely wants their data printed to""" targets = { "current": w.current_buffer(), "weechat": w.buffer_search_main(), "server": w.buffer_search("irc", "server.{}".format(server)), "private": w.buffer_search("irc", "{}.{}".format(server, nick)) } opt = w.config_string(w.config_get("irc.msgbuffer.whois")) if not opt: opt = w.config_string(w.config_get("irc.look.msgbuffer_fallback")) target = "" if opt.lower() in targets: target = targets[opt] return target def print_shared_list(server, nick): """Format and print the shared channel list""" global _shared_channels if _shared_channels: result = format_shared_channels(_shared_channels, filter_channels_by_server(retrieve_channels(server), server)) if result: w.prnt(find_target_buffer(server, nick), fmt_nick(nick) + " " + result) _shared_channels = {"shared": [], "not_shared": []} def should_compare_channels(server): """Determine if we should compare the channels""" if server in w.config_get_plugin("ignored_servers").replace(" ", "").split(","): return False if w.config_get_plugin("compare_only_on_command") in ["true", "on"] and not _force_comparison: return False return True def create_whois_regex_from_isupport(server): """Look into server ISUPPORT to create the ideal regex for removing channel modes from WHOIS output""" isupport_prefix = w.info_get("irc_server_isupport_value", "{},PREFIX".format(server)).split(")")[1] isupport_chantypes = w.info_get("irc_server_isupport_value", "{},CHANTYPES".format(server)) # Strip modes from WHOIS output. whois_regex = re.compile(r"(?:[{}]{})?(([{}])[^ ]+)".format( re.escape(isupport_prefix), "{1," + str(len(isupport_prefix)) + "}", re.escape(isupport_chantypes) )) return whois_regex # Formatting and colour functions/utilities. def fmt_nick(nick): """Format nick in colours for output colouring""" green = w.color("green") reset = w.color("reset") nick_col = w.color(w.info_get("irc_nick_color_name", nick)) return "{}[{}{}{}]{}".format(green, nick_col, nick, green, reset) if import_ok and w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): settings = { "compare_only_on_command": ["off", "Specifically require {} for comparisons?".format(SCRIPT_COMMAND)], "ignored_servers": ["", "Servers to ignore comparisons with, comma separated."], "output_priority": ["shared", "How to display output? smart, shared, not_shared"], "sorting": ["none", "Ensure sorting shared channel WHOIS output? none, alpha, alpha_ignore_case"], "verbose_output": ["on", "Also show output when all or none channels are shared"] } for option, default_value in settings.items(): if not w.config_is_set_plugin(option): w.config_set_plugin(option, default_value[0]) w.config_set_desc_plugin(option, default_value[1]) w.hook_command( SCRIPT_COMMAND, ("Compare channels you share with on the current network.\n" "Hooks into WHOIS output and can be triggered manually via a command."), "", "Nickname to issue a WHOIS on and compare channels with.", "", "cmd_chancomp", "" ) w.hook_signal("*,irc_in_319", "whois_callback_chan", "") w.hook_signal("*,irc_in_312", "whois_callback_afterchan", "")