Skip to content

Commit

Permalink
Merge branch 'FRCDiscord:master' into patch-2
Browse files Browse the repository at this point in the history
  • Loading branch information
skruglov2023 authored Jan 18, 2024
2 parents fb9f64e + cf73004 commit 4bdcba9
Show file tree
Hide file tree
Showing 20 changed files with 880 additions and 261 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ jobs:

steps:
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- uses: actions/setup-python@v2
- uses: actions/setup-python@v3
with:
python-version: '3.10.6'

Expand Down
7 changes: 0 additions & 7 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,9 @@ services:
- "5432:5432"
volumes:
- database:/var/lib/postgresql/data
lavalink:
image: fredboat/lavalink:latest
environment:
LAVALINK_SERVER_PASSWORD: youshallnotpass
SERVER_PORT: 2333
SERVER_HOST: 0.0.0.0
dozer:
depends_on:
- postgres
- lavalink
build: .
volumes:
- ".:/app"
Expand Down
24 changes: 20 additions & 4 deletions dozer/Components/CustomJoinLeaveMessages.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,31 @@ async def send_log(member):
def format_join_leave(template: str, member: discord.Member):
"""Formats join leave message templates
{guild} = guild name
{user} = user's name plus discriminator ex. SnowPlow#5196
{user_name} = user's name without discriminator
{user} = user's name
{user_mention} = user's mention
{user_id} = user's ID
"""
template = template or "{user_mention}\n{user} ({user_id})"
return template.format(guild=member.guild, user=str(member), user_name=member.name,
user_mention=member.mention, user_id=member.id)

subst = [("{guild}", member.guild.name),
("{user}", member),
("{user_mention}", member.mention),
("{user_id}", member.id)]

def helper(s: str, subst: list):
if not subst:
# base case: return self
return s
cur = subst[0]
# split the current string on cur[0].
# for each split segment call the helper on the rest of the substitutions.
# then rejoin on the substitutions (this avoids the substituted values from matching substitution keywords)

# we could make this not recursive but there's an O(1) number of possible recursions anyway
# recursion depth is limited to 5 since the subst list is limited
# breadth is limited by template size (indirectly limited by discord message size)
return str(cur[1]).join([helper(bit, subst[1:]) for bit in s.split(cur[0])])
return helper(template, subst)

