From 2751d329c30d323815115007d390268f35d99c33 Mon Sep 17 00:00:00 2001 From: gpkvt Date: Thu, 18 Aug 2022 16:45:11 +0200 Subject: [PATCH] Quote function added --- .gitignore | 1 + README.md | 21 ++++++- requirements.txt | 1 + tts.py | 156 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 177 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 614de36..25acfa2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build tts.spec tts.exe random*.txt +quotes.txt diff --git a/README.md b/README.md index 84e91ff..5a717ff 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,11 @@ 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) +* `voteresult`: Prefix for the result (will be read out). * `votes`: Suffix to vote count. +* `quotenotfound`: Message if requests quote wasn't found. +* `quoteaddedprefix`: Prefix for `Quote added` message. +* `quoteaddedsuffix`: Suffix for `Quote added` message. ##### log @@ -176,6 +179,22 @@ The `!quickvote` feature implements a simple vote system. If a broadcaster or mo 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. +#### !smartvote + +The `!smartvote` command will read a line from a file called `quotes.txt`. You can add a parameter after the command. If the parameter is numeric the bot will search for a line starting with `#$number`. If the parameter is a string it will search for a line matching the parameter. If no parameter is given it will search for a completely random line. + +The format of `quotes.txt` looks as follows: + +``` lang=text +#1: "the quote" -username (date) +``` + +#### !addquote + +The `!addvote` command adds a new line to `quotes.txt`. It expects two parameters: `!addquote username quote` where `username` is the name of the user to be quoted. + +Only Subs, Mods and Broadcaster are allowed to add quotes. + ## Build If you prefer to build your own `tts.exe` instead of using the shipped one, you can do as follows: diff --git a/requirements.txt b/requirements.txt index bee6c14..3b34052 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ +fuzzywuzzy==0.18.0 PyYAML==6.0 diff --git a/tts.py b/tts.py index 2a442ac..5662a28 100644 --- a/tts.py +++ b/tts.py @@ -41,6 +41,8 @@ from http.server import BaseHTTPRequestHandler import yaml +from fuzzywuzzy import process + class IRC: """ IRC bot """ irc = socket.socket() @@ -207,6 +209,14 @@ class IRC: logging.debug("Ping check received.") self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), "Pong!") + if msg.startswith('!addquote'): + logging.debug("!addquote command detected") + self.Commands.addquote(self, tags, msg) + + if msg.startswith('!smartquote'): + logging.debug("!smartquote command detected") + self.Commands.quote(self, tags, msg) + elif msg.startswith('!dtts'): logging.debug("!dtts command detected") self.Commands.dtts(self, msg) @@ -408,6 +418,146 @@ class IRC: logging.info('Sending TTS message to raw_queue') IRC.send_tts_msg(self, msg, tags) + def addquote(self, tags, msg): + """ !addquote command + + Adds a newline to quotes.txt + """ + + user = tags['user'] + + if IRC.check_user_denied(self, user): + logging.info('User is not allowed to use TTS') + elif IRC.check_subonly(self, tags): + logging.info('TTS is sub-only') + else: + try: + with open("quotes.txt", "rb") as fp: + nol = len(fp.readlines()) + fp.close() + except FileNotFoundError: + logging.warn("quotes.txt does not exists, will create") + nol = 0 + + nol = nol + 1 + quote = msg.replace("!addquote ", "").strip() + quote = quote.split(" ",1) + username = quote[0] + + quote = '#%s: "%s" -%s' % (nol, quote[1], username) + logging.info('Adding quote %s', quote) + + with open("quotes.txt", "ab") as fp: + fp.write(quote.encode('utf-8')) + + msg = "%s #%s %s" % (conf['MESSAGE']['QUOTE_ADDED_PREFIX'], nol, conf['MESSAGE']['QUOTE_ADDED_SUFFIX']) + + raw_msg = { + "TTS": True, + "msg": 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']] + IRC.sendmsg(self, conf['IRC_CHANNEL'], "@"+str(user), msg) + + + def quote(self, tags, msg = False): + """ !q command + + Gets a line from quotes.txt. If a number if given as msg + it fetch the given line number. If a string is given + it fetch the best matching line. If nothing is given + it fetch a random line. + """ + + user = tags['user'] + query = msg.replace('!smartquote', '').strip() + + try: + if query.isdigit(): + logging.info('Fetching quote #%s', query) + + fp = open("quotes.txt", "rb") + quotes = fp.readlines() + + for line in quotes: + if line.decode('utf-8').startswith("#"+str(query)+":"): + quote = line + break + fp.close() + + elif query != "": + logging.info('Fetching match for %s', query) + + fp = open("quotes.txt", "rb") + quotes = fp.readlines() + matches = process.extract(query, quotes, limit=20) + quotes = [] + + for match, score in matches: + if score >= 60: + quotes.append(match) + + quote = random.choice(quotes) + + else: + logging.info('Fetching random quote') + + with open("quotes.txt", "rb") as file: + lines = file.read().splitlines() + quote = random.choice(lines) + except FileNotFoundError: + logging.error('"quotes.txt does not exists.') + + if not 'quote' in vars(): + logging.info('No quote found.') + quote = conf['MESSAGE']['QUOTE_NOT_FOUND'] + IRC.sendmsg(self, conf['IRC_CHANNEL'], "", quote) + return False + + if isinstance(quote, str): + quote = quote + else: + quote = quote.decode('utf-8') + + if IRC.check_tts_disabled(self, user): + logging.info('TTS is disabled') + elif IRC.check_user_denied(self, user): + logging.info('User is not allowed to use TTS') + elif IRC.check_subonly(self, tags): + logging.info('TTS is sub-only') + elif IRC.check_modonly(self, tags): + logging.info('TTS is mod-only') + elif IRC.check_whitelist(self, user): + logging.info('User is not on whitelist') + else: + logging.info('Sending quote to TTS') + logging.debug("Quote: %s", quote) + IRC.sendmsg(self, conf['IRC_CHANNEL'], "", quote) + + quote = quote.rsplit('(', 1)[0] + + raw_msg = { + "TTS": True, + "msg": quote, + "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 + def quickvote(self, msg): """ !quickvote command @@ -760,7 +910,11 @@ def load_config(): 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['MESSAGE']['VOTES'] = cfg.get('messages', {}).get('votes', "Votes") + + conf['MESSAGE']['QUOTE_NOT_FOUND'] = cfg.get('messages', {}).get('quotenotfound', "Sorry, no quote found.") + conf['MESSAGE']['QUOTE_ADDED_PREFIX'] = cfg.get('messages', {}).get('quoteaddedprefix', "Quote:") + conf['MESSAGE']['QUOTE_ADDED_SUFFIX'] = cfg.get('messages', {}).get('quoteaddedsuffix', "added.") conf['USERMAP'] = cfg.get('usermapping', [])