Compare commits

..

16 Commits

Author SHA1 Message Date
tamservo
e5219bc433 Changed processing for race messages because of new format coming through. 2025-05-18 12:24:17 -04:00
tamservo
839ee6306c Added file locking to avoid db contention 2025-05-03 22:07:31 -04:00
tamservo
4b636eed7a Took user restrictions off starting race reporting. Tried to improve sql writes to fix db locking error. 2025-04-21 16:47:05 -04:00
tamservo
069d2ca7d0 Merge branch 'main' of https://git.sdf.org/tamservo/robottas
fix workflow testing branch issue.
2025-01-09 14:37:37 -05:00
tamservo
72f96a493b fixed silly comparison error. 2025-01-09 14:36:29 -05:00
Aaron Colon
e11b2396b6 Testing workflow. 2025-01-01 16:51:39 -05:00
Aaron Colon
0c7d721ddd testing workflow 2025-01-01 16:48:51 -05:00
tamservo
58fe1cdda6 Testing workflow 2025-01-01 16:44:12 -05:00
tamservo
9dd76e16b7 Update README.md 2025-01-01 16:43:36 -05:00
tamservo
a0be0c7607 Updated Readme.md. Testing new workflow. 2025-01-01 16:41:31 -05:00
tamservo
50a45e40d5 testing 2025-01-01 16:22:06 -05:00
tamservo
5b3ade2863 Update README.md 2025-01-01 16:20:27 -05:00
tamservo
93364b8580 fix check on half distance 2024-12-08 09:41:08 -05:00
tamservo
4d288e871c Changed permission filter for race commands because discord changed the name to remove the #<number> portion of the id name. 2024-12-01 17:12:07 -05:00
tamservo
c3716613fe Added missing required module to requirements.txt 2024-11-30 19:32:32 -05:00
tamservo
f853f6c220 Added set_total_laps for when they change the number in the laps in the race (i.e. false start procedure.) Also fixed check for race half distance. 2024-11-24 17:35:36 -05:00
4 changed files with 278 additions and 128 deletions

View File

@@ -2,4 +2,4 @@
Discord bot for reporting F1 information.
Makes use of code from fastf1 python library (https://github.com/theOehrly/Fast-F1)
Makes use of code from fastf1 python library (https://github.com/theOehrly/Fast-F1)

View File

@@ -5,7 +5,10 @@ import logging
import requests
import time
import sqlite3
import sys
from contextlib import closing
from filelock import Timeout, FileLock
from fastf1.signalr_aio import Connection
import fastf1
@@ -84,6 +87,8 @@ class SignalRClient:
"SessionData", "LapCount"]
self.debug = debug
self.messages_db = 'messages.db'
self.messages_lock = FileLock('messages.lock')
self.filename = filename
self.filemode = filemode
self.timeout = timeout
@@ -91,9 +96,11 @@ class SignalRClient:
if not logger:
logging.basicConfig(
format="%(asctime)s - %(levelname)s: %(message)s"
format="%(asctime)s - %(levelname)s: %(message)s", \
stream=sys.stderr
)
self.logger = logging.getLogger('SignalR')
self.logger.warning("Created logger for SignalR")
else:
self.logger = logger
@@ -101,17 +108,15 @@ class SignalRClient:
self._t_last_message = None
def _to_file(self, msg):
"""
self._output_file.write(msg + '\n')
self._output_file.flush()
"""
#print(msg)
con = sqlite3.connect('messages.db')
cur = con.cursor()
cur.execute("insert into messages (message) values(?)", (msg,))
con.commit()
cur.close()
con.close()
try:
with self.messages_lock:
with closing(sqlite3.connect(self.messages_db)) as con:
with closing(con.cursor()) as cur:
#self.logger.warning("about to log: " + msg)
cur.execute("insert into messages (message) values(?)", (msg,))
con.commit()
except:
print(f'Error writing message to db.')
async def _on_do_nothing(self, msg):
# just do nothing with the message; intended for debug mode where some
@@ -186,7 +191,7 @@ class SignalRClient:
f"[v{fastf1.__version__}]")
await asyncio.gather(asyncio.ensure_future(self._supervise()),
asyncio.ensure_future(self._run()))
self._output_file.close()
#self._output_file.close()
self.logger.warning("Exiting...")
def start(self):

1
requirements.txt Executable file → Normal file
View File

