Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2025 Clement Julia
Copyright (c) 2026 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
Expand Down
4 changes: 4 additions & 0 deletions docs/source/base.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ logout
--------
.. autofunction:: moddb.base.logout

get_freeman_cookie
-------------------
.. autofunction:: moddb.base.get_freeman_cookie

front_page
-----------
.. autofunction:: moddb.base.front_page
10 changes: 9 additions & 1 deletion docs/source/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ The page attempt to keep a clear list of breaking/non-breaking changes and new f
:local:
:backlinks: none

v0.15.0
-----------
New Features
#############
* Added cookie-based authentication with `freeman` cookie support in `base.login`, `Client` and `TwoFactorAuthClient`
* Added methods to retrieve the active `freeman` cookie from module and client sessions

v0.14.0
-----------
Bug Fixes
Expand All @@ -18,6 +25,7 @@ Bug Fixes
New Features
#############
* Exposed front page poll url under `FrontPage.poll_url`
* Added `TwoFactorAuthClient` for handling login flows that require 2FA verification


v0.13.0
Expand Down Expand Up @@ -199,4 +207,4 @@ New Features
* :class:`.Search` now behaves like a list
* :class:`.Search` and :class:`.CommentList` have access to get_all_results, an expensive call that iterates through all the pages of results available and returns a list of all the results. This does not return duplicate entries.
* Changelog begins


4 changes: 2 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@
# -- Project information -----------------------------------------------------

project = "moddb"
copyright = "2025, Clement Julia"
copyright = "2026, Clement Julia"
author = "Clement Julia"

# The short X.Y version
version = ""
# The full version, including alpha/beta/rc tags
release = "0.14.0"
release = "0.15.0"


# -- General configuration ---------------------------------------------------
Expand Down
21 changes: 21 additions & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,14 @@ way to circuvement this. As such the library provides a class to do this handsha
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.

If you already have a valid `freeman` cookie, both :class:`Client` and :class:`TwoFactorAuthClient`
can be initialized directly with it::

>> c = moddb.Client(freeman_cookie="...")
>> tfa = moddb.TwoFactorAuthClient(freeman_cookie="...")
>> tfa.login()
True

Searching
----------

Expand Down Expand Up @@ -165,6 +173,19 @@ groups/members. In order to login simply use the built-in library method::
import moddb
moddb.login("valid_username", "valid_password")

If you already have a valid `freeman` cookie, you can authenticate with it directly::

import moddb
moddb.login(freeman_cookie="...")

After logging in, you can retrieve that cookie to re-use it in a future session::

import moddb
moddb.login("valid_username", "valid_password")
cookie = moddb.get_freeman_cookie()
# later
moddb.login(freeman_cookie=cookie)

The session the package uses will be updated and all further requests will now be made logged in as that user.
The function will raise a ValueError if the login fails. You can also log out of the account to disable access
to those restricted elements with the `logout` function::
Expand Down
23 changes: 23 additions & 0 deletions docs/source/snippets.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ Clients represents an independtly logged in user. This allows you to make certai
#standard request, we're just gonna get the update that have been posted.
updates = client.get_updates()
#list of special thumbnails representing the updates this user has
cookie = client.get_freeman_cookie()

If you already have a valid `freeman` cookie, you can initialize a client directly with it::

import moddb

client = moddb.Client(freeman_cookie="...")
updates = client.get_updates()

A simple little example, users have updates which happen when pages upload new articles, files and media. These are based on what pages users follow. But let's say we want to make one of the more general requests provided by the library without overwriting permanently the module session::

Expand All @@ -34,3 +42,18 @@ A simple little example, users have updates which happen when pages upload new a

#now we can see this profile from the persepctive of RegularUser.

The same cookie-based initialization works with :class:`TwoFactorAuthClient` as well::

import moddb

client = moddb.TwoFactorAuthClient(freeman_cookie="...")
assert client.login() is True

You can also export the current session's cookie and re-use it later::

import moddb

moddb.login("Username", "Password")
cookie = moddb.get_freeman_cookie()

client = moddb.Client(freeman_cookie=cookie)
5 changes: 4 additions & 1 deletion docs/source/utils.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ get_page
---------
.. autofunction:: moddb.utils.get_page

get_freeman_cookie
-------------------
.. autofunction:: moddb.utils.get_freeman_cookie

get_views
---------
.. autofunction:: moddb.utils.get_views
Expand All @@ -57,4 +61,3 @@ get_page_type
get_list_stats
---------
.. autofunction:: moddb.utils.get_list_stats

13 changes: 12 additions & 1 deletion moddb/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +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 .base import (
front_page,
get_freeman_cookie,
login,
logout,
parse_page,
parse_results,
rss,
search,
search_tags,
)
from .client import Client, TwoFactorAuthClient, Thread
from .enums import *
from .pages import *
Expand All @@ -15,6 +25,7 @@

__all__ = [
"front_page",
"get_freeman_cookie",
"login",
"logout",
"parse_page",
Expand Down
42 changes: 34 additions & 8 deletions moddb/base.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
from __future__ import annotations

import sys
from typing import TYPE_CHECKING, Any, Tuple, Union
from typing import TYPE_CHECKING, Any, Optional, Tuple, Union

import requests

from .boxes import PartialTag, ResultList, Tag, _parse_results
from .pages import FrontPage, Member
from .utils import BASE_URL, generate_login_cookies, get_page, get_page_type, request, soup
from .utils import (
BASE_URL,
generate_login_cookies,
get_session_freeman_cookie,
get_page,
get_page_type,
login_with_freeman_cookie,
request,
soup,
)

if TYPE_CHECKING:
from .enums import RSSType, SearchCategory


__all__ = ["search", "parse_page", "login", "logout", "front_page", "parse_results"]
__all__ = ["search", "parse_page", "login", "logout", "get_freeman_cookie", "front_page", "parse_results"]


def search(
Expand Down Expand Up @@ -150,29 +159,41 @@ def parse_results(url: str, *, params: dict = {}) -> ResultList:
)


def login(username: str, password: str) -> Member:
def login(username: str = None, password: str = None, freeman_cookie: str = None) -> Member:
"""Login the user to moddb through the library, this allows user to see guest comments and see
private groups they are part of.

Parameters
-----------
username : str
The username of the user
The username of the user. Required unless `freeman_cookie` is provided.
password : str
The password associated to that username
The password associated to that username. Required unless `freeman_cookie`
is provided.
freeman_cookie : str
The freeman cookie for the user session

Raises
-------
AuthError
A 2FA code is required to login
ValueError
The password or username was incorrect
The password, username or freeman cookie was incorrect

Returns
--------
Member
The member you are logged in as
"""

sys.modules["moddb"].SESSION.cookies = generate_login_cookies(username, password)
if freeman_cookie:
username = login_with_freeman_cookie(freeman_cookie)
else:
if not username or not password:
raise ValueError("Username and password must be provided to login")

sys.modules["moddb"].SESSION.cookies = generate_login_cookies(username, password)

return Member(get_page(f"{BASE_URL}/members/{username.replace('_', '-')}"))


Expand All @@ -183,6 +204,11 @@ def logout():
sys.modules["moddb"].SESSION.cookies.clear()


def get_freeman_cookie() -> Optional[str]:
"""Return the `freeman` cookie from the module session, if available"""
return get_session_freeman_cookie()


def front_page() -> FrontPage:
"""This returns a model representing the front page of May sound fancy but it is no more
than a collection of popular mods, files, games and articles. In addition jobs are listed
Expand Down
Loading
Loading