8 changed files with 250 additions and 84 deletions
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
import { MWMediaType, MWQuery } from "providers"; |
||||
import React, { useState } from "react"; |
||||
import { generatePath, useHistory, useRouteMatch } from "react-router-dom"; |
||||
|
||||
export function useSearchQuery(): [MWQuery, (inp: Partial<MWQuery>) => void] { |
||||
const history = useHistory() |
||||
const { path, params } = useRouteMatch<{ type: string, query: string}>() |
||||
const [search, setSearch] = useState<MWQuery>({ |
||||
searchQuery: "", |
||||
type: MWMediaType.MOVIE, |
||||
}); |
||||
|
||||
const updateParams = (inp: Partial<MWQuery>) => { |
||||
const copySearch: MWQuery = {...search}; |
||||
Object.assign(copySearch, inp); |
||||
history.push(generatePath(path, { query: copySearch.searchQuery.length == 0 ? undefined : inp.searchQuery, type: copySearch.type })) |
||||
} |
||||
|
||||
React.useEffect(() => { |
||||
const type = Object.values(MWMediaType).find(v=>params.type === v) || MWMediaType.MOVIE; |
||||
const searchQuery = params.query || ""; |
||||
setSearch({ type, searchQuery }); |
||||
}, [params, setSearch]) |
||||
|
||||
return [search, updateParams] |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
import { MWMediaType } from "providers"; |
||||
import { mediaProviders } from "./providers"; |
||||
|
||||
/* |
||||
** Fetch all enabled providers for a specific type |
||||
*/ |
||||
export function GetProvidersForType(type: MWMediaType) { |
||||
return mediaProviders.filter((v) => v.type.includes(type)); |
||||
} |
||||
|
||||
/* |
||||
** Get a provider by a id |
||||
*/ |
||||
export function getProviderFromId(id: string) { |
||||
return mediaProviders.find((v) => v.id === id); |
||||
} |
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
import { tempScraper } from "providers/list/temp"; |
||||
import { theFlixScraper } from "providers/list/theflix"; |
||||
import { MWWrappedMediaProvider, WrapProvider } from "providers/wrapper"; |
||||
|
||||
const mediaProvidersUnchecked: MWWrappedMediaProvider[] = [ |
||||
WrapProvider(theFlixScraper), |
||||
WrapProvider(tempScraper), |
||||
]; |
||||
export const mediaProviders: MWWrappedMediaProvider[] = |
||||
mediaProvidersUnchecked.filter((v) => v.enabled); |
@ -0,0 +1,87 @@
@@ -0,0 +1,87 @@
|
||||
import Fuse from "fuse.js"; |
||||
import { MWMassProviderOutput, MWMedia, MWQuery } from "providers"; |
||||
import { SimpleCache } from "utils/cache"; |
||||
import { GetProvidersForType } from "./helpers"; |
||||
|
||||
// cache
|
||||
const resultCache = new SimpleCache<MWQuery, MWMassProviderOutput>(); |
||||
resultCache.setCompare((a,b) => a.searchQuery === b.searchQuery && a.type === b.type); |
||||
resultCache.initialize(); |
||||
|
||||
/* |
||||
** actually call all providers with the search query |
||||
*/ |
||||
async function callProviders( |
||||
query: MWQuery |
||||
): Promise<MWMassProviderOutput> { |
||||
const allQueries = GetProvidersForType(query.type).map< |
||||
Promise<{ media: MWMedia[]; success: boolean; id: string }> |
||||
>(async (provider) => { |
||||
try { |
||||
return { |
||||
media: await provider.searchForMedia(query), |
||||
success: true, |
||||
id: provider.id, |
||||
}; |
||||
} catch (err) { |
||||
console.error(`Failed running provider ${provider.id}`, err, query); |
||||
return { |
||||
media: [], |
||||
success: false, |
||||
id: provider.id, |
||||
}; |
||||
} |
||||
}); |
||||
const allResults = await Promise.all(allQueries); |
||||
const providerResults = allResults.map((provider) => ({ |
||||
success: provider.success, |
||||
id: provider.id, |
||||
})); |
||||
const output: MWMassProviderOutput = { |
||||
results: allResults.flatMap((results) => results.media), |
||||
providers: providerResults, |
||||
stats: { |
||||
total: providerResults.length, |
||||
failed: providerResults.filter((v) => !v.success).length, |
||||
succeeded: providerResults.filter((v) => v.success).length, |
||||
}, |
||||
}; |
||||
|
||||
// save in cache if all successfull
|
||||
if (output.stats.failed === 0) { |
||||
resultCache.set(query, output, 60 * 60); // cache for an hour
|
||||
} |
||||
|
||||
return output; |
||||
} |
||||
|
||||
/* |
||||
** sort results based on query |
||||
*/ |
||||
function sortResults(query: MWQuery, providerResults: MWMassProviderOutput): MWMassProviderOutput { |
||||
const fuse = new Fuse(providerResults.results, { threshold: 0.3, keys: ["title"] }); |
||||
providerResults.results = fuse.search(query.searchQuery).map((v) => v.item); |
||||
return providerResults; |
||||
} |
||||
|
||||
/* |
||||
** Call search on all providers that matches query type |
||||
*/ |
||||
export async function SearchProviders( |
||||
query: MWQuery |
||||
): Promise<MWMassProviderOutput> { |
||||
// input normalisation
|
||||
query.searchQuery = query.searchQuery.toLowerCase().trim(); |
||||
|
||||
// consult cache first
|
||||
let output = resultCache.get(query); |
||||
if (!output) |
||||
output = await callProviders(query); |
||||
|
||||
// sort results
|
||||
output = sortResults(query, output); |
||||
|
||||
if (output.stats.total === output.stats.failed) |
||||
throw new Error("All Scrapers failed"); |
||||
return output; |
||||
} |
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
export class SimpleCache<Key, Value> { |
||||
protected readonly INTERVAL_MS = 2 * 60 * 1000; // 2 minutes
|
||||
|
||||
protected _interval: NodeJS.Timer | null = null; |
||||
protected _compare: ((a: Key, b: Key) => boolean) | null = null; |
||||
protected _storage: { key: Key; value: Value; expiry: Date }[] = []; |
||||
|
||||
/* |
||||
** initialize store, will start the interval |
||||
*/ |
||||
public initialize(): void { |
||||
if (this._interval) throw new Error("cache is already initialized"); |
||||
this._interval = setInterval(() => { |
||||
const now = new Date(); |
||||
this._storage.filter((val) => { |
||||
if (val.expiry < now) return false; // remove if expiry date is in the past
|
||||
return true; |
||||
}); |
||||
}, this.INTERVAL_MS); |
||||
} |
||||
|
||||
/* |
||||
** destroy cache instance, its not safe to use the instance after calling this |
||||
*/ |
||||
public destroy(): void { |
||||
if (this._interval) |
||||
clearInterval(this._interval); |
||||
this.clear(); |
||||
} |
||||
|
||||
/* |
||||
** Set compare function, function must return true if A & B are equal |
||||
*/ |
||||
public setCompare(compare: (a: Key, b: Key) => boolean): void { |
||||
this._compare = compare; |
||||
} |
||||
|
||||
/* |
||||
** check if cache contains the item |
||||
*/ |
||||
public has(key: Key): boolean { |
||||
return !!this.get(key); |
||||
} |
||||
|
||||
/* |
||||
** get item from cache |
||||
*/ |
||||
public get(key: Key): Value | undefined { |
||||
if (!this._compare) throw new Error("Compare function not set"); |
||||
const foundValue = this._storage.find(item => this._compare && this._compare(item.key, key)); |
||||
if (!foundValue) |
||||
return undefined; |
||||
return foundValue.value; |
||||
} |
||||
|
||||
/* |
||||
** set item from cache, if it already exists, it will overwrite |
||||
*/ |
||||
public set(key: Key, value: Value, expirySeconds: number): void { |
||||
if (!this._compare) throw new Error("Compare function not set"); |
||||
const foundValue = this._storage.find(item => this._compare && this._compare(item.key, key)); |
||||
const expiry = new Date((new Date().getTime()) + (expirySeconds * 1000)); |
||||
|
||||
// overwrite old value
|
||||
if (foundValue) { |
||||
foundValue.key = key; |
||||
foundValue.value = value; |
||||
foundValue.expiry = expiry; |
||||
return; |
||||
} |
||||
|
||||
// add new value to storage
|
||||
this._storage.push({ |
||||
key, |
||||
value, |
||||
expiry, |
||||
}) |
||||
} |
||||
|
||||
/* |
||||
** remove item from cache |
||||
*/ |
||||
public remove(key: Key): void { |
||||
if (!this._compare) throw new Error("Compare function not set"); |
||||
this._storage.filter((val) => { |
||||
if (this._compare && this._compare(val.key, key)) return false; // remove if compare is success
|
||||
return true; |
||||
}); |
||||
} |
||||
|
||||
/* |
||||
** clear entire cache storage |
||||
*/ |
||||
public clear(): void { |
||||
this._storage = []; |
||||
} |
||||
} |
Loading…
Reference in new issue