Merge branch 'dev' into 'main'

v1.2.0

See merge request gpvkt/twitchtts!2
This commit is contained in:
gpkvt 2022-08-13 07:19:26 +00:00
commit 97542800fa
5 changed files with 130 additions and 38 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ config.yml
build
tts.spec
tts.exe
random*.txt

View File

@ -2,6 +2,20 @@
All notable changes to this project will be documented in this file. If there is a `Changed` section please read carefully, as this often means that you will need to adapt your `config.yml`, otherwise the bot might fail to start.
## [1.2.0] - 2022-08-13
### Added
* `!random` feature (see README.md for details)
### Changed
* The vote result will be read out
### Fixed
* Improved handling of missing config values.
## [1.1.0] - 2022-08-12
### Added

View File

@ -65,6 +65,7 @@ messages:
votestart: "Quickvote started. Send #yourchoice to participate."
voteend: "Quickvote ended. The results are:"
votenobody: "Nobody casted a vote. :("
voteresult: "Voting has ended. The result is:"
votes: "Votes"
log:
@ -117,6 +118,7 @@ Please note that the `oauth_token` is valid for approximately 60 days. If it bec
* `votestart`: Message when a quickvote is started.
* `voteend`: Message if a quickvote ends.
* `votenobody`: Message if quickvote ends, but nobody has voted.
* `voteresult`: Prefix for the result (will be read out)
* `votes`: Suffix to vote count.
##### log
@ -166,7 +168,13 @@ Additional commands (broadcaster and mods only) are:
### 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.
#### !quickvote
The `!quickvote` feature implements a simple vote system. 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.
#### !random
The `!random` command will read a random line from a file called `random.txt`. You can also use multiple files, if you call `!random foo` the bot fetch the random line from a file called `random_foo.txt` instead of `random.txt`. `!random bar` will use the file `random_bar.txt` and so on. The `!random` command is restricted to the broadcaster and moderators.
## Build

View File

@ -29,6 +29,7 @@ messages: # Things the bot can send as chat message
votestart: "Quickvote started. Send #yourchoice to participate."
voteend: "Quickvote ended. The results are:"
votenobody: "Nobody casted a vote. :("
voteresult: "Voting has ended. The result is:"
votes: "Votes"
log:

142
tts.py
View File

