Last active
October 14, 2019 14:04
-
-
Save StepS-/e0defc252f665b2b51e8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import hexchat | |
__module_name__ = 'Twitch' | |
__module_author__ = 'TingPing, frumpy4, StepS' | |
__module_version__ = '6.8' | |
__module_description__ = 'Better integration with Twitch.tv' | |
# Commands from http://help.twitch.tv/customer/portal/articles/659095-chat-moderation-commands | |
# Enable the "commands" and "membership" caps for the best effect | |
# See https://github.com/justintv/Twitch-API/blob/master/chat/capabilities.md for an up-to-date list. | |
# /ban may conflict with other scripts nothing we can do about that | |
# /clear is an existing command, just override it | |
commands = ('timeout', 'slow', 'slowoff', 'subscribers', 'subscribersoff', | |
'mod', 'unmod', 'mods', 'clear', 'ban', 'unban', 'commercial', | |
'r9kbeta', 'r9kbetaoff', 'color', 'host', 'unhost', 'disconnect', 'w') | |
aliases = {'op':'mod', 'deop':'unmod'} | |
ymsgEdited = False | |
yactEdited = False | |
# Clean out some control chars from messages because Twitch servers eat them on delivery. | |
# So the local representation will be consistent with what the receiving end will see. | |
def strip_cchars(str): | |
for list in [range(2, 9), range(11, 13), range(14, 32)]: | |
for cnum in list: | |
str = str.replace(chr(cnum), '') | |
return str | |
def chantype(context): | |
for chan in hexchat.get_list('channels'): | |
if chan.context == context: | |
return chan.type | |
return 0 | |
# From https://github.com/TingPing/plugins/blob/master/HexChat/mymsg.py and modified | |
def self_msg(msg, context=hexchat): | |
mynick = context.get_info('nick') | |
if '\001ACTION' in msg: | |
for repl in ('\001ACTION', '\001'): | |
msg = msg.replace(repl, '', 1) | |
return context.emit_print('Your Action', mynick, msg.strip()) | |
else: | |
return context.emit_print('Your Message', mynick, msg) | |
# For functions that must only take effect on Twitch | |
def twitchOnly(func): | |
def if_twitch(*args, **kwargs): | |
for host in (hexchat.get_info('host'), hexchat.get_info('server')): | |
if host and 'twitch.tv' in host: | |
return func(*args, **kwargs) | |
else: | |
return hexchat.EAT_NONE | |
return if_twitch | |
# Handle chat clean-ups and timeouts. | |
@twitchOnly | |
def clearchat_cb(word, word_eol, userdata): | |
if len(word) == 4: | |
hexchat.emit_print('Server Text','User \00318'+word[3].replace(':','',1)+'\017 has been timed out.') | |
elif len(word) == 3: | |
hexchat.emit_print('Server Text','Chat has been cleared by a moderator.') | |
else: | |
return hexchat.EAT_NONE | |
return hexchat.EAT_ALL | |
# Handle whispers and display them as private messages in the current context. | |
@twitchOnly | |
def whisper_cb(word, word_eol, userdata): | |
whisper = word_eol[3].replace(':','',1) | |
nick = word[0][1:].split('!')[0] | |
context = hexchat.find_context(channel=nick) | |
if context: | |
context.command('recv {} PRIVMSG {}'.format(word[0], word_eol[2])) | |
else: | |
# hexchat.command('recv {} NOTICE {}'.format(word[0], word_eol[2])) | |
# 'Private Message' produces a beep so is preferable | |
hexchat.emit_print('Private Message', nick, whisper) | |
return hexchat.EAT_ALL | |
# Twitch returns a lot of 'unknown command' errors, ignore them. | |
@twitchOnly | |
def servererr_cb(word, word_eol, userdata): | |
if word[3] in ('WHO', 'WHOIS'): | |
return hexchat.EAT_ALL | |
@twitchOnly | |
def eatall(word, word_eol, data): | |
return hexchat.EAT_ALL | |
# Called if a RECONNECT message is received from the server. | |
@twitchOnly | |
def reconnect_cb(word, word_eol, userdata): | |
hexchat.emit_print('Server Error', 'The server has requested you to reconnect. \ | |
If you don\'t do this, you will likely be disconnected after 30 seconds.') | |
return hexchat.EAT_ALL | |
# Called if a Twitch channel is hosting somebody while offline (switches to the target stream in the webbrowser) | |
# Treat it as an invite in HexChat. | |
@twitchOnly | |
def hosttarget_cb(word, word_eol, userdata): | |
source = word[2].replace('#','',1) | |
targchan = word[3].replace(':','#',1) | |
if targchan != '#-': | |
hexchat.emit_print('Invited', targchan, source, hexchat.get_info('server')) | |
return hexchat.EAT_ALL | |
# Print private jtv messages in server tab (if the commands cap is not in use). | |
# Print "channel" jtv/twitchnotify messages in the respective channel tab (with CAPs). | |
@twitchOnly | |
def privmsg_cb(word, word_eol, userdata): | |
nick = word[0][1:].split('!')[0] | |
if nick == 'jtv' or nick == 'twitchnotify': | |
if word[2][0] != '#': | |
for chan in hexchat.get_list('channels'): | |
if chan.type == 1 and chan.id == hexchat.get_prefs('id'): | |
chan.context.emit_print('Server Text', word_eol[3][1:]) | |
return hexchat.EAT_ALL | |
else: | |
hexchat.emit_print('Server Text', word_eol[3][1:]) | |
return hexchat.EAT_ALL | |
# Obsolete, no longer sent by twitch/jtv in this form | |
# elif word[3] in (':USERCOLOR', ':USERSTATE', ':HISTORYEND', ':EMOTESET', ':CLEARCHAT', ':SPECIALUSER'): | |
# return hexchat.EAT_ALL | |
# Eat any message starting with '.' and '/', twitch eats all of them too. | |
# Except for the .w (whisper) messages when formed properly. | |
# Also clean out control chars (see "strip_cchars") | |
@twitchOnly | |
def yourmsg_cb(word, word_eol, userdata): | |
global ymsgEdited | |
if ymsgEdited: | |
return | |
if chantype(hexchat.get_context()) == 2: | |
if word[1][0] in ['.', '/']: | |
args = word[1].split(' ') | |
if args[0][1:] == 'w': | |
if len(args) >= 3: | |
hexchat.emit_print('Notice Send', args[1], strip_cchars(' '.join(args[2:]))) | |
# Otherwise let the Twitch server demonstrate /w usage. | |
return hexchat.EAT_ALL | |
else: | |
ymsgEdited = True | |
hexchat.emit_print('Your Message', word[0], strip_cchars(word[1]), '' if len(word) < 3 else word[2]) | |
ymsgEdited = False | |
return hexchat.EAT_ALL | |
# Clean out control chars from actions (see "strip_cchars") | |
@twitchOnly | |
def youract_cb(word, word_eol, userdata): | |
global yactEdited | |
if yactEdited: | |
return | |
yactEdited = True | |
hexchat.emit_print('Your Action', word[0], strip_cchars(word[1]), '' if len(word) < 3 else word[2]) | |
yactEdited = False | |
return hexchat.EAT_ALL | |
# PM support: PMs are redirected to whispers. It sends a /w to #jtv that gets eaten. | |
# A relay channel is needed for whispers to work, using #jtv as recommended on the official forums. | |
@twitchOnly | |
def my_saymsg_cb(word, word_eol, data): | |
if chantype(hexchat.get_context()) == 3: | |
targ = hexchat.get_info('channel') | |
self_msg(word_eol[0]) | |
hexchat.command('PRIVMSG #jtv :/w {} {}'.format(targ, strip_cchars(word_eol[0]))) | |
return hexchat.EAT_ALL | |
# Redirect /msg and /notice to /w if the target is not a channel | |
@twitchOnly | |
def my_msg_cb(word, word_eol, data): | |
if len(word) > 2 and word[1][0] != '#': | |
hexchat.emit_print('Notice Send', word[1], strip_cchars(word_eol[2])) | |
hexchat.command('PRIVMSG #jtv :/w {}'.format(word_eol[1])) | |
return hexchat.EAT_ALL | |
# Just prefix with a '/' ('.' also works but '/' is preferred officially). | |
@twitchOnly | |
def command_cb(word, word_eol, alias): | |
if chantype(hexchat.get_context()) != 2: | |
hexchat.emit_print('Server Text','You must be on a channel to use the Twitch commands.') | |
return hexchat.EAT_ALL | |
elif alias: | |
if len(word_eol) > 1: | |
hexchat.command('say /{} {}'.format(alias, word_eol[1])) | |
else: | |
hexchat.command('say /{}'.format(alias)) | |
else: | |
hexchat.command('say /{}'.format(word_eol[0])) | |
return hexchat.EAT_ALL | |
for command in commands: | |
hexchat.hook_command(command, command_cb) | |
for command, alias in aliases.items(): | |
hexchat.hook_command(command, command_cb, alias) | |
hexchat.hook_command('', my_saymsg_cb) | |
hexchat.hook_command('msg', my_msg_cb) | |
hexchat.hook_command('notice', my_msg_cb) | |
hexchat.hook_server('421', servererr_cb) | |
hexchat.hook_server('PRIVMSG', privmsg_cb) | |
hexchat.hook_server('CLEARCHAT', clearchat_cb) | |
hexchat.hook_server('WHISPER', whisper_cb) | |
hexchat.hook_server('RECONNECT', reconnect_cb) | |
hexchat.hook_server('HOSTTARGET', hosttarget_cb) | |
hexchat.hook_server('USERSTATE', eatall) | |
hexchat.hook_server('GLOBALUSERSTATE', eatall) | |
hexchat.hook_server('HISTORYEND', eatall) | |
hexchat.hook_server('SPECIALUSER', eatall) | |
hexchat.hook_server('ROOMSTATE', eatall) | |
hexchat.hook_print('Your Message', yourmsg_cb) | |
hexchat.hook_print('Your Action', youract_cb) | |
# Obsolete | |
# hexchat.hook_server('EMOTESET', eatall) | |
# hexchat.hook_server('USERCOLOR', eatall) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment