mirror of
				https://gitlab.com/gpvkt/twitchtts.git
				synced 2025-10-31 17:17:35 +01:00 
			
		
		
		
	Minor Bugfixes, Reworked code structure
This commit is contained in:
		
							parent
							
								
									db9cb2893e
								
							
						
					
					
						commit
						d69717314c
					
				
					 2 changed files with 234 additions and 163 deletions
				
			
		
							
								
								
									
										11
									
								
								CHANGELOG.md
									
										
									
									
									
								
							
							
						
						
									
										11
									
								
								CHANGELOG.md
									
										
									
									
									
								
							|  | @ -2,6 +2,17 @@ | |||
| 
 | ||||
| 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.1] - unreleased | ||||
| 
 | ||||
| ### Changed | ||||
| 
 | ||||
|   * Reworked internal code structure | ||||
| 
 | ||||
| ### Fixed | ||||
| 
 | ||||
|   * Publish vote info in chat when reloading config was not working when TTS was disabled | ||||
|   * Casting votes was allowed for broadcaster and mods only | ||||
| 
 | ||||
| ## [1.2.0] - 2022-08-13 | ||||
| 
 | ||||
| ### Added | ||||
|  |  | |||
							
								
								
									
										386
									
								
								tts.py
									
										
									
									
									
								
							
							
						
						
									
										386
									
								
								tts.py
									
										
									
									
									
								
							|  | @ -49,7 +49,7 @@ class IRC: | |||
|         self.tts_denied = [] | ||||
|         self.tts_allowed = [] | ||||
|         self.tts_status = conf['TTS_STARTENABLED'] | ||||
|         self.quickvote = False | ||||
|         self.quickvote_status = False | ||||
|         self.votemsg = False | ||||
|         self.poll = {} | ||||
|         self.pollcount = 0 | ||||
|  | @ -177,167 +177,45 @@ class IRC: | |||
|             logging.debug('Deny List:') | ||||
|             logging.debug(self.tts_denied) | ||||
| 
 | ||||
|             if msg.startswith('#') and self.quickvote_status is True: | ||||
|                 logging.info('Quickvote: Cast detected') | ||||
|                 self.pollcount += 1 | ||||
|                 self.poll[user] = msg.lower() | ||||
|                 logging.debug(self.poll) | ||||
|                 return True | ||||
| 
 | ||||
|             if 'broadcaster' in badges or 'moderator' in badges: | ||||
|                 if msg.startswith('!dtts'): | ||||
|                     logging.debug("!dtts command detected") | ||||
|                     user = msg.replace('!dtts', '').strip().lower() | ||||
| 
 | ||||
|                     if user.startswith('@'): | ||||
|                         logging.debug('Removing "@" from username') | ||||
|                         user = user.replace('@', '') | ||||
|                     if user not in self.tts_denied: | ||||
|                         logging.info("Adding %s to deny list", user) | ||||
|                         self.tts_denied.append(user) | ||||
|                     if user in self.tts_allowed: | ||||
|                         logging.info("Removing %s from allowed list", user) | ||||
|                         self.tts_allowed.remove(user) | ||||
| 
 | ||||
|                     return True | ||||
| 
 | ||||
|                 if msg.startswith('!ping'): | ||||
|                     logging.debug("Ping check received.") | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), "Pong!") | ||||
|                     return True | ||||
| 
 | ||||
|                 if msg.startswith('!dtts'): | ||||
|                     logging.debug("!dtts command detected") | ||||
|                     self.Commands.dtts(self, msg) | ||||
|                     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']] | ||||
| 
 | ||||
|                     self.Commands.random(self, msg) | ||||
|                     return True | ||||
| 
 | ||||
|                 if msg.startswith('!quickvote'): | ||||
|                     logging.info("!quickvote command detected") | ||||
|                     if self.quickvote: | ||||
|                         logging.debug('Quickvote stopped') | ||||
| 
 | ||||
|                         if self.pollcount == 0: | ||||
|                             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 | ||||
| 
 | ||||
|                         logging.info("Counting votes") | ||||
|                         count = 0 | ||||
|                         count = Counter(self.poll.values()).most_common(5) | ||||
|                         self.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTEEND']) | ||||
| 
 | ||||