@ -20,21 +20,23 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import json
import logging
import socket
import os
import sys
import time
import json
import socket
import random
import logging
import datetime
import socketserver
import urllib.request
import urllib.parse
import webbrowser
import socketserver
import urllib.parse
import urllib.request
from threading import Thread
from http.server import BaseHTTPRequestHandler
from urllib.parse import parse_qs
from collections import Counter
from urllib.parse import parse_qs
from http.server import BaseHTTPRequestHandler
import yaml
@ -77,8 +79,8 @@ class IRC:
try:
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
except ConnectionResetError:
logging.warning('JOIN was refused, will try again in 5 seconds.')
time.sleep(5)
logging.warning('JOIN was refused, will try again in 30 seconds.')
time.sleep(30)
logging.warning('Please check your credentials, if this error persists.')
self.irc.send(bytes("JOIN " + channel + "\r\n", "UTF-8"))
@ -198,6 +200,38 @@ class IRC:
return True
if msg.startswith('!random'):
logging.info('!random command detected')
randomfile = msg.replace('!random', '').strip().lower()
if randomfile:
randomfile = "random_"+str(os.path.basename(randomfile))+".txt"
else:
randomfile = "random.txt"
try:
with open(randomfile,"r", encoding="utf-8") as file:
lines = file.read().splitlines()
random_msg = random.choice(lines)
except FileNotFoundError:
logging.error('%s not found', randomfile)
return False
raw_msg = {
"TTS": True,
"msg": random_msg,
"badges": True,
"subscriber": True,
"msgid": True,
"user": conf['IRC_USERNAME'],
"length": conf['IRC_TTS_LEN'],
"queuetime": datetime.datetime.now(),
"timestamp": str(time.time_ns())
}
msg_queue[raw_msg['timestamp']] = [raw_msg['user'], raw_msg['msg']]
return True
if msg.startswith('!quickvote'):
logging.info("!quickvote command detected")
if self.quickvote:
@ -207,6 +241,23 @@ class IRC:
logging.info("Nobody voted")
self.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTEEND'])
self.sendmsg(conf['IRC_CHANNEL'], "*", conf['MESSAGE']['VOTENOBODY'])
raw_msg = {
"TTS": True,
"msg": conf['MESSAGE']['VOTENOBODY'],
"badges": True,
"subscriber": True,
"msgid": True,
"user": conf['IRC_USERNAME'],
"length": conf['IRC_TTS_LEN'],
"queuetime": datetime.datetime.now(),
"timestamp": str(time.time_ns())
}
msg_queue[raw_msg['timestamp']] = [raw_msg['user'], raw_msg['msg']]
logging.info('The result is: %s', conf['MESSAGE']['VOTENOBODY'])
logging.debug('Votemsg: %s', msg)
self.quickvote = False
self.poll = {}
return False
@ -218,6 +269,22 @@ class IRC:
logging.debug(count)
raw_msg = {
"TTS": True,
"msg": conf['MESSAGE']['VOTERESULT'] +" "+ str(count[0][0].replace('#','')),
"badges": True,
"subscriber": True,
"msgid": True,
"user": conf['IRC_USERNAME'],
"length": conf['IRC_TTS_LEN'],
"queuetime": datetime.datetime.now(),
"timestamp": str(time.time_ns())
}
msg_queue[raw_msg['timestamp']] = [raw_msg['user'], raw_msg['msg']]
logging.info('The result is: %s', conf['MESSAGE']['VOTERESULT'] +" "+ str(count[0]))
logging.debug('Votemsg: %s', msg)
for key, value in count:
self.sendmsg(
conf['IRC_CHANNEL'], "*",
@ -504,39 +571,40 @@ def load_config():
for section in cfg:
try:
logging.debug('Fetching config: %s', section)
conf['IRC_CHANNEL'] = cfg['irc']['channel']
conf['IRC_USERNAME'] = cfg['irc']['username']
conf['IRC_OAUTH_TOKEN'] = cfg['irc']['oauth_token']
conf['IRC_SERVER'] = cfg['irc']['server'] or "irc.chat.twitch.tv"
conf['IRC_CLEARMSG_TIMEOUT'] = cfg['irc']['clearmsg_timeout'] or 60
conf['IRC_CHANNEL'] = cfg.get('irc', {}).get('channel', False)
conf['IRC_USERNAME'] = cfg.get('irc', {}).get('username', False)
conf['IRC_OAUTH_TOKEN'] = cfg.get('irc', {}).get('oauth_token', False)
conf['IRC_SERVER'] = cfg.get('irc', {}).get('server', "irc.chat.twitch.tv")
conf['IRC_CLEARMSG_TIMEOUT'] = cfg.get('irc', {}).get('clearmsg_timeout', 60)
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
conf['TTS_STARTENABLED'] = cfg['bot']['start_enabled'] or False
conf['IRC_SUBONLY'] = cfg.get('bot', {}).get('subonly', False)
conf['IRC_MODONLY'] = cfg.get('bot', {}).get('modonly', False)
conf['IRC_TTS_LEN'] = cfg.get('bot', {}).get('message_length', 200)
conf['TTS_STARTENABLED'] = cfg.get('bot', {}).get('start_enabled', False)
conf['LOG_LEVEL'] = cfg['log']['level'] or "INFO"
conf['HTTP_PORT'] = cfg['http']['port'] or 80
conf['HTTP_BIND'] = cfg['http']['bind'] or "localhost"
conf['LOG_LEVEL'] = cfg.get('log', {}).get('level', "INFO")
conf['HTTP_PORT'] = cfg.get('http', {}).get('port', 80)
conf['HTTP_BIND'] = cfg.get('http', {}).get('bind', "localhost")
conf['MESSAGE'] = {}
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."
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"
conf['MESSAGE']['TOFF'] = cfg.get('messages', {}).get('toff', "TTS is now disabled.")
conf['MESSAGE']['TON'] = cfg.get('messages', {}).get('ton', "TTS is now active.")
conf['MESSAGE']['TOO_LONG'] = cfg.get('messages', {}).get('too_long', "Sorry, your message is too long.")
conf['MESSAGE']['DISABLED'] = cfg.get('messages', {}).get('disabled', "Sorry, TTS is disabled.")
conf['MESSAGE']['DENIED'] = cfg.get('messages', {}).get('denied', "Sorry, you're not allowed to use TTS.")
conf['MESSAGE']['SUBONLY'] = cfg.get('messages', {}).get('subonly', "Sorry, TTS is sub-only.")
conf['MESSAGE']['MODONLY'] = cfg.get('messages', {}).get('modonly', "Sorry, TTS is mod-only.")
conf['MESSAGE']['READY'] = cfg.get('messages', {}).get('ready', "TTS bot is ready.")
conf['MESSAGE']['WHITELISTONLY'] = cfg.get('messages', {}).get('whitelist', False)
conf['MESSAGE']['SAYS'] = cfg.get('messages', {}).get('says', "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['MESSAGE']['VOTESTART'] = cfg.get('messages', {}).get('votestart', "Quickvote started. Send #yourchoice to participate.")
conf['MESSAGE']['VOTEEND'] = cfg.get('messages', {}).get('voteend', "Quickvote ended. The results are:")
conf['MESSAGE']['VOTENOBODY'] = cfg.get('messages', {}).get('votenobody', "Nobody casted a vote. :(")
conf['MESSAGE']['VOTERESULT'] = cfg.get('messages', {}).get('voteresult', "Voting has ended. The result is:")
conf['MESSAGE']['VOTES'] = cfg.get('messages', {}).get('votes', "Stimmen")
conf['USERMAP'] = cfg['usermapping'] or []
conf['USERMAP'] = cfg.get('usermapping', [])
if 'whitelist' in cfg:
conf['WHITELIST'] = True