Soft-Ignore: Filter buggers in XChat

If you, like me, hang out once in a while on IRC to chat with fellow programmers (or with fellow practitioner of your favorite hobby), you may find that some individuals are just not worth your full attention. One easy, and rather definitive way to deal with the problem is to use the /ignore command that allows your IRC client to filter incoming messages from those people, and you just never see them again… quite literally.

However, just /ignoring someone is rude, and may prevent you from keeping a eye on them. You know, the “keep your friends close, your enemies closer” kind of thing.

A long time ago, with a friend, I wrote a mIRC script that shaded the “ignored” people’s text so that it was hard to read (like dark gray on blue), but the text was still available. To view the text, you could either squint or select the text. This week, I present a python version of that script, for XChat, based on the work of Albert W. Hopkins, a.k.a. Marduk, released under the GPL.

XChat lets you plug-in your script in Perl, Python or Tcl. I personally favor Python, but that’s off-topic for now. Xchat’s Python plug-in interface lets you add hook to trap events and process them. To write a plug-in that traps messages and filter them according to their originators (in IRC parlance, ‘nicks’), we must first decide what will be the command names. Since what I’m doing is a soft version of /ignore, I’ll call them:

  • /soft-ignore nick to add nick to the soft ignore list,
  • /soft-unignore nick to remove nick from the soft ignore list,
  • /soft-ignore-list to list the nicks on the soft ignore list.

Once we’re decided on the command names, we add them to XChat’s commands and create a hook on channel messages:

xchat.hook_command('soft-ignore', add_soft_ignore)
xchat.hook_command('soft-ignore-list', soft_ignore_list)
xchat.hook_command('soft-unignore', delete_soft_ignore)

# here we wanna hook in to the channel message event
xchat.hook_print("Channel Message", ignore_message)

We will have to provide, from the Python program, the functions we just enumerated. The first three are somewhat non-interesting since it consist in adding, removing, etc., and item from a list. In Marduk original version, the list structure was overloaded, but I removed it and replaced it by a basic Python list. The list is automagically saved into a database placed in $HOME/.xchat2/soft-ignore.conf. The functions to add, remove or list soft ignored names also print informative messages if the list is empty or if you add a nick that’s already in the list.

Marduk’s original code was also meant to highlight the incoming message (i.e., make something special to make it noticeable) whenever it was coming from someone whose nick was on the list. I try to do the exact opposite, low-keying messages from buggers.

I also decided to include Perl Compatible Regular Expressions to the match as well. At first, I thought of merely using DOS-style wildcards but Python’s regexp engine supports only PCREs. So, instead of writing a DOS-to-PCRE regexp converter, I decided to just use PCRE. Regexp support is convenient if you want to filter someone what keep changing its nick depending on his status (you know, those Dude_at_work, Dude_shower, Dude_brsh_teeth,…).

The code looks like this:

def ignore_message(word, word_eol, userdata):
    nick = remove_color(word[0]).lower() # skips the initial coloring
    if ([p for p in nicks if re.match('^'+p+'$',nick,re.IGNORECASE)] or
        word[1][0]=='~'): # to filter an annoying bot in the channel
        #should be more or less like the normal 'Channel Message' display
        xchat.prnt('\x0314 <%s>\t%s' % (nick,word[1]))
        return xchat.EAT_XCHAT
    else:
        return xchat.EAT_NONE

And, if you are familiar with XChat Python scripting—which I still not am—you may ask yourself why I didn’t rather use:

    if nick in [x.lower() for x in nicks]:
        xchat.emit_print('Channel Message',nick, color_codes+word[1] )
        return xchat.EAT_XCHAT

to change the colors. The problem with this second version of the code, is that for a message to appear normally in the XChat window as a normal message, it must be resent to XChat using the xchat.emit_print function. But xchat.emit_print doesn’t just print the message normally, it re-feeds to XChat so that more scripts can be applied to it. So, yes, you guessed right: more scripts, in my case, means that my script gets called again because I have a hook on Channel Messages. The original code did not have this problem because it returned a Channel Msg Hilight which is not the type of message trapped, therefore XChat would eventually use its default Channel Msg Hilight hook and do its thing.

The shaded lines are from soft-ignored user. Note also that commands begining by ~ are also soft-ignored.

The shaded lines are from soft-ignored user. Note also that commands begining by ~ are also soft-ignored.

However, I haven’t found a way of telling XChat to process that message just one and then just proceed to its normal display, without further script intervention. Maybe there’s a way, maybe I haven’t looked in the right places, I just haven’t found it. So instead of re-emitting the message, I print it as a general string using xchat.prnt and \t for the separator (prnt is misspelled to avoid clashing with print a reserved keyword in Python).

I suppose that the correct way of doing this would have been to reuse another event and re-emit the buggers’ messages as this type of event. If I would have re-emitted the message as, say, a Channel Part event, it would have printed the same as the message you get when you part a channel. Even better would have been to add a new event, say Channel Soft Ignored, and defines its properties correctly in the Settings->Advanced->Text Event dialog, so it would pretty-print just like everything else in XChat. Unfortunately, events are hard-wired and you cannot add one without, I would guess, hacking XChat itself and recompiling everything. Sad thing, because it would have sufficed to offer users Channel Custom Message 1 to Channel Custom Message 9, and let scripts use them.

*
* *

The whole source:

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# Copyright (C) 2009 Steven Pigeon,
#
# based on earlier version by
#
# Copyright (C) 2004 Marduk Enterprises <marduk@python.net>
#
# minor fixes (in addition to changing message type from
# msg highlight to local console print)
#

import xchat
import gdbm
import os

from itertools import dropwhile
import string
import re


__module_name__ = 'soft-ignore'
__module_version__ = '0.95'
__module_description__ = """With this plug-in, you can prevent some people
from getting your attention."""

DBFILE = os.environ['HOME'] + '/.xchat2/soft-ignore.conf'
SEPARATOR=''

xchat.prnt('%(name)s, version %(version)s' % {'name': __module_name__,
    'version': __module_version__})

# loads the database
db = gdbm.open(DBFILE,'c')
try:
    nicks = db['soft-ignore'].split(SEPARATOR)
except KeyError:
    db['soft-ignore'] = ''
    nicks = []

# this function strops the colors
# from the nicknames in a safe(r) fashion
def remove_color( nick ):
    # 0 is x3
    # then follows 1 or 2 numbers
    return "".join(list(dropwhile( lambda x : x.isdigit(), nick[1:] )))

# commit changes to database
#
def save_nicks():
    nicks.sort()
    db['soft-ignore'] = SEPARATOR.join(nicks)

# adds a pattern/nick to the list, and 
# prints the list if no pattern/nick is
# supplied.
#
def add_soft_ignore(word, word_eol, userdata):
    if len(word) == 1:
        return soft_ignore_list(word, word_eol, userdata)
    for nick in word[1:]:
        if nick not in nicks:
            try:
                re.compile(nick)
            except:
                xchat.prnt('\x032* \x034 %s \x032 is not a valid PCRE' % nick)
            else:
                nicks.append(nick)
                xchat.prnt('\x032* %s will be soft-ignored' % nick)
        else:
            xchat.prnt('\x032* %s is already being soft-ignored' % nick)
        save_nicks()
    return xchat.EAT_XCHAT

# shows soft-ignore-list
#
def soft_ignore_list(word, word_eol, userdata):
    xchat.prnt('\x032Current soft-ignore-list: %d soft-ignored.' % len(nicks) )
    for nick in nicks:
        xchat.prnt('\x032 --- %s' % nick)
    xchat.prnt('\x032* End of soft-ignore list')
    return xchat.EAT_XCHAT

# removes a pattern/nick from the list
# or prints a colorful error if pattern/nick
# is not in the list
#
def delete_soft_ignore(word, word_eol, userdata):
    for nick in word[1:]:
        if nick in nicks:
            nicks.remove(nick)
            xchat.prnt('\x032* %s is removed from soft-ignore list' % nick)
        else:
            xchat.prnt('\x032* \x034 %s \x032 is not in soft-ignore list' % nick)
    save_nicks()
    return xchat.EAT_XCHAT

# filter message according to pattern/nicks
#
def ignore_message(word, word_eol, userdata):
    nick = remove_color(word[0]).lower() # skips the initial coloring
    if ([p for p in nicks if re.match('^'+p+'$',nick,re.IGNORECASE)] or
        word[1][0]=='~'):
        #should be more or less like the normal 'Channel Message' display
        xchat.prnt('\x0314 <%s>\t%s' % (nick,word[1]))
        return xchat.EAT_XCHAT
    else:
        return xchat.EAT_NONE


xchat.hook_command('soft-ignore', add_soft_ignore)
xchat.hook_command('soft-ignore-list', soft_ignore_list)
xchat.hook_command('soft-unignore', delete_soft_ignore)

# here we wanna hook in to the channel message event
xchat.hook_print("Channel Message", ignore_message)

To install it, it suffice to copy it in $HOME/.xchat2/. XChat should detect it and load it automagically. If not, use the Windows->Plugins and Scripts dialog to load it. Then you’re all set.

One Response to Soft-Ignore: Filter buggers in XChat

  1. [...] softignore script A few weeks ago, my friend Steven wrote about a softignore script he wrote for X-Chat. The idea is that instead of completely blocking what a user says, the script [...]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 66 other followers

%d bloggers like this: