Skip to content

Commit

Permalink
Format and lint with ruff, leaving one intentional warning to test
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinlul committed Jan 21, 2024
1 parent b458e04 commit 8f41f6f
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 44 deletions.
17 changes: 17 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: © 2024 Kevin Lu
# SPDX-Licence-Identifier: AGPL-3.0-or-later
name: Lint
on:
push:
Expand All @@ -23,3 +25,18 @@ jobs:
with:
category: guarddog-builtin
sarif_file: guarddog.sarif
# https://github.com/astral-sh/ruff
ruff:
runs-on: ubuntu-latest
permissions: {}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.10"
- run: pip install ruff
- run: ruff format --check src
- run: ruff check src
if: ${{ !cancelled() }}
- run: ruff check --output-format=github src
if: ${{ !cancelled() }}
8 changes: 5 additions & 3 deletions src/antiabuse.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# SPDX-FileCopyrightText: © 2023 Kevin Lu, Luna Brand
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu, Luna Brand
# SPDX-Licence-Identifier: AGPL-3.0-or-later
from typing import TYPE_CHECKING


if TYPE_CHECKING:
from praw.models import Comment, Submission
from praw.models.comment_forest import CommentForest


def is_author_me(comment: "Comment") -> bool:
return comment.author is not None and comment.author.name == comment._reddit.user.me().name
return (
comment.author is not None
and comment.author.name == comment._reddit.user.me().name
)


def is_summon_chain(comment: "Comment") -> bool:
Expand Down
4 changes: 2 additions & 2 deletions src/bastion.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: © 2023 Kevin Lu, Luna Brand
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu, Luna Brand
# SPDX-Licence-Identifier: AGPL-3.0-or-later
import logging

Expand All @@ -7,7 +7,7 @@
from clients import get_api_client
from limit_regulation import master_duel_limit_regulation
from mention import MentionsThread
from stream import SubmissionsThread, CommentsThread
from stream import CommentsThread, SubmissionsThread


def main():
Expand Down
6 changes: 4 additions & 2 deletions src/bot_thread.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: © 2023 Kevin Lu, Luna Brand
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu, Luna Brand
# SPDX-Licence-Identifier: AGPL-3.0-or-later
from collections import Counter
from datetime import datetime, timezone
Expand Down Expand Up @@ -43,7 +43,9 @@ def _reply(self, target: Union["Comment", "Submission"], text: str) -> None:
for item in e.items:
if item.error_type == "TOO_LONG":
self._logger.warning(f"{target.id}: reply too long", exc_info=e)
reply: "Comment" = target.reply(f"Sorry, the cards are too long to fit into one comment.{FOOTER}")
reply: "Comment" = target.reply(
f"Sorry, the cards are too long to fit into one comment.{FOOTER}"
)
self._logger.info(f"{target.id}: posted error {reply.id}")
self._reply_counter[target.submission.id] += 1
reply.disable_inbox_replies()
Expand Down
62 changes: 41 additions & 21 deletions src/card.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: © 2023 Kevin Lu, Luna Brand
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu, Luna Brand
# SPDX-Licence-Identifier: AGPL-3.0-or-later
from os import getenv
import re
Expand Down Expand Up @@ -26,12 +26,17 @@ def parse_summons(text: str) -> List[str]:
"""
summons: List[str] = summon_regex.findall(text)
summons = [summon.strip() for summon in summons]
return list(dict.fromkeys(summon.lower() for summon in summons if summon))[:summon_limit]
return list(dict.fromkeys(summon.lower() for summon in summons if summon))[
:summon_limit
]


def get_cards(client: "Client", names: List[str]) -> List[Dict[str, Any]]:
# Could be parallelized, even in a synchronous context
responses = [client.get(f"{getenv('API_URL')}/ocg-tcg/search?name={quote_plus(name)}") for name in names]
responses = [
client.get(f"{getenv('API_URL')}/ocg-tcg/search?name={quote_plus(name)}")
for name in names
]
return [response.json() for response in responses if response.status_code == 200]


Expand Down Expand Up @@ -63,40 +68,41 @@ def get_master_duel_limit_regulation(card: Any) -> int | None:
"N": "Common",
"R": "Rare",
"SR": "Super Rare",
"UR": "Ultra Rare"
"UR": "Ultra Rare",
}


def format_card_text(text: str | None) -> str:
return text.replace("\n", "\n\n") if text else "\u200b"


def format_footer(card: Any) -> str:
if card['password'] and card['konami_id']:
if card["password"] and card["konami_id"]:
text = f"Password: {card['password']} | Konami ID #{card['konami_id']}"
elif not card['password'] and card['konami_id']:
elif not card["password"] and card["konami_id"]:
text = f"No password | Konami ID #{card['konami_id']}"
elif card['password'] and not card['konami_id']:
elif card["password"] and not card["konami_id"]:
text = f"Password: {card['password']} | Not yet released"
else:
text = "Not yet released"
if card.get('fake_password') is not None:
if card.get("fake_password") is not None:
text += f" | Placeholder ID: {card['fake_password']}"
return f"^({text})"


def generate_card_display(card: Any) -> str:
yugipedia_page = card['konami_id'] or quote_plus(card['name']['en'])
yugipedia_page = card["konami_id"] or quote_plus(card["name"]["en"])
yugipedia = f"https://yugipedia.com/wiki/{yugipedia_page}?utm_source=bastion&utm_medium=reddit"
ygoprodeck_term = card['password'] or quote_plus(card['name']['en'])
ygoprodeck_term = card["password"] or quote_plus(card["name"]["en"])
ygoprodeck = f"https://ygoprodeck.com/card/?search={ygoprodeck_term}&utm_source=bastion&utm_medium=reddit"

full_text = f"## [{card['name']['en']}]({ygoprodeck})\n"

links = ""
if card.get('images'):
if card.get("images"):
image_link = f"https://yugipedia.com/wiki/Special:Redirect/file/{card['images'][0]['image']}?utm_source=bastion&utm_medium=reddit"
links += f"[Card Image]({image_link}) | "
if card['konami_id'] is not None:
if card["konami_id"] != None:

Check failure on line 105 in src/card.py

View workflow job for this annotation

GitHub Actions / ruff

Ruff (E711)

src/card.py:105:29: E711 Comparison to `None` should be `cond is not None`
# Official database, does not work for zh locales
official = f"https://www.db.yugioh-card.com/yugiohdb/card_search.action?ope=2&request_locale=en&cid={card['konami_id']}"
rulings = f"https://www.db.yugioh-card.com/yugiohdb/faq_search.action?ope=4&request_locale=ja&cid={card['konami_id']}"
Expand All @@ -106,50 +112,64 @@ def generate_card_display(card: Any) -> str:
description = ""

limit_regulations = [
{"label": "TCG: ", "value": format_limit_regulation(card["limit_regulation"].get("tcg"))},
{"label": "OCG: ", "value": format_limit_regulation(card["limit_regulation"].get("ocg"))},
{
"label": "TCG: ",
"value": format_limit_regulation(card["limit_regulation"].get("tcg")),
},
{
"label": "OCG: ",
"value": format_limit_regulation(card["limit_regulation"].get("ocg")),
},
{"label": "Speed: ", "value": card["limit_regulation"].get("speed")},
{"label": "MD: ", "value": get_master_duel_limit_regulation(card)},
]

limit_regulation_display = " / ".join(f"{reg['label']}{reg['value']}" for reg in limit_regulations if reg["value"] is not None)
limit_regulation_display = " / ".join(
f"{reg['label']}{reg['value']}"
for reg in limit_regulations
if reg["value"] is not None
)

if len(limit_regulation_display) > 0:
description += f"^(**Limit**: {limit_regulation_display}) \n"

if card.get("master_duel_rarity"):
md_rarity_code = card["master_duel_rarity"]
md_rarity = MASTER_DUEL_RARITY[md_rarity_code]
description += f"^(**Master Duel rarity**: {md_rarity} ({md_rarity_code})) \n"
description += (
f"^(**Master Duel rarity**: {md_rarity} ({md_rarity_code})) \n"
)

if card['card_type'] == "Monster":
if card["card_type"] == "Monster":
description += f"^(**Type**: {card['monster_type_line']}) \n"
description += f"^(**Attribute**: {card['attribute']}) \n"

if "rank" in card:
description += f"^(**Rank**: {card['rank']} **ATK**: {card['atk']} **DEF**: {card['def']})"
elif "link_arrows" in card:
arrows = "".join(card['link_arrows'])
arrows = "".join(card["link_arrows"])
description += f"^(**Link Rating**: {len(card['link_arrows'])} **ATK**: {card['atk']} **Link Arrows**: {arrows})"
else:
description += f"^(**Level**: {card['level']} **ATK**: {card['atk']} **DEF**: {card['def']})"

if card.get('pendulum_scale') != None:
if card.get("pendulum_scale") is not None:
formatted_scale = f"{card['pendulum_scale']} / {card['pendulum_scale']}"
description += " "
description += f"^(**Pendulum Scale**: {formatted_scale})"

full_text += f"{description}\n\n"

if card.get('pendulum_effect') != None:
if card.get("pendulum_effect") is not None:
full_text += f"**Pendulum Effect**\n\n{format_card_text(card['pendulum_effect']['en'])}\n\n"

full_text += f"**Card Text**\n\n{format_card_text(card['text']['en'])}"
else:
# Spells and Traps
description += "\n\n"
description += f"{card['property']} {card['card_type']}"
full_text += f"{description}\n\n**Card Text**\n\n{format_card_text(card['text']['en'])}"
full_text += (
f"{description}\n\n**Card Text**\n\n{format_card_text(card['text']['en'])}"
)

full_text += f"\n\n{links}\n\n{format_footer(card)}"
return full_text
Expand Down
4 changes: 2 additions & 2 deletions src/clients.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: © 2023 Kevin Lu, Luna Brand
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu, Luna Brand
# SPDX-Licence-Identifier: AGPL-3.0-or-later
from os import getenv
from platform import python_version
Expand All @@ -23,7 +23,7 @@ def get_reddit_client() -> praw.Reddit:
client_secret=getenv("REDDIT_CLIENT_SECRET"),
username=getenv("REDDIT_USERNAME"),
password=getenv("REDDIT_PASSWORD"),
user_agent=user_agent("praw")
user_agent=user_agent("praw"),
)


Expand Down
16 changes: 12 additions & 4 deletions src/limit_regulation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu
# SPDX-Licence-Identifier: AGPL-3.0-or-later
from datetime import datetime
import logging
from threading import Timer
Expand Down Expand Up @@ -26,9 +28,11 @@ def update(self, initial: bool = False) -> None:
try:
response = self._client.get(self._url)
regulation = response.json()["regulation"]
self._vector = { int(konami_id): limit for konami_id, limit in regulation.items() }
self._vector = {
int(konami_id): limit for konami_id, limit in regulation.items()
}
self._logger.info(f"Read {len(self._vector)} entries")
except:
except Exception:
self._logger.error(f"Failed GET [{self._url}]", exc_info=1)

def get(self, konami_id: int) -> int | None:
Expand All @@ -42,5 +46,9 @@ def cancel(self) -> None:


# Globals, to eventually remove
master_duel_limit_regulation = UpdatingLimitRegulationVector("https://dawnbrandbots.github.io/yaml-yugi-limit-regulation/master-duel/current.vector.json")
rush_duel_limit_regulation = UpdatingLimitRegulationVector("https://dawnbrandbots.github.io/yaml-yugi-limit-regulation/rush/current.vector.json")
master_duel_limit_regulation = UpdatingLimitRegulationVector(
"https://dawnbrandbots.github.io/yaml-yugi-limit-regulation/master-duel/current.vector.json"
)
rush_duel_limit_regulation = UpdatingLimitRegulationVector(
"https://dawnbrandbots.github.io/yaml-yugi-limit-regulation/rush/current.vector.json"
)
16 changes: 11 additions & 5 deletions src/mention.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: © 2023 Kevin Lu, Luna Brand
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu, Luna Brand
# SPDX-Licence-Identifier: AGPL-3.0-or-later
from os import getenv
from typing import TYPE_CHECKING
Expand All @@ -10,7 +10,6 @@
from card import parse_summons, get_cards, display_cards
from footer import FOOTER


if TYPE_CHECKING:
import httpx

Expand All @@ -33,10 +32,17 @@ def _run(self) -> None:
if not comment.new:
self._logger.info(f"{comment.id}|{comment.context}| skip, read")
continue
self._logger.info(f"{comment.id}|{comment.context}|{timestamp_to_iso(comment.created_utc)}")
self._logger.info(
f"{comment.id}|{comment.context}|{timestamp_to_iso(comment.created_utc)}"
)
comment.mark_read()
if self._reply_counter[comment.submission.id] >= self.MAX_REPLIES_PER_SUBMISSION:
self._logger.warning(f"{comment.id}: skip, exceeded limit for {comment.submission.id}")
if (
self._reply_counter[comment.submission.id]
>= self.MAX_REPLIES_PER_SUBMISSION
):
self._logger.warning(
f"{comment.id}: skip, exceeded limit for {comment.submission.id}"
)
continue
if is_summon_chain(comment):
self._logger.info(f"{comment.id}: skip, parent comment is me")
Expand Down
22 changes: 17 additions & 5 deletions src/stream.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# SPDX-FileCopyrightText: © 2023 Kevin Lu, Luna Brand
# SPDX-FileCopyrightText: © 2023–2024 Kevin Lu, Luna Brand
# SPDX-Licence-Identifier: AGPL-3.0-or-later
from abc import abstractmethod
from os import getenv
from typing import Generator, Generic, List, TypeVar, TYPE_CHECKING

from antiabuse import already_replied_to_comment, already_replied_to_submission, is_author_me, is_summon_chain
from antiabuse import (
already_replied_to_comment,
already_replied_to_submission,
is_author_me,
is_summon_chain,
)
from card import parse_summons, get_cards, display_cards
from bot_thread import BotThread, timestamp_to_iso

Expand All @@ -23,7 +28,9 @@ def _parse_summons(self, post: Post) -> List[str]:

def _main_loop(self, stream: Generator[Post, None, None]):
for post in stream:
self._logger.info(f"{post.id}|{post.permalink}|{timestamp_to_iso(post.created_utc)}")
self._logger.info(
f"{post.id}|{post.permalink}|{timestamp_to_iso(post.created_utc)}"
)
summons = self._parse_summons(post)
if len(summons):
cards = get_cards(self._client, summons)
Expand Down Expand Up @@ -61,8 +68,13 @@ def _parse_summons(self, comment):
if is_author_me(comment):
self._logger.info(f"{comment.id}: skip, self")
return []
if self._reply_counter[comment.submission.id] >= self.MAX_REPLIES_PER_SUBMISSION:
self._logger.warning(f"{comment.id}: skip, exceeded limit for {comment.submission.id}")
if (
self._reply_counter[comment.submission.id]
>= self.MAX_REPLIES_PER_SUBMISSION
):
self._logger.warning(
f"{comment.id}: skip, exceeded limit for {comment.submission.id}"
)
return []
summons = parse_summons(comment.body)
self._logger.info(f"{comment.id}| summons: {summons}")
Expand Down

0 comments on commit 8f41f6f

Please sign in to comment.