feat: improve release parsing and filtering (#257)

* feat(releases): improve parsing

* refactor: extend filtering add more tests

* feat: improve macro

* feat: add and remove fields

* feat: add freeleech percent to bonus

* feat: filter by origin
This commit is contained in:
Ludvig Lundgren 2022-04-30 13:43:51 +02:00 committed by GitHub
parent bb62e724a1
commit e6c151a029
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 3210 additions and 3201 deletions

View file

@ -8,10 +8,11 @@ import { classNames, COL_WIDTHS } from "../../utils";
import { SettingsContext } from "../../utils/Context";
interface MultiSelectProps {
name: string;
label?: string;
options?: [] | any;
name: string;
columns?: COL_WIDTHS;
creatable?: boolean;
}
export const MultiSelect = ({
@ -19,8 +20,16 @@ export const MultiSelect = ({
label,
options,
columns,
creatable,
}: MultiSelectProps) => {
const settingsContext = SettingsContext.useValue();
const handleNewField = (value: string) => ({
value: value.toUpperCase(),
label: value.toUpperCase(),
key: value,
});
return (
<div
className={classNames(
@ -42,11 +51,17 @@ export const MultiSelect = ({
<RMSC
{...field}
type="select"
options={options}
options={[...[...options, ...field.value.map((i: any) => ({ value: i.value ?? i, label: i.label ?? i}))].reduce((map, obj) => map.set(obj.value, obj), new Map()).values()]}
labelledBy={name}
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value === item))}
isCreatable={creatable}
onCreateOption={handleNewField}
value={field.value && field.value.map((item: any) => ({
value: item.value ? item.value : item,
label: item.label ? item.label : item,
}))}
onChange={(values: any) => {
const am = values && values.map((i: any) => i.value);
setFieldValue(field.name, am);
}}
className={settingsContext.darkTheme ? "dark" : ""}

View file

@ -12,29 +12,25 @@ export const resolutions = [
export const RESOLUTION_OPTIONS = resolutions.map(r => ({ value: r, label: r, key: r}));
export const codecs = [
"AVC",
"Remux",
"h.264 Remux",
"h.265 Remux",
"HEVC",
"VC-1",
"VC-1 Remux",
"h264",
"h265",
"H.264",
"H.265",
"x264",
"x265",
"h264 10-bit",
"h265 10-bit",
"x264 10-bit",
"x265 10-bit",
"AVC",
"VC-1",
"AV1",
"XviD"
];
export const CODECS_OPTIONS = codecs.map(v => ({ value: v, label: v, key: v}));
export const sources = [
"WEB-DL",
"BluRay",
"UHD.BluRay",
"WEB-DL",
"WEB",
"WEBRip",
"BD5",
"BD9",
"BDr",
@ -51,7 +47,6 @@ export const sources = [
"HDTV",
"Mixed",
"SiteRip",
"Webrip",
];
export const SOURCES_OPTIONS = sources.map(v => ({ value: v, label: v, key: v}));
@ -68,6 +63,7 @@ export const hdr = [
"HDR",
"HDR10",
"HDR10+",
"HLG",
"DV",
"DV HDR",
"DV HDR10",
@ -78,6 +74,13 @@ export const hdr = [
export const HDR_OPTIONS = hdr.map(v => ({ value: v, label: v, key: v}));
export const quality_other = [
"REMUX",
"HYBRID",
"REPACK",
];
export const OTHER_OPTIONS = quality_other.map(v => ({ value: v, label: v, key: v}));
export const formatMusic = [
"MP3",
@ -135,11 +138,20 @@ export const releaseTypeMusic = [
"Demo",
"Concert Recording",
"DJ Mix",
"Unkown",
"Unknown",
];
export const RELEASE_TYPE_MUSIC_OPTIONS = releaseTypeMusic.map(v => ({ value: v, label: v, key: v}));
export const originOptions = [
"P2P",
"Internal",
"SCENE",
"O-SCENE",
];
export const ORIGIN_OPTIONS = originOptions.map(v => ({ value: v, label: v, key: v}));
export interface RadioFieldsetOption {
label: string;
description: string;

View file

@ -26,7 +26,7 @@ import {
FORMATS_OPTIONS,
SOURCES_MUSIC_OPTIONS,
QUALITY_MUSIC_OPTIONS,
RELEASE_TYPE_MUSIC_OPTIONS
RELEASE_TYPE_MUSIC_OPTIONS, OTHER_OPTIONS, ORIGIN_OPTIONS
} from "../../domain/constants";
import { queryClient } from "../../App";
import { APIClient } from "../../api/APIClient";
@ -264,6 +264,8 @@ export default function FilterDetails() {
containers: filter.containers || [],
match_hdr: filter.match_hdr || [],
except_hdr: filter.except_hdr || [],
match_other: filter.match_other || [],
except_other: filter.except_other || [],
seasons: filter.seasons,
episodes: filter.episodes,
match_releases: filter.match_releases,
@ -288,6 +290,7 @@ export default function FilterDetails() {
perfect_flac: filter.perfect_flac,
artists: filter.artists,
albums: filter.albums,
origins: filter.origins || [],
indexers: filter.indexers || [],
actions: filter.actions || [],
} as Filter}
@ -403,18 +406,23 @@ function MoviesTv() {
<TitleSubtitle title="Quality" subtitle="Set resolution, source, codec and related match constraints" />
<div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="resolutions" options={RESOLUTION_OPTIONS} label="resolutions" columns={6} />
<MultiSelect name="sources" options={SOURCES_OPTIONS} label="sources" columns={6} />
<MultiSelect name="resolutions" options={RESOLUTION_OPTIONS} label="resolutions" columns={6} creatable={true} />
<MultiSelect name="sources" options={SOURCES_OPTIONS} label="sources" columns={6} creatable={true} />
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="codecs" options={CODECS_OPTIONS} label="codecs" columns={6} />
<MultiSelect name="containers" options={CONTAINER_OPTIONS} label="containers" columns={6} />
<MultiSelect name="codecs" options={CODECS_OPTIONS} label="codecs" columns={6} creatable={true} />
<MultiSelect name="containers" options={CONTAINER_OPTIONS} label="containers" columns={6} creatable={true} />
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="match_hdr" options={HDR_OPTIONS} label="Match HDR" columns={6} />
<MultiSelect name="except_hdr" options={HDR_OPTIONS} label="Except HDR" columns={6} />
<MultiSelect name="match_hdr" options={HDR_OPTIONS} label="Match HDR" columns={6} creatable={true} />
<MultiSelect name="except_hdr" options={HDR_OPTIONS} label="Except HDR" columns={6} creatable={true} />
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<MultiSelect name="match_other" options={OTHER_OPTIONS} label="Match Other" columns={6} creatable={true} />
<MultiSelect name="except_other" options={OTHER_OPTIONS} label="Except Other" columns={6} creatable={true} />
</div>
</div>
</div>
@ -474,134 +482,76 @@ function Music() {
}
function Advanced() {
const [releasesIsOpen, toggleReleases] = useToggle(false)
const [groupsIsOpen, toggleGroups] = useToggle(false)
const [categoriesIsOpen, toggleCategories] = useToggle(false)
const [uploadersIsOpen, toggleUploaders] = useToggle(false)
const [freeleechIsOpen, toggleFreeleech] = useToggle(false)
return (
<div>
<div className="mt-6 lg:pb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center cursor-pointer" onClick={toggleReleases}>
<div className="-ml-2 -mt-2 flex flex-wrap items-baseline">
<h3 className="ml-2 mt-2 text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">Releases</h3>
<p className="ml-2 mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">Match only certain release names and/or ignore other release names</p>
</div>
<div className="mt-3 sm:mt-0 sm:ml-4">
<button
type="button"
className="inline-flex items-center px-4 py-2 border-transparent text-sm font-medium text-white"
>
{releasesIsOpen ? <ChevronDownIcon className="h-6 w-6 text-gray-500" aria-hidden="true" /> : <ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />}
</button>
</div>
<CollapsableSection title="Releases" subtitle="Match only certain release names and/or ignore other release names">
<TextField name="match_releases" label="Match releases" columns={6} placeholder="eg. *some?movie*,*some?show*s01*" />
<TextField name="except_releases" label="Except releases" columns={6} placeholder="" />
</CollapsableSection>
<CollapsableSection title="Groups" subtitle="Match only certain groups and/or ignore other groups">
<TextField name="match_release_groups" label="Match release groups" columns={6} placeholder="eg. group1,group2" />
<TextField name="except_release_groups" label="Except release groups" columns={6} placeholder="eg. badgroup1,badgroup2" />
</CollapsableSection>
<CollapsableSection title="Categories and tags" subtitle="Match or ignore categories or tags">
<TextField name="match_categories" label="Match categories" columns={6} placeholder="eg. *category*,category1" />
<TextField name="except_categories" label="Except categories" columns={6} placeholder="eg. *category*" />
<TextField name="tags" label="Match tags" columns={6} placeholder="eg. tag1,tag2" />
<TextField name="except_tags" label="Except tags" columns={6} placeholder="eg. tag1,tag2" />
</CollapsableSection>
<CollapsableSection title="Uploaders" subtitle="Match or ignore uploaders">
<TextField name="match_uploaders" label="Match uploaders" columns={6} placeholder="eg. uploader1" />
<TextField name="except_uploaders" label="Except uploaders" columns={6} placeholder="eg. anonymous" />
</CollapsableSection>
<CollapsableSection title="Origins" subtitle="Match Internals, scene, p2p etc if announced">
<MultiSelect name="origins" options={ORIGIN_OPTIONS} label="Origins" columns={6} />
</CollapsableSection>
<CollapsableSection title="Freeleech" subtitle="Match only freeleech and freeleech percent">
<div className="col-span-6">
<SwitchGroup name="freeleech" label="Freeleech" />
</div>
{releasesIsOpen && (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField name="match_releases" label="Match releases" columns={6} placeholder="eg. *some?movie*,*some?show*s01*" />
<TextField name="except_releases" label="Except releases" columns={6} placeholder="" />
</div>
)}
</div>
<div className="mt-6 lg:pb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center cursor-pointer" onClick={toggleGroups}>
<div className="-ml-2 -mt-2 flex flex-wrap items-baseline">
<h3 className="ml-2 mt-2 text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">Groups</h3>
<p className="ml-2 mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">Match only certain groups and/or ignore other groups</p>
</div>
<div className="mt-3 sm:mt-0 sm:ml-4">
<button
type="button"
className="inline-flex items-center px-4 py-2 border-transparent text-sm font-medium text-white"
>
{groupsIsOpen ? <ChevronDownIcon className="h-6 w-6 text-gray-500" aria-hidden="true" /> : <ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />}
</button>
</div>
<TextField name="freeleech_percent" label="Freeleech percent" columns={6} />
</CollapsableSection>
</div>
)
}
interface CollapsableSectionProps {
title: string;
subtitle: string;
children: any;
}
function CollapsableSection({ title, subtitle, children }: CollapsableSectionProps) {
const [isOpen, toggleOpen] = useToggle(false)
return(
<div className="mt-6 lg:pb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center cursor-pointer" onClick={toggleOpen}>
<div className="-ml-2 -mt-2 flex flex-wrap items-baseline">
<h3 className="ml-2 mt-2 text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">{title}</h3>
<p className="ml-2 mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">{subtitle}</p>
</div>
{groupsIsOpen && (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField name="match_release_groups" label="Match release groups" columns={6} placeholder="eg. group1,group2" />
<TextField name="except_release_groups" label="Except release groups" columns={6} placeholder="eg. badgroup1,badgroup2" />
</div>
)}
</div>
<div className="mt-6 lg:pb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center cursor-pointer" onClick={toggleCategories}>
<div className="-ml-2 -mt-2 flex flex-wrap items-baseline">
<h3 className="ml-2 mt-2 text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">Categories and tags</h3>
<p className="ml-2 mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">Match or ignore categories or tags</p>
</div>
<div className="mt-3 sm:mt-0 sm:ml-4">
<button
type="button"
className="inline-flex items-center px-4 py-2 border-transparent text-sm font-medium text-white"
>
{categoriesIsOpen ? <ChevronDownIcon className="h-6 w-6 text-gray-500" aria-hidden="true" /> : <ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />}
</button>
</div>
<div className="mt-3 sm:mt-0 sm:ml-4">
<button
type="button"
className="inline-flex items-center px-4 py-2 border-transparent text-sm font-medium text-white"
>
{isOpen ? <ChevronDownIcon className="h-6 w-6 text-gray-500" aria-hidden="true" /> : <ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />}
</button>
</div>
{categoriesIsOpen && (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField name="match_categories" label="Match categories" columns={6} placeholder="eg. *category*,category1" />
<TextField name="except_categories" label="Except categories" columns={6} placeholder="eg. *category*" />
<TextField name="tags" label="Match tags" columns={6} placeholder="eg. tag1,tag2" />
<TextField name="except_tags" label="Except tags" columns={6} placeholder="eg. tag1,tag2" />
</div>
)}
</div>
<div className="mt-6 lg:pb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center cursor-pointer" onClick={toggleUploaders}>
<div className="-ml-2 -mt-2 flex flex-wrap items-baseline">
<h3 className="ml-2 mt-2 text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">Uploaders</h3>
<p className="ml-2 mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">Match or ignore uploaders</p>
</div>
<div className="mt-3 sm:mt-0 sm:ml-4">
<button
type="button"
className="inline-flex items-center px-4 py-2 border-transparent text-sm font-medium text-white"
>
{uploadersIsOpen ? <ChevronDownIcon className="h-6 w-6 text-gray-500" aria-hidden="true" /> : <ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />}
</button>
</div>
{isOpen && (
<div className="mt-6 grid grid-cols-12 gap-6">
{children}
</div>
{uploadersIsOpen && (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField name="match_uploaders" label="Match uploaders" columns={6} placeholder="eg. uploader1" />
<TextField name="except_uploaders" label="Except uploaders" columns={6} placeholder="eg. anonymous" />
</div>
)}
</div>
<div className="mt-6 lg:pb-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex justify-between items-center cursor-pointer" onClick={toggleFreeleech}>
<div className="-ml-2 -mt-2 flex flex-wrap items-baseline">
<h3 className="ml-2 mt-2 text-lg leading-6 font-medium text-gray-900 dark:text-gray-200">Freeleech</h3>
<p className="ml-2 mt-1 text-sm text-gray-500 dark:text-gray-400 truncate">Match only freeleech and freeleech percent</p>
</div>
<div className="mt-3 sm:mt-0 sm:ml-4">
<button
type="button"
className="inline-flex items-center px-4 py-2 border-transparent text-sm font-medium text-white"
>
{freeleechIsOpen ? <ChevronDownIcon className="h-6 w-6 text-gray-500" aria-hidden="true" /> : <ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />}
</button>
</div>
</div>
{freeleechIsOpen && (
<div className="mt-6 grid grid-cols-12 gap-6">
<div className="col-span-6">
<SwitchGroup name="freeleech" label="Freeleech" />
</div>
<TextField name="freeleech_percent" label="Freeleech percent" columns={6} />
</div>
)}
</div>
)}
</div>
)
}

View file

@ -14,7 +14,7 @@ interface Filter {
match_release_groups: string;
except_release_groups: string;
scene: boolean;
origins: string;
origins: string[];
freeleech: boolean;
freeleech_percent: string;
shows: string;
@ -26,6 +26,8 @@ interface Filter {
containers: string[];
match_hdr: string[];
except_hdr: string[];
match_other: string[];
except_other: string[];
years: string;
artists: string;
albums: string;