mirror of https://gitlab.com/gpvkt/twitchtts.git
Experimental
This commit is contained in:
parent
74240d03f1
commit
facb86e15f
20
CHANGELOG.md
20
CHANGELOG.md
|
@ -2,12 +2,26 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## [1.0.0] - 2022-08-11
|
## [1.1.0] - unreleased
|
||||||
|
|
||||||
Initial Release
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
* `!quickvote` feature (see README.md for details)
|
||||||
|
* `!ping` command added
|
||||||
|
|
||||||
|
* Configoption to start TTS in disabled mode
|
||||||
|
* OAuth-Token generator
|
||||||
|
* Webbrowser autostart
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
* You need to review your `config.yml` as there a new config values added.
|
||||||
|
* The bot replies with a chat message when `!ton` or `!toff` is used
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
* Improved error handling
|
||||||
|
|
||||||
|
## [1.0.0] - 2022-08-11
|
||||||
|
|
||||||
|
Initial Release
|
||||||
|
|
20
README.md
20
README.md
|
@ -45,6 +45,7 @@ http:
|
||||||
bind: "localhost"
|
bind: "localhost"
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
|
start_enabled: True
|
||||||
subonly: False
|
subonly: False
|
||||||
modonly: False
|
modonly: False
|
||||||
message_length: 200
|
message_length: 200
|
||||||
|
@ -59,6 +60,10 @@ messages:
|
||||||
whitelist: "Sorry, you are not allowed to use TTS."
|
whitelist: "Sorry, you are not allowed to use TTS."
|
||||||
ready: "TTS bot alpha ready!"
|
ready: "TTS bot alpha ready!"
|
||||||
says: "says"
|
says: "says"
|
||||||
|
votestart: "Quickvote started. Send #yourchoice to participate."
|
||||||
|
voteend: "Quickvote ended. The results are:"
|
||||||
|
votenobody: "Nobody casted a vote. :("
|
||||||
|
votes: "Votes"
|
||||||
|
|
||||||
log:
|
log:
|
||||||
level: "INFO"
|
level: "INFO"
|
||||||
|
@ -80,6 +85,10 @@ whitelist:
|
||||||
* `server`: Twitch IRC server to be used (default should be fine)
|
* `server`: Twitch IRC server to be used (default should be fine)
|
||||||
* `clearmsg_timeout`: Time to wait for an moderator to delete a message, before it's added to the TTS queue
|
* `clearmsg_timeout`: Time to wait for an moderator to delete a message, before it's added to the TTS queue
|
||||||
|
|
||||||
|
You can generate your `oauth_token` by leaving the value empty when starting `tts.exe/tts.py`. The integrated webserver will then provide an OAuth-Generator. Due to limitations to the `redirect_url` parameter used by twitch, this is only possible if you use Port `8080` or `80` as `http:bind`. If you use a different port, you will need to use another [Twitch OAuth Generator](https://html.duckduckgo.com/html/?q=twitch+oauth+token+generator).
|
||||||
|
|
||||||
|
Please note that the `oauth_token` is valid for approximately 60 days. If it become invalid the bot will not connect anymore and you will have to renew the token.
|
||||||
|
|
||||||
##### http
|
##### http
|
||||||
|
|
||||||
* `port`: Internal Webserver Port to listen to (e.g. 8080)
|
* `port`: Internal Webserver Port to listen to (e.g. 8080)
|
||||||
|
@ -87,6 +96,7 @@ whitelist:
|
||||||
|
|
||||||
##### bot
|
##### bot
|
||||||
|
|
||||||
|
* `start_enabled`: Enable the bot on start? If `False` you need to use `!ton` first to make TTS work.
|
||||||
* `subonly`: If `True` only Subs can use TTS
|
* `subonly`: If `True` only Subs can use TTS
|
||||||
* `modonly`: If `True` only Mods can use TTS
|
* `modonly`: If `True` only Mods can use TTS
|
||||||
* `message_length`: Maximum allowed message length for TTS
|
* `message_length`: Maximum allowed message length for TTS
|
||||||
|
@ -102,11 +112,17 @@ whitelist:
|
||||||
* `whitelist`: The bots reply if `whitelist` is set and user isn't on the list.
|
* `whitelist`: The bots reply if `whitelist` is set and user isn't on the list.
|
||||||
* `ready`: The bots init message
|
* `ready`: The bots init message
|
||||||
* `says`: Prefix to add between username and message
|
* `says`: Prefix to add between username and message
|
||||||
|
* `votestart`: Message when a quickvote is started.
|
||||||
|
* `voteend`: Message if a quickvote ends.
|
||||||
|
* `votenobody`: Message if quickvote ends, but nobody has voted.
|
||||||
|
* `votes`: Suffix to vote count.
|
||||||
|
|
||||||
##### log
|
##### log
|
||||||
|
|
||||||
* `level`: The loglevel, valid values are: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
|
* `level`: The loglevel, valid values are: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
|
||||||
|
|
||||||
|
Do not use `DEBUG` in a production environment.
|
||||||
|
|
||||||
##### usermapping
|
##### usermapping
|
||||||
|
|
||||||
Use this section to define key:value pairs of usernames. The first value is the Twitch username, the second value is how the bot should pronouce the user, when reading the message. This is helpfull if you have regulars with numbers or strangs chars in the name. You can add new/change entries on the fly without restarting the bot (changes took up to 60 seconds).
|
Use this section to define key:value pairs of usernames. The first value is the Twitch username, the second value is how the bot should pronouce the user, when reading the message. This is helpfull if you have regulars with numbers or strangs chars in the name. You can add new/change entries on the fly without restarting the bot (changes took up to 60 seconds).
|
||||||
|
@ -146,6 +162,10 @@ Additional commands (broadcaster and mods only) are:
|
||||||
* `!dtts <username>`: Disable TTS for the given user
|
* `!dtts <username>`: Disable TTS for the given user
|
||||||
* `!ptts <username>`: Allow TTS for the given user
|
* `!ptts <username>`: Allow TTS for the given user
|
||||||
|
|
||||||
|
### Additional features
|
||||||
|
|
||||||
|
The bot also contains a `!quickvote` feature. If a broadcaster or moderator send the `!quickvote` command a vote will be started (or a already running vote will be ended). After a quickvote has been started your community can casts votes by sending a chat message starting with `#`. You can include a message after `!quickvote` (e.g. `!quickvote Is pizza hawaii any good? #yes/#no`). If you do so, this message will be repeated every 60 seconds, so everyone keeps in mind, that a vote is still active.
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
If you prefer to build your own `tts.exe` instead of using the shipped one, you can do as follows:
|
If you prefer to build your own `tts.exe` instead of using the shipped one, you can do as follows:
|
||||||
|
|
|
@ -10,6 +10,7 @@ http:
|
||||||
bind: "localhost"
|
bind: "localhost"
|
||||||
|
|
||||||
bot:
|
bot:
|
||||||
|
start_enabled: True
|
||||||
subonly: False
|
subonly: False
|
||||||
modonly: False
|
modonly: False
|
||||||
message_length: 200
|
message_length: 200
|
||||||
|
@ -25,6 +26,10 @@ messages:
|
||||||
modonly: "Sorry, TTS is a mod-only feature."
|
modonly: "Sorry, TTS is a mod-only feature."
|
||||||
ready: "TTS bot alpha ready!"
|
ready: "TTS bot alpha ready!"
|
||||||
says: "says"
|
says: "says"
|
||||||
|
votestart: "Quickvote started. Send #yourchoice to participate."
|
||||||
|
voteend: "Quickvote ended. The results are:"
|
||||||
|
votenobody: "Nobody casted a vote. :("
|
||||||
|
votes: "Votes"
|
||||||
|
|
||||||
log:
|
log:
|
||||||
level: "INFO"
|
level: "INFO"
|
||||||
|
|
335
tts.py
335
tts.py
|
@ -1,5 +1,6 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
# pylint: disable=line-too-long
|
||||||
|
|
||||||
"""
|
"""
|
||||||
TwitchTTS
|
TwitchTTS
|
||||||
|
@ -20,32 +21,43 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import yaml
|
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
import socketserver
|
import socketserver
|
||||||
|
import urllib.request
|
||||||
|
import urllib.parse
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from http.server import BaseHTTPRequestHandler
|
from http.server import BaseHTTPRequestHandler
|
||||||
from urllib.parse import parse_qs
|
from urllib.parse import parse_qs
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
class IRC:
|
class IRC:
|
||||||
|
"""IRC bot"""
|
||||||
irc = socket.socket()
|
irc = socket.socket()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
self.irc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
self.tts_denied = []
|
self.tts_denied = []
|
||||||
self.tts_allowed = []
|
self.tts_allowed = []
|
||||||
self.tts_status = True
|
self.tts_status = conf['TTS_STARTENABLED']
|
||||||
|
self.quickvote = False
|
||||||
|
self.votemsg = False
|
||||||
|
self.poll = {}
|
||||||
|
self.pollcount = 0
|
||||||
|
|
||||||
if 'WHITELIST_USER' in conf:
|
if 'WHITELIST_USER' in conf:
|
||||||
self.tts_allowed = conf['WHITELIST_USER']
|
self.tts_allowed = conf['WHITELIST_USER']
|
||||||
|
|
||||||
def connect(self, server, port, channel, botnick, botpass):
|
def connect(self, server, port, channel, botnick, botpass):
|
||||||
logging.info("Connecting to: " + server)
|
"""Connect to Twitch IRC servers"""
|
||||||
|
logging.info("Connecting to: %s", server)
|
||||||
try:
|
try:
|
||||||
self.irc.connect((server, port))
|
self.irc.connect((server, port))
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
|
@ -55,7 +67,9 @@ class IRC:
|
||||||
self.irc.settimeout(1)
|
self.irc.settimeout(1)
|
||||||
|
|
||||||
self.irc.send(bytes("PASS " + botpass + "\r\n", "UTF-8"))
|
self.irc.send(bytes("PASS " + botpass + "\r\n", "UTF-8"))
|
||||||
self.irc.send(bytes("USER " + botnick + " " + botnick +" " + botnick + " :python\r\n", "UTF-8"))
|
self.irc.send(bytes(
|
||||||
|
"USER " + botnick + " " + botnick +" " + botnick + " :python\r\n", "UTF-8")
|
||||||
|
)
|
||||||
self.irc.send(bytes("NICK " + botnick + "\r\n", "UTF-8"))
|
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"))
|
self.irc.send(bytes("CAP REQ :twitch.tv/commands twitch.tv/tags \r\n", "UTF-8"))
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
@ -63,22 +77,31 @@ class IRC:
|
||||||
try:
|
try:
|
||||||
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
|
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
logging.warn('JOIN was refused, will try again in 5 seconds.')
|
logging.warning('JOIN was refused, will try again in 5 seconds.')
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
logging.warn('Please check your credentials, if this error persists.')
|
logging.warning('Please check your credentials, if this error persists.')
|
||||||
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
|
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
|
||||||
|
|
||||||
def sendpriv(self, channel, user, msg):
|
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
|
||||||
|
"""
|
||||||
self.irc.send(bytes("PRIVMSG "+channel+" :"+user+" "+msg+"\r\n", "UTF-8"))
|
self.irc.send(bytes("PRIVMSG "+channel+" :"+user+" "+msg+"\r\n", "UTF-8"))
|
||||||
|
|
||||||
def get_response(self):
|
def get_response(self):
|
||||||
|
"""Get and process response from IRC"""
|
||||||
try:
|
try:
|
||||||
resp = self.irc.recv(2048).decode("UTF-8")
|
resp = self.irc.recv(2048).decode("UTF-8")
|
||||||
logging.debug('resp:')
|
logging.debug('resp:')
|
||||||
logging.debug(resp)
|
logging.debug(resp)
|
||||||
except socket.timeout:
|
except socket.timeout:
|
||||||
return False
|
return False
|
||||||
except:
|
except Exception: # pylint: disable=broad-except
|
||||||
logging.exception('An unknown error occured while getting a IRC response.')
|
logging.exception('An unknown error occured while getting a IRC response.')
|
||||||
sys.exit(255)
|
sys.exit(255)
|
||||||
|
|
||||||
|
@ -90,7 +113,7 @@ class IRC:
|
||||||
logging.info('CLEARMSG received')
|
logging.info('CLEARMSG received')
|
||||||
msgid = False
|
msgid = False
|
||||||
|
|
||||||
global msg_queue_raw
|
global msg_queue_raw # pylint: disable=global-statement,invalid-name
|
||||||
filtered_msg_queue = []
|
filtered_msg_queue = []
|
||||||
|
|
||||||
tags = resp.split(';')
|
tags = resp.split(';')
|
||||||
|
@ -102,7 +125,7 @@ class IRC:
|
||||||
|
|
||||||
for msg in list(msg_queue_raw):
|
for msg in list(msg_queue_raw):
|
||||||
if msg['msgid'] == msgid:
|
if msg['msgid'] == msgid:
|
||||||
logging.info('Suppressing message '+str(msgid))
|
logging.info('Suppressing message %s', msgid)
|
||||||
else:
|
else:
|
||||||
filtered_msg_queue.append(msg)
|
filtered_msg_queue.append(msg)
|
||||||
|
|
||||||
|
@ -134,22 +157,21 @@ class IRC:
|
||||||
badges = tag.rsplit('badges=',1)[1]
|
badges = tag.rsplit('badges=',1)[1]
|
||||||
if tag.startswith('subscriber='):
|
if tag.startswith('subscriber='):
|
||||||
subscriber = tag.rsplit('subscriber=',1)[1]
|
subscriber = tag.rsplit('subscriber=',1)[1]
|
||||||
logging.debug('Subscriber: '+str(subscriber))
|
logging.debug('Subscriber: %s', subscriber)
|
||||||
if tag.startswith('id='):
|
if tag.startswith('id='):
|
||||||
msgid = tag.rsplit('id=',1)[1]
|
msgid = tag.rsplit('id=',1)[1]
|
||||||
logging.debug('Message ID: '+str(msgid))
|
logging.debug('Message ID: %s', msgid)
|
||||||
if tag.startswith('display-name='):
|
if tag.startswith('display-name='):
|
||||||
user = tag.rsplit('display-name=',1)[1].lower()
|
user = tag.rsplit('display-name=',1)[1].lower()
|
||||||
logging.debug('Username: '+str(user))
|
logging.debug('Username: %s', user)
|
||||||
|
|
||||||
msg = resp.rsplit('PRIVMSG #',1)[1]
|
msg = resp.rsplit('PRIVMSG #',1)[1]
|
||||||
msg = msg.split(':',1)[1]
|
msg = msg.split(':',1)[1]
|
||||||
msg = msg.replace('\r\n','')
|
msg = msg.replace('\r\n','')
|
||||||
msglen = len(msg)
|
msglen = len(msg)
|
||||||
|
|
||||||
logging.debug('Msg:')
|
logging.debug('Msg: %s', msg)
|
||||||
logging.debug(msg)
|
logging.debug('Msg length: %s', msglen)
|
||||||
logging.debug('Msg length: '+str(msglen))
|
|
||||||
logging.debug('Deny List:')
|
logging.debug('Deny List:')
|
||||||
logging.debug(self.tts_denied)
|
logging.debug(self.tts_denied)
|
||||||
|
|
||||||
|
@ -162,17 +184,62 @@ class IRC:
|
||||||
logging.debug('Removing "@" from username')
|
logging.debug('Removing "@" from username')
|
||||||
user = user.replace('@', '')
|
user = user.replace('@', '')
|
||||||
if user not in self.tts_denied:
|
if user not in self.tts_denied:
|
||||||
logging.info("Adding "+str(user)+" to deny list")
|
logging.info("Adding %s to deny list", user)
|
||||||
self.tts_denied.append(user)
|
self.tts_denied.append(user)
|
||||||
if user in self.tts_allowed:
|
if user in self.tts_allowed:
|
||||||
logging.info("Removing "+str(user)+" from allowed list")
|
logging.info("Removing %s from allowed list", user)
|
||||||
self.tts_allowed.remove(user)
|
self.tts_allowed.remove(user)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
if msg.startswith('!ping'):
|
if msg.startswith('!ping'):
|
||||||
logging.debug('Ping check received.')
|
logging.debug("Ping check received.")
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), "Pong!")
|
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
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -184,21 +251,27 @@ class IRC:
|
||||||
logging.debug('Removing "@" from username')
|
logging.debug('Removing "@" from username')
|
||||||
user = user.replace('@', '')
|
user = user.replace('@', '')
|
||||||
|
|
||||||
logging.info("Adding "+str(user)+" to whitelist")
|
logging.info("Adding %s to whitelist", user)
|
||||||
self.tts_allowed.append(user)
|
self.tts_allowed.append(user)
|
||||||
|
|
||||||
if user in self.tts_denied:
|
if user in self.tts_denied:
|
||||||
logging.info("Removing "+str(user)+" from deny list")
|
logging.info("Removing %s from deny list", user)
|
||||||
self.tts_denied.remove(user)
|
self.tts_denied.remove(user)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
if msg.startswith('!toff'):
|
if msg.startswith('!toff'):
|
||||||
logging.info('TTS is now turned off')
|
logging.info('TTS is now turned off')
|
||||||
msg_queue.clear()
|
msg_queue.clear()
|
||||||
msg_queue_raw.clear()
|
msg_queue_raw.clear()
|
||||||
self.tts_status = False
|
self.tts_status = False
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOFF'])
|
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOFF'])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -207,7 +280,7 @@ class IRC:
|
||||||
msg_queue.clear()
|
msg_queue.clear()
|
||||||
msg_queue_raw.clear()
|
msg_queue_raw.clear()
|
||||||
self.tts_status = True
|
self.tts_status = True
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TON'])
|
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TON'])
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -216,17 +289,20 @@ class IRC:
|
||||||
|
|
||||||
if msglen > conf['IRC_TTS_LEN']:
|
if msglen > conf['IRC_TTS_LEN']:
|
||||||
logging.info('TTS message is to long')
|
logging.info('TTS message is to long')
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOO_LONG'])
|
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOO_LONG'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
logging.debug("tts status: %s", self.tts_status)
|
||||||
|
logging.debug(conf['TTS_STARTENABLED'])
|
||||||
|
|
||||||
if not self.tts_status:
|
if not self.tts_status:
|
||||||
logging.info('TTS is disabled')
|
logging.info('TTS is disabled')
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DISABLED'])
|
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DISABLED'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if user in self.tts_denied:
|
if user in self.tts_denied:
|
||||||
logging.info(str(user) + " is not allowed to use TTS")
|
logging.info("%s is not allowed to use TTS", user)
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DENIED'])
|
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DENIED'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if conf['IRC_SUBONLY']:
|
if conf['IRC_SUBONLY']:
|
||||||
|
@ -234,7 +310,7 @@ class IRC:
|
||||||
logging.debug('TTS is sub-only and user has allowance')
|
logging.debug('TTS is sub-only and user has allowance')
|
||||||
else:
|
else:
|
||||||
logging.info('TTS is sub-only')
|
logging.info('TTS is sub-only')
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['SUBONLY'])
|
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['SUBONLY'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if conf['IRC_MODONLY']:
|
if conf['IRC_MODONLY']:
|
||||||
|
@ -242,25 +318,41 @@ class IRC:
|
||||||
logging.debug('TTS is mod-only and user has allowance')
|
logging.debug('TTS is mod-only and user has allowance')
|
||||||
else:
|
else:
|
||||||
logging.info('TTS is sub-only')
|
logging.info('TTS is sub-only')
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['MODONLY'])
|
self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['MODONLY'])
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if conf['WHITELIST']:
|
if conf['WHITELIST']:
|
||||||
if not user in self.tts_allowed:
|
if user not in self.tts_allowed:
|
||||||
logging.info('User is not on whitelist')
|
logging.info('User is not on whitelist')
|
||||||
logging.info(self.tts_allowed)
|
logging.info(self.tts_allowed)
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['WHITELISTONLY'])
|
self.sendmsg(
|
||||||
|
conf['IRC_CHANNEL'],
|
||||||
|
"@"+str(user), conf['MESSAGE']['WHITELISTONLY']
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logging.info('Nobody is on the whitelist.')
|
logging.info('Nobody is on the whitelist.')
|
||||||
self.sendpriv(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['WHITELISTONLY'])
|
self.sendmsg(
|
||||||
|
conf['IRC_CHANNEL'],
|
||||||
|
"@"+str(user), conf['MESSAGE']['WHITELISTONLY']
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
logging.info('Valid TTS message, adding to raw queue')
|
logging.info('Valid TTS message, adding to raw queue')
|
||||||
tts = True
|
tts = True
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
msg = msg.replace('!tts','',1)
|
msg = msg.replace('!tts','',1)
|
||||||
msg = {"TTS": tts, "msg": msg, "badges": badges, "subscriber": subscriber, "msgid": msgid, "user": user, "length": msglen, "queuetime": now, "timestamp": str(time.time_ns())}
|
msg = {
|
||||||
|
"TTS": tts,
|
||||||
|
"msg": msg,
|
||||||
|
"badges": badges,
|
||||||
|
"subscriber": subscriber,
|
||||||
|
"msgid": msgid,
|
||||||
|
"user": user,
|
||||||
|
"length": msglen,
|
||||||
|
"queuetime": now,
|
||||||
|
"timestamp": str(time.time_ns())
|
||||||
|
}
|
||||||
msg_queue_raw.append(msg)
|
msg_queue_raw.append(msg)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -268,48 +360,52 @@ class IRC:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class HTTPserv(BaseHTTPRequestHandler):
|
class HTTPserv(BaseHTTPRequestHandler):
|
||||||
def log_message(self, format, *args):
|
"""Simple HTTP Server"""
|
||||||
|
|
||||||
|
def log_message(self, format, *args): # pylint: disable=redefined-builtin
|
||||||
|
"""Suppress HTTP log messages"""
|
||||||
return
|
return
|
||||||
|
|
||||||
def do_GET(self):
|
def do_GET(self): # pylint: disable=invalid-name
|
||||||
|
"""Process GET requests"""
|
||||||
if self.path == '/':
|
if self.path == '/':
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'text/html')
|
self.send_header('Content-type', 'text/html')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
with open("tts.html", "rb") as fh:
|
with open("tts.html", "rb") as file:
|
||||||
html = fh.read()
|
html = file.read()
|
||||||
self.wfile.write(html)
|
self.wfile.write(html)
|
||||||
|
|
||||||
elif self.path == '/favicon.ico':
|
elif self.path == '/favicon.ico':
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'image/x-icon')
|
self.send_header('Content-type', 'image/x-icon')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
with open("favicon.ico", "rb") as fh:
|
with open("favicon.ico", "rb") as file:
|
||||||
icon = fh.read()
|
icon = file.read()
|
||||||
self.wfile.write(icon)
|
self.wfile.write(icon)
|
||||||
|
|
||||||
elif self.path == '/tts.js':
|
elif self.path == '/tts.js':
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'text/javascript')
|
self.send_header('Content-type', 'text/javascript')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
with open("tts.js", "rb") as fh:
|
with open("tts.js", "rb") as file:
|
||||||
html = fh.read()
|
html = file.read()
|
||||||
self.wfile.write(html)
|
self.wfile.write(html)
|
||||||
|
|
||||||
elif self.path == '/jquery.js':
|
elif self.path == '/jquery.js':
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'text/javascript')
|
self.send_header('Content-type', 'text/javascript')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
with open("jquery.js", "rb") as fh:
|
with open("jquery.js", "rb") as file:
|
||||||
html = fh.read()
|
html = file.read()
|
||||||
self.wfile.write(html)
|
self.wfile.write(html)
|
||||||
|
|
||||||
elif self.path == '/bootstrap.min.css':
|
elif self.path == '/bootstrap.min.css':
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header('Content-type', 'text/css')
|
self.send_header('Content-type', 'text/css')
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
with open("bootstrap.min.css", "rb") as fh:
|
with open("bootstrap.min.css", "rb") as file:
|
||||||
html = fh.read()
|
html = file.read()
|
||||||
self.wfile.write(html)
|
self.wfile.write(html)
|
||||||
|
|
||||||
elif self.path.startswith('/tts_queue'):
|
elif self.path.startswith('/tts_queue'):
|
||||||
|
@ -325,9 +421,9 @@ class HTTPserv(BaseHTTPRequestHandler):
|
||||||
for key in list(sorted_tts.keys()):
|
for key in list(sorted_tts.keys()):
|
||||||
if key not in tts_done:
|
if key not in tts_done:
|
||||||
if msg_queue[key][0].lower() in usermap:
|
if msg_queue[key][0].lower() in usermap:
|
||||||
tts = {key: usermap[msg_queue[key][0].lower()]+" "+str(conf['MESSAGE']['SAYS'])+":"+msg_queue[key][1]}
|
tts = {str(key): str(usermap[msg_queue[key][0].lower()]) + " " + str(conf['MESSAGE']['SAYS']) + ":" + str(msg_queue[key][1])}
|
||||||
else:
|
else:
|
||||||
tts = {key: msg_queue[key][0]+" "+str(conf['MESSAGE']['SAYS'])+":"+msg_queue[key][1]}
|
tts = {str(key): str(msg_queue[key][0]) + " " + str(conf['MESSAGE']['SAYS']) + ":" + str(msg_queue[key][1])}
|
||||||
|
|
||||||
tts_json = json.dumps(tts)
|
tts_json = json.dumps(tts)
|
||||||
self.wfile.write(bytes(str(tts_json)+"\n", "utf-8"))
|
self.wfile.write(bytes(str(tts_json)+"\n", "utf-8"))
|
||||||
|
@ -347,6 +443,41 @@ class HTTPserv(BaseHTTPRequestHandler):
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
self.wfile.write(bytes("Internal Server error\n", "utf-8"))
|
self.wfile.write(bytes("Internal Server error\n", "utf-8"))
|
||||||
|
|
||||||
|
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.')
|
||||||
else:
|
else:
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
self.send_header('Server', 'TTS')
|
self.send_header('Server', 'TTS')
|
||||||
|
@ -357,13 +488,16 @@ class HTTPserv(BaseHTTPRequestHandler):
|
||||||
return
|
return
|
||||||
|
|
||||||
def http_serve_forever(httpd):
|
def http_serve_forever(httpd):
|
||||||
httpd.serve_forever()
|
"""httpd loop"""
|
||||||
|
httpd.serve_forever()
|
||||||
|
|
||||||
def load_config():
|
def load_config():
|
||||||
|
"""Loading config variables"""
|
||||||
|
|
||||||
logging.info("Loading configfile")
|
logging.info("Loading configfile")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open("config.yml", "r") as ymlfile:
|
with open("config.yml", "r", encoding="UTF-8") as ymlfile:
|
||||||
cfg = yaml.load(ymlfile, Loader=yaml.Loader)
|
cfg = yaml.load(ymlfile, Loader=yaml.Loader)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logging.fatal('Your config file is missing, please copy config-dist.yml to config.yml and review your settings.')
|
logging.fatal('Your config file is missing, please copy config-dist.yml to config.yml and review your settings.')
|
||||||
|
@ -371,7 +505,7 @@ def load_config():
|
||||||
|
|
||||||
for section in cfg:
|
for section in cfg:
|
||||||
try:
|
try:
|
||||||
logging.debug('Fetching config: '+str(section))
|
logging.debug('Fetching config: %s', section)
|
||||||
conf['IRC_CHANNEL'] = cfg['irc']['channel']
|
conf['IRC_CHANNEL'] = cfg['irc']['channel']
|
||||||
conf['IRC_USERNAME'] = cfg['irc']['username']
|
conf['IRC_USERNAME'] = cfg['irc']['username']
|
||||||
conf['IRC_OAUTH_TOKEN'] = cfg['irc']['oauth_token']
|
conf['IRC_OAUTH_TOKEN'] = cfg['irc']['oauth_token']
|
||||||
|
@ -381,6 +515,7 @@ def load_config():
|
||||||
conf['IRC_SUBONLY'] = cfg['bot']['subonly'] or False
|
conf['IRC_SUBONLY'] = cfg['bot']['subonly'] or False
|
||||||
conf['IRC_MODONLY'] = cfg['bot']['modonly'] or False
|
conf['IRC_MODONLY'] = cfg['bot']['modonly'] or False
|
||||||
conf['IRC_TTS_LEN'] = cfg['bot']['message_length'] or 200
|
conf['IRC_TTS_LEN'] = cfg['bot']['message_length'] or 200
|
||||||
|
conf['TTS_STARTENABLED'] = cfg['bot']['start_enabled'] or False
|
||||||
|
|
||||||
conf['LOG_LEVEL'] = cfg['log']['level'] or "INFO"
|
conf['LOG_LEVEL'] = cfg['log']['level'] or "INFO"
|
||||||
conf['HTTP_PORT'] = cfg['http']['port'] or 80
|
conf['HTTP_PORT'] = cfg['http']['port'] or 80
|
||||||
|
@ -398,6 +533,11 @@ def load_config():
|
||||||
conf['MESSAGE']['WHITELISTONLY'] = cfg['messages']['whitelist'] or False
|
conf['MESSAGE']['WHITELISTONLY'] = cfg['messages']['whitelist'] or False
|
||||||
conf['MESSAGE']['SAYS'] = cfg['messages']['says'] or "says"
|
conf['MESSAGE']['SAYS'] = cfg['messages']['says'] or "says"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
conf['USERMAP'] = cfg['usermapping'] or []
|
conf['USERMAP'] = cfg['usermapping'] or []
|
||||||
|
|
||||||
if 'whitelist' in cfg:
|
if 'whitelist' in cfg:
|
||||||
|
@ -420,7 +560,8 @@ def load_config():
|
||||||
if not conf['IRC_USERNAME']:
|
if not conf['IRC_USERNAME']:
|
||||||
raise ValueError('Please add the bots username to config.yml.')
|
raise ValueError('Please add the bots username to config.yml.')
|
||||||
if not conf['IRC_OAUTH_TOKEN']:
|
if not conf['IRC_OAUTH_TOKEN']:
|
||||||
raise ValueError('Please add the bots oauth-token to config.yml.')
|
conf['IRC_OAUTH_TOKEN'] = "Invalid"
|
||||||
|
return conf
|
||||||
if not conf['IRC_OAUTH_TOKEN'].startswith('oauth:'):
|
if not conf['IRC_OAUTH_TOKEN'].startswith('oauth:'):
|
||||||
raise ValueError('Your oauth-token is invalid, it has to start with: "oauth:"')
|
raise ValueError('Your oauth-token is invalid, it has to start with: "oauth:"')
|
||||||
|
|
||||||
|
@ -435,6 +576,10 @@ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(module)s %(thread
|
||||||
sys.tracebacklimit = 0
|
sys.tracebacklimit = 0
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
"""Main loop"""
|
||||||
|
|
||||||
|
global conf # pylint: disable=global-statement,invalid-name
|
||||||
|
|
||||||
conf = load_config()
|
conf = load_config()
|
||||||
lastreload = datetime.datetime.now()
|
lastreload = datetime.datetime.now()
|
||||||
logging.getLogger().setLevel(conf['LOG_LEVEL'])
|
logging.getLogger().setLevel(conf['LOG_LEVEL'])
|
||||||
|
@ -449,50 +594,66 @@ def main():
|
||||||
http_thread = Thread(target=http_serve_forever, daemon=True, args=(httpd, ))
|
http_thread = Thread(target=http_serve_forever, daemon=True, args=(httpd, ))
|
||||||
http_thread.start()
|
http_thread.start()
|
||||||
|
|
||||||
logging.info("Starting IRC bot")
|
if conf['IRC_OAUTH_TOKEN'] == "Invalid":
|
||||||
irc = IRC()
|
logging.error('No OAuth Token, skipping start of IRC bot.')
|
||||||
irc.connect(conf['IRC_SERVER'], 6667, conf['IRC_CHANNEL'], conf['IRC_USERNAME'], conf['IRC_OAUTH_TOKEN'])
|
logging.error('Please open http://%s:%s/token to generate your OAuth-Token.', conf['HTTP_BIND'], conf['HTTP_PORT'])
|
||||||
irc.sendpriv(conf['IRC_CHANNEL'], 'MrDestructoid', conf['MESSAGE']['READY'])
|
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()
|
||||||
|
|
||||||
logging.info("Please open your browser and visit: http://"+str(conf['HTTP_BIND']+":"+str(conf['HTTP_PORT'])+"/"))
|
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'])
|
||||||
|
|
||||||
while True:
|
logging.info("Please open your browser and visit: http://%s:%s/", conf['HTTP_BIND'], conf['HTTP_PORT'])
|
||||||
if conf['LOG_LEVEL'] == "DEBUG":
|
url = 'http://'+str(conf['HTTP_BIND'])+':'+str(conf['HTTP_PORT'])
|
||||||
time.sleep(1)
|
webbrowser.open_new_tab(url)
|
||||||
|
|
||||||
try:
|
while True:
|
||||||
irc.get_response()
|
if conf['LOG_LEVEL'] == "DEBUG":
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
if not irc.tts_status:
|
try:
|
||||||
logging.debug("TTS is disabled")
|
irc.get_response()
|
||||||
if conf['LOG_LEVEL'] == "DEBUG":
|
|
||||||
time.sleep(1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
confreload = datetime.datetime.now()
|
if not irc.tts_status:
|
||||||
if confreload - lastreload > datetime.timedelta(seconds=60):
|
logging.debug("TTS is disabled")
|
||||||
conf = load_config()
|
if conf['LOG_LEVEL'] == "DEBUG":
|
||||||
lastreload = datetime.datetime.now()
|
time.sleep(1)
|
||||||
|
continue
|
||||||
|
|
||||||
logging.debug('Raw message queue:')
|
confreload = datetime.datetime.now()
|
||||||
logging.debug(msg_queue_raw)
|
if confreload - lastreload > datetime.timedelta(seconds=60):
|
||||||
|
conf = load_config()
|
||||||
|
lastreload = datetime.datetime.now()
|
||||||
|
|
||||||
for raw_msg in msg_queue_raw:
|
if irc.quickvote and irc.votemsg:
|
||||||
logging.debug('Raw msg:')
|
logging.info('Quickvote is active')
|
||||||
|
irc.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTESTART'] + " (" + str(irc.votemsg) + ")")
|
||||||
|
|
||||||
|
logging.debug('Raw message queue:')
|
||||||
logging.debug(msg_queue_raw)
|
logging.debug(msg_queue_raw)
|
||||||
|
|
||||||
now = datetime.datetime.now()
|
for raw_msg in msg_queue_raw:
|
||||||
if now - raw_msg['queuetime'] > datetime.timedelta(seconds=conf['IRC_CLEARMSG_TIMEOUT']):
|
logging.debug('Raw msg:')
|
||||||
logging.debug('clearmsg_timeout reached')
|
logging.debug(msg_queue_raw)
|
||||||
if raw_msg['timestamp'] not in msg_queue:
|
|
||||||
logging.info('Sending TTS message')
|
now = datetime.datetime.now()
|
||||||
msg_queue[raw_msg['timestamp']] = [raw_msg['user'], raw_msg['msg']]
|
if now - raw_msg['queuetime'] > datetime.timedelta(seconds=conf['IRC_CLEARMSG_TIMEOUT']):
|
||||||
logging.debug(msg_queue)
|
logging.debug('clearmsg_timeout reached')
|
||||||
else:
|
if raw_msg['timestamp'] not in msg_queue:
|
||||||
logging.debug('Msg is already in queue')
|
logging.info('Sending TTS message')
|
||||||
except KeyboardInterrupt:
|
msg_queue[raw_msg['timestamp']] = [raw_msg['user'], raw_msg['msg']]
|
||||||
logging.info('Exiting...')
|
logging.debug(msg_queue)
|
||||||
sys.exit()
|
else:
|
||||||
|
logging.debug('Msg is already in queue')
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logging.info('Exiting...')
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue