diff --git a/.github/workflows/update-icons.yaml b/.github/workflows/update-icons.yaml
deleted file mode 100644
index a821a2922c..0000000000
--- a/.github/workflows/update-icons.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
-name: Retrieve latest icons from excel file
-
-on:
- schedule:
- - cron: 0 0 */4 * *
- workflow_dispatch:
-
-jobs:
- load-icons:
- if: github.repository == 'Devolutions/UniGetUI'
- runs-on: windows-latest
-
- steps:
- - name: Checkout repository
- uses: actions/checkout@v6
-
- - name: Download icons json file
- run: |
- python -m pip install xlrd==1.2.0
- git config user.email "excelbot@github.com"
- git config user.name "Excel Bot"
- python scripts/generate_json_from_excel.py
- git commit WebBasedData/screenshot-database.json -m "Update icons and screenshots"
- exit 0
-
- - name: Prepare PR Body with Images
- id: prepare_pr_body
- run: |
- echo "## New Images" > pr_body.md
- Get-Content WebBasedData/new_urls.txt | ForEach-Object {
- if (![string]::IsNullOrWhiteSpace($_)) {
- Add-Content pr_body.md "
" -NoNewline
- }
- }
- - name: Create Pull Request
- uses: peter-evans/create-pull-request@v8
- with:
- delete-branch: true
- base: main
- title: "Update icons and screenshots from the excel file"
- reviewers: "${{ github.repository_owner }}"
- author: "Excel Bot "
- commit-message: "Update icons and screenshots from the excel file"
- branch: pull-request/update-icons-and-screenshots
- body-path: pr_body.md
-
-
-
diff --git a/.gitignore b/.gitignore
index 7f0e5891dc..3f0a9f6f34 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,7 +13,6 @@ vcredist.exe
unigetui_bin/
APIKEY.txt
*.pyc
-WebBasedData/screenshot_database.xlsx
WebBasedData/new_urls.txt
WebBasedData/screenshot-database.json.backup
pr_body.md
diff --git a/WebBasedData/screenshot-database-v2.json b/WebBasedData/screenshot-database-v2.json
index 8e37cc187b..93785af71d 100644
--- a/WebBasedData/screenshot-database-v2.json
+++ b/WebBasedData/screenshot-database-v2.json
@@ -1,8 +1,8 @@
{
"package_count": {
- "total": 12449,
- "done": 5962,
- "packages_with_icon": 5962,
+ "total": 12461,
+ "done": 5978,
+ "packages_with_icon": 5978,
"packages_with_screenshot": 1374,
"total_screenshots": 4246
},
@@ -115,7 +115,7 @@
"icon": "https://i.postimg.cc/k4w2mzVS/98217212.png",
"images": [
"https://i.postimg.cc/Vv6dLgMV/top10-domain.png",
- "https://i.postimg.cc/nrPMsmGc/top10-title.png\n"
+ "https://i.postimg.cc/nrPMsmGc/top10-title.png"
]
},
"1password": {
@@ -216,7 +216,7 @@
"3dmark": {
"icon": "https://i.ibb.co/zrwxXrq/download.png",
"images": [
- "http://cooltown2.hattara.fuma.fi/static/images/screenshots/ul-procyon-ai-inference-windows-result-part-1.png"
+ "https://cooltown2.hattara.fuma.fi/static/images/screenshots/ul-procyon-ai-inference-windows-result-part-1.png"
]
},
"3dxware-10": {
@@ -269,21 +269,21 @@
]
},
"4kstogram": {
- "icon": "http://static.4kdownload.com/main/img/logo/stogram-256.732811102182.png",
+ "icon": "https://static.4kdownload.com/main/img/logo/stogram-256.732811102182.png",
"images": [
"https://static.4kdownload.com/main/img/redesign/product-screenshots/cards/new-cards/stogram-card@1x.png",
"https://static.4kdownload.com/main/img/redesign-v2/products-page/stogram/download@1x.png"
]
},
"4ktokkit": {
- "icon": "http://static.4kdownload.com/main/img/logo/tokkit-512.c09c7b19c607.png",
+ "icon": "https://static.4kdownload.com/main/img/logo/tokkit-512.c09c7b19c607.png",
"images": [
"https://static.4kdownload.com/main/img/redesign/product-screenshots/cards/new-cards/tokkit-card@1x.png",
"https://static.4kdownload.com/main/img/redesign-v2/products-page/tokkit/download-tiktok-hashtag.07a06cb13a1b.png"
]
},
"4kvideodownloader": {
- "icon": "http://static.4kdownload.com/main/img/logo/videodownloader-512.7395df698c5e.png",
+ "icon": "https://static.4kdownload.com/main/img/logo/videodownloader-512.7395df698c5e.png",
"images": [
"https://static.4kdownload.com/main/img/redesign/product-screenshots/cards/new-cards/videodownloader-card@1x.png",
"https://static.4kdownload.com/main/img/redesign/product-screenshots/windows/videodownloader/playlist.png"
@@ -509,8 +509,8 @@
"abiword": {
"icon": "https://i.imgur.com/qnBf8D1.png",
"images": [
- "http://www.abisource.com/screenshots/abi-win32.thumb.jpg",
- "http://www.abisource.com/screenshots/abi-yiddish.thumb.jpg"
+ "https://www.abisource.com/screenshots/abi-win32.thumb.jpg",
+ "https://www.abisource.com/screenshots/abi-yiddish.thumb.jpg"
]
},
"abricotine": {
@@ -1085,7 +1085,7 @@
"icon": "https://www.irchelp.org/clients/windows/adiirc_logo_256p.png",
"images": [
"https://www.adiirc.com/images/xss.png.pagespeed.ic.goVp15eGFq.png",
- "http://i.imgur.com/R7PYXze.png",
+ "https://i.imgur.com/R7PYXze.png",
"https://www.irchelp.org/clients/windows/adiirc_screenshot_hackergreen.png",
"https://dev.adiirc.com/attachments/download/790/Monitoring_Panels_02.gif"
]
@@ -1814,7 +1814,7 @@
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/0/02/CD_icon_test.svg/1024px-CD_icon_test.svg.png",
"images": [
"https://www.snapfiles.com/screenfiles/aadownloader.png",
- "http://freewaregenius.com/wp-content/uploads/2008/06/album-art-downloader-screenshot3.jpg",
+ "https://freewaregenius.com/wp-content/uploads/2008/06/album-art-downloader-screenshot3.jpg",
"https://www.chip.de/ii/5/8/4/1/1/0/9/eaf373a13b0756b4.jpg",
"https://www.snapfiles.com/screenfiles/aadownloader2.png"
]
@@ -1842,11 +1842,11 @@
"images": []
},
"algodoo": {
- "icon": "http://www.algodoo.com/mainpage/wp-content/uploads/2013/03/algodoo_logo_algoryx_web.png",
+ "icon": "https://www.algodoo.com/mainpage/wp-content/uploads/2013/03/algodoo_logo_algoryx_web.png",
"images": [
- "http://www.algodoo.com/mainpage/wp-content/uploads/2013/03/algodoo_for_education_pyramid-300x187.png",
- "http://www.algodoo.com/mainpage/wp-content/uploads/2013/03/algodoo_for_education_start-300x187.png",
- "http://www.algodoo.com/mainpage/wp-content/uploads/2013/03/800px-New_algodoo.png"
+ "https://www.algodoo.com/mainpage/wp-content/uploads/2013/03/algodoo_for_education_pyramid-300x187.png",
+ "https://www.algodoo.com/mainpage/wp-content/uploads/2013/03/algodoo_for_education_start-300x187.png",
+ "https://www.algodoo.com/mainpage/wp-content/uploads/2013/03/800px-New_algodoo.png"
]
},
"aliae": {
@@ -2254,7 +2254,7 @@
]
},
"androidstudio-canary": {
- "icon": "http://i.imgur.com/GAcvIsP.png",
+ "icon": "https://i.imgur.com/GAcvIsP.png",
"images": [
"https://i.imgur.com/YjOIZFc.jpg",
"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC2X0sIY_AGvgi6jD8Eh_u8rOdZXKA6PP18tnJdA6jQxR-n4bF6vsIVI2D4FTOnHAlqSY5hJShEjHcRQr7P8QM-YyP3sM3Su_KxFRdBXhg8WUIoXr74luWfFvtgYGJHWdDe_gPnwpCsLR4YhE0U88QcSqrYs3LLjp7dGqQul_pRoerJr__-mD8lUPA/s1600/Android-IO22AndroidDevRecap_Social.png",
@@ -3102,7 +3102,7 @@
"arma3sync": {
"icon": "https://i.imgur.com/WxxNlan.png",
"images": [
- "http://i.imgur.com/Pq8nK5T.png",
+ "https://i.imgur.com/Pq8nK5T.png",
"https://wiki.tacticalteam.de/technik/armasync/armasync_repository_checkupdates.png",
"https://wiki.tacticalteam.de/technik/armasync/armasync_verzeichnis-repoauswahl.png",
"https://sasclan.org/sites/default/files/public/images/downloading-a3-modset-a3sync-3.png"
@@ -3371,7 +3371,7 @@
"images": []
},
"astrogrep": {
- "icon": "http://astrogrep.sourceforge.net/pics/AstroGrep_256x256.png",
+ "icon": "https://astrogrep.sourceforge.net/pics/AstroGrep_256x256.png",
"images": [
"https://a.fsdn.com/con/app/proj/astrogrep/screenshots/ss_main_new.png",
"https://www.portablefreeware.com/screenshots/scrLEgvyw.png",
@@ -3631,6 +3631,10 @@
"icon": "https://community.chocolatey.org/content/packageimages/authy-desktop.2.2.3.png",
"images": []
},
+ "auth-desktop": {
+ "icon": "https://raw.githubusercontent.com/ente-io/ente/refs/heads/main/mobile/apps/auth/assets/icons/auth-icon.png",
+ "images": []
+ },
"autoactions": {
"icon": "",
"images": []
@@ -3745,7 +3749,7 @@
"autopsy": {
"icon": "https://i.imgur.com/MNqvAXk.png",
"images": [
- "http://sleuthkit.org/autopsy/docs/user-docs/4.15.0/portable_case_original_version.png",
+ "https://sleuthkit.org/autopsy/docs/user-docs/4.15.0/portable_case_original_version.png",
"https://media.geeksforgeeks.org/wp-content/uploads/20200830111540/Screenshot8.png"
]
},
@@ -3841,14 +3845,14 @@
"images": []
},
"avisynth": {
- "icon": "http://www.avisynth.org/images/avisynth-logo-gears-mod.png",
+ "icon": "https://www.avisynth.org/images/avisynth-logo-gears-mod.png",
"images": [
"https://www.svp-team.com/w/images/3/3c/Mpchc-avsf.png",
"https://imag.malavida.com/mvimgbig/download-fs/avisynth-11914-1.jpg"
]
},
"avisynthplus": {
- "icon": "http://www.avisynth.org/images/avisynth-logo-gears-mod.png",
+ "icon": "https://www.avisynth.org/images/avisynth-logo-gears-mod.png",
"images": [
"https://www.svp-team.com/w/images/3/3c/Mpchc-avsf.png",
"https://imag.malavida.com/mvimgbig/download-fs/avisynth-11914-1.jpg"
@@ -3886,7 +3890,7 @@
"icon": "https://okkhor52.com/img/designer/001.png",
"images": [
"https://www.omicronlab.com/assets/images/landing/avro_600x337.jpg",
- "http://linux.omicronlab.com/images/screenshot.png",
+ "https://linux.omicronlab.com/images/screenshot.png",
"https://imag.malavida.com/mvimgbig/download-fs/avro-keyboard-22346-1.jpg"
]
},
@@ -4660,7 +4664,7 @@
]
},
"batchimageconverter": {
- "icon": "https://vovsoft.com/icons128/batch-image-converter.png",
+ "icon": "https://i2.wp.com/filecr.com/wp-content/uploads/2022/04/batch-image-converter-logo.png",
"images": [
"https://vovsoft.com/screenshots/batch-image-converter.png"
]
@@ -4813,12 +4817,12 @@
]
},
"battlepainters": {
- "icon": "http://www.saitogames.com/_images/painters_logo.gif",
+ "icon": "https://www.saitogames.com/_images/painters_logo.gif",
"images": [
- "http://www.saitogames.com/_images/painters_screen01.gif",
- "http://www.saitogames.com/_images/painters_screen02.gif",
- "http://www.saitogames.com/_images/painters_screen03.gif",
- "http://www.saitogames.com/_images/painters_screen04.gif"
+ "https://www.saitogames.com/_images/painters_screen01.gif",
+ "https://www.saitogames.com/_images/painters_screen02.gif",
+ "https://www.saitogames.com/_images/painters_screen03.gif",
+ "https://www.saitogames.com/_images/painters_screen04.gif"
]
},
"battoexeconverter": {
@@ -11789,6 +11793,10 @@
"icon": "https://raw.githubusercontent.com/dotnetcore-chocolatey/dotnetcore-chocolateypackages/master/icons/dotnetcore.png",
"images": []
},
+ "dotnet-desktopruntime-10": {
+ "icon": "https://i.ibb.co/sdzNm4Dp/image.png",
+ "images": []
+ },
"dotnet-desktopruntime-3_1": {
"icon": "https://raw.githubusercontent.com/dotnetcore-chocolatey/dotnetcore-chocolateypackages/master/icons/dotnetcore.png",
"images": []
@@ -11806,11 +11814,11 @@
"images": []
},
"dotnet-desktopruntime-8": {
- "icon": "https://raw.githubusercontent.com/dotnetcore-chocolatey/dotnetcore-chocolateypackages/master/icons/dotnetcore.png",
+ "icon": "https://i.ibb.co/sdzNm4Dp/image.png",
"images": []
},
"dotnet-desktopruntime-9": {
- "icon": "https://raw.githubusercontent.com/dotnetcore-chocolatey/dotnetcore-chocolateypackages/master/icons/dotnetcore.png",
+ "icon": "https://i.ibb.co/sdzNm4Dp/image.png",
"images": []
},
"dotnet-desktopruntime-pr\u2026": {
@@ -15497,13 +15505,13 @@
"images": []
},
"fontviewok": {
- "icon": "https://i.postimg.cc/5NSK9JZ4/Font-View-OK-ico.png\n",
+ "icon": "https://i.postimg.cc/5NSK9JZ4/Font-View-OK-ico.png",
"images": [
"https://i.postimg.cc/SNf1Q4wx/Font-View-OK.png"
]
},
"foobar2000": {
- "icon": "https://i.postimg.cc/3NV8dpy4/foobar2000-ico.png\n",
+ "icon": "https://i.postimg.cc/3NV8dpy4/foobar2000-ico.png",
"images": [
"https://i.postimg.cc/PJ9XPZLC/foobar2000-1.png",
"https://i.postimg.cc/0jFkbm6d/foobar2000-3.png",
@@ -16106,7 +16114,7 @@
"images": []
},
"fxsound": {
- "icon": "",
+ "icon": "https://i.ibb.co/0Rx00jW6/image.png",
"images": []
},
"fysty": {
@@ -16576,10 +16584,6 @@
"icon": "",
"images": []
},
- "gettext": {
- "icon": "",
- "images": []
- },
"getuserinfo": {
"icon": "https://community.chocolatey.org/content/packageimages/getuserinfo.2.07.00.png",
"images": []
@@ -26263,7 +26267,7 @@
"https://i.postimg.cc/TPMfctVN/mkvtoolnix-gui-official-screenshot.png"
]
},
- "mlocati-gettext": {
+ "Winget.mlocati.GetText": {
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Heckert_GNU_white.svg/960px-Heckert_GNU_white.svg.png",
"images": [
"https://i.imgur.com/KnVZQsT.png"
@@ -29474,7 +29478,7 @@
"obs-amd-encoder": {
"icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/OBS_Studio_Logo.svg/768px-OBS_Studio_Logo.svg.png",
"images": [
- "http://i.imgur.com/Fferqsd.png"
+ "https://i.imgur.com/Fferqsd.png"
]
},
"obs-asio": {
@@ -30573,7 +30577,7 @@
"images": []
},
"optionsplus": {
- "icon": "http://www.logitech.com/assets/66208/optionsplusicon.png",
+ "icon": "https://www.logitech.com/assets/66208/optionsplusicon.png",
"images": []
},
"optipng": {
@@ -31735,7 +31739,7 @@
"images": []
},
"peazip": {
- "icon": "https://www.iconarchive.com/download/i106134/papirus-team/papirus-apps/peazip.512.png",
+ "icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Peazip_ico.svg/256px-Peazip_ico.svg.png",
"images": [
"https://i.postimg.cc/Qxybz51V/image.png",
"https://i.postimg.cc/DyXJ2bRS/image.png",
@@ -39575,7 +39579,7 @@
"icon": "https://community.chocolatey.org/content/packageimages/sonicvisualiser.4.5.1.png",
"images": []
},
- "sonicwall-netextender\n": {
+ "sonicwall-netextender": {
"icon": "https://paulstsmith.github.io/images/netextender-icon.png",
"images": [
"https://paulstsmith.github.io/images/netextender-screenshot-connected.png",
@@ -41593,10 +41597,10 @@
"switchoff": {
"icon": "https://www.softwarecrew.com/wp-content/uploads/2013/03/AiryTecSwitchOffLogo200-175.png",
"images": [
- "http://www.airytec.com/images/en/screenshots/schedule.png",
- "http://www.airytec.com/images/en/screenshots/one-click.png",
- "http://www.airytec.com/images/en/screenshots/web-interface.png",
- "http://www.airytec.com/images/en/screenshots/notifyicon.png"
+ "https://www.airytec.com/images/en/screenshots/schedule.png",
+ "https://www.airytec.com/images/en/screenshots/one-click.png",
+ "https://www.airytec.com/images/en/screenshots/web-interface.png",
+ "https://www.airytec.com/images/en/screenshots/notifyicon.png"
]
},
"swmm": {
@@ -41983,7 +41987,7 @@
"images": []
},
"tailscale": {
- "icon": "https://community.chocolatey.org/content/packageimages/tailscale.1.62.1.png",
+ "icon": "https://tailscale.com/favicon.png",
"images": []
},
"tailwindcss": {
@@ -44576,7 +44580,7 @@
"images": []
},
"unchecky": {
- "icon": "http://unchecky.com/assets/img/fb-unchecky-logo.png",
+ "icon": "https://unchecky.com/assets/img/fb-unchecky-logo.png",
"images": [
"https://rammichael.com/wp-content/uploads/2014/01/unchecky_0_2.png"
]
@@ -47001,11 +47005,11 @@
"images": []
},
"vncserver": {
- "icon": "https://www.pacisoft.vn/wp-content/uploads/2018/10/vnc-viewer-logo.png ",
+ "icon": "https://www.pacisoft.vn/wp-content/uploads/2018/10/vnc-viewer-logo.png",
"images": []
},
"vncviewer": {
- "icon": "https://www.pacisoft.vn/wp-content/uploads/2018/10/vnc-viewer-logo.png ",
+ "icon": "https://www.pacisoft.vn/wp-content/uploads/2018/10/vnc-viewer-logo.png",
"images": []
},
"vnote": {
@@ -47801,10 +47805,6 @@
"icon": "",
"images": []
},
- "": {
- "icon": "",
- "images": []
- },
"vsts-admin-tools": {
"icon": "https://community.chocolatey.org/content/packageimages/vsts-admin-tools.1.0.12.png",
"images": []
@@ -48385,7 +48385,7 @@
]
},
"wechat-work": {
- "icon": "http://dldir1.qq.com/foxmail/icon/work_logo.png",
+ "icon": "https://dldir1.qq.com/foxmail/icon/work_logo.png",
"images": []
},
"wechatdevtools": {
@@ -48396,7 +48396,7 @@
]
},
"wecom": {
- "icon": "http://dldir1.qq.com/foxmail/icon/work_logo.png",
+ "icon": "https://dldir1.qq.com/foxmail/icon/work_logo.png",
"images": [
"https://res.wx.qq.com/wxdoc/dist/assets/img/application2.803df32d.png",
"https://wwcdn.weixin.qq.com/node/wework/images/wecom-temp-f9247fc9ed69949f3e3cd39480410738.4145f338c0.png"
@@ -52107,7 +52107,7 @@
"https://i.postimg.cc/PrB58zRQ/image.png"
]
},
- "gs-base\n": {
+ "gs-base": {
"icon": "https://i.postimg.cc/hvFwcvjg/gsbase.png",
"images": [
"https://i.postimg.cc/NMyqQZk8/image.png",
@@ -52775,7 +52775,7 @@
"https://i.postimg.cc/4ydqc4Lk/image.png"
]
},
- "xmlviewer ": {
+ "xmlviewer": {
"icon": "https://i.postimg.cc/PxBVCbwZ/xmlv.png",
"images": [
"https://i.postimg.cc/1RGr0Zxb/image.png",
@@ -52813,7 +52813,7 @@
"icon": "https://i.postimg.cc/MH2CsdkZ/dwgseepro.png",
"images": []
},
- "smbiosexplorer ": {
+ "smbiosexplorer": {
"icon": "https://i.postimg.cc/SRRMv7c5/smbe.png",
"images": [
"https://i.postimg.cc/tCRS9LzQ/image.png",
@@ -52832,6 +52832,10 @@
"https://i.postimg.cc/1Xyfk0wP/image.png"
]
},
+ "wise-registry-cleaner": {
+ "icon": "https://i.ibb.co/VYZ84Rt5/image.png",
+ "images": []
+ },
"wiseforcedeleter": {
"icon": "https://i.postimg.cc/T2rt6RM3/image.png",
"images": [
@@ -52839,7 +52843,7 @@
"https://i.postimg.cc/FHBXdwb8/image.png"
]
},
- "wisejetsearch\n": {
+ "wisejetsearch": {
"icon": "https://i.postimg.cc/VN2wT2dn/image.png",
"images": [
"https://i.postimg.cc/jqHTggKn/image.png",
@@ -53365,13 +53369,13 @@
"https://i.postimg.cc/G25hW8Nx/image.png"
]
},
- "Winget.SoftPerfect.WiFiGuard\n": {
+ "Winget.SoftPerfect.WiFiGuard": {
"icon": "https://i.postimg.cc/5Nxbsm6W/image.png",
"images": [
"https://i.postimg.cc/43hgYLFm/image.png"
]
},
- "Winget.SoftPerfect.NetworkScanner\n": {
+ "Winget.SoftPerfect.NetworkScanner": {
"icon": "https://i.postimg.cc/brmMCkF7/image.png",
"images": [
"https://i.postimg.cc/Fsn80bS3/image.png",
@@ -53425,7 +53429,7 @@
"https://i.postimg.cc/6pFPQ235/image.png"
]
},
- "hibernateenableordisable\n": {
+ "hibernateenableordisable": {
"icon": "https://i.postimg.cc/BvrtNgvf/hibernate.png",
"images": [
"https://i.postimg.cc/V6fWsmXp/image.png",
@@ -54992,18 +54996,6 @@
"https://i.postimg.cc/5Ncf02Fk/image.png"
]
},
- "calendartask ": {
- "icon": "https://i.postimg.cc/qq326gN9/image.png",
- "images": [
- "https://i.postimg.cc/sxQ1vSZr/image.png",
- "https://i.postimg.cc/bwHrDqdw/image.png",
- "https://i.postimg.cc/XvsvLxfb/image.png",
- "https://i.postimg.cc/d3ytqYgz/image.png",
- "https://i.postimg.cc/ry3Jy2DJ/image.png",
- "https://i.postimg.cc/sgR4SP9J/image.png",
- "https://i.postimg.cc/nLFKSHWB/image.png"
- ]
- },
"multiping": {
"icon": "https://i.postimg.cc/fTgJQMkh/multiping-icon.png",
"images": [
@@ -55012,7 +55004,7 @@
]
},
"musehub": {
- "icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/MuseHub_Logo.svg/512px-MuseHub_Logo.svg.png ",
+ "icon": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4b/MuseHub_Logo.svg/512px-MuseHub_Logo.svg.png",
"images": []
},
"antigravity": {
@@ -55047,11 +55039,11 @@
"icon": "https://upload.wikimedia.org/wikipedia/commons/e/e4/Robot-framework-logo.png",
"images": []
},
- "Logitech.LogiTune\n": {
+ "Logitech.LogiTune": {
"icon": "https://imgs.search.brave.com/ASjRKcLSMy8TMcfmAKV4Hte2F6IJZNgqHnCwlZv_jJw/rs:fit:860:0:0:0/g:ce/aHR0cHM6Ly93d3cu/bG9naXRlY2guY29t/L2Fzc2V0cy82NTY4/Ni8yL2xvZ2ktdHVu/ZS1hcHAucG5n",
"images": []
},
- " OpenVPNTechnologies.OpenVPN\n": {
+ "OpenVPNTechnologies.OpenVPN": {
"icon": "https://postimg.cc/2LQGvMgm",
"images": []
},
@@ -55090,7 +55082,7 @@
"icon": "https://i.ibb.co/VWMLqzbN/icon.png",
"images": [
"https://i.ibb.co/XZgvrZDt/s1.png",
- "https://i.ibb.co/RT4Qpvpj/s2.png ",
+ "https://i.ibb.co/RT4Qpvpj/s2.png",
"https://i.ibb.co/S40DxfM4/s3.png"
]
},
@@ -55099,7 +55091,7 @@
"images": []
},
"setuptools": {
- "icon": "https://imgur.com/a/E92OhoW",
+ "icon": "https://i.ibb.co/67ZyXb6J/logo-over-white.png",
"images": []
},
"requests": {
@@ -55179,14 +55171,14 @@
]
},
"click": {
- "icon": "https://postimg.cc/21WxJpxY",
+ "icon": "https://i.ibb.co/9H19kg1F/click-logo-1.png",
"images": []
},
"lin-ycv.EverythingCmdPal3": {
"icon": "https://store-images.s-microsoft.com/image/apps.25263.13565088367537471.1afd4527-e5f2-4286-ae89-167b4ff891b1.6b1f6c28-e687-4873-982a-ddf8c246ba6e?h=115",
"images": []
},
- "Python.Launcher\n": {
+ "Python.Launcher": {
"icon": "https://s3.dualstack.us-east-2.amazonaws.com/pythondotorg-assets/media/community/logos/python-logo-only.png",
"images": []
},
@@ -55250,6 +55242,46 @@
"pydantic_core": {
"icon": "https://opensource.muenchen.de/logo/pydantic.png",
"images": []
+ },
+ "winget-cli": {
+ "icon": "https://postimg.cc/LqxRtqj8",
+ "images": []
+ },
+ "dotnetfx": {
+ "icon": "https://community.chocolatey.org/content/packageimages/netfx-4.7.2.4.7.2.0.png",
+ "images": []
+ },
+ "dotnetcore": {
+ "icon": "https://community.chocolatey.org/content/packageimages/dotnetcore.3.1.32.png",
+ "images": []
+ },
+ "KB2919442": {
+ "icon": "https://community.chocolatey.org/content/packageimages/KB2919442.1.0.20160915.png",
+ "images": []
+ },
+ "KB2919355": {
+ "icon": "https://community.chocolatey.org/content/packageimages/KB2919442.1.0.20160915.png",
+ "images": []
+ },
+ "KB3033929": {
+ "icon": "https://community.chocolatey.org/content/packageimages/KB2919442.1.0.20160915.png",
+ "images": []
+ },
+ "KB3035131": {
+ "icon": "https://community.chocolatey.org/content/packageimages/KB2919442.1.0.20160915.png",
+ "images": []
+ },
+ "KB3063858": {
+ "icon": "https://community.chocolatey.org/content/packageimages/KB2919442.1.0.20160915.png",
+ "images": []
+ },
+ "microsoft-ui-xaml-2-7": {
+ "icon": "https://community.chocolatey.org/content/packageimages/microsoft-ui-xaml-2-7.2.7.3.png",
+ "images": []
+ },
+ "microsoft-vclibs-140-00": {
+ "icon": "https://community.chocolatey.org/content/packageimages/KB2919442.1.0.20160915.png",
+ "images": []
}
}
-}
\ No newline at end of file
+}
diff --git a/WebBasedData/screenshot_database.xlsx b/WebBasedData/screenshot_database.xlsx
new file mode 100644
index 0000000000..66b6658ba8
Binary files /dev/null and b/WebBasedData/screenshot_database.xlsx differ
diff --git a/WebBasedData/test_urls.py b/WebBasedData/test_urls.py
deleted file mode 100644
index aa5e6d57a7..0000000000
--- a/WebBasedData/test_urls.py
+++ /dev/null
@@ -1,45 +0,0 @@
-import requests, time
-
-try:
- import os, sys, json
-
- urls = []
-
- #with open("invalid_urls.txt", "w") as f:
- # f.write("")
-
- with open("screenshot-database-v2.json") as f:
- data = json.load(f)
- for package in data["icons_and_screenshots"]:
- try:
- if package <= "nosqlworkbench":
- continue
- if data["icons_and_screenshots"][package]["icon"] != "":
- print("Package:", package, data["icons_and_screenshots"][package]["icon"])
- response = requests.get(data["icons_and_screenshots"][package]["icon"])
- if response.status_code == 404:
- print("Package failed:", package, data["icons_and_screenshots"][package]["icon"])
- with open("invalid_urls.txt", "a") as f:
- f.write(data["icons_and_screenshots"][package]["icon"] + "\n")
- elif response.status_code not in (200, 403):
- print(response.status_code, "failed for:", data["icons_and_screenshots"][package]["icon"])
-
- except requests.exceptions.ConnectionError:
- time.sleep(0.1)
- try:
- if data["icons_and_screenshots"][package]["icon"] != "":
- response = requests.get(data["icons_and_screenshots"][package]["icon"])
- if response.status_code in (403, 404):
- print("Package failed:", package, data["icons_and_screenshots"][package]["icon"])
- elif response.status_code != 200:
- response = requests.get(data["icons_and_screenshots"][package]["icon"])
- if response.status_code != 200:
- print("Failed to resolve DNS for:", data["icons_and_screenshots"][package]["icon"])
- except requests.exceptions.ConnectionError as e:
- print(type(e))
-
-
-
-except Exception as e:
- print(e)
-os.system("pause")
diff --git a/scripts/generate_json_from_excel.py b/scripts/generate_json_from_excel.py
deleted file mode 100644
index 69b138b1f5..0000000000
--- a/scripts/generate_json_from_excel.py
+++ /dev/null
@@ -1,126 +0,0 @@
-import json
-import os
-from urllib.request import urlopen
-import re
-
-import xlrd
-
-root_dir = os.path.join(os.path.dirname(__file__), "..")
-os.chdir(os.path.join(root_dir, "WebBasedData"))
-
-
-with open("screenshot_database.xlsx", "wb") as f:
- f.write(urlopen("https://docs.google.com/spreadsheets/d/1Zxgzs1BiTZipC7EiwNEb9cIchistIdr5/export?format=xlsx").read())
-
-try:
- workbook = xlrd.open_workbook('screenshot_database.xlsx')
-except:
- os.system("python -m pip install xlrd==1.2.0")
- import xlrd
- workbook = xlrd.open_workbook('screenshot_database.xlsx')
-
-worksheet = workbook.sheet_by_index(0)
-
-jsoncontent = {
- "package_count": {
- "total": 0,
- "done": 0,
- "packages_with_icon": 0,
- "packages_with_screenshot": 0,
- "total_screenshots": 0,
- },
- "icons_and_screenshots": {},
-}
-
-with open("invalid_urls.txt", "r") as f:
- forbidden_string = f.read().split("\n")
-
-totalCount = 0
-doneCount = 0
-packagesWithIcon = 0
-packagesWithScreenshot = 0
-screenshotCount = 0
-arrivedAtTheEnd = False
-i = 1
-while not arrivedAtTheEnd:
- try:
- data = [worksheet.cell_value(i, 0), worksheet.cell_value(i, 1), []]
- if len(worksheet.row_values(i)) >= 3:
- packagesWithScreenshot += 1
- j = 2
- while j < len(worksheet.row_values(i)):
- if worksheet.cell_value(i, j) is None or worksheet.cell_value(i, j) == "":
- if j == 2:
- packagesWithScreenshot -= 1
- break
- data[2].append(worksheet.cell_value(i, j))
- screenshotCount += 1
- j += 1
- if j > 23:
- break
- assert (type(data) == list)
- assert (len(data) == 3)
- try:
- assert (type(data[0]) == str)
- except AssertionError as e:
- if data[0] == 115.0:
- data[0] = "115"
- else:
- raise e
- assert (type(data[1]) == str)
- assert (type(data[2]) == list)
- if data[1] != "":
- if(data[1] in forbidden_string):
- data[1] = ""
- else:
- doneCount += 1
- packagesWithIcon += 1
-
- if not data[0] in jsoncontent["icons_and_screenshots"].keys():
- jsoncontent["icons_and_screenshots"][data[0]] = {
- "icon": data[1],
- "images": data[2]
- }
- else:
- jsoncontent["icons_and_screenshots"][data[0]] = {
- "icon": data[1] if jsoncontent["icons_and_screenshots"][data[0]]["icon"] == "" else jsoncontent["icons_and_screenshots"][data[0]]["icon"],
- "images": data[2] if jsoncontent["icons_and_screenshots"][data[0]]["images"] == [] else jsoncontent["icons_and_screenshots"][data[0]]["images"]
- }
- totalCount += 1
- i += 1
- except IndexError as e:
- arrivedAtTheEnd = True
-
-jsoncontent["package_count"]["total"] = totalCount
-jsoncontent["package_count"]["done"] = doneCount
-jsoncontent["package_count"]["packages_with_icon"] = packagesWithIcon
-jsoncontent["package_count"]["packages_with_screenshot"] = packagesWithScreenshot
-jsoncontent["package_count"]["total_screenshots"] = screenshotCount
-
-oldcontent = ""
-newcontent = ""
-
-FILE = "screenshot-database-v2.json"
-
-if os.path.exists(FILE):
- with open(FILE, "r") as infile:
- oldcontent = infile.read()
- # Extract URLs from oldcontent for proper comparison
- old_urls = re.findall(r'https?://[^\s",]+', oldcontent)
-else:
- old_urls = []
-
-with open(FILE, "w") as outfile:
- newcontent = json.dumps(jsoncontent, indent=4)
- outfile.write(newcontent)
-
-new_urls = []
-# Find all URLs in newcontent
-new_urls = re.findall(r'https?://[^\s",]+', newcontent)
-diff_urls = [url for url in new_urls if url not in old_urls]
-
-with open("new_urls.txt", "w") as f:
- for url in diff_urls:
- f.write(url + "\n")
-
-os.system("pause")
diff --git a/scripts/update-icons.ps1 b/scripts/update-icons.ps1
new file mode 100644
index 0000000000..a6b4354da7
--- /dev/null
+++ b/scripts/update-icons.ps1
@@ -0,0 +1,850 @@
+#!/usr/bin/env pwsh
+<#
+.SYNOPSIS
+ Exports or validates the icon database maintained in WebBasedData.
+
+.DESCRIPTION
+ Reads the checked-in screenshot_database.xlsx workbook directly through ZIP/XML APIs,
+ regenerates screenshot-database-v2.json, and can validate icon URLs from the JSON output.
+
+.EXAMPLE
+ ./scripts/update-icons.ps1
+
+ Regenerates WebBasedData/screenshot-database-v2.json and WebBasedData/new_urls.txt.
+
+.EXAMPLE
+ ./scripts/update-icons.ps1 -Validate -MaxPackages 25
+
+ Validates the first 25 icon URLs from screenshot-database-v2.json without mutating invalid_urls.txt.
+
+.EXAMPLE
+ ./scripts/update-icons.ps1 -Validate -AppendInvalidUrls
+
+ Validates icon URLs and appends any 404 URLs to WebBasedData/invalid_urls.txt.
+#>
+
+[CmdletBinding(DefaultParameterSetName = 'Export')]
+param(
+ [Parameter(ParameterSetName = 'Export')]
+ [switch] $Export,
+
+ [Parameter(ParameterSetName = 'Validate')]
+ [switch] $Validate,
+
+ [string] $WorkbookPath = 'WebBasedData/screenshot_database.xlsx',
+ [string] $JsonPath = 'WebBasedData/screenshot-database-v2.json',
+ [string] $InvalidUrlsPath = 'WebBasedData/invalid_urls.txt',
+ [string] $NewUrlsPath = 'WebBasedData/new_urls.txt',
+ [int] $MaxScreenshotsPerPackage = 23,
+ [switch] $AppendInvalidUrls,
+ [int] $MaxPackages,
+ [int] $MaxRetries = 2,
+ [int] $RetryDelayMilliseconds = 200,
+ [int] $RequestTimeoutSeconds = 15
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+Add-Type -AssemblyName System.IO.Compression
+Add-Type -AssemblyName System.IO.Compression.FileSystem
+Add-Type -TypeDefinition @"
+using System.Collections.Generic;
+
+namespace UniGetUI.IconTools
+{
+ public sealed class IconDatabaseDocument
+ {
+ public IconDatabaseDocument()
+ {
+ package_count = new PackageCount();
+ icons_and_screenshots = new Dictionary(System.StringComparer.Ordinal);
+ }
+
+ public PackageCount package_count { get; set; }
+
+ public Dictionary icons_and_screenshots { get; set; }
+ }
+
+ public sealed class PackageCount
+ {
+ public int total { get; set; }
+
+ public int done { get; set; }
+
+ public int packages_with_icon { get; set; }
+
+ public int packages_with_screenshot { get; set; }
+
+ public int total_screenshots { get; set; }
+ }
+
+ public sealed class PackageEntry
+ {
+ public PackageEntry()
+ {
+ icon = string.Empty;
+ images = new List();
+ }
+
+ public string icon { get; set; }
+
+ public List images { get; set; }
+ }
+}
+"@
+
+$RepoRoot = [System.IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..'))
+$OpenXmlRelationshipNamespace = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships'
+$OpenXmlPackageRelationshipNamespace = 'http://schemas.openxmlformats.org/package/2006/relationships'
+
+if (-not $PSBoundParameters.ContainsKey('Export') -and -not $PSBoundParameters.ContainsKey('Validate')) {
+ $Export = $true
+}
+
+function Resolve-RepoPath {
+ param(
+ [Parameter(Mandatory)]
+ [string] $Path
+ )
+
+ if ([System.IO.Path]::IsPathRooted($Path)) {
+ return [System.IO.Path]::GetFullPath($Path)
+ }
+
+ return [System.IO.Path]::GetFullPath((Join-Path $RepoRoot $Path))
+}
+
+function Get-UrlRegex {
+ return [regex] 'https?://[^\s",]+'
+}
+
+function Get-UrlsFromText {
+ param(
+ [string] $Text
+ )
+
+ if ([string]::IsNullOrEmpty($Text)) {
+ return @()
+ }
+
+ $urlRegex = Get-UrlRegex
+ return @($urlRegex.Matches($Text) | ForEach-Object { $_.Value })
+}
+
+function ConvertFrom-JsonEscapedString {
+ param(
+ [Parameter(Mandatory)]
+ [string] $Value
+ )
+
+ $jsonString = '"' + $Value.Replace('\', '\\').Replace('"', '\"') + '"'
+ return [System.Text.Json.JsonSerializer]::Deserialize[string]($jsonString)
+}
+
+function ConvertTo-AsciiEscapedJson {
+ param(
+ [Parameter(Mandatory)]
+ [AllowEmptyString()]
+ [string] $Value
+ )
+
+ $builder = [System.Text.StringBuilder]::new($Value.Length)
+ foreach ($character in $Value.ToCharArray()) {
+ if ([int] $character -le 127) {
+ [void] $builder.Append($character)
+ }
+ else {
+ [void] $builder.AppendFormat('\u{0:x4}', [int] $character)
+ }
+ }
+
+ return $builder.ToString()
+}
+
+function Get-UrlsFromIconDatabaseFile {
+ param(
+ [Parameter(Mandatory)]
+ [string] $Path
+ )
+
+ if (-not (Test-Path -LiteralPath $Path)) {
+ return @()
+ }
+
+ $urls = New-Object System.Collections.Generic.List[string]
+ $jsonDocument = [System.Text.Json.JsonDocument]::Parse([System.IO.File]::ReadAllText($Path))
+ try {
+ $iconsAndScreenshots = $jsonDocument.RootElement.GetProperty('icons_and_screenshots')
+
+ foreach ($package in $iconsAndScreenshots.EnumerateObject()) {
+ $iconUrl = $package.Value.GetProperty('icon').GetString()
+ if (-not [string]::IsNullOrWhiteSpace($iconUrl)) {
+ $urls.Add($iconUrl)
+ }
+
+ foreach ($image in $package.Value.GetProperty('images').EnumerateArray()) {
+ $imageUrl = $image.GetString()
+ if (-not [string]::IsNullOrWhiteSpace($imageUrl)) {
+ $urls.Add($imageUrl)
+ }
+ }
+ }
+ }
+ finally {
+ $jsonDocument.Dispose()
+ }
+
+ return (, $urls.ToArray())
+}
+
+function Write-Utf8File {
+ param(
+ [Parameter(Mandatory)]
+ [string] $Path,
+
+ [Parameter(Mandatory)]
+ [AllowEmptyString()]
+ [string] $Content
+ )
+
+ $encoding = [System.Text.UTF8Encoding]::new($false)
+ [System.IO.File]::WriteAllText($Path, $Content, $encoding)
+}
+
+function Read-ZipEntryText {
+ param(
+ [Parameter(Mandatory)]
+ [System.IO.Compression.ZipArchive] $Archive,
+
+ [Parameter(Mandatory)]
+ [string] $EntryPath
+ )
+
+ $entry = $Archive.GetEntry($EntryPath)
+ if ($null -eq $entry) {
+ throw "Zip entry not found: $EntryPath"
+ }
+
+ $reader = [System.IO.StreamReader]::new($entry.Open())
+ try {
+ return $reader.ReadToEnd()
+ }
+ finally {
+ $reader.Dispose()
+ }
+}
+
+function New-XmlNamespaceManager {
+ param(
+ [Parameter(Mandatory)]
+ [xml] $Document
+ )
+
+ $manager = [System.Xml.XmlNamespaceManager]::new($Document.NameTable)
+ $manager.AddNamespace('x', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main')
+ $manager.AddNamespace('r', $OpenXmlRelationshipNamespace)
+ $manager.AddNamespace('pr', $OpenXmlPackageRelationshipNamespace)
+ return (, $manager)
+}
+
+function Resolve-OpenXmlPath {
+ param(
+ [Parameter(Mandatory)]
+ [string] $BasePath,
+
+ [Parameter(Mandatory)]
+ [string] $TargetPath
+ )
+
+ $baseUri = [System.Uri]::new("https://openxml.local/$BasePath")
+ $resolvedUri = [System.Uri]::new($baseUri, $TargetPath)
+ return $resolvedUri.AbsolutePath.TrimStart('/')
+}
+
+function Convert-SharedStringNodeToText {
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNode] $Node,
+
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNamespaceManager] $NamespaceManager
+ )
+
+ $textNodes = $Node.SelectNodes('.//x:t', $NamespaceManager)
+ if ($null -eq $textNodes -or $textNodes.Count -eq 0) {
+ return ''
+ }
+
+ $builder = [System.Text.StringBuilder]::new()
+ foreach ($textNode in $textNodes) {
+ [void] $builder.Append($textNode.InnerText)
+ }
+
+ return $builder.ToString()
+}
+
+function Get-SharedStrings {
+ param(
+ [Parameter(Mandatory)]
+ [System.IO.Compression.ZipArchive] $Archive
+ )
+
+ $entry = $Archive.GetEntry('xl/sharedStrings.xml')
+ if ($null -eq $entry) {
+ return @()
+ }
+
+ [xml] $document = Read-ZipEntryText -Archive $Archive -EntryPath 'xl/sharedStrings.xml'
+ $namespaceManager = New-XmlNamespaceManager -Document $document
+ $items = $document.SelectNodes('/x:sst/x:si', $namespaceManager)
+ $sharedStrings = [System.Collections.Generic.List[string]]::new()
+
+ foreach ($item in $items) {
+ $sharedStrings.Add((Convert-SharedStringNodeToText -Node $item -NamespaceManager $namespaceManager))
+ }
+
+ return (, $sharedStrings.ToArray())
+}
+
+function Get-FirstWorksheetPath {
+ param(
+ [Parameter(Mandatory)]
+ [System.IO.Compression.ZipArchive] $Archive
+ )
+
+ [xml] $workbookDocument = Read-ZipEntryText -Archive $Archive -EntryPath 'xl/workbook.xml'
+ $workbookNamespaceManager = New-XmlNamespaceManager -Document $workbookDocument
+ $firstSheet = $workbookDocument.SelectSingleNode('/x:workbook/x:sheets/x:sheet[1]', $workbookNamespaceManager)
+ if ($null -eq $firstSheet) {
+ throw 'Workbook does not contain any worksheets.'
+ }
+
+ $relationshipId = $firstSheet.GetAttribute('id', $OpenXmlRelationshipNamespace)
+ if ([string]::IsNullOrWhiteSpace($relationshipId)) {
+ throw 'First worksheet relationship id is missing from workbook.xml.'
+ }
+
+ [xml] $relationshipDocument = Read-ZipEntryText -Archive $Archive -EntryPath 'xl/_rels/workbook.xml.rels'
+ $relationshipNamespaceManager = New-XmlNamespaceManager -Document $relationshipDocument
+ $sheetRelationship = $relationshipDocument.SelectSingleNode(
+ "/pr:Relationships/pr:Relationship[@Id='$relationshipId']",
+ $relationshipNamespaceManager
+ )
+
+ if ($null -eq $sheetRelationship) {
+ throw "Workbook relationship not found for worksheet id '$relationshipId'."
+ }
+
+ return Resolve-OpenXmlPath -BasePath 'xl/workbook.xml' -TargetPath $sheetRelationship.Attributes['Target'].Value
+}
+
+function Convert-ExcelColumnNameToIndex {
+ param(
+ [Parameter(Mandatory)]
+ [string] $ColumnName
+ )
+
+ $index = 0
+ foreach ($character in $ColumnName.ToCharArray()) {
+ if ($character -lt 'A' -or $character -gt 'Z') {
+ throw "Invalid Excel column name: $ColumnName"
+ }
+
+ $index = ($index * 26) + ([int] $character - [int] [char] 'A' + 1)
+ }
+
+ return $index - 1
+}
+
+function Get-SpreadsheetCellColumnIndex {
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNode] $CellNode
+ )
+
+ $reference = $CellNode.Attributes['r'].Value
+ $columnName = [regex]::Match($reference, '^[A-Z]+').Value
+ if ([string]::IsNullOrWhiteSpace($columnName)) {
+ throw "Could not resolve column name from cell reference '$reference'."
+ }
+
+ return Convert-ExcelColumnNameToIndex -ColumnName $columnName
+}
+
+function Get-SpreadsheetCellValue {
+ param(
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNode] $CellNode,
+
+ [Parameter(Mandatory)]
+ [string[]] $SharedStrings,
+
+ [Parameter(Mandatory)]
+ [System.Xml.XmlNamespaceManager] $NamespaceManager
+ )
+
+ $type = if ($CellNode.Attributes['t']) { $CellNode.Attributes['t'].Value } else { '' }
+ $valueNode = $CellNode.SelectSingleNode('x:v', $NamespaceManager)
+
+ switch ($type) {
+ 's' {
+ if ($null -eq $valueNode -or [string]::IsNullOrWhiteSpace($valueNode.InnerText)) {
+ return ''
+ }
+
+ $index = [int] $valueNode.InnerText
+ if ($index -lt 0 -or $index -ge $SharedStrings.Length) {
+ throw "Shared string index '$index' is out of range."
+ }
+
+ return $SharedStrings[$index]
+ }
+ 'inlineStr' {
+ return Convert-SharedStringNodeToText -Node $CellNode -NamespaceManager $NamespaceManager
+ }
+ default {
+ if ($null -eq $valueNode) {
+ return ''
+ }
+
+ return $valueNode.InnerText
+ }
+ }
+}
+
+function Read-WorksheetRows {
+ param(
+ [Parameter(Mandatory)]
+ [System.IO.Compression.ZipArchive] $Archive,
+
+ [Parameter(Mandatory)]
+ [string[]] $SharedStrings
+ )
+
+ $worksheetPath = Get-FirstWorksheetPath -Archive $Archive
+ [xml] $worksheetDocument = Read-ZipEntryText -Archive $Archive -EntryPath $worksheetPath
+ $namespaceManager = New-XmlNamespaceManager -Document $worksheetDocument
+ $rowNodes = $worksheetDocument.SelectNodes('/x:worksheet/x:sheetData/x:row', $namespaceManager)
+
+ foreach ($rowNode in $rowNodes) {
+ $cellMap = [System.Collections.Generic.Dictionary[int, string]]::new()
+
+ foreach ($cellNode in $rowNode.SelectNodes('x:c', $namespaceManager)) {
+ $columnIndex = Get-SpreadsheetCellColumnIndex -CellNode $cellNode
+ $cellMap[$columnIndex] = [string] (Get-SpreadsheetCellValue -CellNode $cellNode -SharedStrings $SharedStrings -NamespaceManager $namespaceManager)
+ }
+
+ [pscustomobject] @{
+ RowNumber = [int] $rowNode.Attributes['r'].Value
+ Cells = $cellMap
+ }
+ }
+}
+
+function ConvertTo-PackageId {
+ param(
+ [AllowNull()]
+ [AllowEmptyString()]
+ [string] $Value
+ )
+
+ if ($null -eq $Value) {
+ return ''
+ }
+
+ $trimmed = $Value.Trim()
+ if ([string]::IsNullOrWhiteSpace($trimmed)) {
+ return ''
+ }
+
+ $numericValue = 0.0
+ if ([double]::TryParse($trimmed, [System.Globalization.NumberStyles]::Float, [System.Globalization.CultureInfo]::InvariantCulture, [ref] $numericValue)) {
+ if ($numericValue % 1 -eq 0) {
+ return [string] ([int64] $numericValue)
+ }
+ }
+
+ return $trimmed
+}
+
+function ConvertTo-TrimmedUrl {
+ param(
+ [AllowNull()]
+ [AllowEmptyString()]
+ [string] $Value
+ )
+
+ if ($null -eq $Value) {
+ return ''
+ }
+
+ return $Value.Trim()
+}
+
+function Get-ForbiddenUrlSet {
+ param(
+ [Parameter(Mandatory)]
+ [string] $Path
+ )
+
+ $forbiddenUrls = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::Ordinal)
+ if (-not (Test-Path -LiteralPath $Path)) {
+ return (, $forbiddenUrls)
+ }
+
+ foreach ($line in [System.IO.File]::ReadAllLines($Path)) {
+ if ($null -eq $line) {
+ continue
+ }
+
+ $trimmed = $line.Trim()
+ if (-not [string]::IsNullOrWhiteSpace($trimmed)) {
+ [void] $forbiddenUrls.Add($trimmed)
+ }
+ }
+
+ return (, $forbiddenUrls)
+}
+
+function Export-IconDatabase {
+ param(
+ [Parameter(Mandatory)]
+ [string] $WorkbookPath,
+
+ [Parameter(Mandatory)]
+ [string] $JsonPath,
+
+ [Parameter(Mandatory)]
+ [string] $InvalidUrlsPath,
+
+ [Parameter(Mandatory)]
+ [string] $NewUrlsPath,
+
+ [Parameter(Mandatory)]
+ [int] $MaxScreenshotsPerPackage
+ )
+
+ if (-not (Test-Path -LiteralPath $WorkbookPath)) {
+ throw "Workbook not found: $WorkbookPath"
+ }
+
+ $forbiddenUrls = Get-ForbiddenUrlSet -Path $InvalidUrlsPath
+ $oldContent = if (Test-Path -LiteralPath $JsonPath) { [System.IO.File]::ReadAllText($JsonPath) } else { '' }
+ $oldUrlLookup = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::Ordinal)
+ foreach ($url in Get-UrlsFromText -Text $oldContent) {
+ [void] $oldUrlLookup.Add((ConvertFrom-JsonEscapedString -Value $url))
+ }
+
+ $document = [UniGetUI.IconTools.IconDatabaseDocument]::new()
+ $totalCount = 0
+ $doneCount = 0
+ $packagesWithIcon = 0
+ $packagesWithScreenshot = 0
+ $totalScreenshots = 0
+
+ $archive = [System.IO.Compression.ZipFile]::OpenRead($WorkbookPath)
+ try {
+ $sharedStrings = Get-SharedStrings -Archive $archive
+ foreach ($row in Read-WorksheetRows -Archive $archive -SharedStrings $sharedStrings) {
+ if ($row.RowNumber -le 1) {
+ continue
+ }
+
+ $packageId = if ($row.Cells.ContainsKey(0)) { ConvertTo-PackageId -Value $row.Cells[0] } else { '' }
+ if ([string]::IsNullOrWhiteSpace($packageId)) {
+ continue
+ }
+
+ $iconUrl = if ($row.Cells.ContainsKey(1)) { ConvertTo-TrimmedUrl -Value $row.Cells[1] } else { '' }
+ if (-not [string]::IsNullOrWhiteSpace($iconUrl)) {
+ if ($forbiddenUrls.Contains($iconUrl)) {
+ $iconUrl = ''
+ }
+ else {
+ $doneCount++
+ $packagesWithIcon++
+ }
+ }
+
+ $images = [System.Collections.Generic.List[string]]::new()
+ for ($columnIndex = 2; $columnIndex -lt (2 + $MaxScreenshotsPerPackage); $columnIndex++) {
+ if (-not $row.Cells.ContainsKey($columnIndex)) {
+ break
+ }
+
+ $imageUrl = ConvertTo-TrimmedUrl -Value $row.Cells[$columnIndex]
+ if ([string]::IsNullOrWhiteSpace($imageUrl)) {
+ break
+ }
+
+ $images.Add($imageUrl)
+ $totalScreenshots++
+ }
+
+ if ($images.Count -gt 0) {
+ $packagesWithScreenshot++
+ }
+
+ if (-not $document.icons_and_screenshots.ContainsKey($packageId)) {
+ $packageEntry = [UniGetUI.IconTools.PackageEntry]::new()
+ $packageEntry.icon = $iconUrl
+ $packageEntry.images.AddRange($images)
+ $document.icons_and_screenshots[$packageId] = $packageEntry
+ }
+ else {
+ $packageEntry = $document.icons_and_screenshots[$packageId]
+ if ([string]::IsNullOrWhiteSpace($packageEntry.icon) -and -not [string]::IsNullOrWhiteSpace($iconUrl)) {
+ $packageEntry.icon = $iconUrl
+ }
+
+ if ($packageEntry.images.Count -eq 0 -and $images.Count -gt 0) {
+ $packageEntry.images.AddRange($images)
+ }
+ }
+
+ $totalCount++
+ }
+ }
+ finally {
+ $archive.Dispose()
+ }
+
+ $document.package_count.total = $totalCount
+ $document.package_count.done = $doneCount
+ $document.package_count.packages_with_icon = $packagesWithIcon
+ $document.package_count.packages_with_screenshot = $packagesWithScreenshot
+ $document.package_count.total_screenshots = $totalScreenshots
+
+ $serializerOptions = [System.Text.Json.JsonSerializerOptions]::new()
+ $serializerOptions.WriteIndented = $true
+ $serializerOptions.IndentCharacter = ' '
+ $serializerOptions.IndentSize = 4
+ $serializerOptions.NewLine = [Environment]::NewLine
+ $serializerOptions.Encoder = [System.Text.Encodings.Web.JavaScriptEncoder]::UnsafeRelaxedJsonEscaping
+ $newContent = [System.Text.Json.JsonSerializer]::Serialize($document, $serializerOptions)
+ $newContent = ConvertTo-AsciiEscapedJson -Value $newContent
+ Write-Utf8File -Path $JsonPath -Content ($newContent + [Environment]::NewLine)
+
+ $newUrls = New-Object System.Collections.Generic.List[string]
+ $seenNewUrls = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::Ordinal)
+ foreach ($packageEntry in $document.icons_and_screenshots.GetEnumerator()) {
+ if (
+ -not [string]::IsNullOrWhiteSpace($packageEntry.Value.icon) -and
+ -not $oldUrlLookup.Contains($packageEntry.Value.icon) -and
+ $seenNewUrls.Add($packageEntry.Value.icon)
+ ) {
+ $newUrls.Add($packageEntry.Value.icon)
+ }
+
+ foreach ($imageUrl in $packageEntry.Value.images) {
+ if (
+ -not [string]::IsNullOrWhiteSpace($imageUrl) -and
+ -not $oldUrlLookup.Contains($imageUrl) -and
+ $seenNewUrls.Add($imageUrl)
+ ) {
+ $newUrls.Add($imageUrl)
+ }
+ }
+ }
+
+ Write-Utf8File -Path $NewUrlsPath -Content (($newUrls -join [Environment]::NewLine) + $(if ($newUrls.Count -gt 0) { [Environment]::NewLine } else { '' }))
+
+ Write-Host "Exported icon database from $WorkbookPath" -ForegroundColor Green
+ Write-Host "Wrote JSON to $JsonPath" -ForegroundColor Green
+ Write-Host "Wrote newly added URLs to $NewUrlsPath" -ForegroundColor Green
+ Write-Host "Rows processed: $totalCount" -ForegroundColor Cyan
+ Write-Host "Rows with icons: $packagesWithIcon" -ForegroundColor Cyan
+ Write-Host "Rows with screenshots: $packagesWithScreenshot" -ForegroundColor Cyan
+ Write-Host "Total screenshots: $totalScreenshots" -ForegroundColor Cyan
+}
+
+function Test-UrlStatus {
+ param(
+ [Parameter(Mandatory)]
+ [System.Net.Http.HttpClient] $Client,
+
+ [Parameter(Mandatory)]
+ [string] $Url,
+
+ [Parameter(Mandatory)]
+ [int] $MaxRetries,
+
+ [Parameter(Mandatory)]
+ [int] $RetryDelayMilliseconds
+ )
+
+ for ($attempt = 0; $attempt -le $MaxRetries; $attempt++) {
+ try {
+ $request = [System.Net.Http.HttpRequestMessage]::new([System.Net.Http.HttpMethod]::Get, $Url)
+ try {
+ $response = $Client.Send($request, [System.Net.Http.HttpCompletionOption]::ResponseHeadersRead)
+ try {
+ return [pscustomobject] @{
+ StatusCode = [int] $response.StatusCode
+ Error = $null
+ }
+ }
+ finally {
+ $response.Dispose()
+ }
+ }
+ finally {
+ $request.Dispose()
+ }
+ }
+ catch [System.Net.Http.HttpRequestException] {
+ if ($attempt -lt $MaxRetries) {
+ Start-Sleep -Milliseconds $RetryDelayMilliseconds
+ continue
+ }
+
+ return [pscustomobject] @{
+ StatusCode = $null
+ Error = $_.Exception.Message
+ }
+ }
+ catch [System.Threading.Tasks.TaskCanceledException] {
+ if ($attempt -lt $MaxRetries) {
+ Start-Sleep -Milliseconds $RetryDelayMilliseconds
+ continue
+ }
+
+ return [pscustomobject] @{
+ StatusCode = $null
+ Error = 'Request timed out.'
+ }
+ }
+ }
+}
+
+function Test-IconUrls {
+ param(
+ [Parameter(Mandatory)]
+ [string] $JsonPath,
+
+ [Parameter(Mandatory)]
+ [string] $InvalidUrlsPath,
+
+ [Parameter(Mandatory)]
+ [switch] $AppendInvalidUrls,
+
+ [Parameter(Mandatory)]
+ [int] $MaxPackages,
+
+ [Parameter(Mandatory)]
+ [int] $MaxRetries,
+
+ [Parameter(Mandatory)]
+ [int] $RetryDelayMilliseconds,
+
+ [Parameter(Mandatory)]
+ [int] $RequestTimeoutSeconds
+ )
+
+ if (-not (Test-Path -LiteralPath $JsonPath)) {
+ throw "JSON file not found: $JsonPath"
+ }
+
+ $existingInvalidUrls = Get-ForbiddenUrlSet -Path $InvalidUrlsPath
+ $invalidToAppend = New-Object System.Collections.Generic.List[string]
+ $httpClient = [System.Net.Http.HttpClient]::new()
+ $httpClient.Timeout = [TimeSpan]::FromSeconds($RequestTimeoutSeconds)
+ $httpClient.DefaultRequestHeaders.UserAgent.ParseAdd('UniGetUI-IconValidator/1.0')
+
+ $processed = 0
+ $accepted = 0
+ $invalid = 0
+ $unexpected = 0
+ $errors = 0
+
+ try {
+ $jsonDocument = [System.Text.Json.JsonDocument]::Parse([System.IO.File]::ReadAllText($JsonPath))
+ try {
+ $packages = $jsonDocument.RootElement.GetProperty('icons_and_screenshots')
+ foreach ($package in $packages.EnumerateObject()) {
+ if ($MaxPackages -gt 0 -and $processed -ge $MaxPackages) {
+ break
+ }
+
+ $iconUrl = $package.Value.GetProperty('icon').GetString()
+ if ([string]::IsNullOrWhiteSpace($iconUrl)) {
+ continue
+ }
+
+ $processed++
+ $result = Test-UrlStatus -Client $httpClient -Url $iconUrl -MaxRetries $MaxRetries -RetryDelayMilliseconds $RetryDelayMilliseconds
+
+ if ($null -eq $result.StatusCode) {
+ $errors++
+ Write-Warning "[$($package.Name)] $iconUrl -> $($result.Error)"
+ continue
+ }
+
+ switch ([int] $result.StatusCode) {
+ 200 {
+ $accepted++
+ }
+ 403 {
+ $accepted++
+ }
+ 404 {
+ $invalid++
+ Write-Warning "[$($package.Name)] $iconUrl returned 404"
+ if ($AppendInvalidUrls -and -not $existingInvalidUrls.Contains($iconUrl)) {
+ [void] $existingInvalidUrls.Add($iconUrl)
+ $invalidToAppend.Add($iconUrl)
+ }
+ }
+ default {
+ $unexpected++
+ Write-Warning "[$($package.Name)] $iconUrl returned status code $($result.StatusCode)"
+ }
+ }
+ }
+ }
+ finally {
+ $jsonDocument.Dispose()
+ }
+ }
+ finally {
+ $httpClient.Dispose()
+ }
+
+ if ($AppendInvalidUrls -and $invalidToAppend.Count -gt 0) {
+ $linesToAppend = ($invalidToAppend -join [Environment]::NewLine) + [Environment]::NewLine
+ $encoding = [System.Text.UTF8Encoding]::new($false)
+ [System.IO.File]::AppendAllText($InvalidUrlsPath, $linesToAppend, $encoding)
+ Write-Host "Appended $($invalidToAppend.Count) invalid URLs to $InvalidUrlsPath" -ForegroundColor Yellow
+ }
+
+ Write-Host "Validated $processed icon URLs from $JsonPath" -ForegroundColor Green
+ Write-Host "Accepted (200/403): $accepted" -ForegroundColor Cyan
+ Write-Host "Invalid (404): $invalid" -ForegroundColor Cyan
+ Write-Host "Unexpected statuses: $unexpected" -ForegroundColor Cyan
+ Write-Host "Request errors: $errors" -ForegroundColor Cyan
+}
+
+$resolvedWorkbookPath = Resolve-RepoPath -Path $WorkbookPath
+$resolvedJsonPath = Resolve-RepoPath -Path $JsonPath
+$resolvedInvalidUrlsPath = Resolve-RepoPath -Path $InvalidUrlsPath
+$resolvedNewUrlsPath = Resolve-RepoPath -Path $NewUrlsPath
+
+if ($Export) {
+ Export-IconDatabase `
+ -WorkbookPath $resolvedWorkbookPath `
+ -JsonPath $resolvedJsonPath `
+ -InvalidUrlsPath $resolvedInvalidUrlsPath `
+ -NewUrlsPath $resolvedNewUrlsPath `
+ -MaxScreenshotsPerPackage $MaxScreenshotsPerPackage
+}
+elseif ($Validate) {
+ Test-IconUrls `
+ -JsonPath $resolvedJsonPath `
+ -InvalidUrlsPath $resolvedInvalidUrlsPath `
+ -AppendInvalidUrls:$AppendInvalidUrls `
+ -MaxPackages $MaxPackages `
+ -MaxRetries $MaxRetries `
+ -RetryDelayMilliseconds $RetryDelayMilliseconds `
+ -RequestTimeoutSeconds $RequestTimeoutSeconds
+}
\ No newline at end of file
diff --git a/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/BasePkgDetailsHelper.cs b/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/BasePkgDetailsHelper.cs
index ebb7ab048b..4b539a1bc9 100644
--- a/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/BasePkgDetailsHelper.cs
+++ b/src/UniGetUI.PackageEngine.PackageManagerClasses/Manager/Helpers/BasePkgDetailsHelper.cs
@@ -87,17 +87,12 @@ public IReadOnlyList GetVersions(IPackage package)
}
}
- // Try to get the icon especially for this package
- string? iconUrl = IconDatabase.Instance.GetIconUrlForId(
- Manager.Name + "." + package.Id
- );
- if (iconUrl is not null)
- return new CacheableIcon(new Uri(iconUrl));
-
- // Try to get other corresponding icons for the package
- iconUrl = IconDatabase.Instance.GetIconUrlForId(package.GetIconId());
- if (iconUrl is not null)
- return new CacheableIcon(new Uri(iconUrl));
+ foreach (string lookupId in GetIconDatabaseLookupIds(package))
+ {
+ string? iconUrl = IconDatabase.Instance.GetIconUrlForId(lookupId);
+ if (iconUrl is not null)
+ return new CacheableIcon(new Uri(iconUrl));
+ }
return null;
}
@@ -131,31 +126,24 @@ public IReadOnlyList GetScreenshots(IPackage package)
// Try to get exact screenshots for this package
if (!URIs.Any())
{
- string[] UrlArray = IconDatabase.Instance.GetScreenshotsUrlForId(
- Manager.Name + "." + package.Id
- );
- List UriList = [];
- foreach (string url in UrlArray)
+ foreach (string lookupId in GetIconDatabaseLookupIds(package))
{
- if (url != "")
- UriList.Add(new Uri(url));
+ string[] UrlArray = IconDatabase.Instance.GetScreenshotsUrlForId(
+ lookupId
+ );
+ List UriList = [];
+ foreach (string url in UrlArray)
+ {
+ if (url != "")
+ UriList.Add(new Uri(url));
+ }
+
+ if (UriList.Count > 0)
+ {
+ URIs = UriList;
+ break;
+ }
}
- URIs = UriList;
- }
-
- // Try to get matching screenshots for this package
- if (!URIs.Any())
- {
- string[] UrlArray = IconDatabase.Instance.GetScreenshotsUrlForId(
- package.GetIconId()
- );
- List UriList = [];
- foreach (string url in UrlArray)
- {
- if (url != "")
- UriList.Add(new Uri(url));
- }
- URIs = UriList;
}
Logger.Info($"Found {URIs.Count} screenshots for package Id={package.Id}");
@@ -172,6 +160,18 @@ public IReadOnlyList GetScreenshots(IPackage package)
}
}
+ private IEnumerable GetIconDatabaseLookupIds(IPackage package)
+ {
+ yield return Manager.Name + "." + package.Id;
+
+ if (Manager.Name == "Winget")
+ {
+ yield return package.Id;
+ }
+
+ yield return package.GetIconId();
+ }
+
protected abstract void GetDetails_UnSafe(IPackageDetails details);
protected abstract IReadOnlyList GetInstallableVersions_UnSafe(IPackage package);
protected abstract CacheableIcon? GetIcon_UnSafe(IPackage package);