mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-26 17:04:35 +01:00
F-Droid repositories can now be added with HTTP basic auth, either as https://user:pass@host/fdroid/repo (as F-Droid's client accepts) or with separate username/password fields (store form, server API, and the CLI's --username/--password-stdin). The login is stripped from the saved URL and kept in a new shared module, apk_sources/_credentials.py, which writes source-credentials.json in the data folder with mode 0600. A credential is only ever added per request to its exact origin (scheme, host, port): redirects elsewhere get nothing and have Authorization removed. HTTPS only. 401 (or 403 with a login) on add gives a "check the username and password" error and saves nothing; signature, pin, TOFU, hash and rollback checks are unchanged. Removing a repository deletes its login. GitHub gets a user-settable token (FRAME_GITHUB_TOKEN still wins), sent as Bearer only to api.github.com. If GitHub answers 401, the request is retried once without it and the rejection is reported in source settings. The source list shows only whether a token is set, never its value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
58 lines
2.5 KiB
Python
58 lines
2.5 KiB
Python
"""APK sources: every place Frame Control can find and download APKs.
|
|
|
|
One module per source kind in this package. Each module exposes the same small
|
|
interface so ``search.py`` can query them all and show where every result came
|
|
from. Python stdlib only; must run on Python 3.9.
|
|
|
|
Module interface
|
|
----------------
|
|
KIND = 'sidequest' # stable id of the source kind
|
|
def sources() -> list[dict] # configured sources of this kind (a kind can have
|
|
# several, e.g. one per F-Droid-format repo)
|
|
def search(source, query, limit=50) -> list[dict] # Entry dicts, best first
|
|
def details(source, entry_id) -> dict # Entry with 'versions'
|
|
def download(source, entry_id, version_code=None) -> dict
|
|
# {'apk': local path, 'obb': [paths], 'sha256': hex or None, 'verified': bool}
|
|
# Raise SourceError with a user-readable message on failure.
|
|
|
|
Source dict
|
|
-----------
|
|
{'id': 'sidequest', 'kind': KIND, 'name': 'SideQuest', 'url': 'https://...',
|
|
'builtin': True, 'enabled': True, 'trust': 'official' | 'community' | 'user'}
|
|
|
|
Entry dict (missing facts are None, never guessed)
|
|
----------
|
|
{'source': source id, 'id': source-local id, 'package': 'org.example.app' or None,
|
|
'name': str, 'summary': str, 'icon': url or None, 'page': url or None,
|
|
'version': '1.2', 'version_code': 12, 'min_sdk': 24, 'abis': ['arm64-v8a'],
|
|
'vr': True/False/None, 'size': bytes, 'free': True, 'license': 'GPL-3.0' or None,
|
|
'updated': 'YYYY-MM-DD', 'downloadable': bool, # False = open page only
|
|
'versions': [ {version, version_code, min_sdk, size, updated}, ... ]} # details() only
|
|
|
|
Rules
|
|
-----
|
|
- Only sources that distribute APKs with the developer's consent: free listings,
|
|
never paid apps re-hosted, no licence or entitlement workarounds.
|
|
- Honour each site's terms and robots rules; if automated download isn't allowed,
|
|
return entries with 'downloadable': False and a 'page' link instead.
|
|
- Cache indexes under frame_host.cache_dir('apk-sources'); send a clear
|
|
User-Agent ('FrameControl/<version>'); keep requests modest.
|
|
- Tests use recorded fixtures, never the network.
|
|
"""
|
|
|
|
|
|
class SourceError(Exception):
|
|
"""User-readable failure from a source (network, format, verification)."""
|
|
|
|
|
|
class SourceAuthError(SourceError):
|
|
"""The server refused our login (HTTP 401, or 403 when a login was sent)."""
|
|
|
|
|
|
class SourceLimited(SourceError):
|
|
"""The source's host asked us to slow down; retry_after is in seconds."""
|
|
|
|
def __init__(self, message, retry_after=None):
|
|
super().__init__(message)
|
|
self.retry_after = retry_after
|