11 KiB
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 frompackage/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.jsoncurrently declares version1.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:
An older CI/CD thread also points to there being no built-in continuous deployment integration for Pling:
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:
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:
https://www.opendesktop.org
The login page is a normal HTML form with these important fields:
emailpasswordredirect_urlcsrftwit
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-uridata-updatepploadfile-uridata-deletepploadfile-uridata-deletepploadfiles-uridata-product-iddata-ppload-collection-id
The server-side flow expects a multipart field named file_upload.
The practical shape is:
- Log in.
- Fetch the product edit page.
- Parse the upload endpoint URLs from the page.
- POST the
.plasmoidartifact asfile_upload. - Read the JSON response.
- Optionally POST metadata such as version, description, compatibility flags, category, and tags.
- 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:
scripts/pling_upload.py
Use:
niquests.Sessionfor cookies and requests- an HTML parser for CSRF and endpoint discovery
argparseortyperfor CLI arguments- explicit dry-run support
- structured, redacted logging
Suggested inputs:
PLING_USERNAME
PLING_PASSWORD
PLING_PROJECT_ID
PLING_BASE_URL=https://www.opendesktop.org
PLING_DELETE_OLD_FILES=false
Suggested CLI:
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:
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:
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:
https://www.opendesktop.org/login/
Parse hidden form fields, especially:
csrfredirect_urltwit
3. Submit Login
POST the login form with:
emailpassword- 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:
https://www.opendesktop.org/p/<project_id>/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:
file_upload
Recommended headers:
X-Requested-With: XMLHttpRequest
Accept: application/json
Referer: https://www.opendesktop.org/p/<project_id>/edit
Require JSON with:
status: ok
Capture the returned file id and collection id.
6. Update File Metadata
POST to the parsed data-updatepploadfile-uri endpoint with:
file_idfile_versionfile_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:
on:
release:
types: [published]
or tag push:
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:
- build the package
- upload it to the GitHub Release
- upload it to Pling
- 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.