diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 2da8254..9f49b06 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -33,10 +33,6 @@ jobs: python -m pip install --upgrade pip if [ -f requirements.txt ]; then pip install -r requirements.txt; fi if [ -f requirements-dev.txt ]; then pip install -r requirements-dev.txt; fi - - name: Echo env - run: | - echo ${{ github.event.pull_request.base.ref }} - echo ${{ github.event.pull_request.base.ref_name }} - name: Black and flake run: | bash run_lint.sh @@ -48,19 +44,20 @@ jobs: run: | bash run_tests.sh full --test-group-count 3 --test-group=1 --reruns 3 --reruns-delay 15 if: github.event.pull_request.base.ref == 'main' + continue-on-error: true - name: Test changes with full tests 2/3 run: | bash run_tests.sh full --test-group-count 3 --test-group=2 --reruns 3 --reruns-delay 15 if: github.event.pull_request.base.ref == 'main' + continue-on-error: true - name: Test changes with full tests 3/3 run: | bash run_tests.sh full --test-group-count 3 --test-group=3 --reruns 3 --reruns-delay 15 if: github.event.pull_request.base.ref == 'main' + continue-on-error: true - name: Upload test results uses: actions/upload-artifact@v4 with: name: Test result path: report.html - - - + if: ${{ always() }} diff --git a/LICENSE b/LICENSE index d8bcaf2..2f90256 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Clement Julia +Copyright (c) 2025 Clement Julia Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index 7658439..16b77d7 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -8,6 +8,18 @@ The page attempt to keep a clear list of breaking/non-breaking changes and new f :local: :backlinks: none +v0.14.0 +----------- +Bug Fixes +########### +* Changed SLA adapter to CurlCffiAdapter +* `Profile.follow` is more reliably parsed + +New Features +############# +* Exposed front page poll url under `FrontPage.poll_url` + + v0.13.0 ----------- Bug Fixes diff --git a/docs/source/client.rst b/docs/source/client.rst index 986392f..a12ff7f 100644 --- a/docs/source/client.rst +++ b/docs/source/client.rst @@ -13,6 +13,13 @@ Client :inherited-members: +TwoFactorAuthClient +-------------------- +.. autoclass:: moddb.client.TwoFactorAuthClient + :members: + :inherited-members: + + ThreadThumbnail ---------------- .. autoclass:: moddb.client.ThreadThumbnail diff --git a/docs/source/conf.py b/docs/source/conf.py index c8a4d8d..e0387f2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -21,13 +21,13 @@ # -- Project information ----------------------------------------------------- project = "moddb" -copyright = "2022, Clement Julia" +copyright = "2025, Clement Julia" author = "Clement Julia" # The short X.Y version version = "" # The full version, including alpha/beta/rc tags -release = "0.13.0" +release = "0.14.0" # -- General configuration --------------------------------------------------- diff --git a/docs/source/index.rst b/docs/source/index.rst index 7c6e758..d685f5c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -80,6 +80,22 @@ that mod page you would be forced to completly parse the result page on your own See more snippets there: :ref:`snippets-ref`. +Two Factor Authentication +-------------------------- +Sometimes ModDB will ask you for a code sent to your email when logging from a new device. There is no +way to circuvement this. As such the library provides a class to do this handshake:: + + >> import moddb + >> e = moddb.TwoFactorAuthClient("MyUser", "*****") + >> e.login() + False + >> e.submit_2fa_code("AZADV") + < Member > + + +This class' `login` method returns false if 2FA is required instead of erroring, allowing you elegantly +check for the code and to send it in a second request. + Searching ---------- diff --git a/moddb/__init__.py b/moddb/__init__.py index abf5750..2a925c4 100644 --- a/moddb/__init__.py +++ b/moddb/__init__.py @@ -1,15 +1,17 @@ +from curl_adapter import CurlCffiAdapter import requests from .base import front_page, login, logout, parse_page, parse_results, rss, search, search_tags -from .client import Client, Thread +from .client import Client, TwoFactorAuthClient, Thread from .enums import * from .pages import * -from .utils import BASE_URL, LOGGER, Object, get_page, request, soup, SSLAdapter +from .utils import BASE_URL, LOGGER, Object, get_page, request, soup SESSION = requests.Session() -SESSION.mount("https://", SSLAdapter()) +SESSION.mount("http://", CurlCffiAdapter()) +SESSION.mount("https://", CurlCffiAdapter()) -__version__ = "0.13.0" +__version__ = "0.14.0" __all__ = [ "front_page", @@ -21,6 +23,7 @@ "search", "search_tags", "Client", + "TwoFactorAuthClient", "Thread", "BASE_URL", "LOGGER", diff --git a/moddb/boxes.py b/moddb/boxes.py index ce005f3..8b49c40 100644 --- a/moddb/boxes.py +++ b/moddb/boxes.py @@ -208,19 +208,8 @@ def __init__(self, html: BeautifulSoup): "div", class_="table tablemenu" ) self.contact = join(html.find("h5", string="Contact").parent.span.a["href"]) - self.follow = join( - profile_raw.find_all( - "h5", - string=[ - "Mod watch", - "Game watch", - "Group watch", - "Engine watch", - "Hardware watch", - "Software watch", - ], - )[0].parent.span.a["href"] - ) + + self.follow = join(html.find("a", title="Follow")["href"]) try: share = profile_raw.find("h5", string="Share").parent.span.find_all("a") @@ -874,8 +863,8 @@ def __init__(self, html: BeautifulSoup): ) try: - self.follow = join(profile_raw.find("h5", string="Member watch").parent.span.a["href"]) - except AttributeError: + self.follow = join(html.find("a", title="Follow")["href"]) + except TypeError: LOGGER.info( "Can't watch yourself, narcissist...", exc_info=LOGGER.level >= logging.DEBUG ) @@ -1073,7 +1062,9 @@ def __init__(self, **kwargs): def __repr__(self): return f"