class CustomJoinLeaveMessages(db.DatabaseTable):
"""Holds custom join leave messages"""
Expand Down
5 changes: 2 additions & 3 deletions dozer/cogs/actionlogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ async def on_raw_message_edit(self, payload: discord.RawMessageUpdateEvent):
embed = discord.Embed(title="Message Edited",
description=f"[MESSAGE]({link}) From {mention}\nEdited In: {mchannel.mention}",
color=0xFFC400)
embed.set_author(name=f"{author['username']}#{author['discriminator']}", icon_url=avatar_link)
embed.set_author(name=f"{author['username']}{'#' + author['discriminator'] if author['discriminator'] != '0' else ''}", icon_url=avatar_link)
embed.add_field(name="Original", value="N/A", inline=False)
if content:
embed.add_field(name="Edited", value=content[0:1023], inline=False)
Expand Down Expand Up @@ -547,8 +547,7 @@ async def help(self,
e.set_footer(text='Triggered by ' + escape_markdown(ctx.author.display_name))
e.description = """
`{guild}` = guild name
`{user}` = user's name plus discriminator ex. SnowPlow#5196
`{user_name}` = user's name without discriminator
`{user}` = user's name
`{user_mention}` = user's mention
`{user_id}` = user's ID
"""
Expand Down
183 changes: 183 additions & 0 deletions dozer/cogs/firstqa.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""Provides commands that pull information from First Q&A Form."""
from typing import Union
import re
import datetime
import json

import aiohttp
from bs4 import BeautifulSoup
import discord
from discord.ext import commands
from discord import app_commands

from dozer.context import DozerContext
from ._utils import *


async def data(ctx: DozerContext, level: str, question: int) -> Union[str, None]:
"""Returns QA Forum info for specified FTC/FRC"""
if level.lower() == "ftc":
async with ctx.cog.ses.get('https://ftc-qa.firstinspires.org/onepage.html') as response:
html_data = await response.text()
forum_url = "https://ftc-qa.firstinspires.org/qa/"
elif level.lower() == "frc":
async with ctx.cog.ses.get('https://frc-qa.firstinspires.org/onepage.html') as response:
html_data = await response.text()
forum_url = "https://frc-qa.firstinspires.org/qa/"
else:
return None

answers = BeautifulSoup(html_data, 'html.parser').get_text()

start = answers.find(f'Q{question} ')
a = ""
if start > 0:
finish = answers.find('answered', start) + 24
a = answers[start:finish]
# remove newlines
a = a.replace("\n", " ")
# remove multiple spaces
a = " ".join(a.split())
embed = discord.Embed(
title=a[:a.find(" Q: ")],
url=forum_url + str(question),
color=discord.Color.blue()
)
embed.add_field(
name="Question",
value=a[a.find(" Q: ") + 1:a.find(" A: ")],
inline=False
)
embed.add_field(
name="Answer",
value=a[a.find(" A: ") + 1:a.find(" ( Asked by ")],
inline=False
)
embed.set_footer(text=a[a.find(" ( Asked by ") + 1:])
return embed

else:
return f"That question was not answered or does not exist.\n{forum_url + str(question)}"

def createRuleEmbed(rulenumber, text):
"""Returns an embed for a given rule number and text"""
year = datetime.datetime.now().year
embed = discord.Embed(
title=f"Rule {rulenumber}",
url=f"https://rules-search.pages.dev/{year}/rule/{rulenumber}",
color=discord.Color.blue()
)

truncated_text = "```\n" + ' '.join(text[:1016].splitlines()) + "```"
embed.add_field(
name="Summary",
value=truncated_text
)
return embed


class QA(commands.Cog):
"""QA commands"""

def __init__(self, bot) -> None:
self.ses = aiohttp.ClientSession()
super().__init__()
self.bot = bot

@command(name="ftcqa", aliases=["ftcqaforum"], pass_context=True)
@bot_has_permissions(embed_links=True)
@app_commands.describe(question="The number of the question you want to look up")
async def ftcqa(self, ctx: DozerContext, question: int):
"""
Shows Answers from the FTC Q&A
"""
result = await data(ctx, "ftc", question)
if isinstance(result, discord.Embed):
await ctx.send(embed=result)
else:
await ctx.send(result)

ftcqa.example_usage = """
`{prefix}ftcqa 19` - show information on FTC Q&A #19
"""

@command(name = "frcqa", aliases = ["frcqaforum"], pass_context = True)
@bot_has_permissions(embed_links = True)
@app_commands.describe(question = "The number of the question you want to look up")
async def frcqa(self, ctx: DozerContext, question: int):
"""
Shows Answers from the FRC Q&A
"""
result = await data(ctx, "frc", question)
if isinstance(result, discord.Embed):
await ctx.send(embed=result)
else:
await ctx.send(result)

frcqa.example_usage = """
`{prefix}frcqa 19` - show information on FRC Q&A #19
"""


@command(name = "frcrule", pass_context = True)
@bot_has_permissions(embed_links = True)
@app_commands.describe(rule = "The rule number")
async def frcrule(self, ctx: DozerContext, *, rule: str):
"""
Shows rules from a rule number or search query
"""
matches = re.match(r'^(?P<letter>[a-zA-Z])(?P<number>\d{3})$', rule)
ephemeral = False

embed = discord.Embed(
title="Error",
color=discord.Color.blue()
)

if matches is None:
await ctx.defer()
async with ctx.cog.ses.post('https://search.grahamsh.com/search',json={'query': rule}) as response:
json_data = await response.content.read()
json_parsed = json.loads(json_data)

if "error" not in json_parsed:
embeds = []
page = 1
for currRule in json_parsed["data"]:
currEmbed = createRuleEmbed(currRule["text"], currRule["textContent"])
currEmbed.set_footer(text=f"Page {page} of 5")
embeds.append(currEmbed)

await paginate(ctx, embeds)
return

else:
letter_part = matches.group('letter')
number_part = matches.group('number')
year = datetime.datetime.now().year
async with ctx.cog.ses.get(f'https://rules-search.pages.dev/api/rule?query={letter_part}{number_part}') as response:
json_data = await response.content.read()

json_parsed = json.loads(json_data)

if "error" not in json_parsed:
text = json_parsed["textContent"]
embed = createRuleEmbed(letter_part.upper() + number_part, text)

else:
ephemeral = True
embed.add_field(
name="Error",
value="No such rule"
)

await ctx.send(embed=embed, ephemeral=ephemeral)
frcrule.example_usage = """
`{prefix}frcrule g301` - sends the summary and link to rule G301
`{prefix}frcrule can i cross the line before teleop` - sends the summary and link to the rule matching the query
"""

async def setup(bot):
"""Adds the QA cog to the bot."""
await bot.add_cog(QA(bot))
Loading

0 comments on commit 4bdcba9

Please sign in to comment.