"""Cospend's authenticated (NC-login) API - same app-password auth as nc_client. This is an OCS API (same family as the Share API), confirmed against the real routes/controller source in julien-nc/cospend-nc: appinfo/routes.php ('ocs' section): GET /api/{v}/projects -> api#getLocalProjects GET /api/{v}/projects/{projectId}/members -> api#getMembers POST /api/{v}/projects/{projectId}/bills -> api#createBill lib/Controller/ApiController.php, createBill() signature: string $projectId, ?string $date, ?string $what, ?int $payer, ?string $payedFor, ?float $amount, ... ?string $comment, ... -> params are camelCase (payedFor, not payed_for), payer/payedFor are numeric member ids (payedFor is a comma-separated string of ids). Being an OCS controller, every call needs the OCS-APIRequest header and the full path is under /ocs/v2.php/apps/cospend/... """ from __future__ import annotations from typing import Any import requests from .config import Config _HEADERS = {"OCS-APIRequest": "true"} def _auth() -> tuple[str, str]: return (Config.NC_USERNAME, Config.NC_APP_PASSWORD) def _base(project_id: str | None = None) -> str: root = f"{Config.NC_BASE_URL}/ocs/v2.php/apps/cospend/api/v1" if project_id is None: return root return f"{root}/projects/{project_id}" def get_projects() -> list[dict[str, Any]]: """Lists Cospend projects visible to the authenticated user.""" resp = requests.get( f"{_base()}/projects", auth=_auth(), headers=_HEADERS, params={"format": "json"}, timeout=15, ) resp.raise_for_status() return resp.json()["ocs"]["data"] def get_members(project_id: str) -> list[dict[str, Any]]: resp = requests.get( f"{_base(project_id)}/members", auth=_auth(), headers=_HEADERS, params={"format": "json"}, timeout=15, ) resp.raise_for_status() return resp.json()["ocs"]["data"] def create_bill( *, project_id: str, what: str, amount: float, payer_id: int, ower_ids: list[int], date: str, comment: str = "", ) -> int: """Creates a bill split evenly among `ower_ids`, paid by `payer_id`. `amount` is the bill total; Cospend divides it across owers itself. Returns the new bill's id - createBill's controller method returns just the int id (`return new DataResponse($newBillId)`), not a bill object. """ resp = requests.post( f"{_base(project_id)}/bills", auth=_auth(), headers=_HEADERS, params={"format": "json"}, json={ "what": what, "amount": amount, "payer": payer_id, "payedFor": ",".join(str(i) for i in ower_ids), "comment": comment, "date": date, # YYYY-MM-DD, the receipt's issue date, not today }, timeout=15, ) resp.raise_for_status() return resp.json()["ocs"]["data"]