Skip to content

Commit

Permalink
Nové funkcionality, novější věci! (#30)
Browse files Browse the repository at this point in the history
Hlavní pull-request na to, aby se nakonec všechny věci zrecenzovaly a
byly na jednom místě, než tyto věci dáme na produkci.

Co je nového? 
- #16 
- #21 
- #23 
- #24 
- Optimalizace hlasování - #29 
- Vytvořena CI pipeline

Co dál dělat?
- Počkat, až bude PR #24 hotový, teprve pak můžeme kód otestovat a
mergnout ho.

Kdo si zaslouží dík? 
- @Martian-0007 Díky moc! :)
  • Loading branch information
TheXer committed Sep 3, 2023
2 parents 90de370 + 8cbbfa6 commit 05f95a0
Show file tree
Hide file tree
Showing 26 changed files with 525 additions and 209 deletions.
25 changes: 25 additions & 0 deletions .github/workflows/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
on: push

jobs:
build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v1

- name: Set up CPython
uses: actions/setup-python@v4

- name: Install dependencies
id: install-deps
run: |
python -m pip install --upgrade pip setuptools wheel ruff
- name: Black format
uses: psf/black@stable

- name: Ruff Check
run: ruff check .



2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ __pycache__
/discord.log
.DS_STORE
.vscode
.ruff_cache
.pytest_cache
44 changes: 20 additions & 24 deletions cogs/error.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,35 @@
import logging

from discord import Interaction
from discord.ext import commands
from loguru import logger

from src.ui.embeds import ErrorMessage
from src.ui.error_view import PrettyError


class Error(commands.Cog):
"""Basic class for catching errors and sending a message"""

def __init__(self, bot):
self.bot = bot

self.logger = logging.getLogger("discord")
self.logger.setLevel(logging.WARN)
handler = logging.FileHandler(filename="discord.log", encoding="utf-8", mode="w")
handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(name)s: %(message)s"))
self.logger.addHandler(handler)
tree = self.bot.tree
self._old_tree_error = tree.on_error
tree.on_error = self.on_app_command_error

@commands.Cog.listener()
async def on_command_error(self, ctx: commands.Context, error: commands.CommandError):
async def on_app_command_error(self, interaction: Interaction, error: Exception):
match error:
case commands.ExpectedClosingQuoteError():
return await ctx.send(f"Pozor! Chybí tady: {error} uvozovka!")

case commands.MissingPermissions():
return await ctx.send("Chybí ti požadovaná práva!")

case commands.CommandNotFound():
pass

case PrettyError():
# if I use only 'error', gives me NoneType. Solved by this
logger.error(f"{error.__class__.__name__}: {interaction.command.name}")
await error.send()
case _:
self.logger.critical(f"{ctx.message.id}, {ctx.message.content} | {error}")
print(error)

@commands.Cog.listener()
async def on_command(self, ctx: commands.Context):
self.logger.info(f"{ctx.message.id} {ctx.message.content}")
logger.critical(error)
await interaction.response.send_message(
embed=ErrorMessage(
"Tato zpráva by se nikdy zobrazit správně neměla. "
"Jsi borec, že jsi mi dokázal rozbít Jáchyma, nechceš mi o tom napsat do issues na githubu?"
)
)


async def setup(bot):
Expand Down
8 changes: 2 additions & 6 deletions cogs/morserovka.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from discord import Message, app_commands
from discord.ext import commands


# Klasická morseovka


