mirror of
				https://gitlab.com/gpvkt/twitchtts.git
				synced 2025-10-31 00:57:35 +01:00 
			
		
		
		
	Fixed too-long-lines
This commit is contained in:
		
							parent
							
								
									41a7269855
								
							
						
					
					
						commit
						4d8e07f393
					
				
					 1 changed files with 183 additions and 54 deletions
				
			
		
							
								
								
									
										237
									
								
								tts.py
									
										
									
									
									
								
							
							
						
						
									
										237
									
								
								tts.py
									
										
									
									
									
								
							|  | @ -1,6 +1,6 @@ | |||
| #!/usr/bin/python3 | ||||
| # -*- coding: utf-8 -*- | ||||
| # pylint: disable=global-statement,broad-except,line-too-long,too-many-lines | ||||
| # pylint: disable=global-statement,broad-except,too-many-lines | ||||
| 
 | ||||
| """ | ||||
|     TwitchTTS | ||||
|  | @ -566,7 +566,7 @@ class IRC: | |||
|                 try: | ||||
|                     token = CONF['IRC_OAUTH_TOKEN'].replace('oauth:','') | ||||
|                     login = CONF['IRC_CHANNEL'].replace('#','') | ||||
|                     api_endpoint = "https://api.twitch.tv/helix/users?login="+str(login) | ||||
|                     api_endpoint = f"https://api.twitch.tv/helix/users?login={login}" | ||||
|                     headers = { | ||||
|                         'Content-type': 'application/x-form-urlencoded', | ||||
|                         'Authorization': 'Bearer '+token, | ||||
|  | @ -576,7 +576,7 @@ class IRC: | |||
|                     data = req.json() | ||||
|                     user_id = data['data'][0]['id'] | ||||
| 
 | ||||
|                     api_endpoint = "https://api.twitch.tv/helix/channels?broadcaster_id="+str(user_id) | ||||
|                     api_endpoint = f"https://api.twitch.tv/helix/channels?broadcaster_id={user_id}" | ||||
|                     headers = { | ||||
|                         'Content-type': 'application/x-form-urlencoded', | ||||
|                         'Authorization': 'Bearer '+token, | ||||
|  | @ -595,7 +595,8 @@ class IRC: | |||
|                 with open("quotes.txt", "ab") as file: | ||||
|                     file.write(quote.encode('utf-8')) | ||||
| 
 | ||||
|                 msg = f"{CONF['MESSAGE']['QUOTE_ADDED_PREFIX']} #{nol} {CONF['MESSAGE']['QUOTE_ADDED_SUFFIX']}" | ||||
|                 msg = f"{CONF['MESSAGE']['QUOTE_ADDED_PREFIX']} \ | ||||
|                         #{nol} {CONF['MESSAGE']['QUOTE_ADDED_SUFFIX']}" | ||||
| 
 | ||||
|                 raw_msg = { | ||||
|                     "TTS": True, | ||||
|  | @ -645,9 +646,11 @@ class IRC: | |||
|                 msg_queue[raw_msg['timestamp']] = [raw_msg['user'], raw_msg['msg']] | ||||
| 
 | ||||
|             except wikipedia.exceptions.DisambiguationError: | ||||
|                 IRC.sendmsg(self, CONF['IRC_CHANNEL'], "@"+str(user), CONF['MESSAGE']['WIKI_TOO_MANY']) | ||||
|                 user = f"@{user}" | ||||
|                 IRC.sendmsg(self, CONF['IRC_CHANNEL'], user, CONF['MESSAGE']['WIKI_TOO_MANY']) | ||||
|             except Exception: | ||||
|                 IRC.sendmsg(self, CONF['IRC_CHANNEL'], "@"+str(user), CONF['MESSAGE']['WIKI_NO_RESULT']) | ||||
|                 user = f"@{user}" | ||||
|                 IRC.sendmsg(self, CONF['IRC_CHANNEL'], user, CONF['MESSAGE']['WIKI_NO_RESULT']) | ||||
| 
 | ||||
|         def quote(self, tags, msg = False): | ||||
|             """ !smartquote command | ||||
|  | @ -818,7 +821,8 @@ class IRC: | |||
|                         logging.info('Got more than the requested number of participants') | ||||
|                     else: | ||||
|                         picks = self.pickme | ||||
|                         logging.info('Got less than or exactly the requested number of participants') | ||||
|                         logging.info('Got less than or exactly the \ | ||||
|                                       requested number of participants') | ||||
| 
 | ||||
|                     converted_picks = [str(element) for element in picks] | ||||
|                     joined_picks = " ".join(converted_picks) | ||||
|  | @ -1111,11 +1115,18 @@ class HTTPserv(BaseHTTPRequestHandler): | |||
|             for key in list(sorted_tts.keys()): | ||||
|                 if key not in tts_done: | ||||
|                     if msg_queue[key][0].lower() in usermap: | ||||
|                         logging.debug('Using usermap for user: %s (%s)', msg_queue[key][0], usermap[msg_queue[key][0].lower()]) | ||||
|                         tts = {str(key): str(usermap[msg_queue[key][0].lower()]) + " " + str(CONF['MESSAGE']['SAYS']) + ":" + str(msg_queue[key][1])} | ||||
|                         logging.debug('Using usermap for user: %s (%s)', | ||||
|                                        msg_queue[key][0], | ||||
|                                        usermap[msg_queue[key][0].lower()] | ||||
|                                      ) | ||||
|                         user = usermap[msg_queue[key][0].lower()] | ||||
|                         tts = {str(key): f"{user} {CONF['MESSAGE']['SAYS']}: {msg_queue[key][1]}"} | ||||
|                     else: | ||||
|                         logging.debug('No usermap entry found for user: %s', msg_queue[key][0]) | ||||
|                         tts = {str(key): str(msg_queue[key][0]) + " " + str(CONF['MESSAGE']['SAYS']) + ":" + str(msg_queue[key][1])} | ||||
|                         tts = { | ||||
|                             str(key): | ||||
|                             f"{msg_queue[key][0]} {CONF['MESSAGE']['SAYS']}: {msg_queue[key][1]}" | ||||
|                         } | ||||
| 
 | ||||
|             tts_json = json.dumps(tts) | ||||
|             self.wfile.write(bytes(str(tts_json)+"\n", "utf-8")) | ||||
|  | @ -1150,7 +1161,12 @@ class HTTPserv(BaseHTTPRequestHandler): | |||
|                 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")) | ||||
|                 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: | ||||
|  | @ -1162,7 +1178,31 @@ class HTTPserv(BaseHTTPRequestHandler): | |||
|                     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")) | ||||
|                     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') | ||||
|  | @ -1192,56 +1232,130 @@ def load_config(): | |||
|         with open("config.yml", "r", encoding="UTF-8") as ymlfile: | ||||
|             cfg = yaml.load(ymlfile, Loader=yaml.Loader) | ||||
|     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.') | ||||
|         sys.exit(253) | ||||
| 
 | ||||
|     for section in cfg: | ||||
|         try: | ||||
|             logging.debug('Fetching config: %s', section) | ||||
|             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_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.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', True) | ||||
|             CONF['WIKI_LANG'] = cfg.get('bot', {}).get('language', 'en') | ||||
|             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', True | ||||
|             ) | ||||
|             CONF['WIKI_LANG'] = cfg.get('bot', {}).get( | ||||
|                 'language', 'en' | ||||
|             ) | ||||
| 
 | ||||
