|
|
@ -1,9 +1,12 @@
|
|
|
|
#!/usr/bin/python3
|
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
|
|
|
|
from subprocess import call
|
|
|
|
from subprocess import run
|
|
|
|
|
|
|
|
from textwrap import dedent
|
|
|
|
import os
|
|
|
|
import os
|
|
|
|
|
|
|
|
import sys
|
|
|
|
import socket
|
|
|
|
import socket
|
|
|
|
import re
|
|
|
|
import re
|
|
|
|
|
|
|
|
import signal
|
|
|
|
|
|
|
|
|
|
|
|
config = {
|
|
|
|
config = {
|
|
|
|
"token": None,
|
|
|
|
"token": None,
|
|
|
@ -13,54 +16,96 @@ config = {
|
|
|
|
"short_notification": True
|
|
|
|
"short_notification": True
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
config['token'] = os.environ['TWITCH_OAUTH']
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
|
|
|
print("Get your OAUTH token here: https://twitchapps.com/tmi/",
|
|
|
|
|
|
|
|
"\nThen pass it as an environment variable:",
|
|
|
|
|
|
|
|
f"\nTWITCH_OAUTH=<token> {__file__}")
|
|
|
|
|
|
|
|
exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
HOST = 'irc.chat.twitch.tv'
|
|
|
|
HOST = 'irc.chat.twitch.tv'
|
|
|
|
PORT = 6667
|
|
|
|
PORT = 6667
|
|
|
|
MESSAGE_REGEX = ':(.*)\!.*@.* PRIVMSG #(.*) :(.*)'
|
|
|
|
MESSAGE_REGEX = r':(.*)!.*@.* PRIVMSG #(.*) :(.*)'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sock = socket.socket()
|
|
|
|
sock = socket.socket()
|
|
|
|
connected = False
|
|
|
|
connected = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_token_from_env():
|
|
|
|
|
|
|
|
"""Get OAUTH token from environment variables or exit"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
|
|
config['token'] = os.environ['TWITCH_OAUTH']
|
|
|
|
|
|
|
|
except KeyError:
|
|
|
|
|
|
|
|
helpText = """
|
|
|
|
|
|
|
|
Get your OAUTH token here: https://twitchapps.com/tmi/
|
|
|
|
|
|
|
|
Then pass it as an environment variable:
|
|
|
|
|
|
|
|
TWITCH_OAUTH=<token> {__file__}"
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
print(dedent(helpText))
|
|
|
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def connect():
|
|
|
|
def connect():
|
|
|
|
|
|
|
|
"""Connect to IRC and authenticate"""
|
|
|
|
|
|
|
|
|
|
|
|
sock.connect((HOST, PORT))
|
|
|
|
sock.connect((HOST, PORT))
|
|
|
|
sock.send(f"PASS {config['token']}\n".encode('utf-8'))
|
|
|
|
sock.send(f"PASS {config['token']}\n".encode('utf-8'))
|
|
|
|
sock.send(f"NICK {config['nickname']}\n".encode('utf-8'))
|
|
|
|
sock.send(f"NICK {config['nickname']}\n".encode('utf-8'))
|
|
|
|
sock.send(f"JOIN {config['channel']}\n".encode('utf-8'))
|
|
|
|
sock.send(f"JOIN {config['channel']}\n".encode('utf-8'))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
connect()
|
|
|
|
def pong(resp):
|
|
|
|
|
|
|
|
"""Answer to server PING to keep socket connection alive"""
|
|
|
|
while True:
|
|
|
|
|
|
|
|
resp = sock.recv(2048).decode('utf-8')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if resp.startswith('PING'):
|
|
|
|
if resp.startswith('PING'):
|
|
|
|
sock.send("PONG\n".encode('utf-8'))
|
|
|
|
sock.send("PONG\n".encode('utf-8'))
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def wait_for_chat(resp):
|
|
|
|
|
|
|
|
"""Wait until all welcome and user list messages are received"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if "End of /NAMES list" in resp:
|
|
|
|
|
|
|
|
global connected
|
|
|
|
|
|
|
|
connected = True
|
|
|
|
|
|
|
|
run(["notify-send",
|
|
|
|
|
|
|
|
f"connected to {config['channel']} as {config['nickname']}"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_message(resp):
|
|
|
|
|
|
|
|
"""Parse the message and send it as a notification"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(resp)
|
|
|
|
|
|
|
|
user, channel, message = re.search(MESSAGE_REGEX, resp).groups()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if user and channel and message:
|
|
|
|
|
|
|
|
if config['short_notification']:
|
|
|
|
|
|
|
|
notification_text = f"@{user}: {message}"
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
|
|
notification_text = f"@{user} in #{channel}\n{message}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not (config['supress_own'] and config['nickname'] == user):
|
|
|
|
|
|
|
|
run(["notify-send", notification_text])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_exit(sig, frame):
|
|
|
|
|
|
|
|
"""Close socket connection before exit"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
print(f"Got {sig.name}, closing socket")
|
|
|
|
|
|
|
|
sock.shutdown(socket.SHUT_RDWR)
|
|
|
|
|
|
|
|
sock.close()
|
|
|
|
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
elif len(resp) > 0:
|
|
|
|
if __name__ == "__main__":
|
|
|
|
if connected:
|
|
|
|
# Register signal handlers
|
|
|
|
user, channel, message = re.search(MESSAGE_REGEX, resp).groups()
|
|
|
|
signal.signal(signal.SIGINT, handle_exit)
|
|
|
|
|
|
|
|
signal.signal(signal.SIGTERM, handle_exit)
|
|
|
|
|
|
|
|
|
|
|
|
if config['short_notification']:
|
|
|
|
get_token_from_env()
|
|
|
|
notification_text = f"@{user}: {message}"
|
|
|
|
connect()
|
|
|
|
else:
|
|
|
|
|
|
|
|
notification_text = f"@{user} in #{channel}\n{message}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if not (config['supress_own'] and config['nickname'] == user):
|
|
|
|
while True:
|
|
|
|
call(["notify-send", notification_text])
|
|
|
|
resp = sock.recv(2048).decode('utf-8')
|
|
|
|
|
|
|
|
|
|
|
|
if "End of /NAMES list" in resp:
|
|
|
|
if len(resp) > 0:
|
|
|
|
connected = True
|
|
|
|
if not (pong(resp)):
|
|
|
|
call([
|
|
|
|
if connected:
|
|
|
|
"notify-send",
|
|
|
|
handle_message(resp)
|
|
|
|
f"connected to {config['channel']} as {config['nickname']}"])
|
|
|
|
else:
|
|
|
|
|
|
|
|
wait_for_chat(resp)
|
|
|
|