From 41684e2d9053992eaa124791ac0c459e84e68496 Mon Sep 17 00:00:00 2001 From: saphid <4596216+saphid@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:24:55 +1000 Subject: [PATCH] Add publisher artwork to GitHub APK source entries Include curated app images and summaries, owner avatars and social banners for topic discovery, and fixture coverage for artwork preservation. Co-Authored-By: GPT-6 Astra --- .claude/NOTES-more-sources.md | 25 ++++++ .claude/tests-more-sources.log | 4 +- docs/apk-sources.md | 11 ++- tests/fixtures/more_sources/README.md | 8 ++ .../fixtures/more_sources/artwork-check.json | 90 +++++++++++++++++++ tests/fixtures/more_sources/topic.json | 15 +++- tests/test_apk_more_sources.py | 40 +++++++++ ui/apk_sources/github.py | 13 ++- ui/apk_sources/github_curated.json | 36 ++++++-- 9 files changed, 225 insertions(+), 17 deletions(-) create mode 100644 tests/fixtures/more_sources/artwork-check.json diff --git a/.claude/NOTES-more-sources.md b/.claude/NOTES-more-sources.md index b699e4d..a125336 100644 --- a/.claude/NOTES-more-sources.md +++ b/.claude/NOTES-more-sources.md @@ -42,3 +42,28 @@ Final verification: - Final artifact: docs/apk-sources.md plus two source modules, a private HTTPS helper, curated JSON, recorded fixtures and tests. Evidence scripts/logs stay in .claude; research HTML and APK cache are explicitly ignored there. + +## Artwork follow-up + +Added curated icon/images metadata and plain-language summaries. Icons use +publisher repository assets pinned to inspected commits. Open Brush's banner +is its README image; three screenshots are from its README-linked Steam page. +SuperTux's banner/screenshot is the upstream gameplay preview in the port's +README, not a Quest capture. hello_xr has its actual Vulkan launcher icon and +repository social banner; no real screenshot was found in its repository or +README, so screenshots stays empty rather than mislabeling branding. + +Topic search uses owner.avatar_url and the requested GitHub social-preview +pattern, retaining curated artwork when a curated app is discovered by topic. +Unknown repository details use GitHub's owner.png avatar endpoint and the same +social banner without an extra API request. Existing itch cover behavior is +unchanged; tests now assert icon/banner equality and empty screenshots because +the recorded RSS provides no separate screenshots. + +Validation: all nine distinct curated artwork URLs returned HTTP 200, image +Content-Type and image magic. Of six topic artwork URLs, five returned images +and LWJGL's social preview returned HTTP 429. Recorded in +`tests/fixtures/more_sources/artwork-check.json`; no repeated retry. +`python3 -m unittest discover -s tests`: 176 passed in 5.879s, real exit 0. +`git diff --check`: exit 0. No UI rendering/headset testing or independent +review; parent retains integration/review, and delegation remains prohibited. diff --git a/.claude/tests-more-sources.log b/.claude/tests-more-sources.log index 8834d11..9f5e810 100644 --- a/.claude/tests-more-sources.log +++ b/.claude/tests-more-sources.log @@ -1,5 +1,5 @@ -.............................................................................................................................................................................. +................................................................................................................................................................................ ---------------------------------------------------------------------- -Ran 174 tests in 5.569s +Ran 176 tests in 5.879s OK diff --git a/docs/apk-sources.md b/docs/apk-sources.md index 15aa0b5..7caca71 100644 --- a/docs/apk-sources.md +++ b/docs/apk-sources.md @@ -60,8 +60,15 @@ does not suppress the other at the aggregator level. Queries filter the current feed window locally: this is not an exhaustive historical itch search. Only explicit zero-price Android entries are returned. Covers are exposed in `images`; absent screenshots, APK version, ABI and minimum SDK stay unknown. -GitHub's release metadata has no standard app artwork field, so artwork stays -empty rather than presenting a repository-owner avatar as an app icon. VR is +Curated GitHub entries include publisher artwork and plain-language summaries. +Repository image URLs are pinned to inspected commits. Open Brush screenshots +come from its README-linked Steam listing; SuperTux uses the upstream gameplay +preview embedded in the port's README (not a headset capture). The hello_xr +sample has a launcher icon and GitHub social banner; no published screenshot +was found in the inspected repository/README, so its screenshot list is empty. +Uncurated topic results use the owner's avatar and GitHub's repository social +preview. These are repository placeholders, not app screenshots. Itch's recorded +RSS includes only covers, so screenshot lists remain empty without page scraping. VR is based on curated evidence or a VR-specific feed/topic, not a compatibility claim. Downloads stream to unique temporary files, require an APK manifest entry, diff --git a/tests/fixtures/more_sources/README.md b/tests/fixtures/more_sources/README.md index be6d3c8..8f33165 100644 --- a/tests/fixtures/more_sources/README.md +++ b/tests/fixtures/more_sources/README.md @@ -13,3 +13,11 @@ values are unchanged. No token, cookies or signed download URL is included. The synthetic ZIP in tests is only a transport/integrity fixture, not an installable APK. Actual APK parsing was verified separately on the downloaded Khronos Vulkan sample; see docs/apk-sources.md. + +Artwork follow-up: `topic.json` now retains `owner.avatar_url` from authenticated +repository API reads. `artwork-check.json` records HTTPS response status, +Content-Type and image magic checks for all curated URLs and three topic +results. All curated URLs returned real images; LWJGL's social preview returned +HTTP 429. This fixture is evidence of a point-in-time check, not an uptime test. +Open Brush screenshots came from the Steam appdetails response for app 1634870, +linked by its README. SuperTux's README links the recorded upstream screenshot. diff --git a/tests/fixtures/more_sources/artwork-check.json b/tests/fixtures/more_sources/artwork-check.json new file mode 100644 index 0000000..8457ca4 --- /dev/null +++ b/tests/fixtures/more_sources/artwork-check.json @@ -0,0 +1,90 @@ +[ + { + "url": "https://avatars.githubusercontent.com/u/2757344?v=4", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://avatars.githubusercontent.com/u/784805?v=4", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://avatars.githubusercontent.com/u/94376830?v=4", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://opengraph.githubassets.com/1/KhronosGroup/OpenXR-SDK-Source", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://opengraph.githubassets.com/1/LWJGL/lwjgl3", + "error": "HTTP Error 429: Too Many Requests" + }, + { + "url": "https://opengraph.githubassets.com/1/bjornbytes/lovr", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://opengraph.githubassets.com/1/sahibzada-allahyar/YC-Killer", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/3ed64d0f9bb680f24b80a085091e5c8fab38f7b7/src/tests/hello_xr/android_resources/vulkan/mipmap-xxxhdpi/ic_helloxr_launcher.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/SgtBilko76/SuperTux-3D/1955493ee6f1000e048c58db40d4904df827210e/data/images/engine/icons/supertux-256x256.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/Assets/Resources/DefaultImages/OpenBrushLogo.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/open-brush.png", + "status": 200, + "content_type": "image/png", + "image": true + }, + { + "url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_0a9c208e26a43cf34879c2ca361d4c8f18af8cba.1920x1080.jpg", + "status": 200, + "content_type": "image/jpeg", + "image": true + }, + { + "url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_19b25b86ef55c0d8769a65135d60eaae8fa40553.1920x1080.jpg", + "status": 200, + "content_type": "image/jpeg", + "image": true + }, + { + "url": "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_785ea37d63378146dfe0f0ffa3f1d5c155ca978f.1920x1080.jpg", + "status": 200, + "content_type": "image/jpeg", + "image": true + }, + { + "url": "https://www.supertux.org/images/0_7_0/github_preview.png", + "status": 200, + "content_type": "image/png", + "image": true + } +] diff --git a/tests/fixtures/more_sources/topic.json b/tests/fixtures/more_sources/topic.json index 129a59a..2a87d01 100644 --- a/tests/fixtures/more_sources/topic.json +++ b/tests/fixtures/more_sources/topic.json @@ -3,17 +3,26 @@ { "full_name": "LWJGL/lwjgl3", "name": "lwjgl3", - "description": "LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL, Vulkan, bgfx), audio (OpenAL, Opus), parallel computing (OpenCL, CUDA) and XR (OpenVR, LibOVR, OpenXR) applications." + "description": "LWJGL is a Java library that enables cross-platform access to popular native APIs useful in the development of graphics (OpenGL, Vulkan, bgfx), audio (OpenAL, Opus), parallel computing (OpenCL, CUDA) and XR (OpenVR, LibOVR, OpenXR) applications.", + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/2757344?v=4" + } }, { "full_name": "sahibzada-allahyar/YC-Killer", "name": "YC-Killer", - "description": "A library of enterprise-grade AI agents designed to democratize artificial intelligence and provide free, open-source alternatives to overvalued Y Combinator startups." + "description": "A library of enterprise-grade AI agents designed to democratize artificial intelligence and provide free, open-source alternatives to overvalued Y Combinator startups.", + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/94376830?v=4" + } }, { "full_name": "bjornbytes/lovr", "name": "lovr", - "description": "Lua Virtual Reality Framework" + "description": "Lua Virtual Reality Framework", + "owner": { + "avatar_url": "https://avatars.githubusercontent.com/u/784805?v=4" + } } ] } diff --git a/tests/test_apk_more_sources.py b/tests/test_apk_more_sources.py index a21dd90..1709e2d 100644 --- a/tests/test_apk_more_sources.py +++ b/tests/test_apk_more_sources.py @@ -23,6 +23,33 @@ class PublisherSources(unittest.TestCase): self.assertEqual(github.search(github.sources()[0], 'hello')[0]['id'], 'KhronosGroup/OpenXR-SDK-Source') self.assertEqual(github.search(github.sources()[0], '', 0), []) + def test_curated_artwork_has_recorded_image_evidence(self): + evidence = {r['url']: r for r in json.loads((FIX / 'artwork-check.json').read_text())} + entries = github.search(github.sources()[0], '') + self.assertEqual(len(entries), 3) + for entry in entries: + self.assertTrue(entry['summary']) + images = entry['images'] + self.assertEqual(entry['icon'], images['icon']) + for url in [images['icon'], images['banner']] + images['screenshots']: + self.assertTrue(url.startswith('https://')) + self.assertEqual(evidence[url]['status'], 200) + self.assertTrue(evidence[url]['image']) + self.assertTrue(entries[1]['images']['screenshots']) + self.assertTrue(entries[2]['images']['screenshots']) + + def test_topic_keeps_curated_artwork(self): + curated = github._curated()[1] + repo = {'full_name': curated['repo'], 'name': 'open-brush', + 'owner': {'avatar_url': 'https://avatars.githubusercontent.com/u/1'}} + with patch.object(github, '_api', return_value={'items': [repo]}): + entry = github.search(github.sources()[0], 'topic:openxr')[0] + self.assertEqual(entry['images'], curated['images']) + unknown = github.details(github.sources()[0], 'unknown/project') + self.assertEqual(unknown['icon'], 'https://github.com/unknown.png') + self.assertEqual(unknown['images']['banner'], + 'https://opengraph.githubassets.com/1/unknown/project') + def test_real_releases(self): for key, repo in [('khronos', 'KhronosGroup/OpenXR-SDK-Source'), ('brush', 'icosa-foundation/open-brush'), ('tux', 'SgtBilko76/SuperTux-3D')]: @@ -31,6 +58,9 @@ class PublisherSources(unittest.TestCase): self.assertTrue(e['downloadable']) self.assertTrue(e['versions'][0]['name'].endswith('.apk')) self.assertIsNone(e['version_code']) + search_entry = github.search(github.sources()[0], repo)[0] + self.assertEqual(e['images'], search_entry['images']) + self.assertEqual(e['icon'], e['images']['icon']) with self.assertRaises(SourceError): github.download(github.sources()[0], repo, 123) @@ -40,6 +70,12 @@ class PublisherSources(unittest.TestCase): entries = github.search(github.sources()[0], 'topic:openxr') self.assertTrue(entries) self.assertTrue(any(not e['downloadable'] for e in entries)) + for entry, repo in zip(entries, data['items']): + self.assertEqual(entry['icon'], repo['owner']['avatar_url']) + self.assertEqual(entry['images']['icon'], entry['icon']) + self.assertEqual(entry['images']['banner'], + 'https://opengraph.githubassets.com/1/' + repo['full_name']) + self.assertEqual(entry['images']['screenshots'], []) self.assertFalse(github.details(github.sources()[0], 'unknown/project')['downloadable']) with self.assertRaises(SourceError): github.search(github.sources()[0], 'topic:piracy') @@ -51,6 +87,10 @@ class PublisherSources(unittest.TestCase): e = itch.details(itch.sources()[0], entries[0]['id']) self.assertTrue(e['vr']) self.assertTrue(e['images']['banner']) + for item in entries: + self.assertEqual(item['icon'], item['images']['icon']) + self.assertEqual(item['icon'], item['images']['banner']) + self.assertEqual(item['images']['screenshots'], []) self.assertFalse(e['downloadable']) self.assertEqual(itch.search(itch.sources()[0], 'off nominal')[0]['name'], 'Off Nominal') with self.assertRaises(SourceError): diff --git a/ui/apk_sources/github.py b/ui/apk_sources/github.py index 6c7516a..757bec2 100644 --- a/ui/apk_sources/github.py +++ b/ui/apk_sources/github.py @@ -31,9 +31,14 @@ def _api(path): def _entry(source, c, approved=True): + artwork = c.get('images') or {} + icon = artwork.get('icon') or c.get('icon') or 'https://github.com/' + c['repo'].split('/')[0] + '.png' + images = {'icon': icon, + 'banner': artwork.get('banner') or 'https://opengraph.githubassets.com/1/' + c['repo'], + 'screenshots': list(artwork.get('screenshots') or [])} return {'source': source['id'], 'id': c['repo'], 'package': None, - 'name': c['name'], 'summary': c.get('summary') or '', 'icon': None, - 'images': {'icon': None, 'banner': None, 'screenshots': []}, + 'name': c['name'], 'summary': c.get('summary') or '', 'icon': icon, + 'images': images, 'page': 'https://github.com/' + c['repo'], 'version': None, 'version_code': None, 'min_sdk': None, 'abis': None, 'vr': c.get('vr'), 'size': None, 'free': True if approved else None, 'license': c.get('license'), 'updated': None, @@ -55,8 +60,8 @@ def search(source, query, limit=50): for repo in data.get('items', []): c = curated.get(repo['full_name'].lower()) entry = _entry(source, c or {'repo': repo['full_name'], 'name': repo['name'], - 'summary': repo.get('description'), 'vr': True}, bool(c)) - # Repository owners' avatars are not app artwork. + 'summary': repo.get('description'), 'vr': True, + 'icon': (repo.get('owner') or {}).get('avatar_url')}, bool(c)) out.append(entry) return out words = query.lower().split() diff --git a/ui/apk_sources/github_curated.json b/ui/apk_sources/github_curated.json index 9f7fc14..113515e 100644 --- a/ui/apk_sources/github_curated.json +++ b/ui/apk_sources/github_curated.json @@ -2,28 +2,52 @@ { "repo": "KhronosGroup/OpenXR-SDK-Source", "name": "hello_xr", - "summary": "Khronos OpenXR sample (Vulkan and OpenGL ES)", + "summary": "A simple VR scene for checking that OpenXR graphics and controllers work.", "license": "Apache-2.0", "vr": true, "asset_pattern": "hello_xr-Vulkan-*.apk", - "allow_prerelease": false + "allow_prerelease": false, + "images": { + "icon": "https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/3ed64d0f9bb680f24b80a085091e5c8fab38f7b7/src/tests/hello_xr/android_resources/vulkan/mipmap-xxxhdpi/ic_helloxr_launcher.png", + "banner": "https://opengraph.githubassets.com/1/KhronosGroup/OpenXR-SDK-Source", + "screenshots": [] + }, + "icon": "https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK-Source/3ed64d0f9bb680f24b80a085091e5c8fab38f7b7/src/tests/hello_xr/android_resources/vulkan/mipmap-xxxhdpi/ic_helloxr_launcher.png" }, { "repo": "icosa-foundation/open-brush", "name": "Open Brush", - "summary": "Open source spatial painting", + "summary": "Paint in the air and create colorful 3D artwork in VR.", "license": "Apache-2.0", "vr": true, "asset_pattern": "*Quest*.apk", - "allow_prerelease": true + "allow_prerelease": true, + "images": { + "icon": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/Assets/Resources/DefaultImages/OpenBrushLogo.png", + "banner": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/open-brush.png", + "screenshots": [ + "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_785ea37d63378146dfe0f0ffa3f1d5c155ca978f.1920x1080.jpg", + "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_0a9c208e26a43cf34879c2ca361d4c8f18af8cba.1920x1080.jpg", + "https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/1634870/ss_19b25b86ef55c0d8769a65135d60eaae8fa40553.1920x1080.jpg" + ] + }, + "icon": "https://raw.githubusercontent.com/icosa-foundation/open-brush/56acbce831c7e9f257bfee9e21853da99773787b/Assets/Resources/DefaultImages/OpenBrushLogo.png" }, { "repo": "SgtBilko76/SuperTux-3D", "name": "SuperTux 3D", - "summary": "OpenXR stereoscopic platform game for Quest and PICO", + "summary": "Run and jump through Tux’s platform adventure on a layered 3D screen in VR.", "license": "GPL-3.0", "vr": true, "asset_pattern": "*Quest*.apk", - "allow_prerelease": true + "allow_prerelease": true, + "images": { + "icon": "https://raw.githubusercontent.com/SgtBilko76/SuperTux-3D/1955493ee6f1000e048c58db40d4904df827210e/data/images/engine/icons/supertux-256x256.png", + "banner": "https://www.supertux.org/images/0_7_0/github_preview.png", + "screenshots": [ + "https://www.supertux.org/images/0_7_0/github_preview.png" + ] + }, + "icon": "https://raw.githubusercontent.com/SgtBilko76/SuperTux-3D/1955493ee6f1000e048c58db40d4904df827210e/data/images/engine/icons/supertux-256x256.png" } ]