Scrape scene by name (#1712)

* Support scrape scene by name in configs
* Initial scene querying
* Add to manual
This commit is contained in:
WithoutPants
2021-09-14 14:54:53 +10:00
committed by GitHub
parent 565064b441
commit 1a3a2f1f83
18 changed files with 786 additions and 192 deletions

View File

@@ -7,6 +7,7 @@ import {
Form,
Col,
Row,
ButtonGroup,
} from "react-bootstrap";
import Mousetrap from "mousetrap";
import * as GQL from "src/core/generated-graphql";
@@ -18,7 +19,7 @@ import {
useSceneUpdate,
mutateReloadScrapers,
useConfiguration,
queryStashBoxScene,
queryScrapeSceneQueryFragment,
} from "src/core/StashService";
import {
PerformerSelect,
@@ -37,6 +38,7 @@ import { Prompt } from "react-router";
import { SceneMovieTable } from "./SceneMovieTable";
import { RatingStars } from "./RatingStars";
import { SceneScrapeDialog } from "./SceneScrapeDialog";
import { SceneQueryModal } from "./SceneQueryModal";
interface IProps {
scene: GQL.SceneDataFragment;
@@ -60,8 +62,14 @@ export const SceneEditPanel: React.FC<IProps> = ({
);
const Scrapers = useListSceneScrapers();
const [fragmentScrapers, setFragmentScrapers] = useState<GQL.Scraper[]>([]);
const [queryableScrapers, setQueryableScrapers] = useState<GQL.Scraper[]>([]);
const [scraper, setScraper] = useState<GQL.ScraperSourceInput | undefined>();
const [
isScraperQueryModalOpen,
setIsScraperQueryModalOpen,
] = useState<boolean>(false);
const [scrapedScene, setScrapedScene] = useState<GQL.ScrapedScene | null>();
const [coverImagePreview, setCoverImagePreview] = useState<
@@ -181,12 +189,16 @@ export const SceneEditPanel: React.FC<IProps> = ({
});
useEffect(() => {
const newQueryableScrapers = (
Scrapers?.data?.listSceneScrapers ?? []
).filter((s) =>
const toFilter = Scrapers?.data?.listSceneScrapers ?? [];
const newFragmentScrapers = toFilter.filter((s) =>
s.scene?.supported_scrapes.includes(GQL.ScrapeType.Fragment)
);
const newQueryableScrapers = toFilter.filter((s) =>
s.scene?.supported_scrapes.includes(GQL.ScrapeType.Name)
);
setFragmentScrapers(newFragmentScrapers);
setQueryableScrapers(newQueryableScrapers);
}, [Scrapers, stashConfig]);
@@ -273,32 +285,10 @@ export const SceneEditPanel: React.FC<IProps> = ({
ImageUtils.onImageChange(event, onImageLoad);
}
async function onScrapeStashBoxClicked(stashBoxIndex: number) {
async function onScrapeClicked(s: GQL.ScraperSourceInput) {
setIsLoading(true);
try {
const result = await queryStashBoxScene(stashBoxIndex, scene.id);
if (!result.data || !result.data.scrapeSingleScene) {
return;
}
if (result.data.scrapeSingleScene.length > 0) {
setScrapedScene(result.data.scrapeSingleScene[0]);
} else {
Toast.success({
content: "No scenes found",
});
}
} catch (e) {
Toast.error(e);
} finally {
setIsLoading(false);
}
}
async function onScrapeClicked(scraper: GQL.Scraper) {
setIsLoading(true);
try {
const result = await queryScrapeScene(scraper.id, scene.id);
const result = await queryScrapeScene(s, scene.id);
if (!result.data || !result.data.scrapeSingleScene?.length) {
Toast.success({
content: "No scenes found",
@@ -314,6 +304,41 @@ export const SceneEditPanel: React.FC<IProps> = ({
}
}
async function scrapeFromQuery(
s: GQL.ScraperSourceInput,
fragment: GQL.ScrapedSceneDataFragment
) {
setIsLoading(true);
try {
const input: GQL.ScrapedSceneInput = {
date: fragment.date,
details: fragment.details,
remote_site_id: fragment.remote_site_id,
title: fragment.title,
url: fragment.url,
};
const result = await queryScrapeSceneQueryFragment(s, input);
if (!result.data || !result.data.scrapeSingleScene?.length) {
Toast.success({
content: "No scenes found",
});
return;
}
// assume one returned scene
setScrapedScene(result.data.scrapeSingleScene[0]);
} catch (e) {
Toast.error(e);
} finally {
setIsLoading(false);
}
}
function onScrapeQueryClicked(s: GQL.ScraperSourceInput) {
setScraper(s);
setIsScraperQueryModalOpen(true);
}
async function onReloadScrapers() {
setIsLoading(true);
try {
@@ -354,10 +379,79 @@ export const SceneEditPanel: React.FC<IProps> = ({
);
}
function renderScrapeQueryMenu() {
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
if (stashBoxes.length === 0 && queryableScrapers.length === 0) return;
return (
<Dropdown title={intl.formatMessage({ id: "actions.scrape_query" })}>
<Dropdown.Toggle variant="secondary">
<Icon icon="search" />
</Dropdown.Toggle>
<Dropdown.Menu>
{stashBoxes.map((s, index) => (
<Dropdown.Item
key={s.endpoint}
onClick={() => onScrapeQueryClicked({ stash_box_index: index })}
>
{s.name ?? "Stash-Box"}
</Dropdown.Item>
))}
{queryableScrapers.map((s) => (
<Dropdown.Item
key={s.name}
onClick={() => onScrapeQueryClicked({ scraper_id: s.id })}
>
{s.name}
</Dropdown.Item>
))}
<Dropdown.Item onClick={() => onReloadScrapers()}>
<span className="fa-icon">
<Icon icon="sync-alt" />
</span>
<span>
<FormattedMessage id="actions.reload_scrapers" />
</span>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
}
function onSceneSelected(s: GQL.ScrapedSceneDataFragment) {
if (!scraper) return;
if (scraper?.stash_box_index !== undefined) {
// must be stash-box - assume full scene
setScrapedScene(s);
} else {
// must be scraper
scrapeFromQuery(scraper, s);
}
}
const renderScrapeQueryModal = () => {
if (!isScraperQueryModalOpen || !scraper) return;
return (
<SceneQueryModal
scraper={scraper}
onHide={() => setScraper(undefined)}
onSelectScene={(s) => {
setIsScraperQueryModalOpen(false);
setScraper(undefined);
onSceneSelected(s);
}}
name={formik.values.title || ""}
/>
);
};
function renderScraperMenu() {
const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
// TODO - change name based on stashbox configuration
return (
<DropdownButton
className="d-inline-block"
@@ -367,13 +461,16 @@ export const SceneEditPanel: React.FC<IProps> = ({
{stashBoxes.map((s, index) => (
<Dropdown.Item
key={s.endpoint}
onClick={() => onScrapeStashBoxClicked(index)}
onClick={() => onScrapeClicked({ stash_box_index: index })}
>
{s.name ?? "Stash-Box"}
</Dropdown.Item>
))}
{queryableScrapers.map((s) => (
<Dropdown.Item key={s.name} onClick={() => onScrapeClicked(s)}>
{fragmentScrapers.map((s) => (
<Dropdown.Item
key={s.name}
onClick={() => onScrapeClicked({ scraper_id: s.id })}
>
{s.name}
</Dropdown.Item>
))}
@@ -389,44 +486,6 @@ export const SceneEditPanel: React.FC<IProps> = ({
);
}
function maybeRenderStashboxQueryButton() {
// const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? [];
// if (stashBoxes.length === 0) {
// return;
// }
// TODO - hide this button for now, with the view to add it when we get
// the query dialog going
// if (stashBoxes.length === 1) {
// return (
// <Button
// className="mr-1"
// onClick={() => onStashBoxQueryClicked(0)}
// title="Query"
// >
// <Icon className="fa-fw" icon="search" />
// </Button>
// );
// }
// // TODO - change name based on stashbox configuration
// return (
// <Dropdown className="d-inline-block mr-1">
// <Dropdown.Toggle id="stashbox-query-dropdown">
// <Icon className="fa-fw" icon="search" />
// </Dropdown.Toggle>
// <Dropdown.Menu>
// {stashBoxes.map((s, index) => (
// <Dropdown.Item
// key={s.endpoint}
// onClick={() => onStashBoxQueryClicked(index)}
// >
// stash-box
// </Dropdown.Item>
// ))}
// </Dropdown.Menu>
// </Dropdown>
// );
}
function urlScrapable(scrapedUrl: string): boolean {
return (Scrapers?.data?.listSceneScrapers ?? []).some((s) =>
(s?.scene?.urls ?? []).some((u) => scrapedUrl.includes(u))
@@ -556,10 +615,11 @@ export const SceneEditPanel: React.FC<IProps> = ({
message={intl.formatMessage({ id: "dialogs.unsaved_changes" })}
/>
{renderScrapeQueryModal()}
{maybeRenderScrapeDialog()}
<Form noValidate onSubmit={formik.handleSubmit}>
<div className="form-container row px-3 pt-3">
<div className="col-6 edit-buttons mb-3 pl-0">
<div className="edit-buttons mb-3 pl-0">
<Button
className="edit-button"
variant="primary"
@@ -576,10 +636,12 @@ export const SceneEditPanel: React.FC<IProps> = ({
<FormattedMessage id="actions.delete" />
</Button>
</div>
<Col xs={6} className="text-right">
{maybeRenderStashboxQueryButton()}
{renderScraperMenu()}
</Col>
<div className="ml-auto pr-3 text-right d-flex">
<ButtonGroup className="scraper-group">
{renderScraperMenu()}
{renderScrapeQueryMenu()}
</ButtonGroup>
</div>
</div>
<div className="form-container row px-3">
<div className="col-12 col-lg-6 col-xl-12">