|                         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'], "*", | ||||
|                                 str(key)+" ("+str(value)+ " "+ conf['MESSAGE']['VOTES'] + ")" | ||||
|                             ) | ||||
| 
 | ||||
|                         self.quickvote = False | ||||
|                         self.poll = {} | ||||
|                         self.pollcount = 0 | ||||
|                         return True | ||||
|                     else: | ||||
|                         logging.debug('Quickvote started') | ||||
|                         self.quickvote = True | ||||
|                         self.votemsg = resp.split('!quickvote', 1)[1].strip() | ||||
|                         if self.votemsg: | ||||
|                             self.sendmsg( | ||||
|                                 conf['IRC_CHANNEL'], "@chat", | ||||
|                                 conf['MESSAGE']['VOTESTART'] + " (" + str(self.votemsg) + ")" | ||||
|                             ) | ||||
|                         else: | ||||
|                             self.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTESTART']) | ||||
|                     self.Commands.quickvote(self, msg) | ||||
|                     return True | ||||
| 
 | ||||
|                 if msg.startswith('!ptts'): | ||||
|                     logging.debug("!ptts command detected") | ||||
|                     user = msg.replace('!ptts', '').strip().lower() | ||||
| 
 | ||||
|                     if user.startswith('@'): | ||||
|                         logging.debug('Removing "@" from username') | ||||
|                         user = user.replace('@', '') | ||||
| 
 | ||||
|                     logging.info("Adding %s to whitelist", user) | ||||
|                     self.tts_allowed.append(user) | ||||
| 
 | ||||
|                     if user in self.tts_denied: | ||||
|                         logging.info("Removing %s from deny list", user) | ||||
|                         self.tts_denied.remove(user) | ||||
| 
 | ||||
|                     self.Commands.ptts(self, msg) | ||||
|                     return True | ||||
| 
 | ||||
|                 if msg.startswith('#') and self.quickvote is True: | ||||
|                     logging.info('Quickvote: Cast detected') | ||||
|                     self.pollcount += 1 | ||||
|                     self.poll[user] = msg.lower() | ||||
|                     logging.debug(self.poll) | ||||
| 
 | ||||
|                 if msg.startswith('!toff'): | ||||
|                     logging.info('TTS is now turned off') | ||||
|                     msg_queue.clear() | ||||
|                     msg_queue_raw.clear() | ||||
|                     self.tts_status = False | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOFF']) | ||||
| 
 | ||||
|                     return True | ||||
| 
 | ||||
|                 if msg.startswith('!ton'): | ||||
|  | @ -346,28 +224,20 @@ class IRC: | |||
|                     msg_queue_raw.clear() | ||||
|                     self.tts_status = True | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TON']) | ||||
| 
 | ||||
|                     return True | ||||
| 
 | ||||
|             if msg.startswith('!tts'): | ||||
|                 logging.debug('!tts command detected') | ||||
| 
 | ||||
|                 if msglen > conf['IRC_TTS_LEN']: | ||||
|                     logging.info('TTS message is to long') | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOO_LONG']) | ||||
|                     return False | ||||
| 
 | ||||
|                 logging.debug("tts status: %s", self.tts_status) | ||||
|                 logging.debug(conf['TTS_STARTENABLED']) | ||||
| 
 | ||||
|                 if not self.tts_status: | ||||
|                     logging.info('TTS is disabled') | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DISABLED']) | ||||
|                     return False | ||||
| 
 | ||||
|                 if user in self.tts_denied: | ||||
|                     logging.info("%s is not allowed to use TTS", user) | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DENIED']) | ||||
|                 if msglen > conf['IRC_TTS_LEN']: | ||||
|                     logging.info('TTS message is to long') | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['TOO_LONG']) | ||||
|                     return False | ||||
| 
 | ||||
|                 if conf['IRC_SUBONLY']: | ||||
|  | @ -386,6 +256,11 @@ class IRC: | |||
|                         self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['MODONLY']) | ||||
|                         return False | ||||
| 
 | ||||
|                 if user in self.tts_denied: | ||||
|                     logging.info("%s is not allowed to use TTS", user) | ||||
|                     self.sendmsg(conf['IRC_CHANNEL'], "@"+str(user), conf['MESSAGE']['DENIED']) | ||||
|                     return False | ||||
| 
 | ||||