Expand Down Expand Up @@ -69,7 +68,7 @@ def __init__(self, bot):
async def zasifruj(self, interaction: discord.Interaction, message: str) -> Message:
cipher = ""
for letter in message:
if letter.upper() in self.MORSE_CODE_DICT.keys():
if letter.upper() in self.MORSE_CODE_DICT:
cipher += self.MORSE_CODE_DICT[letter.upper()] + "/"
else:
return await interaction.response.send_message(
Expand All @@ -81,10 +80,7 @@ async def zasifruj(self, interaction: discord.Interaction, message: str) -> Mess
@app_commands.command(name="desifruj", description="Dešifruj text z morserovky!")
@app_commands.describe(message="Věta nebo slovo pro dešifrování")
async def desifruj(self, interaction: discord.Interaction, message: str) -> Message:
decipher = "".join(
self.REVERSED_MORSE_CODE_DICT.get(letter)
for letter in re.split(r"\/|\\|\|", message)
)
decipher = "".join(self.REVERSED_MORSE_CODE_DICT.get(letter) for letter in re.split(r"\/|\\|\|", message))
return await interaction.response.send_message(decipher)


Expand Down
90 changes: 57 additions & 33 deletions cogs/poll_command.py
Original file line number Diff line number Diff line change
@@ -1,73 +1,97 @@
import re
import asyncio
import datetime

import discord
from discord import app_commands
from discord.ext import commands
from discord.app_commands import Transform
from discord.ext import commands, tasks
from loguru import logger

from src.db_folder.databases import PollDatabase, VoteButtonDatabase
from src.jachym import Jachym
from src.ui.embeds import PollEmbed, PollEmbedBase
from src.ui.poll import Poll
from src.ui.poll_view import PollView


def error_handling(answer: list[str]) -> str:
if len(answer) > Poll.MAX_OPTIONS:
return f"Zadal jsi příliš mnoho odpovědí, můžeš maximálně {Poll.MAX_OPTIONS}!"
elif len(answer) < Poll.MIN_OPTIONS:
return f"Zadal jsi příliš málo odpovědí, můžeš alespoň {Poll.MIN_OPTIONS}!"
from src.ui.transformers import DatetimeTransformer, OptionsTransformer


class PollCreate(commands.Cog):
POLL_PARAMETERS = {
"name": "anketa",
"description": "Anketa pro hlasování. Jsou vidět všichni hlasovatelé.",
"question": "Otázka, na kterou potřebuješ vědět odpověď",
"answer": 'Odpovědi, rozděluješ odpovědi uvozovkou ("), maximálně pouze 10 možností',
"help": """
Jednoduchá anketa, která obsahuje otázku a odpovědi. Povoleno je 10 možností.
""",
}

# Bugfix for iPhone users who have different font for aposthrofe
REGEX_PATTERN = ['"', "”", "“", "„"]

def __init__(self, bot: Jachym):
self.bot = bot

@app_commands.command(name="anketa", description="Anketa pro hlasování. Jsou vidět všichni hlasovatelé.")
@app_commands.rename(question="otázka", answer="odpovědi")
@app_commands.command(
name="anketa",
description="Anketa pro hlasování. Jsou vidět všichni hlasovatelé.",
)
@app_commands.rename(
question="otázka",
answer="odpovědi",
date_time="datum",
)
@app_commands.describe(
question="Otázka, kterou chceš položit.",
answer='Odpovědi, rozděluješ odpovědi uvozovkou ("), maximálně pouze 10 možností',
date_time="Den, na který anketa skončí.",
)
async def pool(self, interaction: discord.Interaction, question: str, answer: str) -> discord.Message:
await interaction.response.send_message(embed=PollEmbedBase("Dělám na tom, vydrž!"))
async def pool(
self,
interaction: discord.Interaction,
question: str,
answer: Transform[list[str, ...], OptionsTransformer],
date_time: Transform[datetime.datetime, DatetimeTransformer] | None,
):
await interaction.response.send_message(embed=PollEmbedBase("Nahrávám anketu..."))
message = await interaction.original_response()

answers = re.split("|".join(self.REGEX_PATTERN), answer)
if error_handling(answers):
return await message.edit(embed=PollEmbedBase(error_handling(answers)))

poll = Poll(
message_id=message.id,
channel_id=message.channel.id,
question=question,
options=answers,
options=answer,
user_id=interaction.user.id,
date_created=date_time,
)

embed = PollEmbed(poll)
view = PollView(poll, embed, db_poll=self.bot.pool)
await PollDatabase(self.bot.pool).add(poll)
await VoteButtonDatabase(self.bot.pool).add_options(poll)

self.bot.active_discord_polls.add(poll)
await self.bot.set_presence()
logger.info(f"Successfully added Pool - {message.id}")
return await message.edit(embed=embed, view=view)

await message.edit(embed=embed, view=view)
self.bot.active_discord_polls.add((poll, message))


class PollTaskLoops(commands.Cog):
def __init__(self, bot: Jachym):
self.bot = bot
self.send_completed_pool.start()

@tasks.loop(seconds=5)
async def send_completed_pool(self):
for poll, message in self.bot.active_discord_polls.copy():
if poll.created_at is None or datetime.datetime.now() < poll.created_at:
continue

embed = message.embeds[0]
embed_copy = embed.copy()
embed_copy.title = f"{embed.title[0]} [UZAVŘENO] {embed.title[1:]}"
embed_copy.remove_field(len(embed_copy.fields) - 1)

channel = self.bot.get_channel(poll.channel_id)
await channel.send(embed=embed_copy)

asyncio.create_task(PollDatabase(self.bot.pool).remove(poll.message_id))
asyncio.create_task(message.delete())
self.bot.active_discord_polls.remove((poll, message))

@send_completed_pool.before_loop
async def prepare_loop(self):
await self.bot.wait_until_ready()


async def setup(bot):
await bot.add_cog(PollCreate(bot))
await bot.add_cog(PollTaskLoops(bot))
8 changes: 4 additions & 4 deletions cogs/sync_command.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
from typing import Literal, Optional, TYPE_CHECKING
from typing import TYPE_CHECKING, Literal

import discord
from discord.ext import commands
from discord.ext.commands import Greedy, Context
from discord.ext.commands import Context, Greedy

if TYPE_CHECKING:
from src.jachym import Jachym
Expand All @@ -19,7 +19,7 @@ async def sync(
self,
ctx: Context,
guilds: Greedy[discord.Guild],
spec: Optional[Literal["-", "*", "^"]] = None,
spec: Literal["-", "*", "^"] | None = None,
) -> None:
"""
A command to sync all slash commands to servers user requires. Works like this:
Expand Down Expand Up @@ -57,7 +57,7 @@ async def sync(
synced = await self.bot.tree.sync()

await ctx.send(
f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}"
f"Synced {len(synced)} commands {'globally' if spec is None else 'to the current guild.'}",
)
return

Expand Down
21 changes: 13 additions & 8 deletions cogs/utility.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ def __init__(self, bot):
self.bot = bot

@app_commands.command(
name="pomoc", description="Pomocníček, který ti pomůže s různými věcmi."
name="pomoc",
description="Pomocníček, který ti pomůže s různými věcmi.",
)
async def pomoc(self, interaction: discord.Interaction) -> Message:
embed = EmbedFromJSON().add_fields_from_json("help")
return await interaction.response.send_message(
embed=embed, file=EmbedFromJSON.PICTURE, ephemeral=True
embed=embed,
file=EmbedFromJSON.PICTURE,
ephemeral=True,
)

@app_commands.command(
Expand All @@ -29,7 +32,9 @@ async def pomoc(self, interaction: discord.Interaction) -> Message:
async def rozcestnik(self, interaction: discord.Interaction) -> Message:
embed = EmbedFromJSON().add_fields_from_json("rozcestnik")
return await interaction.response.send_message(
embed=embed, file=EmbedFromJSON.PICTURE, ephemeral=True
embed=embed,
file=EmbedFromJSON.PICTURE,
ephemeral=True,
)

@app_commands.command(
Expand All @@ -54,10 +59,9 @@ async def clear(self, ctx: commands.Context, limit: int) -> Message:
if 1 < limit < 100:
deleted = await ctx.channel.purge(limit=limit)
return await ctx.send(
"Smazáno {deleted} zpráv.".format(deleted=len(deleted))
f"Smazáno {len(deleted)} zpráv.",
)
else:
return await ctx.send("Limit musí být někde mezi 1 nebo 99!")
return await ctx.send("Limit musí být někde mezi 1 nebo 99!")

@commands.command()
async def time(self, ctx: commands.Context):
Expand All @@ -67,11 +71,12 @@ async def time(self, ctx: commands.Context):
async def birthday(self, ctx):
today = datetime.date.today()
bot_birthday = datetime.datetime.strptime(
self.bot.MY_BIRTHDAY, "%d.%m.%Y"
self.bot.MY_BIRTHDAY,
"%d.%m.%Y",
).replace(year=today.year)
days_until_birthday = (bot_birthday.date() - today).days
await ctx.send(
f"Moje narozeniny jsou 27. prosince 2020 a zbývá přesně {days_until_birthday} dní do mých narozenin!"
f"Moje narozeniny jsou 27. prosince 2020 a zbývá přesně {days_until_birthday} dní do mých narozenin!",
)


Expand Down
6 changes: 5 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import asyncio
from os import getenv

import discord.utils
from dotenv import load_dotenv

from src.jachym import Jachym

load_dotenv("password.env")


async def main():
async def main() -> None:
bot = Jachym()
async with bot:
discord.utils.setup_logging()
await bot.load_extensions()
await bot.start(getenv("DISCORD_TOKEN"))

Expand Down
23 changes: 23 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[tool.ruff]

select = [
"E", # pycodestyle
"F", # pyflakes
"UP", # pyupgrade,
"I", # isort
"UP", # pyupgrade
"ASYNC",
"BLE", # Blind Exception
"T20", # Found a print!
"RET", # Unnecessary return
"SIM", # Simplify
]
exclude = [
"tests",
]

line-length = 120

[tool.black]

line-length = 120
1 change: 0 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

<h1 align=center>
<img src="fotky/Jáchym.png" alt="Logo Jáchyma">
<br>
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ python-dotenv==0.17.1

aiomysql>=0.0.22
pytest>=7.3.1
loguru>=0.7.0
loguru>=0.7.0
dateparser>=1.1.8
Empty file added src/__init__.py
Empty file.
Loading

0 comments on commit 05f95a0

Please sign in to comment.