|             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['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.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']['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.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', "Votes") | ||||
|             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', "Votes" | ||||
|             ) | ||||
| 
 | ||||
|             CONF['MESSAGE']['PICKSTART'] = cfg.get('messages', {}).get('pickstart', "Pick started. Send #pickme to participate.") | ||||
|             CONF['MESSAGE']['PICKRESULT'] = cfg.get('messages', {}).get('pickresult', "Pick ended. The results are:") | ||||
|             CONF['MESSAGE']['PICKNONE'] = cfg.get('messages', {}).get('picknone', "Pick ended. Nobody was picked.") | ||||
|             CONF['MESSAGE']['PICKSTART'] = cfg.get('messages', {}).get( | ||||
|                 'pickstart', "Pick started. Send #pickme to participate." | ||||
|             ) | ||||
|             CONF['MESSAGE']['PICKRESULT'] = cfg.get('messages', {}).get( | ||||
|                 'pickresult', "Pick ended. The results are:" | ||||
|             ) | ||||
|             CONF['MESSAGE']['PICKNONE'] = cfg.get('messages', {}).get( | ||||
|                 'picknone', "Pick ended. Nobody was picked." | ||||
|             ) | ||||
| 
 | ||||
|             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['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['MESSAGE']['WIKI_TOO_MANY'] = cfg.get('messages', {}).get('wiki_too_many', "Sorry, there are too many possible results. Try a more narrow search.") | ||||
|             CONF['MESSAGE']['WIKI_NO_RESULT'] = cfg.get('messages', {}).get('wiki_no_result', "Sorry, there was an error fetching the wikipedia answer.") | ||||
|             CONF['MESSAGE']['WIKI_TOO_MANY'] = cfg.get('messages', {}).get( | ||||
|                 'wiki_too_many', "Sorry, there are too many possible results. \ | ||||
|                 Try a more narrow search." | ||||
|             ) | ||||
|             CONF['MESSAGE']['WIKI_NO_RESULT'] = cfg.get('messages', {}).get( | ||||
|                 'wiki_no_result', "Sorry, there was an error fetching the wikipedia answer." | ||||
|             ) | ||||
| 
 | ||||
|             CONF['FEATURE'] = {} | ||||
|             CONF['FEATURE']['WIKI'] = cfg.get('features', {}).get('wiki', True) | ||||
|  | @ -1325,11 +1439,13 @@ def check_oauth_token(): | |||
|         urllib.request.urlopen(request) | ||||
|     except HTTPError: | ||||
|         logging.fatal('Twitch rejected your OAuth Token. Please check and generate a new one.') | ||||
|         logging.info('Please open http://%s:%s/token to generate your OAuth-Token.', CONF['HTTP_BIND'], CONF['HTTP_PORT']) | ||||
|         logging.info('Please open http://%s:%s/token to generate your OAuth-Token.', | ||||
|                       CONF['HTTP_BIND'], CONF['HTTP_PORT']) | ||||
| 
 | ||||
|         url = get_url("token") | ||||
|         webbrowser.open_new_tab(url) | ||||
|         logging.info('Please complete the OAuth process and add the token into your "config.yml" within the next 5 minutes.') | ||||
|         logging.info('Please complete the OAuth process and add the token into your "config.yml" \ | ||||
|             within the next 5 minutes.') | ||||
|         time.sleep(300) | ||||
|         CONF = load_config() | ||||
|         check_oauth_token() | ||||
|  | @ -1358,7 +1474,13 @@ def main(): | |||
|     logging.info("Starting IRC bot") | ||||
|     irc = IRC() | ||||
| 
 | ||||
|     irc.connect(CONF['IRC_SERVER'], 6667, CONF['IRC_CHANNEL'], CONF['IRC_USERNAME'], CONF['IRC_OAUTH_TOKEN']) | ||||
|     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']) | ||||
| 
 | ||||
|     logging.info('Connected and joined') | ||||
|  | @ -1380,7 +1502,11 @@ def main(): | |||
| 
 | ||||
|                 if irc.quickvote_status and irc.votemsg: | ||||
|                     logging.info('Quickvote is active') | ||||
|                     irc.sendmsg(CONF['IRC_CHANNEL'], "@chat", CONF['MESSAGE']['VOTESTART'] + " (" + str(irc.votemsg) + ")") | ||||
|                     irc.sendmsg( | ||||
|                         CONF['IRC_CHANNEL'], | ||||
|                         "@chat", | ||||
|                         CONF['MESSAGE']['VOTESTART'] + " (" + str(irc.votemsg) + ")" | ||||
|                     ) | ||||
| 
 | ||||
|             if not irc.tts_status: | ||||
|                 continue | ||||
|  | @ -1394,7 +1520,10 @@ def main(): | |||
|             os.kill(os.getpid(), signal.SIGTERM) | ||||
| 
 | ||||
| if __name__ == "__main__": | ||||
|     logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(module)s %(threadName)s %(levelname)s: %(message)s') | ||||
|     logging.basicConfig( | ||||
|         level=logging.DEBUG, | ||||
|         format='%(asctime)s %(module)s %(threadName)s %(levelname)s: %(message)s' | ||||
|     ) | ||||
|     sys.tracebacklimit = 3 | ||||
| 
 | ||||
|     VERSION = "1.7.1" | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue