2023-06-14 20:39:36 +02:00
|
|
|
import { Mapillary } from "./Mapillary"
|
|
|
|
import { WikimediaImageProvider } from "./WikimediaImageProvider"
|
|
|
|
import { Imgur } from "./Imgur"
|
|
|
|
import GenericImageProvider from "./GenericImageProvider"
|
2025-04-09 17:09:11 +02:00
|
|
|
import { ImmutableStore, Store, Stores } from "../UIEventSource"
|
2023-06-14 20:39:36 +02:00
|
|
|
import ImageProvider, { ProvidedImage } from "./ImageProvider"
|
|
|
|
import { WikidataImageProvider } from "./WikidataImageProvider"
|
2024-09-26 19:15:20 +02:00
|
|
|
import Panoramax from "./Panoramax"
|
2024-09-28 02:04:14 +02:00
|
|
|
import { Utils } from "../../Utils"
|
2021-06-22 14:21:32 +02:00
|
|
|
|
2021-09-29 23:56:59 +02:00
|
|
|
/**
|
|
|
|
* A generic 'from the interwebz' image picker, without attribution
|
|
|
|
*/
|
2021-09-09 00:05:51 +02:00
|
|
|
export default class AllImageProviders {
|
2024-02-21 00:13:11 +01:00
|
|
|
private static dontLoadFromPrefixes = ["https://photos.app.goo.gl/"]
|
|
|
|
|
2024-07-27 12:59:38 +02:00
|
|
|
/**
|
|
|
|
* The 'genericImageProvider' is a fallback that scans various other tags for tags, unless the URL starts with one of the given prefixes
|
|
|
|
*/
|
|
|
|
public static genericImageProvider = new GenericImageProvider([
|
|
|
|
...Imgur.defaultValuePrefix,
|
|
|
|
...WikimediaImageProvider.commonsPrefixes,
|
|
|
|
...Mapillary.valuePrefixes,
|
|
|
|
...AllImageProviders.dontLoadFromPrefixes,
|
2025-01-28 15:42:34 +01:00
|
|
|
"Category:",
|
2024-07-27 12:59:38 +02:00
|
|
|
])
|
|
|
|
|
2025-01-26 01:42:41 +01:00
|
|
|
private static imageAttributionSources: ImageProvider[] = [
|
2021-09-29 23:56:59 +02:00
|
|
|
Imgur.singleton,
|
|
|
|
Mapillary.singleton,
|
|
|
|
WikidataImageProvider.singleton,
|
|
|
|
WikimediaImageProvider.singleton,
|
2024-09-26 19:15:20 +02:00
|
|
|
Panoramax.singleton,
|
2025-01-28 15:42:34 +01:00
|
|
|
AllImageProviders.genericImageProvider,
|
2021-10-06 17:48:07 +02:00
|
|
|
]
|
2023-09-27 22:21:35 +02:00
|
|
|
public static apiUrls: string[] = [].concat(
|
2025-01-26 01:42:41 +01:00
|
|
|
...AllImageProviders.imageAttributionSources.map((src) => src.apiUrls())
|
2023-09-27 22:21:35 +02:00
|
|
|
)
|
2025-04-09 17:09:11 +02:00
|
|
|
public static defaultKeys: string[] = [].concat(
|
|
|
|
...AllImageProviders.imageAttributionSources.map((provider) => provider.defaultKeyPrefixes)
|
2023-09-27 22:21:35 +02:00
|
|
|
)
|
2022-05-06 12:41:24 +02:00
|
|
|
private static providersByName = {
|
|
|
|
imgur: Imgur.singleton,
|
|
|
|
mapillary: Mapillary.singleton,
|
|
|
|
wikidata: WikidataImageProvider.singleton,
|
|
|
|
wikimedia: WikimediaImageProvider.singleton,
|
2025-01-28 15:42:34 +01:00
|
|
|
panoramax: Panoramax.singleton,
|
2022-05-06 12:41:24 +02:00
|
|
|
}
|
2021-09-29 23:56:59 +02:00
|
|
|
|
2023-09-27 22:21:35 +02:00
|
|
|
public static byName(name: string) {
|
|
|
|
return AllImageProviders.providersByName[name.toLowerCase()]
|
|
|
|
}
|
|
|
|
|
2024-07-27 12:59:38 +02:00
|
|
|
public static async selectBestProvider(key: string, value: string): Promise<ImageProvider> {
|
2025-01-26 01:42:41 +01:00
|
|
|
for (const imageProvider of AllImageProviders.imageAttributionSources) {
|
2024-08-09 16:55:08 +02:00
|
|
|
try {
|
|
|
|
const extracted = await Promise.all(await imageProvider.ExtractUrls(key, value))
|
|
|
|
if (extracted?.length > 0) {
|
|
|
|
return imageProvider
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2024-07-27 12:59:38 +02:00
|
|
|
console.warn("Provider gave an error while trying to determine a match:", e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return AllImageProviders.genericImageProvider
|
|
|
|
}
|
|
|
|
|
2024-11-05 00:18:16 +01:00
|
|
|
private static readonly _cachedImageStores: Record<string, Store<ProvidedImage[]>> = {}
|
2025-01-17 16:01:40 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Does a guess on the number of images that are probably there.
|
|
|
|
* Will simply count all image tags
|
|
|
|
*
|
2025-01-17 16:47:56 +01:00
|
|
|
* AllImageProviders.estimateNumberOfImages({image:"abc", "mapillary": "123", "panoramax:0": "xyz"}) // => 3
|
2025-01-26 03:32:03 +01:00
|
|
|
* AllImageProviders.estimateNumberOfImages({wikidata:"Q123", "wikipedia": "nl:xyz"}) // => 0
|
|
|
|
*
|
2025-04-28 01:05:44 +02:00
|
|
|
* AllImageProviders.estimateNumberOfImages({image:"https://photos.app.goo.gl/tjt5FsQZtpkQRVw38"}) // => 0
|
2025-01-17 16:01:40 +01:00
|
|
|
*
|
|
|
|
*/
|
2025-01-28 15:42:34 +01:00
|
|
|
public static estimateNumberOfImages(
|
|
|
|
tags: Record<string, string>,
|
|
|
|
prefixes: string[] = undefined
|
|
|
|
): number {
|
2025-01-17 16:01:40 +01:00
|
|
|
let count = 0
|
|
|
|
|
2025-02-10 02:04:58 +01:00
|
|
|
const sources = [
|
|
|
|
Imgur.singleton,
|
2025-01-26 01:42:41 +01:00
|
|
|
Mapillary.singleton,
|
|
|
|
Panoramax.singleton,
|
2025-02-10 02:04:58 +01:00
|
|
|
AllImageProviders.genericImageProvider,
|
|
|
|
]
|
|
|
|
const allPrefixes = Utils.Dedup(
|
|
|
|
prefixes ?? [].concat(...sources.map((s) => s.defaultKeyPrefixes))
|
|
|
|
)
|
2025-01-17 16:47:56 +01:00
|
|
|
for (const prefix of allPrefixes) {
|
|
|
|
for (const k in tags) {
|
2025-04-28 01:05:44 +02:00
|
|
|
const v = tags[k]
|
|
|
|
if (!v) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if (AllImageProviders.dontLoadFromPrefixes.some(prefix => v.startsWith(prefix))) {
|
2025-02-04 01:02:57 +01:00
|
|
|
continue
|
|
|
|
}
|
2025-01-17 16:01:40 +01:00
|
|
|
if (k === prefix || k.startsWith(prefix + ":")) {
|
|
|
|
count++
|
2025-01-17 16:47:56 +01:00
|
|
|
continue
|
2025-01-17 16:01:40 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return count
|
|
|
|
}
|
|
|
|
|
2024-09-26 19:15:20 +02:00
|
|
|
/**
|
2024-11-14 18:25:27 +01:00
|
|
|
* Tries to extract all image data for this image. Cached on tags?.data?.id
|
2024-09-26 19:15:20 +02:00
|
|
|
*/
|
2025-01-17 14:25:30 +01:00
|
|
|
public static loadImagesFor(
|
2023-06-14 20:39:36 +02:00
|
|
|
tags: Store<Record<string, string>>,
|
2024-10-19 14:44:55 +02:00
|
|
|
tagKey?: string[]
|
2023-06-14 20:39:36 +02:00
|
|
|
): Store<ProvidedImage[]> {
|
2024-09-28 02:04:14 +02:00
|
|
|
if (tags?.data?.id === undefined) {
|
2021-09-29 23:56:59 +02:00
|
|
|
return undefined
|
|
|
|
}
|
2024-11-05 00:18:16 +01:00
|
|
|
const id = tags?.data?.id
|
2024-11-14 18:25:27 +01:00
|
|
|
const cachekey = id + (tagKey?.join(";") ?? "")
|
|
|
|
if (this._cachedImageStores[cachekey]) {
|
|
|
|
return this._cachedImageStores[cachekey]
|
2024-11-05 00:18:16 +01:00
|
|
|
}
|
2021-09-29 23:56:59 +02:00
|
|
|
|
2024-07-27 12:59:38 +02:00
|
|
|
const allSources: Store<ProvidedImage[]>[] = []
|
2025-01-26 01:42:41 +01:00
|
|
|
for (const imageProvider of AllImageProviders.imageAttributionSources) {
|
2024-09-28 02:04:14 +02:00
|
|
|
/*
|
|
|
|
By default, 'GetRelevantUrls' uses the defaultKeyPrefixes.
|
|
|
|
However, we override them if a custom image tag is set, e.g. 'image:menu'
|
|
|
|
*/
|
|
|
|
const prefixes = tagKey ?? imageProvider.defaultKeyPrefixes
|
2024-10-19 14:44:55 +02:00
|
|
|
const singleSource = tags.bindD((tags) => imageProvider.getRelevantUrls(tags, prefixes))
|
2021-09-29 23:56:59 +02:00
|
|
|
allSources.push(singleSource)
|
|
|
|
}
|
2025-04-15 18:18:44 +02:00
|
|
|
const source = Stores.fromStoresArray(allSources).map((result) => {
|
2025-04-09 17:09:11 +02:00
|
|
|
const all = [].concat(...result)
|
|
|
|
return Utils.DedupOnId(all, (i) => i?.id ?? i?.url)
|
|
|
|
})
|
2024-11-14 18:25:27 +01:00
|
|
|
this._cachedImageStores[cachekey] = source
|
2021-09-29 23:56:59 +02:00
|
|
|
return source
|
|
|
|
}
|
2024-10-08 12:41:14 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Given a list of URLs, tries to detect the images. Used in e.g. the comments
|
|
|
|
*/
|
|
|
|
public static loadImagesFrom(urls: string[]): Store<ProvidedImage[]> {
|
|
|
|
const tags = {
|
2025-01-28 15:42:34 +01:00
|
|
|
id: urls.join(";"),
|
2024-10-08 12:41:14 +02:00
|
|
|
}
|
|
|
|
for (let i = 0; i < urls.length; i++) {
|
2024-11-24 23:54:13 +01:00
|
|
|
tags["image:" + i] = urls[i]
|
2024-10-08 12:41:14 +02:00
|
|
|
}
|
2025-01-17 14:25:30 +01:00
|
|
|
return this.loadImagesFor(new ImmutableStore(tags))
|
2024-10-08 12:41:14 +02:00
|
|
|
}
|
2021-06-22 14:21:32 +02:00
|
|
|
}
|