Authentication and account features¶
Public endpoints — releases, the catalog, the schedule and so on — need no account. The account features need a session:
| Namespace | What it does |
|---|---|
client.auth |
logging in and out (password, one-time code, social network), password recovery |
client.me.profile() |
the user's profile |
client.me.favorites |
favorite releases |
client.me.collections |
the five collections: planned, watching, watched, postponed, abandoned |
client.me.views |
watch progress (timecodes) and history |
client.releases.ratings |
the user's own 1–10 scores |
client.releases.timecodes(), client.episodes.timecode() |
stored watch records of a release / an episode |
Runnable examples, each a short tutorial:
18_authentication.py,
19_otp_device_login.py,
20_favorites.py,
21_collections.py,
22_watch_progress.py,
23_release_ratings.py.
Logging in¶
import os
from aniliberty import AniLiberty
async with AniLiberty() as client:
await client.auth.login(
login=os.environ["ANILIBERTY_LOGIN"], # the login or the account's e-mail
password=os.environ["ANILIBERTY_PASSWORD"],
)
profile = await client.me.profile()
login() returns an
AuthToken and stores its token on the client as
client.token. From then on every request carries
Authorization: Bearer <token>. The login is case-insensitive; an e-mail works in its place.
Wrong credentials raise AuthRequiredError with HTTP 401
and the API's reason (in Russian) in error.message. A login is a POST, so it is never
retried.
Keep credentials and the token out of your code
Read the login and password from the environment or a secret store. The token gives full
access to the account: don't log it, don't commit it. The same goes for the tracker
passkey in profile.torrents.passkey — the SDK leaves it out of the model's repr.
Reusing a session¶
Every login creates a new, independent session on the account. A script that logs in on every run piles them up, so save the token and pass it back next time:
token = client.token # after client.auth.login(...); store it somewhere private
async with AniLiberty(token=token) as client: # no login request
await client.me.favorites.ids()
You can also assign client.token = ... at any time; None stops sending it.
What the API tells about the token (see API notes):
- It's an opaque string (32 characters), not a JWT: there is no expiry inside to read, and there is no refresh endpoint. The site's cookie for it lasts 8 days; whether the session on the server ends then hasn't been verified.
- When a token stops working, you get the same answer as with no token at all — see below. Log in again and save the new token.
- Public endpoints don't care: a stale token doesn't break them.
Logging out¶
logout() ends this session only — other
logins of the account (other devices, other scripts) stay valid — and clears
client.token. It's also cleared when the API answers that the token is already invalid;
after a network error or a 5xx it's kept, since the session may still be alive.
When a session is missing or invalid¶
Every account endpoint answers 403 "This action is unauthorized.", raised as
AuthRequiredError, when the client has no token, a malformed one, a logged-out one or an
expired one — the API doesn't say which. The SDK doesn't check for a token before sending,
so all of these cases look the same:
from aniliberty import AuthRequiredError
try:
await client.me.profile()
except AuthRequiredError as error:
if error.status_code == 403:
... # log in again
| Status | Meaning |
|---|---|
| 401 | wrong login or password at client.auth.login() |
| 403 | no valid session for an account endpoint |
Logging a device in with a one-time code¶
For a TV app or anything else without a comfortable keyboard,
client.auth.otp logs a device in with a code that the
user confirms from a device where they are already logged in:
issued = await tv.auth.otp.issue(device_id=device_id) # no session needed
show_on_screen(issued.otp.code) # 6 digits, a string; lives ~5 minutes
await phone.auth.otp.accept(issued.otp.code) # the phone has a session
await tv.auth.otp.login(code=issued.otp.code, device_id=device_id) # the TV gets its own
device_idis any string the device keeps (a UUID, say);issue()andlogin()must get the same one.- Keep the code a string: it can start with
0. login()before the code is accepted fails with aServerError(the API answers 500); an unknown, expired or already used code is aNotFoundError.- The API refuses an OTP login from a client that is already logged in, so the SDK sends that request without the token; the new token then replaces the old one.
Social networks¶
client.auth.social supports the providers of the
spec: "vk", "google", "patreon", "discord".
start = await client.auth.social.login("discord")
print("Open in a browser:", start.url) # the user logs in there
...
await client.auth.social.authenticate(start.state) # then trade the state for a session
authenticate() raises NotFoundError until the user has finished on the OAuth page (or
for an unknown state). The full browser round trip wasn't exercised while building the SDK.
Password recovery¶
await client.auth.password.forget(email="me@example.com") # sends a reset link
await client.auth.password.reset(token=token_from_the_email, password=new_password)
forget() answers the same whether or not the e-mail belongs to an account. reset()
doesn't log in — call login() with the new password afterwards.
Things the API does that you should know about¶
These were found by trying, and are described in detail in the API notes:
- Favorites and collections accept release IDs that don't exist. They show up in
ids()but never inreleases(). Adding something twice or removing what isn't there is a silent no-op. - A release is in one collection at most: adding it to another moves it.
- Collection changes are also applied in the background. A release removed right
after it was added can reappear about a minute later, and a removal can take minutes
to stick. If you need a known state, remove, wait a minute, check
ids()and repeat (see21_collections.py). - Timecodes are stored in two steps.
client.me.views.timecodes()has a saved timecode at once; the history,client.episodes.timecode()andclient.releases.timecodes()get it about a minute later. Deleting lags the same way, andtimecodes()can keep listing a deleted timecode until it's deleted once more after that minute. - Your rating doesn't move
Release.ratingright away: the overall score is recomputed on the API's own schedule. - Only numeric release IDs work for ratings (and for the franchise/torrent lookups by
release); the SDK rejects an alias with a
TypeError.
Testing code that uses an account¶
The SDK's own tests record live responses with VCR. If you do the same, scrub the secrets
before committing cassettes: the password in the login body, the token in the login
response and the Authorization header, the _al1 cookie (the API sets it to the token
too), and the passkey and e-mail in profile, torrent and release responses. The SDK's
tests/conftest.py
is a working example.