|                 if conf['WHITELIST']: | ||||
|                     if user not in self.tts_allowed: | ||||
|                         logging.info('User is not on whitelist') | ||||
|  | @ -395,13 +270,13 @@ class IRC: | |||
|                             "@"+str(user), conf['MESSAGE']['WHITELISTONLY'] | ||||
|                         ) | ||||
|                         return False | ||||
|                     else: | ||||
|                         logging.info('Nobody is on the whitelist.') | ||||
|                         self.sendmsg( | ||||
|                             conf['IRC_CHANNEL'], | ||||
|                             "@"+str(user), conf['MESSAGE']['WHITELISTONLY'] | ||||
|                         ) | ||||
|                         return False | ||||
| 
 | ||||
|                     logging.warning('Nobody is on the whitelist.') | ||||
|                     self.sendmsg( | ||||
|                         conf['IRC_CHANNEL'], | ||||
|                         "@"+str(user), conf['MESSAGE']['WHITELISTONLY'] | ||||
|                     ) | ||||
|                     return False | ||||
| 
 | ||||
|                 logging.info('Valid TTS message, adding to raw queue') | ||||
|                 tts = True | ||||
|  | @ -424,6 +299,191 @@ class IRC: | |||
| 
 | ||||
|         return False | ||||
| 
 | ||||
|     class Commands(): | ||||
|         """ Bot commands """ | ||||
| 
 | ||||
|         def __init__(self): | ||||
|             self.tts_denied = [] | ||||
|             self.tts_allowed = [] | ||||
|             self.quickvote_status = self.quickvote_status | ||||
|             self.votemsg = self.votemsg | ||||
|             self.poll = self.poll | ||||
|             self.pollcount = self.pollcount | ||||
| 
 | ||||
|         def quickvote(self, msg): | ||||
|             """ !quickvote command | ||||
| 
 | ||||
|                 Starts or stops the !quickvote function. On stop calculates the 5 most casted | ||||
|                 votes and send them to chat. The highest vote is send to msg_queue. | ||||
| 
 | ||||
|                 :param str msg: The IRC message triggering the command | ||||
|             """ | ||||
| 
 | ||||
|             if self.quickvote_status: | ||||
|                 logging.debug('Quickvote stopped') | ||||
| 
 | ||||