@@ -3,3 +3,4 @@ discord==2.2.3
fastf1==3.0.3
jdata==0.5.3
Requests==2.31.0
websockets==11.0.3

View File

@@ -17,9 +17,10 @@ from subprocess import Popen
import time
import discord
from contextlib import closing
from filelock import Timeout, FileLock
from discord.ext import commands, tasks
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
class Robottas(commands.Bot):
@@ -29,15 +30,16 @@ class Robottas(commands.Bot):
# the Livetiming client.
@staticmethod
def convert_message(raw):
def convert_message(raw, logger):
data = raw.replace("'", '"') \
.replace('True', 'true') \
.replace('False', 'false')
try:
data = json.loads(data)
#logger.warn(f"convert_message returning: {data}")
return data
except json.JSONDecodeError:
logger.warning("json decode error for")
return ""
# End section adapted from FastF1
@@ -79,8 +81,9 @@ class Robottas(commands.Bot):
while len(self.message_queue) > 0 and \
self.message_queue[0][0] < time.time() - self.delay:
message = self.message_queue.pop(0)[1]
self.logger.warning(f'process_delay_messages: {message[:50]}')
await self.send_message(message)
await asyncio.sleep(1)
await asyncio.sleep(3)
async def send_status_report(self, report):
self.send_delay_message(report)
@@ -123,49 +126,74 @@ class Robottas(commands.Bot):
# Return None or flag message
return report
async def load_rcm_messages_helper(self, msg):
self.logger.warning(f'helper msg: {msg}')
report = None
if 'Category' in msg.keys():
category = msg['Category']
category = category.upper()
if category == "FLAG":
report = await self.load_flag_message(msg)
elif category == "OTHER":
if self.session_type == "RACE" and "DELETED" in msg['Message']:
pass
elif "SLIPPERY" in msg['Message'] and \
not self.is_slippery_reported:
self.is_slippery_reported = True
report = "It's slippery out there!"
else:
report = msg['Message']
elif category == "DRS":
report = msg['Message']
elif category == "CAREVENT" and \
(self.session_type == "PRACTICE" or
self.session_type == "QUALI"):
report = msg['Message']
elif category == "SAFETYCAR":
if msg["Mode"] == 'VIRTUAL SAFETY CAR':
report = f"{self.flag_dict['VSC']}" + \
f"{msg['Message']}" + \
f"{self.flag_dict['VSC']}"
elif msg["Mode"] == 'SAFETY CAR':
report = f"{self.flag_dict['SC']}" + \
f"{msg['Message']}" + \
f"{self.flag_dict['SC']}"
self.logger.warning(f'rcm helper - report: {report}')
return report
async def load_rcm_messages(self, data):
if "Messages" in data.keys():
report = None
for key in data['Messages'].keys():
message = data['Messages'][key]
if 'Category' in message.keys():
category = message["Category"]
category = category.upper()
if category == "FLAG":
report = await self.load_flag_message(message)
self.logger.warning(f'load_rcm_messages - data: {data}')
# Value of 'Messages' can be scalar or array.
# If scalar, pass to helper. If array, loop and send to helper
msgs_value = data['Messages']
self.logger.warning(f'load_rcm_messages - msgs_value: {msgs_value} type: {type(msgs_value)}')
elif category == "OTHER":
if self.session_type == "RACE" and "DELETED" in message['Message']:
pass
elif "SLIPPERY" in message['Message'] and \
not self.is_slippery_reported:
self.is_slippery_reported = True
report = "It's slippery out there!"
else:
report = message['Message']
elif category == "DRS":
report = message['Message']
elif category == "CAREVENT" and \
(self.session_type == "PRACTICE" or
self.session_type == "QUALI"):
report = message['Message']
elif category == "SAFETYCAR":
if message["Mode"] == 'VIRTUAL SAFETY CAR':
report = f"{self.flag_dict['VSC']}" + \
f"{message['Message']}" + \
f"{self.flag_dict['VSC']}"
elif message["Mode"] == 'SAFETY CAR':
report = f"{self.flag_dict['SC']}" + \
f"{message['Message']}" + \
f"{self.flag_dict['SC']}"
if report is not None:
await self.send_status_report(report)
# If value is a list
if isinstance(msgs_value, list):
for msg in msgs_value:
self.logger.warning(f'load_rcm_messages array msg: {msg}')
report = await self.load_rcm_messages_helper(msg)
if report is not None:
await self.send_status_report(report)
else:
self.logger.warning(f'load_rcm_messages scalar msg: {msgs_value}')
for msg in msgs_value.values():
report = await self.load_rcm_messages_helper(msg)
if report is not None:
await self.send_status_report(report)
async def load_lap_data(self, data):
if "CurrentLap" in data.keys():
@@ -179,9 +207,10 @@ class Robottas(commands.Bot):
if "TotalLaps" in data.keys():
self.total_laps = int(data["TotalLaps"])
#Check to see about sending half distance message
if self.current_lap > int(self.total_laps / 2):
await self.send_message("We've passed half distance! Has Lance binned it yet?")
#Check to see about sending half distance message
if self.current_lap == int(self.total_laps / 2) + 1:
await self.send_message("We've passed half distance! Has Lance binned it yet?")
# Notify on lap change if matches a driver
key = str(self.total_laps - int(current_lap))
if key in self.driver_dict.keys():
@@ -335,6 +364,7 @@ class Robottas(commands.Bot):
self.weather = weather_txt
def load_timing_stats_data(self, data):
self.logger.warning(f'load_timing_stats_data: {data}')
if "Lines" in data.keys():
lines = data["Lines"]
for driver_num in lines.keys():
@@ -379,6 +409,7 @@ class Robottas(commands.Bot):
await self.print_driver_range(ctx, 10, 20)
def load_initial(self, message):
#self.logger.warning("in load_initial")
# Load podium data
if 'R' in message.keys():
if 'TopThree' in message['R'].keys():
@@ -391,6 +422,7 @@ class Robottas(commands.Bot):
# Load driver list
if 'DriverList' in message['R'].keys():
#self.logger.warning("loading DriverList")
for driver_num in message['R']['DriverList'].keys():
if driver_num == '_kf':
continue
@@ -428,6 +460,8 @@ class Robottas(commands.Bot):
async def process_message(self, message):
try:
if isinstance(message, collections.abc.Sequence):
self.logger.warning(f"in process_message {message[:50]}")
if message[0] == 'Heartbeat':
return
@@ -458,47 +492,54 @@ class Robottas(commands.Bot):
self.load_initial(message)
except Exception as e:
pass
self.logger.warning(f"Error in process_message: {e}")
def get_messages_from_db(self):
try:
messages = []
con = sqlite3.connect(self.dbfile)
cur = con.cursor()
cur2 = con.cursor()
for row in cur.execute('select id, message from messages order by id asc'):
messages.append(self.convert_message(row[1]))
with self.messages_lock:
with closing(sqlite3.connect(self.dbfile)) as con:
with closing(con.cursor()) as cur:
with closing(con.cursor()) as cur2:
for row in cur.execute('select id, message from messages order by id asc'):
msg = row[1]
messages.append(self.convert_message(msg, self.logger))
self.logger.warning(f"get_messages_from_db: {msg[:50]}")
# Now that we have the message, delete this row from the dbfile
cur2.execute(f"delete from messages where id = {row[0]}")
# Now that we have the message, delete this row from the dbfile
cur2.execute(f"delete from messages where id = {row[0]}")
con.commit()
cur.close()
cur2.close()
con.close()
con.commit()
return messages
except:
self.logger.warning(f"Error retrieving messages.")
return []
def clear_messages_from_db(self):
try:
con = sqlite3.connect(self.dbfile)
cur = con.cursor()
cur.execute('delete from messges')
con.commit()
cur.close()
con.close()
except:
pass
with self.messages_lock:
with closing(sqlite3.connect(self.dbfile)) as con:
with closing(con.cursor()) as cur:
cur.execute('delete from messages')
con.commit()
except Exception as e:
self.logger.warning(f"error in clear_messages_from_db: {e}")
async def _race_report(self, ctx):
if self.is_reporting:
await self.rbstop()
self.clear_messages_from_db()
self.report_deleted_lap = False
self.session_type = 'RACE'
await self._report(ctx)
async def _quali_report(self, ctx):
if self.is_reporting:
await self.rbstop()
self.clear_messages_from_db()
self.report_deleted_lap = True
self.session_type = 'QUALI'
@@ -506,6 +547,9 @@ class Robottas(commands.Bot):
await self._report(ctx)
async def _practice_report(self, ctx):
if self.is_reporting:
await self.rbstop()
self.clear_messages_from_db()
self.report_deleted_lap = True
self.session_type = 'PRACTICE'
@@ -518,17 +562,19 @@ class Robottas(commands.Bot):
while self.is_reporting:
# Do processing
await asyncio.sleep(5)
#self.logger.warning("in is_reporting")
# process any new messages in the db
messages = self.get_messages_from_db()
try:
for message in messages:
self.logger.warning(f"processing message: {message}")
await self.process_message(message)
logging.debug(message)
await asyncio.sleep(3)
except:
pass
except Exception as e:
self.logger.warning(f'Error in _report: {e}')
# process any messages in the delay queue
await self.process_delay_messages()
@@ -622,14 +668,55 @@ class Robottas(commands.Bot):
command_txt += self.collector_params
self.collector_proc = Popen(command_txt.split())
async def start_test_collect(self, ctx):
await self.stop_test_collect()
self.is_test_collecting = True
self.channel = ctx.channel
dir_path = os.path.dirname(os.path.realpath(__file__))
command_txt = os.path.join(dir_path, self.test_collector_command)
self.collector_proc = Popen(command_txt.split())
while self.is_test_collecting:
await asyncio.sleep(10)
messages = self.get_messages_from_db()
try:
for msg in messages:
self.logger.warning(f"processing message{msg}")
await self.process_message(msg)
except Exception as e:
self.logger.warning(f"Error in start_test_collect: {e}")
await self.process_delay_messages()
async def stop_collect(self):
self.is_collecting = False
self.is_test_collecting = False
try:
if self.collector_proc != None:
self.collector_proc.kill()
self.clear_messages_from_db()
except:
pass
self.logger.warning("error in stop_collect.")
try:
if self.test_collector_proc != None:
self.collector_proc.kill()
self.clear_messages_from_db()
except:
self.logger.warning("error in stop_collect for test.")
async def stop_test_collect(self):
self.is_test_collecting = False
try:
if self.test_collector_proc != None:
self.test_collector_proc.kill()
self.clear_messages_from_db()
except Exception as e:
self.logger.warning(f"Error in stop_test_collect: {e}")
def decode_watched(self, w):
if w == 0:
@@ -855,14 +942,24 @@ class Robottas(commands.Bot):
# Set debug or not
self.debug = True
self.messages_lock = FileLock('messages.lock')
#configure logging
logging.basicConfig(stream=sys.stderr, \
format="%(asctime)s - %(levelname)s: %(message)s")
self.logger = logging.getLogger('robottas')
self.bingo = Bingo()
# Discord authentication token
self.token = self.get_token("token.txt")
self.collector_command = "robottas_collector.py"
self.test_collector_command = "test_messager.py"
self.collector_params = " save dummy.txt"
self.collector_proc = None
self.test_collector_proc = None
self.is_collecting = False
self.test_collecting = False
# Preface messages with the following
self.report_preamble = ':robot::peach: Alert!'
@@ -901,56 +998,60 @@ class Robottas(commands.Bot):
self.driver_dict = {
'1': '<:VER:1067541523748630570>',
'3': '<:RIC:1067870312949108887>',
'5': '<:VET:1067964065516884079>',
'4': '<:NOR:1067840487593082941>',
'5': '<:BOR:1345026018938716161>',
'6': '<:HAD:1345027038104387674>',
'7': '<:DOO:1289936390993215601>',
'10': '<:GAS:1067836596495327283>',
'11': '<:PER:1067822335123525732>',
'12': '<:ANT:1289237308805222491>',
'14': '<:ALO:1067876094033793054>',
'40': '<:LAW:1289237140051464204>',
'16': '<:LEC:1067544797050585198>',
'18': '<:STR:1067871582854336663>',
'20': '<:MAG:1067883814992486510>',
'22': '<:TSU:1067888851676315660>',
'23': '<:ALB:1067874026871074887>',
'24': '<:ZHO:1067865955117568010>',
'27': '<:HUL:1067880110918742187>',
'30': '<:LAW:1289237140051464204>',
'31': '<:OCO:1067834157465612398>',
'43': '<:COL:1289237227049844849>',
'44': '<:HAM:1067828533746991165>',
'55': '<:SAI:1067824776502067270>',
'63': '<:RUS:1067831294748274728>',
'16': '<:LEC:1067544797050585198>',
'18': '<:STR:1067871582854336663>',
'4': '<:NOR:1067840487593082941>',
'10': '<:GAS:1067836596495327283>',
'27': '<:HUL:1067880110918742187>',
'31': '<:OCO:1067834157465612398>',
'77': '<:BOT:1067819716527276032>',
'81': '<:PIA:1067844998369914961>',
'24': '<:ZHO:1067865955117568010>',
'22': '<:TSU:1067888851676315660>',
'20': '<:MAG:1067883814992486510>',
'23': '<:ALB:1067874026871074887>',
'38': '<:BEA:1289237392649224278>',
'87': '<:BEA:1289237392649224278>'
}
# Holds dictionary for driver 3-letter code to icon
self.name_dict = {
'VER': '<:VER:1067541523748630570>',
'RIC': '<:RIC:1067870312949108887>',
'VET': '<:VET:1067964065516884079>',
'PER': '<:PER:1067822335123525732>',
'ALB': '<:ALB:1067874026871074887>',
'ALO': '<:ALO:1067876094033793054>',
'HAM': '<:HAM:1067828533746991165>',
'SAI': '<:SAI:1067824776502067270>',
'RUS': '<:RUS:1067831294748274728>',
'LEC': '<:LEC:1067544797050585198>',
'STR': '<:STR:1067871582854336663>',
'NOR': '<:NOR:1067840487593082941>',
'ANT': '<:ANT:1289237308805222491>',
'BEA': '<:BEA:1289237392649224278>',
'BOR': '<:BOR:1345026018938716161>',
'BOT': '<:BOT:1067819716527276032>',
'COL': '<:COL:1289237227049844849>',
'DOO': '<:DOO:1289936390993215601>',
'GAS': '<:GAS:1067836596495327283>',
'HAD': '<:HAD:1345027038104387674>',
'HAM': '<:HAM:1067828533746991165>',
'HUL': '<:HUL:1067880110918742187>',
'LAW': '<:LAW:1289237140051464204>',
'OCO': '<:OCO:1067834157465612398>',
'BOT': '<:BOT:1067819716527276032>',
'PIA': '<:PIA:1067844998369914961>',
'ZHO': '<:ZHO:1067865955117568010>',
'TSU': '<:TSU:1067888851676315660>',
'LEC': '<:LEC:1067544797050585198>',
'MAG': '<:MAG:1067883814992486510>',
'ALB': '<:ALB:1067874026871074887>',
'BEA': '<:BEA:1289237392649224278>',
'COL': '<:COL:1289237227049844849>',
'ANT': '<:ANT:1289237308805222491>'
'NOR': '<:NOR:1067840487593082941>',
'OCO': '<:OCO:1067834157465612398>',
'PIA': '<:PIA:1067844998369914961>',
'RIC': '<:RIC:1067870312949108887>',
'RUS': '<:RUS:1067831294748274728>',
'SAI': '<:SAI:1067824776502067270>',
'STR': '<:STR:1067871582854336663>',
'TSU': '<:TSU:1067888851676315660>',
'VER': '<:VER:1067541523748630570>',
'VET': '<:VET:1067964065516884079>',
'ZHO': '<:ZHO:1067865955117568010>'
}
# Holds dictionary for race states to icons
@@ -983,7 +1084,7 @@ class Robottas(commands.Bot):
self.started = False
# Hold message delay
self.delay = 45
self.delay = 20
self.message_queue = []
# Hold whether to report deleted lap messages
@@ -1032,6 +1133,8 @@ class Robottas(commands.Bot):
" !calm - keep calm but come on.\n" +
" !censored\n" +
" !ciao\n" +
" !danger\n" +
" !dangerbull\n" +
" !encouragement\n" +
" !forecast - what happened to that podium...\n" +
" !grandma\n" +
@@ -1039,18 +1142,22 @@ class Robottas(commands.Bot):
" !hard\n" +
" !inin - in in in in in\n" +
" !liked\n" +
" !mariachi\n" +
" !no\n" +
" !noengine\n" +
" !pants\n" +
" !paddock\n" +
" !penalty\n" +
" !rain\n" +
" !ricky\n" +
" !rude - Charles thinks it's rude.\n" +
" !slight - Charles did a slight touch.\n" +
" !stupid\n" +
" !steer\n" +
" !tm\n" +
" !undercut\n" +
" !wall - Lance is in the wall")
" !wall - Lance is in the wall\n" +
" !yuki")
await ctx.send("bot commands:\n" +
" !register_next_race_alerts - get an alert for the next race on Monday.\n" +
@@ -1120,21 +1227,24 @@ class Robottas(commands.Bot):
@self.command()
async def race(ctx):
if str(ctx.author) == "tamservo#0" or ctx.author.guild_permissions.administrator:
await ctx.send(":robot::peach: Ready to report for the race!")
await self._race_report(ctx)
#if ctx.author.id == 581960756271251457 or \
# ctx.author.guild_permissions.administrator:
await ctx.send(":robot::peach: Ready to report for the race!")
await self._race_report(ctx)
@self.command()
async def quali(ctx):
if str(ctx.author) == "tamservo#0" or ctx.author.guild_permissions.administrator:
await ctx.send(":robot::peach: Ready to report on quali!")
await self._quali_report(ctx)
#if ctx.author.id == 581960756271251457 or \
# ctx.author.guild_permissions.administrator:
await ctx.send(":robot::peach: Ready to report on quali!")
await self._quali_report(ctx)
@self.command()
async def practice(ctx):
if str(ctx.author) == "tamservo#0" or ctx.author.guild_permissions.administrator:
await ctx.send(":robot::peach: Ready to report on practice!")
await self._practice_report(ctx)
#if ctx.author.id == 581960756271251457 or \
# ctx.author.guild_permissions.administrator:
await ctx.send(":robot::peach: Ready to report on practice!")
await self._practice_report(ctx)
@self.command()
async def flap(ctx):
@@ -1151,7 +1261,9 @@ class Robottas(commands.Bot):
@self.command()
async def start_collect(ctx):
if str(ctx.author) == "tamservo#0" or ctx.author.guild_permissions.administrator:
# Check id for tamservo or admin on channel
if ctx.author.id == 581960756271251457 or \
ctx.author.guild_permissions.administrator:
# if an authorized user, start the collection script that
# puts records into the database
await self.start_collect()
@@ -1160,6 +1272,10 @@ class Robottas(commands.Bot):
async def danger(ctx):
await ctx.send("That's some dangerous driving! " + self.name_dict["HAM"])
@self.command()
async def dangerbull(ctx):
await self.send_image(ctx, "images/dangerbull.png")
@self.command()
async def dotd(ctx):
await ctx.send("I don't know, probably " + self.name_dict["ALB"])
@@ -1300,6 +1416,10 @@ class Robottas(commands.Bot):
async def liked(ctx):
await self.send_image(ctx, "images/liked.png")
@self.command()
async def mariachi(ctx):
await self.send_image(ctx, "images/mariachi.mp4")
@self.command()
async def no(ctx):
await self.send_image(ctx, "images/no.png")
@@ -1321,6 +1441,10 @@ class Robottas(commands.Bot):
async def penalty(ctx):
await self.send_image(ctx, "images/penalty.png")
@self.command()
async def rain(ctx):
await self.send_image(ctx, "images/rain.gif")
@self.command()
async def ricky(ctx):
await self.send_image(ctx, "images/ricky.gif")
@@ -1341,6 +1465,10 @@ class Robottas(commands.Bot):
async def steer(ctx):
await self.send_image(ctx, "images/steer.png")
@self.command()
async def tm(ctx):
await self.send_image(ctx, "images/tm.png")
@self.command()
async def undercut(ctx):
await self.send_image(ctx, "images/undercut.png")
@@ -1360,6 +1488,10 @@ class Robottas(commands.Bot):
async def wall(ctx):
await self.send_image(ctx, "images/wall.mp4")
@self.command()
async def yuki(ctx):
await self.send_image(ctx, "images/yuki.mp4")
## Calendar Commands
# Give days, hours, minutes until the next event
@@ -1387,6 +1519,12 @@ class Robottas(commands.Bot):
async def register_next_event_alerts(ctx):
await self.register_next_event_alerts(ctx)
# Set total laps ( like when they shorten the race laps )
@self.command()
async def set_total_laps(ctx, laps):
self.total_laps = laps
await ctx.send(f"Total laps set to {self.total_laps}")
# Unregister to get monday alerts
@self.command()
async def unregister_alerts(ctx):
@@ -1407,6 +1545,12 @@ class Robottas(commands.Bot):
await self.show_bing(ctx)
# test processing messages
@self.command()
async def test_message_processing(ctx):
await self.start_test_collect(ctx)
if __name__ == '__main__':
rb = Robottas()
rb.run_robottas()