Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 247 lines (203 sloc) 8.69 KB
#! /usr/bin/env python
# xchat script to play different sounds in different circumstances.
# Copyright 2012 by Akkana Peck,
# Share and enjoy under the GPLv2 or (at your option) any later version.
from __future__ import print_function
__module_name__ = "chatsounds"
__module_version__ = "0.3"
__module_description__ = "Plays sounds when it sees keywords"
__module_author__ = "Akkana Peck <>"
import xchat
import sys, os, subprocess
import time
# Configuration: things you might want to change.
NORMAL_SOUND = "pop.wav"
SUBTLE_SOUND = "SingleClick.wav"
SPECIAL_SOUND = "akk.wav"
# Channels silenced by default, because they're too active:
SILENCED_CHANNELS = [ '#twitter_akkakk', '#python' ]
# End configuration.
# The debugging log file.
# If it's set, we might get debug messages written to it.
Debug = None
# Debug = sys.stderr
def debugprint(*args, **kwargs):
if 'file' not in kwargs:
outfile = kwargs['file']
# Can't seem to pass just *args to print in python 2
print(' '.join(map(str, args)), file=outfile)
class SoundPlayer :
Play sounds that don't overlap in time.
PLAYER = "/usr/bin/aplay"
def __init__(self) :
self.curpath = None
self.current = None
def __del__(self) :
def play(self, path) :
if self.current :
if self.current.poll() == None :
# Current process hasn't finished yet. Is this the same sound?
if path == self.curpath :
# A repeat of the currently playing sound.
# Don't play it more than once.
#print path, "is still playing. Not playing again"
else :
# Trying to play a different sound.
# Wait on the current sound then play the new one.
#print "Different sound; first waiting for", self.curpath
self.current = None
self.curpath = None
#print "Trying to play", path
self.curpath = path
self.current = subprocess.Popen([ SoundPlayer.PLAYER, '-q', path ] )
def wait(self) :
if self.current and self.current.poll() == None :
class XchatSoundHandler :
''' Play alert sound depending on the channel and circumstances.
STARTUP_DELAY = 25 # No sounds will be played in the first few seconds
# Xchat events. Comment out any events for which you don't want alerts:
"Channel Action",
"Channel Action Hilight",
"Channel Message",
"Channel Msg Hilight",
"Channel Notice",
"Generic Message",
# "Motd",
# "Part with Reason",
"Private Message",
"Private Message to Dialog",
# "Quit",
"Receive Wallops",
"Server Notice",
"Server Text",
# "Topic",
# "Topic Change",
def __init__(self) :
self.start_time = time.time()
for event in XchatSoundHandler.EVENTS :
xchat.hook_print(event, self.handle_message, event)
xchat.hook_command("chatsounds", self.handle_prefs)
xchat.hook_command("cs", self.handle_prefs)
self.player = SoundPlayer()
self.sound_dir = os.path.expanduser("~/.xchat2/sounds/")
self.silenced_channels = SILENCED_CHANNELS
def handle_message(self, word, word_eol, userdata):
Handle a message in xchat.
word is something like:
[ '\xaaaanick', "the message we're acting on" ]
where aaaa is a number like \x0328
This, incidentally, is not what the doc says it should be at
userdata is something like: 'Channel Message', from EVENTS,
so you can play different sounds depending on what happened.
# If it's too soon after startup, don't do anything.
# Then we won't hear a slew of alerts from past scrollback,
# NickServ 'You are now identified for" messages, etc.
if time.time() - self.start_time < XchatSoundHandler.STARTUP_DELAY :
return xchat.EAT_NONE
# You may want to use channel name, network name or variables
# in the xchat context to decide which alerts to play.
channel = xchat.get_info('channel')
network = xchat.get_info('network')
ctxt = xchat.get_context()
mynick = ctxt.get_info("nick")
line = word[1]
# For debugging. But this goes to the channel and makes things
# hard to follow. Would be better to debug to a log file.
if Debug :
debugprint("Channel %s, network %s: %s" % (str(channel), str(network),
str(line)), file=Debug)
# Now, customize the rest as desired. Here are some examples:
# Anyone addressing or mentioning my nick:
if line.find(mynick) > 0 and word[0] != 'NickServ' :
if Debug :
debugprint(">>> chatsounds", userdata,
"network =", network,
"channel =", channel,
">>>>> Contains my nick!", userdata,
file=Debug), SPECIAL_SOUND))
return xchat.EAT_NONE
if userdata == "Channel Msg Hilight" or \
userdata == "Channel Action Hilight" :
#if channel == 'root' || channel == '&bitlbee'
# Don't play sounds for bitlbee channel actions,
# because it's constantly losing connection and restarting.
# In fact, if we could just delete those tabs it would be great.
if network != 'Bitlbee' :
if Debug :
debugprint(">>> chatsounds", userdata,
"network =", network,
"channel =", channel,
file=Debug), SPECIAL_SOUND))
if Debug :
debugprint(">>> chatsounds skipping bitlbee",
userdata, "network =", network,
"channel =", channel,
return xchat.EAT_NONE
# Private message:
elif userdata.startswith("Private Message") :
if channel == "root":
if Debug :
debugprint("Skipping channel==root")
if Debug :
debugprint(">>> chatsounds private message!",
"network =", network,
"channel =", channel,
file=Debug), SPECIAL_SOUND))
return xchat.EAT_NONE
# Now check whether we're silenced.
# Note that nick references and private messages are exempt
# from this check -- you'll hear them even on silenced channels.
if channel in self.silenced_channels :
return xchat.EAT_NONE
# More subtle sound for bitlbee/twitter, since they're so numerous:
if channel == "#twitter_" + mynick :
# print ">>>>> Twitter channel!", SUBTLE_SOUND))
# if you want to be fairly noisy or don't have many active channels,
# you might want an alert for every channel message:
elif userdata.startswith("Channel M") or \
userdata.startswith("Channel Action") :, NORMAL_SOUND))
return xchat.EAT_NONE
def handle_prefs(self, word, word_eol, userdata) :
''' Use this for any prefs/actions, like silence/unsilence.
channel = xchat.get_info('channel')
if word[1] == 'silence' :
if channel not in self.silenced_channels :
debugprint("chatsounds: silenced", channel, self.silenced_channels)
elif word[1] == 'unsilence' :
if channel in self.silenced_channels :
debugprint("chatsounds: unsilenced", channel, self.silenced_channels)
return xchat.EAT_ALL
if __name__ == "__main__" :
# Debug log, line buffered:
Debug = open("/tmp/chatsounds.log", "w", buffering=1)
chathandler = XchatSoundHandler()