From d4ea20965f9a94f57a2bd8eebd65caac89db1efe Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 31 Jul 2025 19:51:20 +0200 Subject: [PATCH 1/8] syntax --- NerdyPy/utils/format.py | 11 ++++++----- NerdyPy/utils/helpers.py | 3 ++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/NerdyPy/utils/format.py b/NerdyPy/utils/format.py index a1ba2ae..0d90b1f 100644 --- a/NerdyPy/utils/format.py +++ b/NerdyPy/utils/format.py @@ -7,22 +7,23 @@ class MLStripper(HTMLParser): """Markup Language Stripper""" - def __init__(self): + def __init__(self) -> None: super().__init__() self.reset() self.strict = False self.convert_charrefs = True - self.fed = [] + self.fed: list[str] = [] - def handle_data(self, data): + def handle_data(self, data: str) -> None: """handle data""" self.fed.append(data) - def get_data(self): + def get_data(self) -> str: """return data""" return "".join(self.fed) - def error(self, message): + @staticmethod + def error(message): """had to do this cuz abstract""" return message diff --git a/NerdyPy/utils/helpers.py b/NerdyPy/utils/helpers.py index 7fbedc4..207aef8 100644 --- a/NerdyPy/utils/helpers.py +++ b/NerdyPy/utils/helpers.py @@ -14,7 +14,8 @@ def empty_subcommand(ctx: Context): if len(args) > 2: raise NerpyException("Command not found!") elif len(args) <= 1: - return ctx.send_help(ctx.command) + ctx.send_help(ctx.command) + return def youtube(yt_key, return_type, query): From 8c7137c662aaf1f9cecdf7952853b1076fbdcbb8 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 31 Jul 2025 19:51:26 +0200 Subject: [PATCH 2/8] use helper function --- NerdyPy/modules/wow.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NerdyPy/modules/wow.py b/NerdyPy/modules/wow.py index 4f04184..c541725 100644 --- a/NerdyPy/modules/wow.py +++ b/NerdyPy/modules/wow.py @@ -11,7 +11,7 @@ from discord.ext.commands import Context, GroupCog, bot_has_permissions, hybrid_command, hybrid_group from models.wow import WoW from utils.errors import NerpyException -from utils.helpers import send_hidden_message +from utils.helpers import send_hidden_message, empty_subcommand class WowApiLanguage(Enum): @@ -125,8 +125,7 @@ def _get_best_mythic_keys(region, realm, name): @hybrid_group(name="language", aliases=["lang", "locale"]) async def _wow_language(self, ctx: Context): - if ctx.invoked_subcommand is None: - await ctx.send_help(ctx.command) + empty_subcommand(ctx) @_wow_language.command(name="get") async def _wow_language_get(self, ctx: Context): From b8db2de4c7346eb533b330f1897d03c9d6d73e00 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 31 Jul 2025 19:51:37 +0200 Subject: [PATCH 3/8] use openweather lib --- .gitignore | 1 + NerdyPy/modules/utility.py | 134 ++++++++++++------------- pyproject.toml | 3 + uv.lock | 194 +++++++++++++++++++++++++++++++++++++ 4 files changed, 259 insertions(+), 73 deletions(-) diff --git a/.gitignore b/.gitignore index 155e588..492f3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ NerdyPy/*.dll NerdyPy/tmp/ NerdyPy/config.yaml NerdyPy/config_humanmusic.yaml +NerdyPy/weather_cache.sqlite NerpyBot.code-workspace ### macOS template diff --git a/NerdyPy/modules/utility.py b/NerdyPy/modules/utility.py index a79d363..d7e1191 100644 --- a/NerdyPy/modules/utility.py +++ b/NerdyPy/modules/utility.py @@ -2,9 +2,10 @@ from datetime import UTC, datetime -from aiohttp import ClientSession -from discord import Embed, app_commands -from discord.ext.commands import Cog, Context, bot_has_permissions, hybrid_command +from discord import app_commands, Embed +from discord.ext.commands import Cog, hybrid_command, bot_has_permissions, Context +from openweather.weather import OpenWeather + from utils import format as fmt from utils.errors import NerpyException from utils.helpers import send_hidden_message @@ -16,7 +17,7 @@ def __init__(self, bot): bot.log.info(f"loaded {__name__}") self.bot = bot - self.weather_api_key = self.bot.config.get("utility", "openweather") + self.weather_api = OpenWeather(self.bot.config["utility"]["openweather"]) @hybrid_command(name="ping", hidden=True) @bot_has_permissions(send_messages=True) @@ -37,75 +38,62 @@ async def _uptime(self, ctx: Context): @bot_has_permissions(embed_links=True) async def _get_weather(self, ctx: Context, *, query: str): """outputs weather information""" - location_url = f"http://api.openweathermap.org/geo/1.0/direct?q={query}&appid={self.weather_api_key}" - - for umlaut in ["ä", "ö", "ü"]: - if umlaut in query: - await send_hidden_message(ctx, "Please use english names only!") - return - - async with ClientSession() as session: - async with session.get(location_url) as response: - if response.status != 200: - err = f"The api-webserver responded with a code: {response.status} - {response.reason}" - raise NerpyException(err) - location = await response.json() - - if location is None: - return - - lat = location[0].get("lat") - lon = location[0].get("lon") - weather_url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={self.weather_api_key}&units=metric" - - async with ClientSession() as session: - async with session.get(weather_url) as response: - if response.status != 200: - err = f"The api-webserver responded with a code: {response.status} - {response.reason}" - raise NerpyException(err) - weather = await response.json() - - conditions = [] - for w in weather.get("weather", dict()): - conditions.append(w.get("main")) - - sunrise = datetime.fromtimestamp(int(weather.get("sys", dict()).get("sunrise"))).strftime("%H:%M") - sunset = datetime.fromtimestamp(int(weather.get("sys", dict()).get("sunset"))).strftime("%H:%M") - - emb = Embed() - emb.add_field( - name=f":earth_africa: {fmt.bold('location')}", - value=f"""[{weather.get("name")}, - {weather.get("sys", dict()).get("country")}](https://openweathermap.org/city/{weather.get("id")})""", - ) - emb.add_field( - name=f":thermometer: {fmt.bold('temperature')}", - value=f"{weather['main']['temp']}°C", - ) - emb.add_field( - name=f":cloud: {fmt.bold('condition')}", - value=str.join(", ", conditions), - ) - emb.add_field( - name=f":sweat_drops: {fmt.bold('humidity')}", - value=f"{weather['main']['humidity']}%", - ) - emb.add_field( - name=f":wind_chime: {fmt.bold('wind')}", - value=f"{weather['wind']['speed']} m/s", - ) - emb.add_field( - name=f"🔆 {fmt.bold('min-max')}", - value=f"{weather['main']['temp_min']}°C - {weather['main']['temp_max']}°C", - ) - emb.add_field(name=f":city_sunrise: {fmt.bold('sunrise')}", value=f"{sunrise} UTC") - emb.add_field(name=f":city_sunset: {fmt.bold('sunset')}", value=f"{sunset} UTC") - emb.set_footer( - text="Powered by openweathermap.org", - icon_url=f"http://openweathermap.org/img/w/{weather.get('weather', list())[0].get('icon')}.png", - ) - - await ctx.send(embed=emb) + try: + async with ctx.typing(): + for umlaut in ["ä", "ö", "ü"]: + if umlaut in query: + await send_hidden_message(ctx, "Please use english names only!") + return + + weather = self.weather_api.get_weather(city=query) + + conditions = [] + for w in weather.get("weather", dict()): + conditions.append(w.get("main")) + + sunrise = datetime.fromtimestamp(int(weather.get("sys", dict()).get("sunrise"))).strftime("%H:%M") + sunset = datetime.fromtimestamp(int(weather.get("sys", dict()).get("sunset"))).strftime("%H:%M") + + emb = Embed() + emb.add_field( + name=f':earth_africa: {fmt.bold("location")}', + value=f"""[{weather.get("name")}, + {weather.get("sys", dict()).get("country")}](https://openweathermap.org/city/{weather.get("id")})""", + ) + temp = self.weather_api.convert_temperature(weather['main']['temp']) + emb.add_field( + name=f':thermometer: {fmt.bold("temperature")}', + value=f"{temp:.2f}°C", + ) + emb.add_field( + name=f':cloud: {fmt.bold("condition")}', + value=str.join(", ", conditions), + ) + emb.add_field( + name=f':sweat_drops: {fmt.bold("humidity")}', + value=f"{weather['main']['humidity']}%", + ) + emb.add_field( + name=f':wind_chime: {fmt.bold("wind")}', + value=f"{weather['wind']['speed']} m/s", + ) + temp_min = self.weather_api.convert_temperature(weather['main']['temp_min']) + temp_max = self.weather_api.convert_temperature(weather['main']['temp_max']) + emb.add_field( + name=f'🔆 {fmt.bold("min-max")}', + value=f"{temp_min:.2f}°C - {temp_max:.2f}°C", + ) + emb.add_field(name=f':city_sunrise: {fmt.bold("sunrise")}', value=f"{sunrise} UTC") + emb.add_field(name=f':city_sunset: {fmt.bold("sunset")}', value=f"{sunset} UTC") + emb.set_footer( + text="Powered by openweathermap.org", + icon_url=f'http://openweathermap.org/img/w/{weather.get("weather", list())[0].get("icon")}.png', + ) + + await ctx.send(embed=emb) + except Exception as ex: + self.bot.log.error(f"Error while fetching weather: {ex}") + await send_hidden_message(ctx, "An error occurred while fetching the weather information.") async def setup(bot): diff --git a/pyproject.toml b/pyproject.toml index 0cc8902..36c99d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,9 @@ bot = [ "pynacl>=1.5.0", "pyyaml>=6.0.2", "blizzapi>=1.0.2", + "openweather-wrapper>=0.1.1", + "requests-cache>=1.2.1", + "matplotlib>=3.10.3", ] test = [ "pytest>=8.4.1", diff --git a/uv.lock b/uv.lock index cc90b38..32dabd1 100644 --- a/uv.lock +++ b/uv.lock @@ -127,6 +127,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" }, ] +[[package]] +name = "cattrs" +version = "25.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/57/2b/561d78f488dcc303da4639e02021311728fb7fda8006dd2835550cddd9ed/cattrs-25.1.1.tar.gz", hash = "sha256:c914b734e0f2d59e5b720d145ee010f1fd9a13ee93900922a2f3f9d593b8382c", size = 435016, upload-time = "2025-06-04T20:27:15.44Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/b0/215274ef0d835bbc1056392a367646648b6084e39d489099959aefcca2af/cattrs-25.1.1-py3-none-any.whl", hash = "sha256:1b40b2d3402af7be79a7e7e097a9b4cd16d4c06e6d526644b0b26a063a1cc064", size = 69386, upload-time = "2025-06-04T20:27:13.969Z" }, +] + [[package]] name = "certifi" version = "2025.6.15" @@ -201,6 +214,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "contourpy" +version = "1.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, + { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, + { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, + { url = "https://files.pythonhosted.org/packages/63/12/897aeebfb475b7748ea67b61e045accdfcf0d971f8a588b67108ed7f5512/contourpy-1.3.3-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2e8faa0ed68cb29af51edd8e24798bb661eac3bd9f65420c1887b6ca89987c8", size = 379536, upload-time = "2025-07-26T12:01:25.91Z" }, + { url = "https://files.pythonhosted.org/packages/43/8a/a8c584b82deb248930ce069e71576fc09bd7174bbd35183b7943fb1064fd/contourpy-1.3.3-cp312-cp312-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:626d60935cf668e70a5ce6ff184fd713e9683fb458898e4249b63be9e28286ea", size = 384397, upload-time = "2025-07-26T12:01:27.152Z" }, + { url = "https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d00e655fcef08aba35ec9610536bfe90267d7ab5ba944f7032549c55a146da1", size = 362601, upload-time = "2025-07-26T12:01:28.808Z" }, + { url = "https://files.pythonhosted.org/packages/05/0a/a3fe3be3ee2dceb3e615ebb4df97ae6f3828aa915d3e10549ce016302bd1/contourpy-1.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:451e71b5a7d597379ef572de31eeb909a87246974d960049a9848c3bc6c41bf7", size = 1331288, upload-time = "2025-07-26T12:01:31.198Z" }, + { url = "https://files.pythonhosted.org/packages/33/1d/acad9bd4e97f13f3e2b18a3977fe1b4a37ecf3d38d815333980c6c72e963/contourpy-1.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:459c1f020cd59fcfe6650180678a9993932d80d44ccde1fa1868977438f0b411", size = 1403386, upload-time = "2025-07-26T12:01:33.947Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, + { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, + { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, +] + [[package]] name = "coverage" version = "7.9.2" @@ -221,6 +256,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload-time = "2025-07-03T10:54:13.491Z" }, ] +[[package]] +name = "cycler" +version = "0.12.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, +] + [[package]] name = "discord-py" version = "2.5.2" @@ -264,6 +308,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/56/13ab06b4f93ca7cac71078fbe37fcea175d3216f31f85c3168a6bbd0bb9a/flake8-7.3.0-py2.py3-none-any.whl", hash = "sha256:b9696257b9ce8beb888cdbe31cf885c90d31928fe202be0889a7cdafad32f01e", size = 57922, upload-time = "2025-06-20T19:31:34.425Z" }, ] +[[package]] +name = "fonttools" +version = "4.59.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/27/ec3c723bfdf86f34c5c82bf6305df3e0f0d8ea798d2d3a7cb0c0a866d286/fonttools-4.59.0.tar.gz", hash = "sha256:be392ec3529e2f57faa28709d60723a763904f71a2b63aabe14fee6648fe3b14", size = 3532521, upload-time = "2025-07-16T12:04:54.613Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e2/77/b1c8af22f4265e951cd2e5535dbef8859efcef4fb8dee742d368c967cddb/fonttools-4.59.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f9b3a78f69dcbd803cf2fb3f972779875b244c1115481dfbdd567b2c22b31f6b", size = 2767562, upload-time = "2025-07-16T12:04:06.895Z" }, + { url = "https://files.pythonhosted.org/packages/ff/5a/aeb975699588176bb357e8b398dfd27e5d3a2230d92b81ab8cbb6187358d/fonttools-4.59.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:57bb7e26928573ee7c6504f54c05860d867fd35e675769f3ce01b52af38d48e2", size = 2335168, upload-time = "2025-07-16T12:04:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/54/97/c6101a7e60ae138c4ef75b22434373a0da50a707dad523dd19a4889315bf/fonttools-4.59.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4536f2695fe5c1ffb528d84a35a7d3967e5558d2af58b4775e7ab1449d65767b", size = 4909850, upload-time = "2025-07-16T12:04:10.761Z" }, + { url = "https://files.pythonhosted.org/packages/bd/6c/fa4d18d641054f7bff878cbea14aa9433f292b9057cb1700d8e91a4d5f4f/fonttools-4.59.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:885bde7d26e5b40e15c47bd5def48b38cbd50830a65f98122a8fb90962af7cd1", size = 4955131, upload-time = "2025-07-16T12:04:12.846Z" }, + { url = "https://files.pythonhosted.org/packages/20/5c/331947fc1377deb928a69bde49f9003364f5115e5cbe351eea99e39412a2/fonttools-4.59.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6801aeddb6acb2c42eafa45bc1cb98ba236871ae6f33f31e984670b749a8e58e", size = 4899667, upload-time = "2025-07-16T12:04:14.558Z" }, + { url = "https://files.pythonhosted.org/packages/8a/46/b66469dfa26b8ff0baa7654b2cc7851206c6d57fe3abdabbaab22079a119/fonttools-4.59.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:31003b6a10f70742a63126b80863ab48175fb8272a18ca0846c0482968f0588e", size = 5051349, upload-time = "2025-07-16T12:04:16.388Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/ebfb6b1f3a4328ab69787d106a7d92ccde77ce66e98659df0f9e3f28d93d/fonttools-4.59.0-cp312-cp312-win32.whl", hash = "sha256:fbce6dae41b692a5973d0f2158f782b9ad05babc2c2019a970a1094a23909b1b", size = 2201315, upload-time = "2025-07-16T12:04:18.557Z" }, + { url = "https://files.pythonhosted.org/packages/09/45/d2bdc9ea20bbadec1016fd0db45696d573d7a26d95ab5174ffcb6d74340b/fonttools-4.59.0-cp312-cp312-win_amd64.whl", hash = "sha256:332bfe685d1ac58ca8d62b8d6c71c2e52a6c64bc218dc8f7825c9ea51385aa01", size = 2249408, upload-time = "2025-07-16T12:04:20.489Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9c/df0ef2c51845a13043e5088f7bb988ca6cd5bb82d5d4203d6a158aa58cf2/fonttools-4.59.0-py3-none-any.whl", hash = "sha256:241313683afd3baacb32a6bd124d0bce7404bc5280e12e291bae1b9bba28711d", size = 1128050, upload-time = "2025-07-16T12:04:52.687Z" }, +] + [[package]] name = "frozenlist" version = "1.7.0" @@ -439,6 +500,29 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, ] +[[package]] +name = "kiwisolver" +version = "1.4.8" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/59/7c91426a8ac292e1cdd53a63b6d9439abd573c875c3f92c146767dd33faf/kiwisolver-1.4.8.tar.gz", hash = "sha256:23d5f023bdc8c7e54eb65f03ca5d5bb25b601eac4d7f1a042888a1f45237987e", size = 97538, upload-time = "2024-12-24T18:30:51.519Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fc/aa/cea685c4ab647f349c3bc92d2daf7ae34c8e8cf405a6dcd3a497f58a2ac3/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d6af5e8815fd02997cb6ad9bbed0ee1e60014438ee1a5c2444c96f87b8843502", size = 124152, upload-time = "2024-12-24T18:29:16.85Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0b/8db6d2e2452d60d5ebc4ce4b204feeb16176a851fd42462f66ade6808084/kiwisolver-1.4.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bade438f86e21d91e0cf5dd7c0ed00cda0f77c8c1616bd83f9fc157fa6760d31", size = 66555, upload-time = "2024-12-24T18:29:19.146Z" }, + { url = "https://files.pythonhosted.org/packages/60/26/d6a0db6785dd35d3ba5bf2b2df0aedc5af089962c6eb2cbf67a15b81369e/kiwisolver-1.4.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b83dc6769ddbc57613280118fb4ce3cd08899cc3369f7d0e0fab518a7cf37fdb", size = 65067, upload-time = "2024-12-24T18:29:20.096Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ed/1d97f7e3561e09757a196231edccc1bcf59d55ddccefa2afc9c615abd8e0/kiwisolver-1.4.8-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:111793b232842991be367ed828076b03d96202c19221b5ebab421ce8bcad016f", size = 1378443, upload-time = "2024-12-24T18:29:22.843Z" }, + { url = "https://files.pythonhosted.org/packages/29/61/39d30b99954e6b46f760e6289c12fede2ab96a254c443639052d1b573fbc/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:257af1622860e51b1a9d0ce387bf5c2c4f36a90594cb9514f55b074bcc787cfc", size = 1472728, upload-time = "2024-12-24T18:29:24.463Z" }, + { url = "https://files.pythonhosted.org/packages/0c/3e/804163b932f7603ef256e4a715e5843a9600802bb23a68b4e08c8c0ff61d/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b5637c3f316cab1ec1c9a12b8c5f4750a4c4b71af9157645bf32830e39c03a", size = 1478388, upload-time = "2024-12-24T18:29:25.776Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9e/60eaa75169a154700be74f875a4d9961b11ba048bef315fbe89cb6999056/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:782bb86f245ec18009890e7cb8d13a5ef54dcf2ebe18ed65f795e635a96a1c6a", size = 1413849, upload-time = "2024-12-24T18:29:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/bc/b3/9458adb9472e61a998c8c4d95cfdfec91c73c53a375b30b1428310f923e4/kiwisolver-1.4.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc978a80a0db3a66d25767b03688f1147a69e6237175c0f4ffffaaedf744055a", size = 1475533, upload-time = "2024-12-24T18:29:28.638Z" }, + { url = "https://files.pythonhosted.org/packages/e4/7a/0a42d9571e35798de80aef4bb43a9b672aa7f8e58643d7bd1950398ffb0a/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:36dbbfd34838500a31f52c9786990d00150860e46cd5041386f217101350f0d3", size = 2268898, upload-time = "2024-12-24T18:29:30.368Z" }, + { url = "https://files.pythonhosted.org/packages/d9/07/1255dc8d80271400126ed8db35a1795b1a2c098ac3a72645075d06fe5c5d/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:eaa973f1e05131de5ff3569bbba7f5fd07ea0595d3870ed4a526d486fe57fa1b", size = 2425605, upload-time = "2024-12-24T18:29:33.151Z" }, + { url = "https://files.pythonhosted.org/packages/84/df/5a3b4cf13780ef6f6942df67b138b03b7e79e9f1f08f57c49957d5867f6e/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a66f60f8d0c87ab7f59b6fb80e642ebb29fec354a4dfad687ca4092ae69d04f4", size = 2375801, upload-time = "2024-12-24T18:29:34.584Z" }, + { url = "https://files.pythonhosted.org/packages/8f/10/2348d068e8b0f635c8c86892788dac7a6b5c0cb12356620ab575775aad89/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858416b7fb777a53f0c59ca08190ce24e9abbd3cffa18886a5781b8e3e26f65d", size = 2520077, upload-time = "2024-12-24T18:29:36.138Z" }, + { url = "https://files.pythonhosted.org/packages/32/d8/014b89fee5d4dce157d814303b0fce4d31385a2af4c41fed194b173b81ac/kiwisolver-1.4.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:085940635c62697391baafaaeabdf3dd7a6c3643577dde337f4d66eba021b2b8", size = 2338410, upload-time = "2024-12-24T18:29:39.991Z" }, + { url = "https://files.pythonhosted.org/packages/bd/72/dfff0cc97f2a0776e1c9eb5bef1ddfd45f46246c6533b0191887a427bca5/kiwisolver-1.4.8-cp312-cp312-win_amd64.whl", hash = "sha256:01c3d31902c7db5fb6182832713d3b4122ad9317c2c5877d0539227d96bb2e50", size = 71853, upload-time = "2024-12-24T18:29:42.006Z" }, + { url = "https://files.pythonhosted.org/packages/dc/85/220d13d914485c0948a00f0b9eb419efaf6da81b7d72e88ce2391f7aed8d/kiwisolver-1.4.8-cp312-cp312-win_arm64.whl", hash = "sha256:a3c44cb68861de93f0c4a8175fbaa691f0aa22550c331fefef02b618a9dcb476", size = 65424, upload-time = "2024-12-24T18:29:44.38Z" }, +] + [[package]] name = "mako" version = "1.3.10" @@ -469,6 +553,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, ] +[[package]] +name = "matplotlib" +version = "3.10.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "contourpy" }, + { name = "cycler" }, + { name = "fonttools" }, + { name = "kiwisolver" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pillow" }, + { name = "pyparsing" }, + { name = "python-dateutil" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/26/91/d49359a21893183ed2a5b6c76bec40e0b1dcbf8ca148f864d134897cfc75/matplotlib-3.10.3.tar.gz", hash = "sha256:2f82d2c5bb7ae93aaaa4cd42aca65d76ce6376f83304fa3a630b569aca274df0", size = 34799811, upload-time = "2025-05-08T19:10:54.39Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/43/6b80eb47d1071f234ef0c96ca370c2ca621f91c12045f1401b5c9b28a639/matplotlib-3.10.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0ab1affc11d1f495ab9e6362b8174a25afc19c081ba5b0775ef00533a4236eea", size = 8179689, upload-time = "2025-05-08T19:10:07.602Z" }, + { url = "https://files.pythonhosted.org/packages/0f/70/d61a591958325c357204870b5e7b164f93f2a8cca1dc6ce940f563909a13/matplotlib-3.10.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a818d8bdcafa7ed2eed74487fdb071c09c1ae24152d403952adad11fa3c65b4", size = 8050466, upload-time = "2025-05-08T19:10:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/e7/75/70c9d2306203148cc7902a961240c5927dd8728afedf35e6a77e105a2985/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:748ebc3470c253e770b17d8b0557f0aa85cf8c63fd52f1a61af5b27ec0b7ffee", size = 8456252, upload-time = "2025-05-08T19:10:11.958Z" }, + { url = "https://files.pythonhosted.org/packages/c4/91/ba0ae1ff4b3f30972ad01cd4a8029e70a0ec3b8ea5be04764b128b66f763/matplotlib-3.10.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed70453fd99733293ace1aec568255bc51c6361cb0da94fa5ebf0649fdb2150a", size = 8601321, upload-time = "2025-05-08T19:10:14.47Z" }, + { url = "https://files.pythonhosted.org/packages/d2/88/d636041eb54a84b889e11872d91f7cbf036b3b0e194a70fa064eb8b04f7a/matplotlib-3.10.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dbed9917b44070e55640bd13419de83b4c918e52d97561544814ba463811cbc7", size = 9406972, upload-time = "2025-05-08T19:10:16.569Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/0d1c165eac44405a86478082e225fce87874f7198300bbebc55faaf6d28d/matplotlib-3.10.3-cp312-cp312-win_amd64.whl", hash = "sha256:cf37d8c6ef1a48829443e8ba5227b44236d7fcaf7647caa3178a4ff9f7a5be05", size = 8067954, upload-time = "2025-05-08T19:10:18.663Z" }, +] + [[package]] name = "mccabe" version = "0.7.0" @@ -528,10 +637,13 @@ bot = [ { name = "humanize" }, { name = "idna" }, { name = "igdb-api-v4" }, + { name = "matplotlib" }, + { name = "openweather-wrapper" }, { name = "pymysql" }, { name = "pynacl" }, { name = "pytimeparse2" }, { name = "pyyaml" }, + { name = "requests-cache" }, { name = "sqlalchemy" }, { name = "twitchapi" }, { name = "youtube-dl" }, @@ -559,10 +671,13 @@ bot = [ { name = "humanize", specifier = ">=4,<5" }, { name = "idna", specifier = ">=3.7" }, { name = "igdb-api-v4", specifier = ">=0.3,<0.4" }, + { name = "matplotlib", specifier = ">=3.10.3" }, + { name = "openweather-wrapper", specifier = ">=0.1.1" }, { name = "pymysql", specifier = ">=1.1,<1.2" }, { name = "pynacl", specifier = ">=1.5.0" }, { name = "pytimeparse2", specifier = ">=1.7,<1.8" }, { name = "pyyaml", specifier = ">=6.0.2" }, + { name = "requests-cache", specifier = ">=1.2.1" }, { name = "sqlalchemy", specifier = ">=2.0,<2.1" }, { name = "twitchapi", specifier = ">=4.2.0,<4.3.0" }, { name = "youtube-dl", git = "https://github.com/ytdl-org/youtube-dl.git?branch=master" }, @@ -578,6 +693,25 @@ test = [ { name = "pytest-cov", specifier = ">=6.2.1" }, ] +[[package]] +name = "numpy" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, + { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, + { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, +] + [[package]] name = "oauthlib" version = "3.3.1" @@ -587,6 +721,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, ] +[[package]] +name = "openweather-wrapper" +version = "0.1.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/d2/bdcb597e1d155baebf54172a8dbb28e23755fea00c7b52408e700c33026c/openweather_wrapper-0.1.1.tar.gz", hash = "sha256:fa34371d8527846ed6cffec08fa8191fa70793af4442770333fb90d322693234", size = 11715, upload-time = "2024-12-17T18:23:39.294Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/50/41/2eb23ae2d58f66dbd07d79588a5ba56060a0d533eaa9722ad84ed1c9a5b1/openweather_wrapper-0.1.1-py3-none-any.whl", hash = "sha256:e5240911a6d2340f396605091bf0b6a26f5a15da9cc6f82087eb859c5478b5d2", size = 10843, upload-time = "2024-12-17T18:23:31.637Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -605,6 +751,25 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, ] +[[package]] +name = "pillow" +version = "11.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, + { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, + { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, + { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, + { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, + { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, + { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, + { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, + { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, + { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, +] + [[package]] name = "platformdirs" version = "4.3.8" @@ -852,6 +1017,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, ] +[[package]] +name = "requests-cache" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "cattrs" }, + { name = "platformdirs" }, + { name = "requests" }, + { name = "url-normalize" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/be/7b2a95a9e7a7c3e774e43d067c51244e61dea8b120ae2deff7089a93fb2b/requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1", size = 3018209, upload-time = "2024-06-18T17:18:03.774Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/2e/8f4051119f460cfc786aa91f212165bb6e643283b533db572d7b33952bd2/requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603", size = 61425, upload-time = "2024-06-18T17:17:45Z" }, +] + [[package]] name = "requests-oauthlib" version = "2.0.0" @@ -939,6 +1121,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/99/3ae339466c9183ea5b8ae87b34c0b897eda475d2aec2307cae60e5cd4f29/uritemplate-4.2.0-py3-none-any.whl", hash = "sha256:962201ba1c4edcab02e60f9a0d3821e82dfc5d2d6662a21abd533879bdb8a686", size = 11488, upload-time = "2025-06-02T15:12:03.405Z" }, ] +[[package]] +name = "url-normalize" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/80/31/febb777441e5fcdaacb4522316bf2a527c44551430a4873b052d545e3279/url_normalize-2.2.1.tar.gz", hash = "sha256:74a540a3b6eba1d95bdc610c24f2c0141639f3ba903501e61a52a8730247ff37", size = 18846, upload-time = "2025-04-26T20:37:58.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/d9/5ec15501b675f7bc07c5d16aa70d8d778b12375686b6efd47656efdc67cd/url_normalize-2.2.1-py3-none-any.whl", hash = "sha256:3deb687587dc91f7b25c9ae5162ffc0f057ae85d22b1e15cf5698311247f567b", size = 14728, upload-time = "2025-04-26T20:37:57.217Z" }, +] + [[package]] name = "urllib3" version = "2.5.0" From dfb8d6eb992d1d7eca6d9fd9ce662294a03f14e3 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 7 Aug 2025 13:38:24 +0200 Subject: [PATCH 4/8] sort imports --- NerdyPy/modules/admin.py | 1 + NerdyPy/modules/moderation.py | 9 +- NerdyPy/modules/music.py | 3 +- NerdyPy/modules/raidplaner.py | 6 +- NerdyPy/modules/random.py | 5 +- NerdyPy/modules/reminder.py | 3 +- NerdyPy/modules/search.py | 3 +- NerdyPy/modules/tagging.py | 3 +- NerdyPy/modules/utility.py | 2 +- NerdyPy/modules/wow.py | 8 +- NerdyPy/utils/checks.py | 1 + NerdyPy/utils/download.py | 217 +++++++++++++++++----------------- NerdyPy/utils/helpers.py | 1 + 13 files changed, 140 insertions(+), 122 deletions(-) diff --git a/NerdyPy/modules/admin.py b/NerdyPy/modules/admin.py index 00de150..f4d041a 100644 --- a/NerdyPy/modules/admin.py +++ b/NerdyPy/modules/admin.py @@ -7,6 +7,7 @@ from discord.app_commands import CommandSyncFailure, MissingApplicationID, TranslationError, checks from discord.ext.commands import Cog, Context, Greedy, command, group, guild_only from models.admin import GuildPrefix + from utils.errors import NerpyException diff --git a/NerdyPy/modules/moderation.py b/NerdyPy/modules/moderation.py index c647818..0ce20fe 100644 --- a/NerdyPy/modules/moderation.py +++ b/NerdyPy/modules/moderation.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- -from datetime import UTC, datetime, time, timedelta, timezone + +from datetime import timezone, time, datetime, timedelta, UTC from typing import Optional, Union from discord import Embed, Member, TextChannel @@ -9,7 +10,11 @@ from humanize import naturaldate from models.moderation import AutoDelete, AutoKicker from pytimeparse2 import parse -from utils import format as fmt + +from models.moderation import AutoDelete +from models.moderation import AutoKicker + +import utils.format as fmt from utils.errors import NerpyException from utils.helpers import empty_subcommand, send_hidden_message diff --git a/NerdyPy/modules/music.py b/NerdyPy/modules/music.py index f8d2ea8..ea831e5 100644 --- a/NerdyPy/modules/music.py +++ b/NerdyPy/modules/music.py @@ -11,7 +11,8 @@ hybrid_command, hybrid_group, ) -from utils import format as fmt + +import utils.format as fmt from utils.audio import QueuedSong from utils.checks import is_connected_to_voice from utils.download import download, fetch_yt_infos diff --git a/NerdyPy/modules/raidplaner.py b/NerdyPy/modules/raidplaner.py index 2631217..1d48ee3 100644 --- a/NerdyPy/modules/raidplaner.py +++ b/NerdyPy/modules/raidplaner.py @@ -4,8 +4,10 @@ from enum import Enum from discord import Embed -from discord.ext.commands import Cog, Context, command -from models.raidplaner import RaidEncounter, RaidEncounterRole, RaidEvent, RaidTemplate +from discord.ext.commands import Cog, command, Context + +from models.raidplaner import RaidTemplate, RaidEncounter, RaidEncounterRole, RaidEvent + from utils.conversation import Conversation diff --git a/NerdyPy/modules/random.py b/NerdyPy/modules/random.py index 2aad844..5b13575 100644 --- a/NerdyPy/modules/random.py +++ b/NerdyPy/modules/random.py @@ -5,8 +5,9 @@ from aiohttp import ClientSession from discord import Embed -from discord.ext.commands import Cog, Context, bot_has_permissions, hybrid_command -from utils import format as fmt +from discord.ext.commands import Cog, hybrid_command, bot_has_permissions, Context + +import utils.format as fmt from utils.errors import NerpyException diff --git a/NerdyPy/modules/reminder.py b/NerdyPy/modules/reminder.py index 491b228..102eb74 100644 --- a/NerdyPy/modules/reminder.py +++ b/NerdyPy/modules/reminder.py @@ -7,7 +7,8 @@ from discord.ext import tasks from discord.ext.commands import Context, GroupCog, hybrid_command from models.reminder import ReminderMessage -from utils.format import box, pagify + +from utils.format import pagify, box from utils.helpers import send_hidden_message diff --git a/NerdyPy/modules/search.py b/NerdyPy/modules/search.py index 707d1c9..f92d6ed 100644 --- a/NerdyPy/modules/search.py +++ b/NerdyPy/modules/search.py @@ -11,7 +11,8 @@ from discord.ext.commands import Context, GroupCog, bot_has_permissions, hybrid_command from igdb.wrapper import IGDBWrapper from requests import post -from utils import format as fmt + +import utils.format as fmt from utils.errors import NerpyException from utils.helpers import youtube diff --git a/NerdyPy/modules/tagging.py b/NerdyPy/modules/tagging.py index 6f5246d..40a9dab 100644 --- a/NerdyPy/modules/tagging.py +++ b/NerdyPy/modules/tagging.py @@ -15,7 +15,8 @@ hybrid_group, ) from models.tagging import Tag, TagType, TagTypeConverter -from utils import format as fmt + +import utils.format as fmt from utils.audio import QueuedSong from utils.checks import is_connected_to_voice from utils.download import download diff --git a/NerdyPy/modules/utility.py b/NerdyPy/modules/utility.py index d7e1191..78f8d72 100644 --- a/NerdyPy/modules/utility.py +++ b/NerdyPy/modules/utility.py @@ -6,7 +6,7 @@ from discord.ext.commands import Cog, hybrid_command, bot_has_permissions, Context from openweather.weather import OpenWeather -from utils import format as fmt +import utils.format as fmt from utils.errors import NerpyException from utils.helpers import send_hidden_message diff --git a/NerdyPy/modules/wow.py b/NerdyPy/modules/wow.py index c541725..b4842e7 100644 --- a/NerdyPy/modules/wow.py +++ b/NerdyPy/modules/wow.py @@ -6,10 +6,12 @@ from typing import Dict, Literal, LiteralString, Optional, Tuple import requests -from blizzapi import Language, Region, RetailClient -from discord import Color, Embed -from discord.ext.commands import Context, GroupCog, bot_has_permissions, hybrid_command, hybrid_group +from blizzapi import RetailClient, Region, Language +from discord import Embed, Color +from discord.ext.commands import GroupCog, hybrid_command, bot_has_permissions, Context, hybrid_group + from models.wow import WoW + from utils.errors import NerpyException from utils.helpers import send_hidden_message, empty_subcommand diff --git a/NerdyPy/utils/checks.py b/NerdyPy/utils/checks.py index 5b914ca..004122d 100644 --- a/NerdyPy/utils/checks.py +++ b/NerdyPy/utils/checks.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + from utils.helpers import send_hidden_message diff --git a/NerdyPy/utils/download.py b/NerdyPy/utils/download.py index 1ed6fe6..1f03041 100644 --- a/NerdyPy/utils/download.py +++ b/NerdyPy/utils/download.py @@ -1,108 +1,109 @@ -# -*- coding: utf-8 -*- -""" -download and conversion method for Audio Content -""" - -import logging -import os -from io import BytesIO - -import ffmpeg -import requests -import youtube_dl -from cachetools import TTLCache -from discord import FFmpegOpusAudio -from utils.errors import NerpyException - -LOG = logging.getLogger("nerpybot") -FFMPEG_OPTIONS = {"options": "-vn"} -CACHE = TTLCache(maxsize=100, ttl=600) - -DL_DIR = "tmp" -if not os.path.exists(DL_DIR): - os.makedirs(DL_DIR) - -YTDL_ARGS = { - "format": "bestaudio/best", - "outtmpl": os.path.join(DL_DIR, "%(id)s"), - "restrictfilenames": True, - "noplaylist": True, - "nocheckcertificate": True, - "ignoreerrors": False, - "logtostderr": False, - "quiet": True, - "verbose": False, - "no_warnings": True, - "extractaudio": True, - "audioformat": "mp3", - "default_search": "auto", - "source_address": "0.0.0.0", # bind to ipv4 since ipv6 addresses cause issues sometimes -} -YTDL = youtube_dl.YoutubeDL(YTDL_ARGS) - - -def convert(source, tag=False, is_stream=True): - """Convert downloaded file to playable ByteStream""" - LOG.info("Converting File...") - if tag: - process = ( - ffmpeg.input("pipe:") - .filter("loudnorm") - .output(filename="pipe:", format="mp3", ac=2, ar="48000") - .run_async(pipe_stdin=True, pipe_stdout=True, quiet=True, overwrite_output=True) - ) - stream, _ = process.communicate(input=source.read()) - return stream - else: - return FFmpegOpusAudio(source, **FFMPEG_OPTIONS, pipe=is_stream) - - -def lookup_file(file_name): - for file in os.listdir(f"{DL_DIR}"): - if file.startswith(file_name): - return os.path.join(DL_DIR, file) - return None - - -def fetch_yt_infos(url: str): - """Fetches information about a youtube video""" - if url in CACHE: - LOG.info("Using cached information for URL: %s", url) - return CACHE[url] - - LOG.info("Fetching Information about Video from Youtube...") - data = None - try: - data = YTDL.extract_info(url, download=False) - except youtube_dl.utils.DownloadError as e: - if "Sign in to confirm you’re not a bot" in str(e): - data = YTDL.extract_info(url, download=False) - - CACHE[url] = data - return data - - -def download(url: str, tag: bool = False, video_id: str = None): - """download audio content (maybe transform?)""" - - if video_id is None: - req_headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.143 Safari/537.36", - } - - with requests.get(url, headers=req_headers, stream=True) as response: - response.raise_for_status() - audio_bytes = BytesIO(response.content) - - if audio_bytes is None: - raise NerpyException(f"could not find a valid source in: {url}") - - return convert(audio_bytes, tag) - else: - dlfile = lookup_file(video_id) - - if dlfile is None: - _ = YTDL.download([url]) - dlfile = lookup_file(video_id) - - return convert(dlfile, is_stream=False) +# -*- coding: utf-8 -*- +""" +download and conversion method for Audio Content +""" + +import os +import logging +from io import BytesIO + +import ffmpeg +import requests +import youtube_dl +from discord import FFmpegOpusAudio +from cachetools import TTLCache + +from utils.errors import NerpyException + +LOG = logging.getLogger("nerpybot") +FFMPEG_OPTIONS = {"options": "-vn"} +CACHE = TTLCache(maxsize=100, ttl=600) + +DL_DIR = "tmp" +if not os.path.exists(DL_DIR): + os.makedirs(DL_DIR) + +YTDL_ARGS = { + "format": "bestaudio/best", + "outtmpl": os.path.join(DL_DIR, "%(id)s"), + "restrictfilenames": True, + "noplaylist": True, + "nocheckcertificate": True, + "ignoreerrors": False, + "logtostderr": False, + "quiet": True, + "verbose": False, + "no_warnings": True, + "extractaudio": True, + "audioformat": "mp3", + "default_search": "auto", + "source_address": "0.0.0.0", # bind to ipv4 since ipv6 addresses cause issues sometimes +} +YTDL = youtube_dl.YoutubeDL(YTDL_ARGS) + + +def convert(source, tag=False, is_stream=True): + """Convert downloaded file to playable ByteStream""" + LOG.info("Converting File...") + if tag: + process = ( + ffmpeg.input("pipe:") + .filter("loudnorm") + .output(filename="pipe:", format="mp3", ac=2, ar="48000") + .run_async(pipe_stdin=True, pipe_stdout=True, quiet=True, overwrite_output=True) + ) + stream, _ = process.communicate(input=source.read()) + return stream + else: + return FFmpegOpusAudio(source, **FFMPEG_OPTIONS, pipe=is_stream) + + +def lookup_file(file_name): + for file in os.listdir(f"{DL_DIR}"): + if file.startswith(file_name): + return os.path.join(DL_DIR, file) + return None + + +def fetch_yt_infos(url: str): + """Fetches information about a youtube video""" + if url in CACHE: + LOG.info("Using cached information for URL: %s", url) + return CACHE[url] + + LOG.info("Fetching Information about Video from Youtube...") + data = None + try: + data = YTDL.extract_info(url, download=False) + except youtube_dl.utils.DownloadError as e: + if "Sign in to confirm you’re not a bot" in str(e): + data = YTDL.extract_info(url, download=False) + + CACHE[url] = data + return data + + +def download(url: str, tag: bool = False, video_id: str = None): + """download audio content (maybe transform?)""" + + if video_id is None: + req_headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.143 Safari/537.36", + } + + with requests.get(url, headers=req_headers, stream=True) as response: + response.raise_for_status() + audio_bytes = BytesIO(response.content) + + if audio_bytes is None: + raise NerpyException(f"could not find a valid source in: {url}") + + return convert(audio_bytes, tag) + else: + dlfile = lookup_file(video_id) + + if dlfile is None: + _ = YTDL.download([url]) + dlfile = lookup_file(video_id) + + return convert(dlfile, is_stream=False) diff --git a/NerdyPy/utils/helpers.py b/NerdyPy/utils/helpers.py index 207aef8..64f4930 100644 --- a/NerdyPy/utils/helpers.py +++ b/NerdyPy/utils/helpers.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + from discord.ext.commands import Context from googleapiclient.discovery import build from utils.errors import NerpyException From beed003a68439aebe700cd6e188d1b65fc2cae33 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 7 Aug 2025 14:16:42 +0200 Subject: [PATCH 5/8] use yt-dlp instead of youtube-dl --- NerdyPy/utils/download.py | 23 +++++++++++++---------- pyproject.toml | 5 +---- uv.lock | 16 ++++++++++------ 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/NerdyPy/utils/download.py b/NerdyPy/utils/download.py index 1f03041..c4cb674 100644 --- a/NerdyPy/utils/download.py +++ b/NerdyPy/utils/download.py @@ -9,9 +9,10 @@ import ffmpeg import requests -import youtube_dl from discord import FFmpegOpusAudio from cachetools import TTLCache +from yt_dlp import YoutubeDL +from yt_dlp.utils import DownloadError from utils.errors import NerpyException @@ -39,7 +40,7 @@ "default_search": "auto", "source_address": "0.0.0.0", # bind to ipv4 since ipv6 addresses cause issues sometimes } -YTDL = youtube_dl.YoutubeDL(YTDL_ARGS) +YTDL = YoutubeDL(YTDL_ARGS) def convert(source, tag=False, is_stream=True): @@ -50,7 +51,9 @@ def convert(source, tag=False, is_stream=True): ffmpeg.input("pipe:") .filter("loudnorm") .output(filename="pipe:", format="mp3", ac=2, ar="48000") - .run_async(pipe_stdin=True, pipe_stdout=True, quiet=True, overwrite_output=True) + .run_async( + pipe_stdin=True, pipe_stdout=True, quiet=True, overwrite_output=True + ) ) stream, _ = process.communicate(input=source.read()) return stream @@ -66,7 +69,7 @@ def lookup_file(file_name): def fetch_yt_infos(url: str): - """Fetches information about a youtube video""" + """Fetches information about a YouTube video""" if url in CACHE: LOG.info("Using cached information for URL: %s", url) return CACHE[url] @@ -75,7 +78,7 @@ def fetch_yt_infos(url: str): data = None try: data = YTDL.extract_info(url, download=False) - except youtube_dl.utils.DownloadError as e: + except DownloadError as e: if "Sign in to confirm you’re not a bot" in str(e): data = YTDL.extract_info(url, download=False) @@ -96,14 +99,14 @@ def download(url: str, tag: bool = False, video_id: str = None): audio_bytes = BytesIO(response.content) if audio_bytes is None: - raise NerpyException(f"could not find a valid source in: {url}") + raise NerpyException(f"Could not find a valid source in: {url}") return convert(audio_bytes, tag) else: - dlfile = lookup_file(video_id) + dl_file = lookup_file(video_id) - if dlfile is None: + if dl_file is None: _ = YTDL.download([url]) - dlfile = lookup_file(video_id) + dl_file = lookup_file(video_id) - return convert(dlfile, is_stream=False) + return convert(dl_file, is_stream=False) diff --git a/pyproject.toml b/pyproject.toml index 36c99d4..9448c16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,13 +23,13 @@ bot = [ "aiohttp>=3.10.11", "idna>=3.7", "discord-py[voice]==2.5.2", - "youtube-dl", "pynacl>=1.5.0", "pyyaml>=6.0.2", "blizzapi>=1.0.2", "openweather-wrapper>=0.1.1", "requests-cache>=1.2.1", "matplotlib>=3.10.3", + "yt-dlp>=2025.7.21", ] test = [ "pytest>=8.4.1", @@ -50,6 +50,3 @@ line-ending = "lf" [tool.uv] default-groups = ["bot"] - -[tool.uv.sources] -youtube-dl = { git = "https://github.com/ytdl-org/youtube-dl.git", branch = "master" } diff --git a/uv.lock b/uv.lock index 32dabd1..35cb672 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 2 +revision = 3 requires-python = "==3.12.*" [[package]] @@ -646,7 +646,7 @@ bot = [ { name = "requests-cache" }, { name = "sqlalchemy" }, { name = "twitchapi" }, - { name = "youtube-dl" }, + { name = "yt-dlp" }, ] migrations = [ { name = "alembic" }, @@ -680,7 +680,7 @@ bot = [ { name = "requests-cache", specifier = ">=1.2.1" }, { name = "sqlalchemy", specifier = ">=2.0,<2.1" }, { name = "twitchapi", specifier = ">=4.2.0,<4.3.0" }, - { name = "youtube-dl", git = "https://github.com/ytdl-org/youtube-dl.git?branch=master" }, + { name = "yt-dlp", specifier = ">=2025.7.21" }, ] migrations = [ { name = "alembic", specifier = ">=1.16.2" }, @@ -1174,6 +1174,10 @@ wheels = [ ] [[package]] -name = "youtube-dl" -version = "2025.4.7" -source = { git = "https://github.com/ytdl-org/youtube-dl.git?branch=master#a084c80f7bac9ae343075a97cc0fb2c1c96ade89" } +name = "yt-dlp" +version = "2025.7.21" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/3a/343f7a0024ddd4c30f150e8d8f57fd7b924846f97d99fc0dcd75ea8d2773/yt_dlp-2025.7.21.tar.gz", hash = "sha256:46fbb53eab1afbe184c45b4c17e9a6eba614be680e4c09de58b782629d0d7f43", size = 3050219, upload-time = "2025-07-21T23:59:03.826Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/2f/abe59a3204c749fed494849ea29176bcefa186ec8898def9e43f649ddbcf/yt_dlp-2025.7.21-py3-none-any.whl", hash = "sha256:d7aa2b53f9b2f35453346360f41811a0dad1e956e70b35a4ae95039d4d815d15", size = 3288681, upload-time = "2025-07-21T23:59:01.788Z" }, +] From b169b7f51e94c476bff716d8a51cae63d485ce4d Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 7 Aug 2025 14:18:01 +0200 Subject: [PATCH 6/8] format --- NerdyPy/modules/moderation.py | 1 - NerdyPy/modules/utility.py | 24 ++-- NerdyPy/utils/download.py | 222 +++++++++++++++++----------------- 3 files changed, 122 insertions(+), 125 deletions(-) diff --git a/NerdyPy/modules/moderation.py b/NerdyPy/modules/moderation.py index 0ce20fe..3a5af66 100644 --- a/NerdyPy/modules/moderation.py +++ b/NerdyPy/modules/moderation.py @@ -8,7 +8,6 @@ from discord.ext import tasks from discord.ext.commands import Cog, Context, command, hybrid_command, hybrid_group from humanize import naturaldate -from models.moderation import AutoDelete, AutoKicker from pytimeparse2 import parse from models.moderation import AutoDelete diff --git a/NerdyPy/modules/utility.py b/NerdyPy/modules/utility.py index 78f8d72..50f768f 100644 --- a/NerdyPy/modules/utility.py +++ b/NerdyPy/modules/utility.py @@ -56,38 +56,38 @@ async def _get_weather(self, ctx: Context, *, query: str): emb = Embed() emb.add_field( - name=f':earth_africa: {fmt.bold("location")}', + name=f":earth_africa: {fmt.bold('location')}", value=f"""[{weather.get("name")}, {weather.get("sys", dict()).get("country")}](https://openweathermap.org/city/{weather.get("id")})""", ) - temp = self.weather_api.convert_temperature(weather['main']['temp']) + temp = self.weather_api.convert_temperature(weather["main"]["temp"]) emb.add_field( - name=f':thermometer: {fmt.bold("temperature")}', + name=f":thermometer: {fmt.bold('temperature')}", value=f"{temp:.2f}°C", ) emb.add_field( - name=f':cloud: {fmt.bold("condition")}', + name=f":cloud: {fmt.bold('condition')}", value=str.join(", ", conditions), ) emb.add_field( - name=f':sweat_drops: {fmt.bold("humidity")}', + name=f":sweat_drops: {fmt.bold('humidity')}", value=f"{weather['main']['humidity']}%", ) emb.add_field( - name=f':wind_chime: {fmt.bold("wind")}', + name=f":wind_chime: {fmt.bold('wind')}", value=f"{weather['wind']['speed']} m/s", ) - temp_min = self.weather_api.convert_temperature(weather['main']['temp_min']) - temp_max = self.weather_api.convert_temperature(weather['main']['temp_max']) + temp_min = self.weather_api.convert_temperature(weather["main"]["temp_min"]) + temp_max = self.weather_api.convert_temperature(weather["main"]["temp_max"]) emb.add_field( - name=f'🔆 {fmt.bold("min-max")}', + name=f"🔆 {fmt.bold('min-max')}", value=f"{temp_min:.2f}°C - {temp_max:.2f}°C", ) - emb.add_field(name=f':city_sunrise: {fmt.bold("sunrise")}', value=f"{sunrise} UTC") - emb.add_field(name=f':city_sunset: {fmt.bold("sunset")}', value=f"{sunset} UTC") + emb.add_field(name=f":city_sunrise: {fmt.bold('sunrise')}", value=f"{sunrise} UTC") + emb.add_field(name=f":city_sunset: {fmt.bold('sunset')}", value=f"{sunset} UTC") emb.set_footer( text="Powered by openweathermap.org", - icon_url=f'http://openweathermap.org/img/w/{weather.get("weather", list())[0].get("icon")}.png', + icon_url=f"http://openweathermap.org/img/w/{weather.get('weather', list())[0].get('icon')}.png", ) await ctx.send(embed=emb) diff --git a/NerdyPy/utils/download.py b/NerdyPy/utils/download.py index c4cb674..39ca6e7 100644 --- a/NerdyPy/utils/download.py +++ b/NerdyPy/utils/download.py @@ -1,112 +1,110 @@ -# -*- coding: utf-8 -*- -""" -download and conversion method for Audio Content -""" - -import os -import logging -from io import BytesIO - -import ffmpeg -import requests -from discord import FFmpegOpusAudio -from cachetools import TTLCache -from yt_dlp import YoutubeDL -from yt_dlp.utils import DownloadError - -from utils.errors import NerpyException - -LOG = logging.getLogger("nerpybot") -FFMPEG_OPTIONS = {"options": "-vn"} -CACHE = TTLCache(maxsize=100, ttl=600) - -DL_DIR = "tmp" -if not os.path.exists(DL_DIR): - os.makedirs(DL_DIR) - -YTDL_ARGS = { - "format": "bestaudio/best", - "outtmpl": os.path.join(DL_DIR, "%(id)s"), - "restrictfilenames": True, - "noplaylist": True, - "nocheckcertificate": True, - "ignoreerrors": False, - "logtostderr": False, - "quiet": True, - "verbose": False, - "no_warnings": True, - "extractaudio": True, - "audioformat": "mp3", - "default_search": "auto", - "source_address": "0.0.0.0", # bind to ipv4 since ipv6 addresses cause issues sometimes -} -YTDL = YoutubeDL(YTDL_ARGS) - - -def convert(source, tag=False, is_stream=True): - """Convert downloaded file to playable ByteStream""" - LOG.info("Converting File...") - if tag: - process = ( - ffmpeg.input("pipe:") - .filter("loudnorm") - .output(filename="pipe:", format="mp3", ac=2, ar="48000") - .run_async( - pipe_stdin=True, pipe_stdout=True, quiet=True, overwrite_output=True - ) - ) - stream, _ = process.communicate(input=source.read()) - return stream - else: - return FFmpegOpusAudio(source, **FFMPEG_OPTIONS, pipe=is_stream) - - -def lookup_file(file_name): - for file in os.listdir(f"{DL_DIR}"): - if file.startswith(file_name): - return os.path.join(DL_DIR, file) - return None - - -def fetch_yt_infos(url: str): - """Fetches information about a YouTube video""" - if url in CACHE: - LOG.info("Using cached information for URL: %s", url) - return CACHE[url] - - LOG.info("Fetching Information about Video from Youtube...") - data = None - try: - data = YTDL.extract_info(url, download=False) - except DownloadError as e: - if "Sign in to confirm you’re not a bot" in str(e): - data = YTDL.extract_info(url, download=False) - - CACHE[url] = data - return data - - -def download(url: str, tag: bool = False, video_id: str = None): - """download audio content (maybe transform?)""" - - if video_id is None: - req_headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.143 Safari/537.36", - } - - with requests.get(url, headers=req_headers, stream=True) as response: - response.raise_for_status() - audio_bytes = BytesIO(response.content) - - if audio_bytes is None: - raise NerpyException(f"Could not find a valid source in: {url}") - - return convert(audio_bytes, tag) - else: - dl_file = lookup_file(video_id) - - if dl_file is None: - _ = YTDL.download([url]) - dl_file = lookup_file(video_id) - - return convert(dl_file, is_stream=False) +# -*- coding: utf-8 -*- +""" +download and conversion method for Audio Content +""" + +import os +import logging +from io import BytesIO + +import ffmpeg +import requests +from discord import FFmpegOpusAudio +from cachetools import TTLCache +from yt_dlp import YoutubeDL +from yt_dlp.utils import DownloadError + +from utils.errors import NerpyException + +LOG = logging.getLogger("nerpybot") +FFMPEG_OPTIONS = {"options": "-vn"} +CACHE = TTLCache(maxsize=100, ttl=600) + +DL_DIR = "tmp" +if not os.path.exists(DL_DIR): + os.makedirs(DL_DIR) + +YTDL_ARGS = { + "format": "bestaudio/best", + "outtmpl": os.path.join(DL_DIR, "%(id)s"), + "restrictfilenames": True, + "noplaylist": True, + "nocheckcertificate": True, + "ignoreerrors": False, + "logtostderr": False, + "quiet": True, + "verbose": False, + "no_warnings": True, + "extractaudio": True, + "audioformat": "mp3", + "default_search": "auto", + "source_address": "0.0.0.0", # bind to ipv4 since ipv6 addresses cause issues sometimes +} +YTDL = YoutubeDL(YTDL_ARGS) + + +def convert(source, tag=False, is_stream=True): + """Convert downloaded file to playable ByteStream""" + LOG.info("Converting File...") + if tag: + process = ( + ffmpeg.input("pipe:") + .filter("loudnorm") + .output(filename="pipe:", format="mp3", ac=2, ar="48000") + .run_async(pipe_stdin=True, pipe_stdout=True, quiet=True, overwrite_output=True) + ) + stream, _ = process.communicate(input=source.read()) + return stream + else: + return FFmpegOpusAudio(source, **FFMPEG_OPTIONS, pipe=is_stream) + + +def lookup_file(file_name): + for file in os.listdir(f"{DL_DIR}"): + if file.startswith(file_name): + return os.path.join(DL_DIR, file) + return None + + +def fetch_yt_infos(url: str): + """Fetches information about a YouTube video""" + if url in CACHE: + LOG.info("Using cached information for URL: %s", url) + return CACHE[url] + + LOG.info("Fetching Information about Video from Youtube...") + data = None + try: + data = YTDL.extract_info(url, download=False) + except DownloadError as e: + if "Sign in to confirm you’re not a bot" in str(e): + data = YTDL.extract_info(url, download=False) + + CACHE[url] = data + return data + + +def download(url: str, tag: bool = False, video_id: str = None): + """download audio content (maybe transform?)""" + + if video_id is None: + req_headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.7204.143 Safari/537.36", + } + + with requests.get(url, headers=req_headers, stream=True) as response: + response.raise_for_status() + audio_bytes = BytesIO(response.content) + + if audio_bytes is None: + raise NerpyException(f"Could not find a valid source in: {url}") + + return convert(audio_bytes, tag) + else: + dl_file = lookup_file(video_id) + + if dl_file is None: + _ = YTDL.download([url]) + dl_file = lookup_file(video_id) + + return convert(dl_file, is_stream=False) From 07d0a345f385227108275c432f2c5cef0ba49a82 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 7 Aug 2025 14:29:09 +0200 Subject: [PATCH 7/8] remove obsolete import --- NerdyPy/modules/moderation.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/NerdyPy/modules/moderation.py b/NerdyPy/modules/moderation.py index 3a5af66..7eb405e 100644 --- a/NerdyPy/modules/moderation.py +++ b/NerdyPy/modules/moderation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from datetime import timezone, time, datetime, timedelta, UTC +from datetime import time, datetime, timedelta, UTC from typing import Optional, Union from discord import Embed, Member, TextChannel @@ -17,9 +17,8 @@ from utils.errors import NerpyException from utils.helpers import empty_subcommand, send_hidden_message -utc = timezone.utc # If no tzinfo is given then UTC is assumed. -loop_run_time = time(hour=12, minute=30, tzinfo=utc) +LOOP_RUN_TIME = time(hour=12, minute=30, tzinfo=UTC) class Moderation(Cog): @@ -36,7 +35,7 @@ def cog_unload(self): self._autokicker_loop.cancel() self._autodeleter_loop.cancel() - @tasks.loop(time=loop_run_time) + @tasks.loop(time=LOOP_RUN_TIME) async def _autokicker_loop(self): self.bot.log.debug("Start Autokicker Loop!") try: @@ -53,9 +52,9 @@ async def _autokicker_loop(self): if len(member.roles) == 1: self.bot.log.debug(f"Found member without role: {member.display_name}") kick_reminder = datetime.now(UTC) - timedelta(seconds=(configuration.KickAfter / 2)) - kick_reminder = kick_reminder.replace(tzinfo=timezone.utc) + kick_reminder = kick_reminder.replace(tzinfo=UTC) kick_after = datetime.now(UTC) - timedelta(seconds=configuration.KickAfter) - kick_after = kick_after.replace(tzinfo=timezone.utc) + kick_after = kick_after.replace(tzinfo=UTC) if member.joined_at < kick_after: self.bot.log.debug(f"Kick member {member.display_name}!") @@ -89,7 +88,7 @@ async def _autodeleter_loop(self): list_before = None else: list_before = datetime.now(UTC) - timedelta(seconds=configuration.DeleteOlderThan) - list_before = list_before.replace(tzinfo=timezone.utc) + list_before = list_before.replace(tzinfo=UTC) channel = guild.get_channel(configuration.ChannelId) messages = [] From 82a627b2d272972eb949c5a328057edcf70cbd06 Mon Sep 17 00:00:00 2001 From: Dennis Date: Thu, 7 Aug 2025 14:36:37 +0200 Subject: [PATCH 8/8] sort imports with ruff --- NerdyPy/modules/moderation.py | 5 ++--- NerdyPy/modules/raidplaner.py | 4 ++-- NerdyPy/modules/random.py | 2 +- NerdyPy/modules/reminder.py | 3 ++- NerdyPy/modules/utility.py | 4 ++-- NerdyPy/modules/wow.py | 8 ++++---- NerdyPy/utils/download.py | 4 ++-- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/NerdyPy/modules/moderation.py b/NerdyPy/modules/moderation.py index 7eb405e..4111f32 100644 --- a/NerdyPy/modules/moderation.py +++ b/NerdyPy/modules/moderation.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from datetime import time, datetime, timedelta, UTC +from datetime import UTC, datetime, time, timedelta from typing import Optional, Union from discord import Embed, Member, TextChannel @@ -10,8 +10,7 @@ from humanize import naturaldate from pytimeparse2 import parse -from models.moderation import AutoDelete -from models.moderation import AutoKicker +from models.moderation import AutoDelete, AutoKicker import utils.format as fmt from utils.errors import NerpyException diff --git a/NerdyPy/modules/raidplaner.py b/NerdyPy/modules/raidplaner.py index 1d48ee3..2f40802 100644 --- a/NerdyPy/modules/raidplaner.py +++ b/NerdyPy/modules/raidplaner.py @@ -4,9 +4,9 @@ from enum import Enum from discord import Embed -from discord.ext.commands import Cog, command, Context +from discord.ext.commands import Cog, Context, command -from models.raidplaner import RaidTemplate, RaidEncounter, RaidEncounterRole, RaidEvent +from models.raidplaner import RaidEncounter, RaidEncounterRole, RaidEvent, RaidTemplate from utils.conversation import Conversation diff --git a/NerdyPy/modules/random.py b/NerdyPy/modules/random.py index 5b13575..81d7fb9 100644 --- a/NerdyPy/modules/random.py +++ b/NerdyPy/modules/random.py @@ -5,7 +5,7 @@ from aiohttp import ClientSession from discord import Embed -from discord.ext.commands import Cog, hybrid_command, bot_has_permissions, Context +from discord.ext.commands import Cog, Context, bot_has_permissions, hybrid_command import utils.format as fmt from utils.errors import NerpyException diff --git a/NerdyPy/modules/reminder.py b/NerdyPy/modules/reminder.py index 102eb74..7bcb596 100644 --- a/NerdyPy/modules/reminder.py +++ b/NerdyPy/modules/reminder.py @@ -6,9 +6,10 @@ from discord import TextChannel from discord.ext import tasks from discord.ext.commands import Context, GroupCog, hybrid_command + from models.reminder import ReminderMessage -from utils.format import pagify, box +from utils.format import box, pagify from utils.helpers import send_hidden_message diff --git a/NerdyPy/modules/utility.py b/NerdyPy/modules/utility.py index 50f768f..a6fe1d3 100644 --- a/NerdyPy/modules/utility.py +++ b/NerdyPy/modules/utility.py @@ -2,8 +2,8 @@ from datetime import UTC, datetime -from discord import app_commands, Embed -from discord.ext.commands import Cog, hybrid_command, bot_has_permissions, Context +from discord import Embed, app_commands +from discord.ext.commands import Cog, Context, bot_has_permissions, hybrid_command from openweather.weather import OpenWeather import utils.format as fmt diff --git a/NerdyPy/modules/wow.py b/NerdyPy/modules/wow.py index b4842e7..73a5087 100644 --- a/NerdyPy/modules/wow.py +++ b/NerdyPy/modules/wow.py @@ -6,14 +6,14 @@ from typing import Dict, Literal, LiteralString, Optional, Tuple import requests -from blizzapi import RetailClient, Region, Language -from discord import Embed, Color -from discord.ext.commands import GroupCog, hybrid_command, bot_has_permissions, Context, hybrid_group +from blizzapi import Language, Region, RetailClient +from discord import Color, Embed +from discord.ext.commands import Context, GroupCog, bot_has_permissions, hybrid_command, hybrid_group from models.wow import WoW from utils.errors import NerpyException -from utils.helpers import send_hidden_message, empty_subcommand +from utils.helpers import empty_subcommand, send_hidden_message class WowApiLanguage(Enum): diff --git a/NerdyPy/utils/download.py b/NerdyPy/utils/download.py index 39ca6e7..7212ef4 100644 --- a/NerdyPy/utils/download.py +++ b/NerdyPy/utils/download.py @@ -3,14 +3,14 @@ download and conversion method for Audio Content """ -import os import logging +import os from io import BytesIO import ffmpeg import requests -from discord import FFmpegOpusAudio from cachetools import TTLCache +from discord import FFmpegOpusAudio from yt_dlp import YoutubeDL from yt_dlp.utils import DownloadError