Compare commits

...

32 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
tamservo@mx.sdf.org
fabecd836b Added encouragement, slight commands. 2024-09-27 16:22:31 -04:00
tamservo@mx.sdf.org
09bdd939f2 Added steer command. 2024-09-27 16:03:00 -04:00
tamservo@mx.sdf.org
c85d850218 Added new icons for Lawson, Antonelli, Bearman, and Colapinto. 2024-09-27 15:53:01 -04:00
tamservo@mx.sdf.org
083fd06395 Added beans emoji for Colapinto 2024-09-22 10:54:08 -04:00
tamservo@mx.sdf.org
2f9b0af5d8 moved \!in to \!inin 2024-08-13 13:32:33 -04:00
tamservo@mx.sdf.org
b5c4c988f0 added \!in 2024-08-13 13:28:46 -04:00
tamservo@mx.sdf.org
082c4c16b9 Updated rbhelp command 2024-05-19 15:26:23 -04:00
tamservo@mx.sdf.org
0e837958d9 Added help line for \!wall 2024-03-09 15:22:07 -05:00
tamservo
116d69ece9 added wall cmd. 2024-03-09 14:27:24 -05:00
tamservo
35f818d00d Logging messages from db. 2024-03-09 10:14:14 -05:00
tamservo
3f3b8c53b2 Setting up logging 2024-03-09 10:07:12 -05:00
tamservo
f197ae3ab5 Add icon for Bearman 2024-03-09 10:01:14 -05:00
tamservo
5b58bfbb84 Added binger. Fixed issue with delta string. 2024-03-07 21:00:01 -05:00
tamservo
b9f10ebb0e added variety of free squares 2024-02-17 10:25:31 -05:00
tamservo
cf2af10110 added bingo card commands 2024-02-17 08:41:15 -05:00
tamservo
745300c921 adding bingo module 2024-02-17 08:01:17 -05:00
58 changed files with 572 additions and 163 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):

BIN
bing/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

BIN
bing/10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 KiB

BIN
bing/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

BIN
bing/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

BIN
bing/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 236 KiB

BIN
bing/5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

BIN
bing/6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

BIN
bing/7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

BIN
bing/8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

BIN
bing/9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

26
binger.py Executable file
View File

@@ -0,0 +1,26 @@
import glob
from random import randrange
import sqlite3
BING_IMGS_PATH = "bing/*"
DB = "bing.db"
def get_image_count():
# Choose a random image
bing_files = glob.glob(BING_IMGS_PATH)
image_idx = randrange(len(bing_files))
image = bing_files[image_idx]
# Get the latest count and increment
con = sqlite3.connect(DB)
cur = con.cursor()
cur.execute("UPDATE BING_COUNT SET COUNT = COUNT + 1")
con.commit()
result = cur.execute("SELECT COUNT FROM BING_COUNT")
count = result.fetchone()
count = count[0]
cur.close()
con.close()
return(image, count)

56
bingo.py Executable file
View File

@@ -0,0 +1,56 @@
import glob
from PIL import Image
from random import randrange
from uuid import uuid4
class Bingo:
"""Class which allows creation and cleanup of F1 bingo card images."""
def __init__(self):
self.LAYOUT_WIDTH = 88
self.X_OFFSET = 10
self.Y_OFFSET = 110
self.SQUARES_PATH = "bingo_images/squares/*.png"
self.FREE_SQUARES_PATH = "bingo_images/free_squares/*.png"
self.BLANK_CARD_PATH = "bingo_images/card_blank.png"
self.TEMP_FOLDER = "bingo_images/temp/"
def get_card(self):
square_files = glob.glob(self.SQUARES_PATH)
free_square_files = glob.glob(self.FREE_SQUARES_PATH)
used_files = set()
with Image.open(self.BLANK_CARD_PATH) as card_img:
card_img.load()
card_img = card_img.convert('RGBA')
# Fill the grid
for y in range(5):
for x in range(5):
square_file = ""
# If this is the center square, pick a random free square
if x == 2 and y == 2:
square_file = \
free_square_files[randrange(len(free_square_files))]
# otherwise, find a random file that hasn't been used yet
else:
rand_file_idx = randrange(len(square_files))
while rand_file_idx in used_files:
rand_file_idx = randrange(len(square_files))
square_file = square_files[rand_file_idx]
used_files.add(rand_file_idx)
with Image.open(square_file) as square:
position = (self.X_OFFSET + (x * self.LAYOUT_WIDTH),
self.Y_OFFSET + (y * self.LAYOUT_WIDTH))
card_img.paste(square, position, square)
# Write image to temp file
outfile = "".join((self.TEMP_FOLDER, str(uuid4()), ".png"))
print(f"{outfile=}")
card_img.save(outfile)
return outfile

