# Pling Upload Findings And Plan This repository can already build a distributable KDE Plasma widget package with `make package`. The missing piece is publishing the generated `.plasmoid` file to Pling/OpenDesktop as part of a release workflow. The safest route is to own a small uploader script instead of depending on the unofficial `pling-publisher` package. A Python implementation using `niquests` should be possible without Playwright for the normal path. ## Repository Context - The package artifact is produced by `make package`. - The expected output is `build/org.kde.plasma.starter.plasmoid`, derived from `package/metadata.json`. - There is currently no GitHub Actions workflow in this repository. - The package includes a compiled Qt/QML C++ module, so CI needs a runner with the right KDE/Qt build dependencies. - `package/metadata.json` currently declares version `1.0`; release automation should decide whether the source of truth is the Git tag, this metadata file, or both. ## Pling/OpenDesktop Findings ### Official API There does not appear to be a supported official upload API for editing content files on Pling/OpenDesktop. A recent OpenDesktop forum answer says the OCS API is effectively read-only for this use case and that content-edit upload support is not implemented: - https://forum.opendesktop.org/t/ocs-api-content-edit-unknown-request/20947 An older CI/CD thread also points to there being no built-in continuous deployment integration for Pling: - https://forum.opendesktop.org/t/there-is-a-way-to-setup-a-continuous-deployment-from-a-app-in-pling/18876 So this should be treated as browser/API automation of the existing web app, not as use of a stable public publishing API. ### Existing Unofficial Publisher `pling-publisher` is an unofficial Python package: - https://pypi.org/project/pling-publisher/ - https://github.com/dmzoneill/pling-publisher Its general approach is useful as prior art, but I would not make this repository depend on it. It logs in with a session, scrapes edit-page values, and posts multipart upload data to Pling's internal upload endpoints. Owning a small script gives us better control over: - dependency trust - logging and secret handling - failure behavior - compatibility fixes when Pling changes its pages - dry-run and CI behavior ### Current Web Flow Pling redirects to OpenDesktop for the account and product edit flow. The canonical base URL should be: ```text https://www.opendesktop.org ``` The login page is a normal HTML form with these important fields: - `email` - `password` - `redirect_url` - `csrf` - `twit` The site also sets and checks bot-detection signals. In local testing, a normal browser-like request could fetch the login form, and a fake login returned the expected "incorrect login" page rather than a hard block. That means a `niquests.Session` implementation is likely viable today. The current first-party upload UI posts files through product controller endpoints, not only through raw ppload internals. KDE's hosted source shows the relevant behavior here: - https://lxr.kde.org/source/webapps/ocs-webserver/application/modules/default/views/scripts/user/products.phtml - https://lxr.kde.org/source/webapps/ocs-webserver/application/modules/default/controllers/ProductController.php Important upload/edit endpoints are exposed as HTML data attributes on the edit page, especially around the upload modal: - `data-addpploadfile-uri` - `data-updatepploadfile-uri` - `data-deletepploadfile-uri` - `data-deletepploadfiles-uri` - `data-product-id` - `data-ppload-collection-id` The server-side flow expects a multipart field named `file_upload`. The practical shape is: 1. Log in. 2. Fetch the product edit page. 3. Parse the upload endpoint URLs from the page. 4. POST the `.plasmoid` artifact as `file_upload`. 5. Read the JSON response. 6. Optionally POST metadata such as version, description, compatibility flags, category, and tags. 7. Optionally delete/archive older uploaded files. ## Is This A Playwright Automation? Not as the first choice. The recommended implementation is HTTP automation with `niquests`, plus an HTML parser such as `selectolax`, `beautifulsoup4`, or `lxml`. Playwright should be kept as a fallback or diagnostic tool because: - the normal upload flow is form posts and JSON responses - HTTP automation is easier to run in CI - HTTP automation is easier to test and log safely - Playwright/headless browsers are more likely to trigger bot checks - Playwright can accidentally become a brittle click-script tied to UI layout Playwright may become necessary if Pling changes the flow to require a real browser-only challenge, but the script should fail closed rather than trying to bypass CAPTCHA, 2FA, or anti-bot checks. ## Recommended Python Design Create a small repository-owned uploader, for example: ```text scripts/pling_upload.py ``` Use: - `niquests.Session` for cookies and requests - an HTML parser for CSRF and endpoint discovery - `argparse` or `typer` for CLI arguments - explicit dry-run support - structured, redacted logging Suggested inputs: ```text PLING_USERNAME PLING_PASSWORD PLING_PROJECT_ID PLING_BASE_URL=https://www.opendesktop.org PLING_DELETE_OLD_FILES=false ``` Suggested CLI: ```text python scripts/pling_upload.py \ --artifact build/org.kde.plasma.starter.plasmoid \ --project-id "$PLING_PROJECT_ID" \ --version "$VERSION" \ --description-file RELEASE_NOTES.md ``` Suggested dry run: ```text python scripts/pling_upload.py \ --project-id "$PLING_PROJECT_ID" \ --dry-run ``` Dry run should: - log in - fetch the product edit page - parse upload endpoints - optionally list existing files - avoid uploading or deleting anything ## Recommended HTTP Flow ### 1. Start A Session Use a browser-like user agent and keep redirects enabled. Set conservative headers: ```text User-Agent: Mozilla/5.0 ... Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.9 ``` The site currently uses a `verified=1` cookie in its browser-side checks. The script can set it before login, but should not rely on that as a stable contract. ### 2. Fetch Login Page GET: ```text https://www.opendesktop.org/login/ ``` Parse hidden form fields, especially: - `csrf` - `redirect_url` - `twit` ### 3. Submit Login POST the login form with: - `email` - `password` - parsed hidden fields After login, verify success by checking that the session can access the product edit page. Do not treat HTTP 200 alone as success. ### 4. Fetch Product Edit Page GET: ```text https://www.opendesktop.org/p//edit ``` Parse upload-related data attributes from the page. Fail clearly if any required endpoint is missing. ### 5. Upload Artifact POST multipart form data to the parsed `data-addpploadfile-uri` endpoint. Use multipart field: ```text file_upload ``` Recommended headers: ```text X-Requested-With: XMLHttpRequest Accept: application/json Referer: https://www.opendesktop.org/p//edit ``` Require JSON with: ```text status: ok ``` Capture the returned file id and collection id. ### 6. Update File Metadata POST to the parsed `data-updatepploadfile-uri` endpoint with: - `file_id` - `file_version` - `file_description` - optional `file_category` - optional `file_tags` - optional `ocs_compatible=1` This step should also require JSON `status: ok`. ### 7. Optional Cleanup Old file cleanup should be opt-in. It is useful, but destructive. The script can list files via the edit page's file listing endpoint and then POST old file ids to `data-deletepploadfile-uri`. Default behavior should be to keep all uploaded files until the workflow has been proven on a test product. ## CI/CD Plan ### Stage 1: GitHub Release Artifact First add CI that builds the `.plasmoid` and attaches it to a GitHub Release. This is the most reliable publishing target and gives users a fallback if Pling upload fails. Trigger: ```text on: release: types: [published] ``` or tag push: ```text on: push: tags: - "v*" ``` ### Stage 2: Manual Pling Dry Run Add a manual `workflow_dispatch` job that only logs in, parses the edit page, and reports whether upload endpoints are available. This validates secrets and Pling compatibility without publishing anything. ### Stage 3: Manual Test Upload Run the uploader manually against either: - a disposable Pling test product, or - the real product with old-file cleanup disabled Verify the uploaded file manually from the Pling UI. ### Stage 4: Release Upload Enable automatic Pling upload after GitHub release artifact creation succeeds. The release workflow should: 1. build the package 2. upload it to the GitHub Release 3. upload it to Pling 4. fail visibly if Pling upload fails The workflow should not delete old Pling files by default. ### Stage 5: Optional Old-File Policy After the upload path is stable, decide whether to: - keep all historical files - keep the latest N files - delete/archive all files older than the current release This should be a separate explicit setting, not a default. ## Security Notes - Store credentials only as GitHub Actions secrets. - Prefer a dedicated Pling/OpenDesktop account if project ownership allows it. - Do not log passwords, cookies, CSRF tokens, or full response bodies from authenticated pages. - Mask usernames/project ids in logs if desired. - Do not attempt to bypass CAPTCHA, 2FA, or stronger anti-bot challenges. - Keep upload retries conservative to avoid account lockouts. - Use GitHub protected environments if releases should require approval before publishing to Pling. ## Failure Modes To Expect - Login succeeds locally but fails in GitHub Actions because of bot checks. - The edit page HTML changes and endpoint parsing breaks. - Pling/OpenDesktop redirects domains differently. - The upload succeeds but metadata update fails. - The account lacks permission to edit the product id. - The file upload endpoint returns HTML instead of JSON on auth failure. - Deleting old files removes something that should have stayed available. Each of these should produce a clear, non-secret error message and stop the workflow. ## Open Questions - Should the release version come from the Git tag or `package/metadata.json`? - Should the Pling file description come from GitHub release notes, a checked-in changelog, or a static text field? - Should old Pling files be kept forever at first? - Is there a separate Pling test product available for the first upload? - Does the real product need special GHNS/OCS compatibility metadata beyond `ocs_compatible=1`? ## Recommendation Implement a repository-owned `niquests` uploader first, not Playwright. Treat Pling upload as a best-effort release publishing step built on top of the current web UI. Keep GitHub Releases as the reliable primary artifact host, then publish to Pling after the artifact exists. Add dry-run and manual test upload before making Pling upload automatic on release.