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 e434c3b128
6 changed files with 130 additions and 38 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ config.yml
build build
tts.spec tts.spec
tts.exe 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. 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 ## [1.1.0] - 2022-08-12
### Added ### Added

View File

@ -65,6 +65,7 @@ messages:
votestart: "Quickvote started. Send #yourchoice to participate." votestart: "Quickvote started. Send #yourchoice to participate."
voteend: "Quickvote ended. The results are:" voteend: "Quickvote ended. The results are:"
votenobody: "Nobody casted a vote. :(" votenobody: "Nobody casted a vote. :("
voteresult: "Voting has ended. The result is:"
votes: "Votes" votes: "Votes"
log: 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. * `votestart`: Message when a quickvote is started.
* `voteend`: Message if a quickvote ends. * `voteend`: Message if a quickvote ends.
* `votenobody`: Message if quickvote ends, but nobody has voted. * `votenobody`: Message if quickvote ends, but nobody has voted.
* `voteresult`: Prefix for the result (will be read out)
* `votes`: Suffix to vote count. * `votes`: Suffix to vote count.
##### log ##### log
@ -166,7 +168,13 @@ Additional commands (broadcaster and mods only) are:
### Additional features ### 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 ## Build

View File

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

BIN
dist/tts.exe vendored

Binary file not shown.

142
tts.py
View File

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