BIN
bingo_images/card_blank.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
bingo_images/card_blank.xcf Normal file

Binary file not shown.

BIN
bingo_images/free_space.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

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

@@ -1,11 +1,14 @@
#!/usr/bin/python3
import asyncio
from bingo import Bingo
import binger
import collections.abc
import datetime
import json
import logging
import os
import pathlib
import random
import re
import sqlite3
@@ -14,9 +17,12 @@ from subprocess import Popen
import time
import discord
from contextlib import closing
from filelock import Timeout, FileLock
from discord.ext import commands, tasks
class Robottas(commands.Bot):
# The following section is adapted from code by theOehrly on GitHub FastF1 project
@@ -24,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
@@ -74,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)
@@ -118,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():
@@ -173,6 +206,11 @@ class Robottas(commands.Bot):
# i.e. shortened due to rain.
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) + 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():
@@ -326,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():
@@ -370,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():
@@ -382,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
@@ -419,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
@@ -449,41 +492,65 @@ 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:
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'
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'
await self._report(ctx)
@@ -495,16 +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)
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()
@@ -598,49 +668,120 @@ 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:
return 'Not Watched Yet'
else:
return 'Watched Already'
def get_delta_str(self, delta):
min_str = "minute"
hour_str = "hour"
day_str = "day"
(hours, minutes, seconds) = (-1, -1, -1)
(days, rest) = (0, "")
delta_str = str(delta)
if " " in delta_str:
(days, _, rest) = delta_str.split(" ")
rest = rest.split(".")[0]
(hours, minutes, seconds) = rest.split(":")
else:
rest = delta_str.split(".")[0]
(hours, minutes, seconds) = rest.split(":")
if int(days) != 1:
day_str = "days"
if int(minutes) != 1:
min_str = "minutes"
if int(hours) != 1:
hour_str = "hours"
return f"{days} {day_str} {hours} {hour_str} {minutes} {min_str}"
async def report_next_event(self, ctx):
try:
tz = datetime.timezone.utc
con = sqlite3.connect('schedule.db')
cur = con.cursor()
now_str = datetime.datetime.now(tz=tz).isoformat(sep=' ')
now_str = now_str.split(".")[0]
t1 = datetime.datetime.now(tz=tz)
now_str = f"{t1.year}-{t1.month:02d}-{t1.day:02d} {t1.hour:02d}:{t1.minute:02d}:{t1.second}"
query = 'select * from schedule where date_start > ? ' + \
'order by date_start asc limit 1'
cur.execute(query, (now_str,))
rows = cur.fetchall()
for row in rows:
t1 = datetime.datetime.fromisoformat(now_str)
t2 = datetime.datetime.fromisoformat(row[3])
t2 = datetime.datetime.fromisoformat(row[3] + "+00:00")
delta = t2 - t1
delta_str = self.get_delta_str(delta)
message = f"The next event is the {row[1]} - {row[2]} which is {delta} from now."
message = f"The next event is Round {row[5]}: {row[1]} in {row[4]} which is {delta_str} from now."
await ctx.send(message)
break # There should only be one row anyway
except:
except Exception as e:
await ctx.send("Sorry, hit the wall trying to find the answer...")
print(e, file=sys.stderr)
async def report_next_race(self, ctx):
@@ -648,8 +789,8 @@ class Robottas(commands.Bot):
tz = datetime.timezone.utc
con = sqlite3.connect('schedule.db')
cur = con.cursor()
now_str = datetime.datetime.now(tz=tz).isoformat(sep=' ')
now_str = now_str.split(".")[0]
t1 = datetime.datetime.now(tz=tz)
now_str = f"{t1.year}-{t1.month:02d}-{t1.day:02d} {t1.hour:02d}:{t1.minute}:{t1.second}"
query = "SELECT * FROM schedule WHERE date_start > ? AND " + \
"session_type = 'Race' ORDER BY date_start ASC LIMIT 1"
@@ -658,11 +799,12 @@ class Robottas(commands.Bot):
rows = cur.fetchall()
for row in rows:
t1 = datetime.datetime.fromisoformat(now_str)
t2 = datetime.datetime.fromisoformat(row[3])
t2 = datetime.datetime.fromisoformat(row[3] + "+00:00")
delta = t2 - t1
message = f"The next race is the {row[1]} which is {delta} from now."
delta_str = self.get_delta_str(delta)
message = f"The next race is Round {row[5]}: {row[1]} in {row[4]} which is {delta_str} from now."
await ctx.send(message)
break
@@ -670,6 +812,25 @@ class Robottas(commands.Bot):
except:
await ctx.send("Sorry, hit the wall tring to find the next race.")
async def report_all_races(self, ctx):
try:
con = sqlite3.connect('schedule.db')
cur = con.cursor()
query = "SELECT * FROM schedule where session_type = 'Race' ORDER BY date_start ASC"
cur.execute(query)
rows = cur.fetchall()
await ctx.send( "All times UTC\n" )
for row in rows:
await ctx.send( f"Round {row[5]}: {row[1]} in {row[4]} which takes place {row[3]}\n" )
except:
await ctx.send("Sorry, hit the wall tring to show all races.")
# Register to alert for next race
async def register_next_race_alerts(self, ctx):
@@ -771,16 +932,34 @@ class Robottas(commands.Bot):
con.close()
async def show_bing(self, ctx):
(image, count) = binger.get_image_count()
await self.send_image(ctx, image)
await ctx.send(f"Bing count: {count}")
def __init__(self):
# 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!'
@@ -819,52 +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': ':kiwi:',
'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>',
'2': '<:SAR:1067890949197414410>',
'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>',
'ALO': '<:ALO:1067876094033793054>',
'HAM': '<:HAM:1067828533746991165>',
'SAI': '<:SAI:1067824776502067270>',
'RUS': '<:RUS:1067831294748274728>',
'LEC': '<:LEC:1067544797050585198>',
'STR': '<:STR:1067871582854336663>',
'NOR': '<:NOR:1067840487593082941>',
'GAS': '<:GAS:1067836596495327283>',
'HUL': '<:HUL:1067880110918742187>',
'LAW': ':kiwi:',
'OCO': '<:OCO:1067834157465612398>',
'BOT': '<:BOT:1067819716527276032>',
'PIA': '<:PIA:1067844998369914961>',
'ZHO': '<:ZHO:1067865955117568010>',
'TSU': '<:TSU:1067888851676315660>',
'MAG': '<:MAG:1067883814992486510>',
'ALB': '<:ALB:1067874026871074887>',
'SAR': '<:SAR:1067890949197414410>',
'ALO': '<:ALO:1067876094033793054>',
'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>',
'LEC': '<:LEC:1067544797050585198>',
'MAG': '<:MAG:1067883814992486510>',
'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
@@ -897,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
@@ -921,40 +1108,68 @@ class Robottas(commands.Bot):
@self.command()
async def rbhelp(ctx):
await ctx.send("commands: \n" +
"!rbhelp - Print this help message " +
"(but you knew that already)\n" +
"!next_event - Prints time until the next event.\n" +
"!next_race - Prints time until the next race.\n" +
"!rbname - I will tell you my name.\n" +
"!rbroot - I will tell you who I root for.\n" +
"!rbreport - I will start race reporting in this channel. " +
"I will try to tell you about current flags, laps left,\n" +
"and safety cars.\n" +
"!rbstop - Stop reporting.\n" +
"!podium - Display podium positions.\n" +
"!q1cut - Show drivers in Q1 cut positions.\n" +
"!q2cut - Show drivers in Q2 cut positions.\n" +
"!rbdelay - Set the race messaging delay in seconds.\n" +
"The following display race control messages:\n" +
" !animal\n" +
" !bwoken\n" +
" !calm\n" +
" !forecast\n" +
" !grandma\n" +
" !grass\n" +
" !no\n" +
" !noengine\n" +
" !pants\n" +
" !paddock\n" +
" !penalty\n" +
" !stupid\n" +
" !undercut\n" +
"The following register / unregister scheduled next race / event messages.\n" +
"!register_next_race_alerts - get an alert for the next race on Monday.\n" +
"!register_next_event_alerts - get an alert for the next event on Monday.\n" +
"!unregister_alerts - stop getting alerts on this channel.\n"
)
await ctx.send("Robottas commands:\n" +
" !rbhelp - show this help message (but you knew that)")
await ctx.send("race reporting:\n" +
" !rbstop - stop reporting. If you started reporting on an event " +
"please run this when you are done." +
" !race - start reporting on a race\n" +
" !quali - start reporting on quali\n" +
" !practice - start reporting on practice\n" +
" !podium - show the podium positions\n" +
" !q1cut - show drivers cut in q1 positions\n" +
" !q2cut - show drivers cut in q2 positions")
await ctx.send("event info:\n" +
" !next_event - Prints time until the next event.\n" +
" !next_race - Prints time until the next race.\n" +
" !all_races - Prints the loation and date of all races in the schedule.")
await ctx.send("meme type commands:\n" +
" !animal - animal on the track\n" +
" !bing - count Max bings\n" +
" !bwoken - it's bwoken\n" +
" !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" +
" !grass\n" +
" !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\n" +
" !yuki")
await ctx.send("bot commands:\n" +
" !register_next_race_alerts - get an alert for the next race on Monday.\n" +
" !register_next_event_alerts - get an alert for the next event on Monday.\n" +
" !unregister_alerts - stop getting alerts on this channel.\n" +
" !rbname - I will tell you my name.\n" +
" !rbroot - I will tell you who I root for.\n" +
" !rbdelay - Set the race messaging delay in seconds.")
await ctx.send("bingo:\n" +
" !bingo_card - get a random bingo card.\n" +
" !bingo - announce that you've won bingo.")
@self.command()
async def rbroot(ctx):
@@ -1012,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):
@@ -1043,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()
@@ -1052,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"])
@@ -1147,10 +1371,27 @@ class Robottas(commands.Bot):
await ctx.send("Bono, my tyres are gone " + self.name_dict["HAM"])
# Commands that send images
@self.command()
async def bingo(ctx):
await self.send_image(ctx, "images/bingo_win.png")
@self.command()
async def calm(ctx):
await self.send_image(ctx, "images/calm.png")
@self.command()
async def censored(ctx):
await self.send_image(ctx, "images/censored.png")
@self.command()
async def ciao(ctx):
await self.send_image(ctx, "images/ciao.png")
@self.command()
async def encouragement(ctx):
await self.send_image(ctx, "images/encouragement.png")
@self.command()
async def grandma(ctx):
await self.send_image(ctx, "images/grandma.png")
@@ -1163,6 +1404,22 @@ class Robottas(commands.Bot):
async def forecast(ctx):
await self.send_image(ctx, "images/forecast.png")
@self.command()
async def hard(ctx):
await self.send_image(ctx, "images/hard.gif")
@self.command()
async def inin(ctx):
await self.send_image(ctx, "images/in.png")
@self.command()
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")
@@ -1184,10 +1441,34 @@ 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")
@self.command()
async def rude(ctx):
await self.send_image(ctx, "images/rude.mp4")
@self.command()
async def slight(ctx):
await self.send_image(ctx, "images/slight.png")
@self.command()
async def stupid(ctx):
await self.send_image(ctx, "images/stupid.png")
@self.command()
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")
@@ -1202,6 +1483,15 @@ class Robottas(commands.Bot):
await self.send_image(ctx, "images/bwoken.png")
await self.send_audio(ctx, "bwoken.mp3", "audio/bwoken.mp3")
@self.command()
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
@@ -1214,6 +1504,11 @@ class Robottas(commands.Bot):
async def next_race(ctx):
await self.report_next_race(ctx)
# Show all races
@self.command()
async def all_races(ctx):
await self.report_all_races(ctx)
# Register to get monday next race alerts
@self.command()
async def register_next_race_alerts(ctx):
@@ -1224,12 +1519,38 @@ 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):
await self.unregister_alerts(ctx)
# Bingo card
@self.command()
async def bingo_card(ctx):
card_file = self.bingo.get_card()
await self.send_image(ctx, card_file)
os.remove(card_file)
# Big counter
@self.command()
async def bing(ctx):
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()