feat: add DuckDuckGo image proxy support for marketplace and photo pack images

- Implemented `getProxiedImageUrl` function to apply DuckDuckGo image proxy to image URLs based on user settings.
- Updated various components to use the proxied image URLs, including background loader, photo information, lightbox, and item cards.
- Added a new setting in the advanced options to enable/disable the DuckDuckGo image proxy.
- Updated localization files to include new strings for the image proxy feature.
- Initialized the proxy setting in default settings to false.
This commit is contained in:
alexsparkes
2026-01-31 16:54:55 +00:00
parent 41e438ead4
commit 7415b9cd5c
41 changed files with 125 additions and 7 deletions

View File

@@ -5,6 +5,7 @@ import { randomColourStyleBuilder } from './randomColour';
import videoCheck from './videoCheck';
import { getAllBackgrounds, getAllBackgroundsWithMetadata } from 'utils/customBackgroundDB';
import { BackgroundQueueManager } from 'utils/backgroundQueue';
import { getProxiedImageUrl } from 'utils/marketplace';
const parseJSON = (key, fallback = null) => {
const item = localStorage.getItem(key);
@@ -324,13 +325,14 @@ function getPhotoPackBackground(isOffline) {
const selected = photos[index];
photoData = {
url: selected.url.default,
url: getProxiedImageUrl(selected.url.default),
type: 'photo_pack',
photoInfo: {
hidden: false,
credit: selected.photographer,
location: selected.location,
blur_hash: selected.blur_hash || null,
url: selected.url.default,
},
};
}
@@ -378,13 +380,14 @@ async function prefetchPhotoPackImages(queueManager, allPhotos, currentPhoto, cu
// Normalize metadata
const normalized = selected.map((photo) => ({
url: photo.url.default,
url: getProxiedImageUrl(photo.url.default),
type: 'photo_pack',
photoInfo: {
hidden: false,
credit: photo.photographer,
location: photo.location,
blur_hash: photo.blur_hash || null,
url: photo.url.default,
},
}));

View File

@@ -16,6 +16,7 @@ import {
MdVisibilityOff as VisibilityOff,
} from 'react-icons/md';
import { Tooltip } from 'components/Elements';
import { getProxiedImageUrl } from 'utils/marketplace';
import Modal from 'react-modal';
import { ShareModal } from 'components/Elements';
@@ -46,7 +47,7 @@ const formatText = (text) => {
*/
const downloadImage = async (info) => {
const link = document.createElement('a');
link.href = await toDataURL(info.url);
link.href = await toDataURL(getProxiedImageUrl(info.url));
link.download = `mue-${formatText(info.credit)}-${formatText(info.location)}.jpg`; // image is more likely to be webp or avif btw
document.body.appendChild(link);
link.click();
@@ -98,7 +99,7 @@ function PhotoInformation({ info, url, api }) {
setWidth(event.target.width);
setHeight(event.target.height);
};
img.src = url;
img.src = getProxiedImageUrl(url);
// info is still there because we want the favourite button to work
if (localStorage.getItem('photoInformation') === 'false') {

View File

@@ -1,5 +1,6 @@
import { memo } from 'react';
import variables from 'config/variables';
import { getProxiedImageUrl } from 'utils/marketplace';
function Lightbox({ modalClose, img }) {
variables.stats.postEvent('modal', 'Opened lightbox');
@@ -9,7 +10,7 @@ function Lightbox({ modalClose, img }) {
<span className="closeModal" onClick={modalClose}>
&times;
</span>
<img src={img} className="lightboximg" draggable={false} alt="ItemPage screenshot" />
<img src={getProxiedImageUrl(img)} className="lightboximg" draggable={false} alt="ItemPage screenshot" />
</>
);
}

View File

@@ -9,6 +9,7 @@ import placeholderIcon from 'assets/icons/marketplace-placeholder.png';
import { Tooltip } from 'components/Elements';
import Dropdown from '../../../../components/Form/Settings/Dropdown/Dropdown';
import { getProxiedImageUrl } from 'utils/marketplace';
function filterItems(item, filter, categoryFilter) {
const lowerCaseFilter = filter.toLowerCase();
@@ -101,7 +102,7 @@ function ItemCard({ item, toggleFunction, type, onCollection, isCurator, isInsta
className="item-icon"
alt="icon"
draggable={false}
src={item.icon_url}
src={getProxiedImageUrl(item.icon_url)}
onError={(e) => {
e.target.onerror = null;
e.target.src = placeholderIcon;

View File

@@ -14,6 +14,7 @@ import {
import '../customwidgets.scss';
import { exportSettings, importSettings } from 'utils/settings';
import { clearBackgroundQueues } from 'utils/queueOperations';
import { FileUpload, Text, Switch, Dropdown } from 'components/Form/Settings';
import { ResetModal, Button } from 'components/Elements';
@@ -369,6 +370,26 @@ function AdvancedOptions({ currentSubSection, onSubSectionChange, sectionName })
</Action>
</Row>
<Row>
<Content
title={variables.getMessage('modals.main.settings.sections.advanced.marketplace_img_proxy')}
subtitle={variables.getMessage(
'modals.main.settings.sections.advanced.marketplace_img_proxy_subtitle',
)}
/>
<Action>
<Switch
name="marketplaceDDGProxy"
element=".other"
onChange={() => {
// Clear all prefetch queues when proxy setting changes
// so new images are fetched with correct proxy state
clearBackgroundQueues('all');
}}
/>
</Action>
</Row>
<Row>
<Content
title={variables.getMessage('modals.main.settings.sections.advanced.timezone.title')}