twitchtts/tts.py

666 lines
27 KiB
Python
Raw Normal View History

2022-08-10 20:58:51 +02:00
#!/usr/bin/python3
# -*- coding: utf-8 -*-
2022-08-12 18:54:40 +02:00
# pylint: disable=line-too-long
2022-08-10 20:58:51 +02:00
2022-08-11 13:32:23 +02:00
"""
TwitchTTS
Copyright (C) 2022 gpkvt
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 3 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
2022-08-10 20:58:51 +02:00
import json
import logging
import socket
import sys
import time
import datetime
import socketserver
2022-08-12 18:54:40 +02:00
import urllib.request
import urllib.parse
import webbrowser
2022-08-10 20:58:51 +02:00
2022-08-11 01:27:27 +02:00
from threading import Thread
2022-08-10 20:58:51 +02:00
from http.server import BaseHTTPRequestHandler
2022-08-11 01:27:27 +02:00
from urllib.parse import parse_qs
2022-08-12 18:54:40 +02:00
from collections import Counter
import yaml
2022-08-10 20:58:51 +02:00
class IRC:
2022-08-12 18:54:40 +02:00
"""IRC bot"""
2022-08-10 20:58:51 +02:00
irc = socket.socket()
def __init__(self):
self.irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tts_denied = []
self.tts_allowed = []
2022-08-12 18:54:40 +02:00
self.tts_status = conf['TTS_STARTENABLED']
self.quickvote = False
self.votemsg = False
self.poll = {}
self.pollcount = 0
if 'WHITELIST_USER' in conf:
2022-08-10 20:58:51 +02:00
self.tts_allowed = conf['WHITELIST_USER']
def connect(self, server, port, channel, botnick, botpass):
2022-08-12 18:54:40 +02:00
"""Connect to Twitch IRC servers"""
logging.info("Connecting to: %s", server)
2022-08-10 20:58:51 +02:00
try:
self.irc.connect((server, port))
except ConnectionResetError:
logging.fatal('Twitch refused to connect, please check your settings and try again.')
2022-08-10 20:58:51 +02:00
sys.exit(252)
self.irc.settimeout(1)
self.irc.send(bytes("PASS " + botpass + "\r\n", "UTF-8"))
2022-08-12 18:54:40 +02:00
self.irc.send(bytes(
"USER " + botnick + " " + botnick +" " + botnick + " :python\r\n", "UTF-8")
)
2022-08-10 20:58:51 +02:00
self.irc.send(bytes("NICK " + botnick + "\r\n", "UTF-8"))
self.irc.send(bytes("CAP REQ :twitch.tv/commands twitch.tv/tags \r\n", "UTF-8"))
time.sleep(5)
try:
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
except ConnectionResetError:
2022-08-12 18:54:40 +02:00
logging.warning('JOIN was refused, will try again in 5 seconds.')
2022-08-11 20:38:30 +02:00
time.sleep(5)
2022-08-12 18:54:40 +02:00
logging.warning('Please check your credentials, if this error persists.')
2022-08-10 20:58:51 +02:00
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
2022-08-12 18:54:40 +02:00
def sendmsg(self, channel, user, msg):
"""
Send (a public) message to IRC channel
Parameters:
channel (str): Channel to post msg to
user (str): User to address message to
msg (str): Message to send
"""
2022-08-10 20:58:51 +02:00
self.irc.send(bytes("PRIVMSG "+channel+" :"+user+" "+msg+"\r\n", "UTF-8"))
def get_response(self):
2022-08-12 18:54:40 +02:00
"""Get and process response from IRC"""
2022-08-10 20:58:51 +02:00
try:
resp = self.irc.recv(2048).decode("UTF-8")
logging.debug('resp:')
logging.debug(resp)
except socket.timeout:
return False
2022-08-12 18:54:40 +02:00
except Exception: # pylint: disable=broad-except
2022-08-12 10:06:42 +02:00
logging.exception('An unknown error occured while getting a IRC response.')
2022-08-10 20:58:51 +02:00
sys.exit(255)
if resp.find('PING') != -1:
2022-08-11 10:46:12 +02:00
logging.debug('PING received')
2022-08-10 20:58:51 +02:00
self.irc.send(bytes('PONG :tmi.twitch.tv\r\n', "UTF-8"))
if resp.find('CLEARMSG') != -1:
logging.info('CLEARMSG received')
msgid = False
2022-08-12 18:54:40 +02:00
global msg_queue_raw # pylint: disable=global-statement,invalid-name
filtered_msg_queue = []
2022-08-10 20:58:51 +02:00
tags = resp.split(';')
for tag in tags:
if "target-msg-id=" in tag:
msgid = tag.rsplit('target-msg-id=',1)[1]
logging.debug('Trying to suppress message')
logging.debug(msgid)
for msg in list(msg_queue_raw):
if msg['msgid'] == msgid:
2022-08-12 18:54:40 +02:00
logging.info('Suppressing message %s', msgid)
else:
filtered_msg_queue.append(msg)
msg_queue_raw = filtered_msg_queue
2022-08-10 20:58:51 +02:00
return True
2022-08-12 10:06:42 +02:00
if resp.find('NOTICE') != -1:
if 'Login authentication failed' in resp:
try:
raise RuntimeError()
except RuntimeError:
logging.exception('Login failed, please check your credentials and try again.')
sys.exit(251)
2022-08-10 20:58:51 +02:00
if resp.find('PRIVMSG') != -1:
logging.debug('PRIVMSG received')
badges = False
subscriber = False
msgid = False
msg = False
msglen = False
user = False
tts = False
tags = resp.split(';')
for tag in tags:
if tag.startswith('badges='):
badges = tag.rsplit('badges=',1)[1]
if tag.startswith('subscriber='):
subscriber = tag.rsplit('subscriber=',1)[1]
2022-08-12 18:54:40 +02:00
logging.debug('Subscriber: %s', subscriber)
2022-08-10 20:58:51 +02:00
if tag.startswith('id='):
msgid = tag.rsplit('id=',1)[1]
2022-08-12 18:54:40 +02:00
logging.debug('Message ID: %s', msgid)
2022-08-10 20:58:51 +02:00
if tag.startswith('display-name='):
2022-08-11 20:04:01 +02:00
user = tag.rsplit('display-name=',1)[1].lower()
2022-08-12 18:54:40 +02:00
logging.debug('Username: %s', user)
2022-08-10 20:58:51 +02:00
msg = resp.rsplit('PRIVMSG #',1)[1]
msg = msg.split(':',1)[1]
msg = msg.replace('\r\n','')
msglen = len(msg)
2022-08-12 18:54:40 +02:00
logging.debug('Msg: %s', msg)
logging.debug('Msg length: %s', msglen)
logging.debug('Deny List:')
logging.debug(self.tts_denied)
2022-08-10 20:58:51 +02:00
if 'broadcaster' in badges or 'moderator' in badges:
if msg.startswith('!dtts'):
logging.debug("!dtts command detected")
2022-08-11 20:04:01 +02:00
user = msg.replace('!dtts', '').strip().lower()
if user.startswith('@'):
logging.debug('Removing "@" from username')
user = user.replace('@', '')
2022-08-10 20:58:51 +02:00
if user not in self.tts_denied:
2022-08-12 18:54:40 +02:00
logging.info("Adding %s to deny list", user)
2022-08-10 20:58:51 +02:00
self.tts_denied.append(user)
if user in self.tts_allowed:
2022-08-12 18:54:40 +02:00
logging.info("Removing %s from allowed list", user)
2022-08-10 20:58:51 +02:00
self.tts_allowed.remove(user)
return True
2022-08-12 10:19:00 +02:00
if msg.startswith('!ping'):
2022-08-12 18:54:40 +02:00
logging.debug("Ping check received.")
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), "Pong!")
return True
if msg.startswith('!quickvote'):
logging.info("!quickvote command detected")
if self.quickvote:
logging.debug('Quickvote stopped')
if self.pollcount == 0:
logging.info("Nobody voted")
self.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTEEND'])
self.sendmsg(conf['IRC_CHANNEL'], "*", conf['MESSAGE']['VOTENOBODY'])
self.quickvote = False
self.poll = {}
return False
logging.info("Counting votes")
count = 0
count = Counter(self.poll.values()).most_common(5)
self.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTEEND'])
logging.debug(count)
for key, value in count:
self.sendmsg(
conf['IRC_CHANNEL'], "*",
str(key)+" ("+str(value)+ " "+ conf['MESSAGE']['VOTES'] + ")"
)
self.quickvote = False
self.poll = {}
self.pollcount = 0
return True
else:
logging.debug('Quickvote started')
self.quickvote = True
self.votemsg = resp.split('!quickvote', 1)[1].strip()
if self.votemsg:
self.sendmsg(
conf['IRC_CHANNEL'], "@chat",
conf['MESSAGE']['VOTESTART'] + " (" + str(self.votemsg) + ")"
)
else:
self.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTESTART'])
return True
2022-08-12 10:19:00 +02:00
2022-08-12 10:22:38 +02:00
return True
2022-08-10 20:58:51 +02:00
if msg.startswith('!ptts'):
logging.debug("!ptts command detected")
2022-08-11 20:04:01 +02:00
user = msg.replace('!ptts', '').strip().lower()
2022-08-10 20:58:51 +02:00
if user.startswith('@'):
logging.debug('Removing "@" from username')
user = user.replace('@', '')
2022-08-12 18:54:40 +02:00
logging.info("Adding %s to whitelist", user)
2022-08-10 20:58:51 +02:00
self.tts_allowed.append(user)
if user in self.tts_denied:
2022-08-12 18:54:40 +02:00
logging.info("Removing %s from deny list", user)
2022-08-10 20:58:51 +02:00
self.tts_denied.remove(user)
return True
2022-08-10 20:58:51 +02:00
2022-08-12 18:54:40 +02:00
if msg.startswith('#') and self.quickvote is True:
logging.info('Quickvote: Cast detected')
self.pollcount += 1
self.poll[user] = msg.lower()
logging.debug(self.poll)
2022-08-10 20:58:51 +02:00
if msg.startswith('!toff'):
logging.info('TTS is now turned off')
msg_queue.clear()
2022-08-11 20:38:30 +02:00
msg_queue_raw.clear()
2022-08-10 20:58:51 +02:00
self.tts_status = False
2022-08-12 18:54:40 +02:00
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOFF'])
return True
2022-08-10 20:58:51 +02:00
if msg.startswith('!ton'):
logging.info('TTS is now turned on')
msg_queue.clear()
2022-08-11 20:38:30 +02:00
msg_queue_raw.clear()
2022-08-10 20:58:51 +02:00
self.tts_status = True
2022-08-12 18:54:40 +02:00
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TON'])
2022-08-10 20:58:51 +02:00
return True
2022-08-10 20:58:51 +02:00
if msg.startswith('!tts'):
logging.debug('!tts command detected')
if msglen > conf['IRC_TTS_LEN']:
logging.info('TTS message is to long')
2022-08-12 18:54:40 +02:00
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOO_LONG'])
2022-08-10 20:58:51 +02:00
return False
2022-08-12 18:54:40 +02:00
logging.debug("tts status: %s", self.tts_status)
logging.debug(conf['TTS_STARTENABLED'])
2022-08-10 20:58:51 +02:00
if not self.tts_status:
logging.info('TTS is disabled')
2022-08-12 18:54:40 +02:00
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DISABLED'])
2022-08-10 20:58:51 +02:00
return False
if user in self.tts_denied:
2022-08-12 18:54:40 +02:00
logging.info("%s is not allowed to use TTS", user)
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DENIED'])
2022-08-10 20:58:51 +02:00
return False
if conf['IRC_SUBONLY']:
if subscriber != "0" or 'moderator' in badges or 'broadcaster' in badges:
logging.debug('TTS is sub-only and user has allowance')
else:
logging.info('TTS is sub-only')
2022-08-12 18:54:40 +02:00
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['SUBONLY'])
2022-08-10 20:58:51 +02:00
return False
if conf['IRC_MODONLY']:
if 'moderator' in badges or 'broadcaster' in badges:
logging.debug('TTS is mod-only and user has allowance')
else:
logging.info('TTS is sub-only')
2022-08-12 18:54:40 +02:00
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['MODONLY'])
2022-08-10 20:58:51 +02:00
return False
if conf['WHITELIST']:
2022-08-12 18:54:40 +02:00
if user not in self.tts_allowed:
2022-08-10 20:58:51 +02:00
logging.info('User is not on whitelist')
logging.info(self.tts_allowed)
2022-08-12 18:54:40 +02:00
self.sendmsg(
conf['IRC_CHANNEL'],
"@"+str(user), conf['MESSAGE']['WHITELISTONLY']
)
2022-08-10 20:58:51 +02:00
return False
else:
logging.info('Nobody is on the whitelist.')
2022-08-12 18:54:40 +02:00
self.sendmsg(
conf['IRC_CHANNEL'],
"@"+str(user), conf['MESSAGE']['WHITELISTONLY']
)
2022-08-10 20:58:51 +02:00
return False
logging.info('Valid TTS message, adding to raw queue')
tts = True
now = datetime.datetime.now()
msg = msg.replace('!tts','',1)
2022-08-12 18:54:40 +02:00
msg = {
"TTS": tts,
"msg": msg,
"badges": badges,
"subscriber": subscriber,
"msgid": msgid,
"user": user,
"length": msglen,
"queuetime": now,
"timestamp": str(time.time_ns())
}
2022-08-10 20:58:51 +02:00
msg_queue_raw.append(msg)
return True
return False
2022-08-10 20:58:51 +02:00
class HTTPserv(BaseHTTPRequestHandler):
2022-08-12 18:54:40 +02:00
"""Simple HTTP Server"""
def log_message(self, format, *args): # pylint: disable=redefined-builtin
"""Suppress HTTP log messages"""
return
2022-08-12 18:54:40 +02:00
def do_GET(self): # pylint: disable=invalid-name
"""Process GET requests"""
2022-08-10 20:58:51 +02:00
if self.path == '/':
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
2022-08-12 18:54:40 +02:00
with open("tts.html", "rb") as file:
html = file.read()
2022-08-10 20:58:51 +02:00
self.wfile.write(html)
elif self.path == '/favicon.ico':
self.send_response(200)
self.send_header('Content-type', 'image/x-icon')
self.end_headers()
2022-08-12 18:54:40 +02:00
with open("favicon.ico", "rb") as file:
icon = file.read()
self.wfile.write(icon)
2022-08-11 10:42:56 +02:00
elif self.path == '/tts.js':
2022-08-10 20:58:51 +02:00
self.send_response(200)
self.send_header('Content-type', 'text/javascript')
self.end_headers()
2022-08-12 18:54:40 +02:00
with open("tts.js", "rb") as file:
html = file.read()
2022-08-10 20:58:51 +02:00
self.wfile.write(html)
2022-08-11 10:42:56 +02:00
elif self.path == '/jquery.js':
2022-08-10 20:58:51 +02:00
self.send_response(200)
self.send_header('Content-type', 'text/javascript')
self.end_headers()
2022-08-12 18:54:40 +02:00
with open("jquery.js", "rb") as file:
html = file.read()
2022-08-10 20:58:51 +02:00
self.wfile.write(html)
2022-08-11 10:42:56 +02:00
elif self.path == '/bootstrap.min.css':
2022-08-10 20:58:51 +02:00
self.send_response(200)
self.send_header('Content-type', 'text/css')
self.end_headers()
2022-08-12 18:54:40 +02:00
with open("bootstrap.min.css", "rb") as file:
html = file.read()
2022-08-10 20:58:51 +02:00
self.wfile.write(html)
2022-08-11 10:42:56 +02:00
elif self.path.startswith('/tts_queue'):
2022-08-10 20:58:51 +02:00
tts_json = ""
tts = {}
self.send_response(200)
self.send_header('Content-type', 'text/json')
self.end_headers()
usermap = conf['USERMAP']
sorted_tts = {k: msg_queue[k] for k in sorted(msg_queue, reverse=False)}
for key in list(sorted_tts.keys()):
if key not in tts_done:
if msg_queue[key][0].lower() in usermap:
2022-08-12 18:54:40 +02:00
tts = {str(key): str(usermap[msg_queue[key][0].lower()]) + " " + str(conf['MESSAGE']['SAYS']) + ":" + str(msg_queue[key][1])}
2022-08-10 20:58:51 +02:00
else:
2022-08-12 18:54:40 +02:00
tts = {str(key): str(msg_queue[key][0]) + " " + str(conf['MESSAGE']['SAYS']) + ":" + str(msg_queue[key][1])}
2022-08-10 20:58:51 +02:00
tts_json = json.dumps(tts)
self.wfile.write(bytes(str(tts_json)+"\n", "utf-8"))
2022-08-11 10:42:56 +02:00
elif self.path.startswith('/tts_done'):
2022-08-10 20:58:51 +02:00
get_params = parse_qs(self.path)
if '/tts_done?id' in get_params:
logging.info("Removing message from queue")
tts_done.append(get_params['/tts_done?id'][0])
self.send_response(200)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(bytes("OK\n", "utf-8"))
else:
self.send_response(500)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(bytes("Internal Server error\n", "utf-8"))
2022-08-12 18:54:40 +02:00
elif self.path.startswith('/token') and conf['IRC_OAUTH_TOKEN'] == "Invalid":
data = {}
data['client_id'] = "ebo548vs6tq54c9zlrgin2yfzzlrrs"
data['response_type'] = "token"
data['scope'] = "chat:edit chat:read"
if conf['HTTP_PORT'] == 80:
data['redirect_uri'] = "http://localhost/token"
elif conf['HTTP_PORT'] == 8080:
data['redirect_uri'] = "http://localhost:8080/token"
elif conf['HTTP_PORT'] == 3000:
data['redirect_uri'] = "http://localhost:3000/token"
else:
self.send_response(500)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(bytes("You can only use this function if HTTP_PORT is 80, 8080 or 3000. Please change your port, or use https://www.21x9.org/twitch instead.\n", "utf-8"))
return False
try:
url_values = urllib.parse.urlencode(data)
url = "https://id.twitch.tv/oauth2/authorize"
full_url = url + "?" + url_values
data = urllib.request.urlopen(full_url)
if data:
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(bytes("<html><head><title>OAuth Token Generator</title></head><body onload=\"displayCode();\"><div id=\"code\"><a href=\""+str(data.geturl())+"\">Click to start the OAuth process.</a></div><script>function displayCode() { var url = window.location.href; var test = url.indexOf(\"access_token\");if (test != -1) { token = url.substring(42,72); document.getElementById(\"code\").innerHTML = \"<p>oauth:\" + token + \"</p><p>Copy the token into your config.yml and restart the bot.</p>\";}}</script></body></html>\n", "utf-8"))
else:
self.send_response(500)
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(bytes("Could not get OAuth-URL from Twitch\n", "utf-8"))
except Exception: # pylint: disable=broad-except
logging.error('Could not fetch OAuth-URL from Twitch.')
2022-08-11 10:42:56 +02:00
else:
self.send_response(404)
self.send_header('Server', 'TTS')
self.send_header('Content-type', 'text/plain')
self.end_headers()
self.wfile.write(bytes("File not found.\n", "utf-8"))
2022-08-10 20:58:51 +02:00
return
def http_serve_forever(httpd):
2022-08-12 18:54:40 +02:00
"""httpd loop"""
httpd.serve_forever()
2022-08-10 20:58:51 +02:00
def load_config():
2022-08-12 18:54:40 +02:00
"""Loading config variables"""
2022-08-10 20:58:51 +02:00
logging.info("Loading configfile")
try:
2022-08-12 18:54:40 +02:00
with open("config.yml", "r", encoding="UTF-8") as ymlfile:
2022-08-10 20:58:51 +02:00
cfg = yaml.load(ymlfile, Loader=yaml.Loader)
except FileNotFoundError:
logging.fatal('Your config file is missing, please copy config-dist.yml to config.yml and review your settings.')
sys.exit(253)
for section in cfg:
try:
2022-08-12 18:54:40 +02:00
logging.debug('Fetching config: %s', section)
2022-08-10 20:58:51 +02:00
conf['IRC_CHANNEL'] = cfg['irc']['channel']
conf['IRC_USERNAME'] = cfg['irc']['username']
conf['IRC_OAUTH_TOKEN'] = cfg['irc']['oauth_token']
2022-08-12 10:06:42 +02:00
conf['IRC_SERVER'] = cfg['irc']['server'] or "irc.chat.twitch.tv"
conf['IRC_CLEARMSG_TIMEOUT'] = cfg['irc']['clearmsg_timeout'] or 60
2022-08-12 18:54:40 +02:00
2022-08-12 10:06:42 +02:00
conf['IRC_SUBONLY'] = cfg['bot']['subonly'] or False
conf['IRC_MODONLY'] = cfg['bot']['modonly'] or False
conf['IRC_TTS_LEN'] = cfg['bot']['message_length'] or 200
2022-08-12 18:54:40 +02:00
conf['TTS_STARTENABLED'] = cfg['bot']['start_enabled'] or False
2022-08-12 10:06:42 +02:00
conf['LOG_LEVEL'] = cfg['log']['level'] or "INFO"
conf['HTTP_PORT'] = cfg['http']['port'] or 80
conf['HTTP_BIND'] = cfg['http']['bind'] or "localhost"
2022-08-10 20:58:51 +02:00
conf['MESSAGE'] = {}
2022-08-12 10:15:15 +02:00
conf['MESSAGE']['TOFF'] = cfg['messages']['toff'] or "TTS is now disabled."
conf['MESSAGE']['TON'] = cfg['messages']['ton'] or "TTS is now active."
conf['MESSAGE']['TOO_LONG'] = cfg['messages']['too_long'] or "Sorry, your message is too long."
2022-08-11 10:26:33 +02:00
conf['MESSAGE']['DISABLED'] = cfg['messages']['disabled'] or "Sorry, TTS is disabled."
conf['MESSAGE']['DENIED'] = cfg['messages']['denied'] or "Sorry, you're not allowed to use TTS."
conf['MESSAGE']['SUBONLY'] = cfg['messages']['subonly'] or "Sorry, TTS is sub-only."
conf['MESSAGE']['MODONLY'] = cfg['messages']['modonly'] or "Sorry, TTS is mod-only."
conf['MESSAGE']['READY'] = cfg['messages']['ready'] or "TTS bot is ready."
conf['MESSAGE']['WHITELISTONLY'] = cfg['messages']['whitelist'] or False
conf['MESSAGE']['SAYS'] = cfg['messages']['says'] or "says"
2022-08-10 20:58:51 +02:00
2022-08-12 18:54:40 +02:00
conf['MESSAGE']['VOTESTART'] = cfg['messages']['votestart'] or "Quickvote started. Send #yourchoice to participate."
conf['MESSAGE']['VOTEEND'] = cfg['messages']['voteend'] or "Quickvote ended. The results are:"
conf['MESSAGE']['VOTENOBODY'] = cfg['messages']['votenobody'] or "Nobody casted a vote. :("
conf['MESSAGE']['VOTES'] = cfg['messages']['votes'] or "Stimmen"
2022-08-11 10:26:33 +02:00
conf['USERMAP'] = cfg['usermapping'] or []
2022-08-10 20:58:51 +02:00
if 'whitelist' in cfg:
conf['WHITELIST'] = True
conf['WHITELIST_USER'] = cfg['whitelist']
else:
conf['WHITELIST'] = False
2022-08-11 10:26:33 +02:00
except KeyError:
2022-08-10 20:58:51 +02:00
logging.exception('Your config file is invalid, please check and try again.')
sys.exit(254)
if conf['WHITELIST']:
logging.info('Whitelist mode enabled')
logging.debug('Whitelist:')
logging.debug(conf['WHITELIST_USER'])
2022-08-11 10:26:33 +02:00
if not conf['IRC_CHANNEL']:
raise ValueError('Please add your twitch channel to config.yml.')
if not conf['IRC_USERNAME']:
raise ValueError('Please add the bots username to config.yml.')
if not conf['IRC_OAUTH_TOKEN']:
2022-08-12 18:54:40 +02:00
conf['IRC_OAUTH_TOKEN'] = "Invalid"
return conf
2022-08-11 10:26:33 +02:00
if not conf['IRC_OAUTH_TOKEN'].startswith('oauth:'):
raise ValueError('Your oauth-token is invalid, it has to start with: "oauth:"')
2022-08-10 20:58:51 +02:00
return conf
conf = {}
tts_done = []
msg_queue_raw = []
msg_queue = {}
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(module)s %(threadName)s %(levelname)s: %(message)s')
2022-08-12 10:06:42 +02:00
sys.tracebacklimit = 0
2022-08-10 20:58:51 +02:00
2022-08-12 19:05:49 +02:00
if sys.argv[1:]:
if sys.argv[1] == "--version":
print('Simple TTS Bot')
print('Version 1.1.0')
sys.exit(1)
2022-08-10 20:58:51 +02:00
def main():
2022-08-12 18:54:40 +02:00
"""Main loop"""
global conf # pylint: disable=global-statement,invalid-name
2022-08-10 20:58:51 +02:00
conf = load_config()
lastreload = datetime.datetime.now()
logging.getLogger().setLevel(conf['LOG_LEVEL'])
2022-08-11 10:26:33 +02:00
if conf['LOG_LEVEL'] == 'DEBUG':
sys.tracebacklimit = 5
2022-08-12 18:54:40 +02:00
2022-08-10 20:58:51 +02:00
logging.info("Starting Webserver")
httpd = socketserver.TCPServer((conf['HTTP_BIND'], conf['HTTP_PORT']), HTTPserv)
httpd.allow_reuse_port = True
httpd.allow_reuse_address = True
http_thread = Thread(target=http_serve_forever, daemon=True, args=(httpd, ))
http_thread.start()
2022-08-12 18:54:40 +02:00
if conf['IRC_OAUTH_TOKEN'] == "Invalid":
logging.error('No OAuth Token, skipping start of IRC bot.')
logging.error('Please open http://%s:%s/token to generate your OAuth-Token.', conf['HTTP_BIND'], conf['HTTP_PORT'])
url = 'http://'+str(conf['HTTP_BIND'])+':'+str(conf['HTTP_PORT'])+'/token'
webbrowser.open_new_tab(url)
logging.info('Please complete the OAuth process within the next 15 minutes.')
time.sleep(900)
sys.exit(250)
else:
logging.info("Starting IRC bot")
irc = IRC()
irc.connect(conf['IRC_SERVER'], 6667, conf['IRC_CHANNEL'], conf['IRC_USERNAME'], conf['IRC_OAUTH_TOKEN'])
irc.sendmsg(conf['IRC_CHANNEL'], 'MrDestructoid', conf['MESSAGE']['READY'])
logging.info("Please open your browser and visit: http://%s:%s/", conf['HTTP_BIND'], conf['HTTP_PORT'])
url = 'http://'+str(conf['HTTP_BIND'])+':'+str(conf['HTTP_PORT'])
webbrowser.open_new_tab(url)
while True:
if conf['LOG_LEVEL'] == "DEBUG":
time.sleep(1)
try:
irc.get_response()
if not irc.tts_status:
logging.debug("TTS is disabled")
if conf['LOG_LEVEL'] == "DEBUG":
time.sleep(1)
continue
confreload = datetime.datetime.now()
if confreload - lastreload > datetime.timedelta(seconds=60):
conf = load_config()
lastreload = datetime.datetime.now()
if irc.quickvote and irc.votemsg:
logging.info('Quickvote is active')
irc.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTESTART'] + " (" + str(irc.votemsg) + ")")
logging.debug('Raw message queue:')
2022-08-10 20:58:51 +02:00
logging.debug(msg_queue_raw)
2022-08-12 18:54:40 +02:00
for raw_msg in msg_queue_raw:
logging.debug('Raw msg:')
logging.debug(msg_queue_raw)
now = datetime.datetime.now()
if now - raw_msg['queuetime'] > datetime.timedelta(seconds=conf['IRC_CLEARMSG_TIMEOUT']):
logging.debug('clearmsg_timeout reached')
if raw_msg['timestamp'] not in msg_queue:
logging.info('Sending TTS message')
msg_queue[raw_msg['timestamp']] = [raw_msg['user'], raw_msg['msg']]
logging.debug(msg_queue)
else:
logging.debug('Msg is already in queue')
except KeyboardInterrupt:
logging.info('Exiting...')
sys.exit()
2022-08-10 20:58:51 +02:00
if __name__ == "__main__":
main()