witness-fracture/.venv/lib/python3.12/site-packages/internetarchive/account.py

270 lines
8.7 KiB
Python
Raw Normal View History

import json
from dataclasses import dataclass, field
from typing import ClassVar, Dict, List, Optional
import requests
from internetarchive import get_session
from internetarchive.exceptions import AccountAPIError
from internetarchive.session import ArchiveSession
"""
internetarchive.account
~~~~~~~~~~~~~~~~~~~~~~~
:copyright: (C) 2012-2025 by Internet Archive.
:license: AGPL 3, see LICENSE for more details.
This module provides the `Account` class for interacting with user accounts on the
Internet Archive. It requires administrative privileges.
"""
@dataclass
class Account:
"""
A class for interacting with user accounts on the Internet Archive.
Note:
This class requires administrative privileges.
This class provides methods to:
- Fetch account details using various identifiers (e.g., email, screenname, itemname).
- Lock and unlock accounts.
- Convert account data to a dictionary for serialization.
Example Usage:
>>> from internetarchive.account import Account
>>> account = Account.from_account_lookup('email', 'foo@example.com')
>>> account.lock(comment="Locked spam account")
>>> print(account.to_dict())
"""
locked: bool
verified: bool
email: str
canonical_email: str
itemname: str
screenname: str
notifications: List[str]
has_disability_access: bool
lastlogin: str
createdate: str
session: ArchiveSession = field(default_factory=get_session)
API_BASE_URL: str = '/services/xauthn/'
API_INFO_PARAMS: ClassVar[Dict[str, str]] = {'op': 'info'}
API_LOCK_UNLOCK_PARAMS: ClassVar[Dict[str, str]] = {'op': 'lock_unlock'}
def _get_api_base_url(self) -> str:
"""Dynamically construct the API base URL using the session's host."""
return f'https://{self.session.host}{self.API_BASE_URL}' # type: ignore[attr-defined]
def _post_api_request(
self,
endpoint: str,
params: Dict[str, str],
data: Dict[str, str],
session: Optional[ArchiveSession] = None
) -> requests.Response:
"""
Helper method to make API requests.
Args:
endpoint: The API endpoint to call.
params: Query parameters for the request.
data: Data to send in the request body.
session: Optional session to use for the request. Defaults to self.session.
Returns:
The response from the API.
Raises:
requests.exceptions.RequestException: If the API request fails.
"""
session = session or self.session
url = f'https://{session.host}{endpoint}' # type: ignore[attr-defined]
response = session.post(url, params=params, data=data)
response.raise_for_status()
return response
@classmethod
def from_account_lookup(
cls,
identifier_type: str,
identifier: str,
session: Optional[ArchiveSession] = None
) -> "Account":
"""
Factory method to initialize an Account using an identifier type and value.
Args:
identifier_type: The type of identifier (e.g., 'email', 'screenname').
identifier: The value of the identifier (e.g., 'foo@example.com').
session: Optional session to use for the request.
Returns:
An instance of Account.
"""
json_data = cls._fetch_account_data_from_api(identifier_type, identifier, session)
return cls.from_json(json_data, session)
@classmethod
def _fetch_account_data_from_api(
cls,
identifier_type: str,
identifier: str,
session: Optional[ArchiveSession] = None
) -> Dict:
"""
Fetches account data from the API using an identifier type and value.
Args:
identifier_type: The type of identifier (e.g., 'email', 'screenname').
identifier: The value of the identifier (e.g., 'foo@example.com').
session: Optional session to use for the request.
Returns:
A dictionary containing the account data.
Raises:
requests.exceptions.RequestException: If the API request fails.
ValueError: If the API response is invalid or missing required data.
"""
data = {identifier_type: identifier}
session = session or get_session()
try:
response = session.post(
f'https://{session.host}{cls.API_BASE_URL}', # type: ignore[attr-defined]
params=cls.API_INFO_PARAMS,
data=json.dumps(data)
)
response.raise_for_status()
j = response.json()
if j.get("error") or not j.get("values"):
raise AccountAPIError(j.get("error", "Unknown error"), error_data=j)
return j["values"]
except requests.exceptions.RequestException as e:
raise AccountAPIError(f"Failed to fetch account data: {e}")
@classmethod
def from_json(
cls,
json_data: Dict,
session: Optional[ArchiveSession] = None
) -> "Account":
"""
Factory method to initialize an Account using JSON data.
Args:
json_data: A dictionary containing account data.
session: Optional session to use for the request.
Returns:
An instance of Account.
Raises:
ValueError: If required fields are missing in the JSON data.
"""
required_fields = [
"canonical_email",
"email",
"has_disability_access",
"itemname",
"locked",
"notifications",
"screenname",
"verified",
"lastlogin",
"createdate",
]
for requried_field in required_fields:
if requried_field not in json_data:
raise ValueError(f"Missing required requried_field in JSON data: {requried_field}")
# Ensure session is of type ArchiveSession
if session is None:
session = get_session() # Default to ArchiveSession
elif not isinstance(session, ArchiveSession):
raise TypeError(f"Expected session to be of type ArchiveSession, got {type(session)}")
return cls(
locked=json_data["locked"],
verified=json_data["verified"],
email=json_data["email"],
canonical_email=json_data["canonical_email"],
itemname=json_data["itemname"],
screenname=json_data["screenname"],
notifications=json_data["notifications"],
has_disability_access=json_data["has_disability_access"],
lastlogin=json_data["lastlogin"],
createdate=json_data["createdate"],
session=session
)
def lock(self,
comment: Optional[str] = None,
session: Optional[ArchiveSession] = None) -> requests.Response:
"""
Lock the account.
Args:
comment: An optional comment for the lock operation.
session: Optional session to use for the request.
Returns:
The response from the API.
"""
data = {'itemname': self.itemname, 'is_lock': '1'}
if comment:
data['comments'] = comment
return self._post_api_request(
self.API_BASE_URL,
params=self.API_LOCK_UNLOCK_PARAMS,
data=data,
session=session
)
def unlock(self,
comment: Optional[str] = None,
session: Optional[ArchiveSession] = None) -> requests.Response:
"""
Unlock the account.
Args:
comment: An optional comment for the unlock operation.
session: Optional session to use for the request.
Returns:
The response from the API.
"""
data = {'itemname': self.itemname, 'is_lock': '0'}
if comment:
data['comments'] = comment
return self._post_api_request(
self.API_BASE_URL,
params=self.API_LOCK_UNLOCK_PARAMS,
data=data,
session=session
)
def to_dict(self) -> Dict:
"""
Converts the Account instance to a dictionary.
Returns:
A dictionary representation of the Account instance.
"""
return {
"locked": self.locked,
"verified": self.verified,
"email": self.email,
"canonical_email": self.canonical_email,
"itemname": self.itemname,
"screenname": self.screenname,
"notifications": self.notifications,
"has_disability_access": self.has_disability_access,
"lastlogin": self.lastlogin,
"createdate": self.createdate,
}