|                 if self.pollcount == 0: | ||||
|                     logging.info("Nobody voted") | ||||
|                     IRC.sendmsg(self, conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTEEND']) | ||||
|                     IRC.sendmsg(self, 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_status = False | ||||
|                     self.poll = {} | ||||
|                     return False | ||||
| 
 | ||||
|                 logging.info("Counting votes") | ||||
|                 count = 0 | ||||
|                 count = Counter(self.poll.values()).most_common(5) | ||||
|                 IRC.sendmsg(self, conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTEEND']) | ||||
| 
 | ||||
|                 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: | ||||
|                     IRC.sendmsg( | ||||
|                         self, | ||||
|                         conf['IRC_CHANNEL'], "*", | ||||
|                         str(key)+" ("+str(value)+ " "+ conf['MESSAGE']['VOTES'] + ")" | ||||
|                     ) | ||||
| 
 | ||||
|                 self.quickvote_status = False | ||||
|                 self.poll = {} | ||||
|                 self.pollcount = 0 | ||||
|                 return | ||||
| 
 | ||||
|             logging.debug('Quickvote started') | ||||
|             self.quickvote_status = True | ||||
|             self.votemsg = msg.split('!quickvote', 1)[1].strip() | ||||
|             if self.votemsg: | ||||
|                 IRC.sendmsg(self, | ||||
|                     conf['IRC_CHANNEL'], "@chat", | ||||
|                     conf['MESSAGE']['VOTESTART'] + " (" + str(self.votemsg) + ")" | ||||
|                 ) | ||||
|             else: | ||||
|                 IRC.sendmsg(self, conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTESTART']) | ||||
| 
 | ||||
|             return | ||||
| 
 | ||||
|         def random(self, msg): | ||||
|             """ !random command | ||||
| 
 | ||||
|                 Read a random line from randomfile and put it into msg_queue | ||||
|                 If no file is given in msg a standard file will be used | ||||
| 
 | ||||
|                 :param str msg: The IRC message triggering the command | ||||
|                 :raise: FileNotFoundError if randomfile does not exists | ||||
|                 :return: True if line was successfully read and added to msg_queue | ||||
|                 :rtype: bool | ||||
|             """ | ||||
| 
 | ||||
|             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 | ||||
| 
 | ||||
|         def ptts(self, msg): | ||||
|             """ !ptts command | ||||
| 
 | ||||
|                 Add user to tts_allowed list and remove user from tts_denied list | ||||
| 
 | ||||
|                 :param str msg: The IRC message triggering the command | ||||
|             """ | ||||
| 
 | ||||
|             user = msg.replace('!ptts', '').strip().lower() | ||||
| 
 | ||||
|             if user.startswith('@'): | ||||
|                 logging.debug('Removing "@" from username') | ||||
|                 user = user.replace('@', '') | ||||
| 
 | ||||
|             logging.info("Adding %s to whitelist", user) | ||||
|             self.tts_allowed.append(user) | ||||
| 
 | ||||
|             if user in self.tts_denied: | ||||
|                 logging.info("Removing %s from deny list", user) | ||||
|                 self.tts_denied.remove(user) | ||||
| 
 | ||||
|             return | ||||
| 
 | ||||
|         def dtts(self, msg): | ||||
|             """ !dtts command | ||||
| 
 | ||||
|                 Add user to tts_denied list and remove user from tts_allowed list | ||||
| 
 | ||||
|                 :param str msg: The IRC message triggering the command | ||||
|             """ | ||||
| 
 | ||||
|             user = msg.replace('!dtts', '').strip().lower() | ||||
| 
 | ||||
|             if user.startswith('@'): | ||||
|                 logging.debug('Removing "@" from username') | ||||
|                 user = user.replace('@', '') | ||||
| 
 | ||||
|             if user not in self.tts_denied: | ||||
|                 logging.info("Adding %s to deny list", user) | ||||
|                 self.tts_denied.append(user) | ||||
| 
 | ||||
|             if user in self.tts_allowed: | ||||
|                 logging.info("Removing %s from allowed list", user) | ||||
|                 self.tts_allowed.remove(user) | ||||
| 
 | ||||
|             return | ||||
| 
 | ||||
| class HTTPserv(BaseHTTPRequestHandler): | ||||
|     """Simple HTTP Server""" | ||||
| 
 | ||||
|  | @ -644,7 +704,7 @@ sys.tracebacklimit = 0 | |||
| if sys.argv[1:]: | ||||
|     if sys.argv[1] == "--version": | ||||
|         print('Simple TTS Bot') | ||||
|         print('Version 1.2.0') | ||||
|         print('Version 1.2.1') | ||||
|         sys.exit(1) | ||||
| 
 | ||||
| def main(): | ||||
|  | @ -692,21 +752,21 @@ def main(): | |||
|             try: | ||||
|                 irc.get_response() | ||||
| 
 | ||||
|                 if not irc.tts_status: | ||||
|                     logging.debug("TTS is disabled") | ||||
|                     if conf['LOG_LEVEL'] == "DEBUG": | ||||
|                         time.sleep(1) | ||||
|                     continue | ||||
| 
 | ||||
|                 confreload = datetime.datetime.now() | ||||
|                 if confreload - lastreload > datetime.timedelta(seconds=60): | ||||
|                     conf = load_config() | ||||
|                     lastreload = datetime.datetime.now() | ||||
| 
 | ||||
|                     if irc.quickvote and irc.votemsg: | ||||
|                     if irc.quickvote_status and irc.votemsg: | ||||
|                         logging.info('Quickvote is active') | ||||
|                         irc.sendmsg(conf['IRC_CHANNEL'], "@chat", conf['MESSAGE']['VOTESTART'] + " (" + str(irc.votemsg) + ")") | ||||
| 
 | ||||
|                 if not irc.tts_status: | ||||
|                     logging.debug("TTS is disabled") | ||||
|                     if conf['LOG_LEVEL'] == "DEBUG": | ||||
|                         time.sleep(1) | ||||
|                     continue | ||||
| 
 | ||||
|                 logging.debug('Raw message queue:') | ||||
|                 logging.debug(msg_queue_raw) | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue