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);