mirror of https://gitlab.com/gpvkt/twitchtts.git
Merge branch 'dev' into 'main'
v1.2.0 See merge request gpvkt/twitchtts!2
This commit is contained in:
commit
97542800fa
|
@ -2,3 +2,4 @@ config.yml
|
||||||
build
|
build
|
||||||
tts.spec
|
tts.spec
|
||||||
tts.exe
|
tts.exe
|
||||||
|
random*.txt
|
||||||
|
|
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -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
|
||||||
|
|
10
README.md
10
README.md
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
142
tts.py
142
tts.py
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue