feat: Allow matching of folders based on parsed date (#2074)
- For: #1748
This commit is contained in:
@@ -164,7 +164,7 @@ export class HAFoldersEngine implements FoldersEngine {
|
||||
this._metadataGenerator.generate(media, parent, nextComponent?.ha?.parsers),
|
||||
|
||||
...(nextComponent && {
|
||||
matcher: (media: BrowseMedia) =>
|
||||
matcher: (media: RichBrowseMedia<BrowseMediaMetadata>) =>
|
||||
this._mediaMatcher.match(hass, media, {
|
||||
matchers: nextComponent.ha?.matchers,
|
||||
// Set foldersOnly to true if there are more stages in the path,
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { sub } from 'date-fns';
|
||||
import { ConditionState } from '../../../conditions/types';
|
||||
import { Matcher, TemplateMatcher, TitleMatcher } from '../../../config/schema/folders';
|
||||
import { BrowseMedia } from '../../../ha/browse-media/types';
|
||||
import {
|
||||
DateMatcher,
|
||||
Matcher,
|
||||
StartDateMatcher,
|
||||
TemplateMatcher,
|
||||
TitleMatcher,
|
||||
} from '../../../config/schema/folders';
|
||||
import { BrowseMediaMetadata, RichBrowseMedia } from '../../../ha/browse-media/types';
|
||||
import { HomeAssistant } from '../../../ha/types';
|
||||
import { regexpExtract } from '../../../utils/regexp-extract';
|
||||
import { TemplateRenderer } from '../../templates';
|
||||
@@ -11,7 +18,7 @@ export class MediaMatcher {
|
||||
|
||||
public match(
|
||||
hass: HomeAssistant,
|
||||
media: BrowseMedia,
|
||||
media: RichBrowseMedia<BrowseMediaMetadata>,
|
||||
options?: {
|
||||
foldersOnly?: boolean;
|
||||
matchers?: Matcher[];
|
||||
@@ -24,16 +31,25 @@ export class MediaMatcher {
|
||||
|
||||
for (const matcher of options?.matchers ?? []) {
|
||||
switch (matcher.type) {
|
||||
case 'date':
|
||||
case 'startdate':
|
||||
if (!this._matchStartDate(matcher, media)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'template':
|
||||
if (!this._matchTemplate(hass, matcher, media, options?.conditionState)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'title':
|
||||
if (!this._matchTitle(matcher, media)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'or':
|
||||
if (
|
||||
!matcher.matchers.some((subMatcher) =>
|
||||
@@ -53,10 +69,28 @@ export class MediaMatcher {
|
||||
return true;
|
||||
}
|
||||
|
||||
private _matchStartDate(
|
||||
matcher: DateMatcher | StartDateMatcher,
|
||||
media: RichBrowseMedia<BrowseMediaMetadata>,
|
||||
): boolean {
|
||||
const startDate = media._metadata?.startDate;
|
||||
return (
|
||||
!!startDate &&
|
||||
startDate >=
|
||||
sub(new Date(), {
|
||||
years: matcher.since.years ?? 0,
|
||||
months: matcher.since.months ?? 0,
|
||||
days: matcher.since.days ?? 0,
|
||||
hours: matcher.since.hours ?? 0,
|
||||
minutes: matcher.since.minutes ?? 0,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _matchTemplate(
|
||||
hass: HomeAssistant,
|
||||
matcher: TemplateMatcher,
|
||||
media: BrowseMedia,
|
||||
media: RichBrowseMedia<BrowseMediaMetadata>,
|
||||
conditionState?: ConditionState,
|
||||
): boolean {
|
||||
return (
|
||||
@@ -70,7 +104,10 @@ export class MediaMatcher {
|
||||
);
|
||||
}
|
||||
|
||||
private _matchTitle(matcher: TitleMatcher, media: BrowseMedia): boolean {
|
||||
private _matchTitle(
|
||||
matcher: TitleMatcher,
|
||||
media: RichBrowseMedia<BrowseMediaMetadata>,
|
||||
): boolean {
|
||||
const valueToMatch = matcher.regexp
|
||||
? regexpExtract(matcher.regexp, media.title, { groupName: REGEXP_GROUP_VALUE_KEY })
|
||||
: media.title;
|
||||
|
||||
@@ -14,12 +14,10 @@ const folderConfigDefault = {
|
||||
ha: {},
|
||||
};
|
||||
|
||||
const parserBaseSchema = z.object({
|
||||
regexp: regexSchema.optional(),
|
||||
});
|
||||
const startdateParserSchema = parserBaseSchema.extend({
|
||||
const startdateParserSchema = z.object({
|
||||
type: z.literal('startdate'),
|
||||
format: z.string().optional(),
|
||||
regexp: regexSchema.optional(),
|
||||
});
|
||||
// Simple alias date -> startdate.
|
||||
const dateParserSchema = startdateParserSchema.extend({
|
||||
@@ -31,13 +29,31 @@ const parserSchema = z.discriminatedUnion('type', [
|
||||
]);
|
||||
export type Parser = z.infer<typeof parserSchema>;
|
||||
|
||||
const templateMatcherSchema = parserBaseSchema.extend({
|
||||
const startDateMatcherSchema = z.object({
|
||||
type: z.literal('startdate'),
|
||||
since: z.object({
|
||||
years: z.number().int().min(0).optional(),
|
||||
months: z.number().int().min(0).optional(),
|
||||
days: z.number().int().min(0).optional(),
|
||||
hours: z.number().int().min(0).optional(),
|
||||
minutes: z.number().int().min(0).optional(),
|
||||
}),
|
||||
});
|
||||
export type StartDateMatcher = z.infer<typeof startDateMatcherSchema>;
|
||||
|
||||
// Simple alias date -> startdate.
|
||||
const dateMatcherSchema = startDateMatcherSchema.extend({
|
||||
type: z.literal('date'),
|
||||
});
|
||||
export type DateMatcher = z.infer<typeof dateMatcherSchema>;
|
||||
|
||||
const templateMatcherSchema = z.object({
|
||||
type: z.literal('template'),
|
||||
value_template: z.string(),
|
||||
});
|
||||
export type TemplateMatcher = z.infer<typeof templateMatcherSchema>;
|
||||
|
||||
const titleMatcherSchema = parserBaseSchema.extend({
|
||||
const titleMatcherSchema = z.object({
|
||||
type: z.literal('title'),
|
||||
regexp: regexSchema.optional(),
|
||||
title: z.string().optional(),
|
||||
@@ -53,7 +69,9 @@ const orMatcherSchema: z.ZodSchema<OrMatcher, z.ZodTypeDef> = z.object({
|
||||
matchers: z.array(z.lazy(() => matcherSchema)),
|
||||
});
|
||||
export const matcherSchema = z.union([
|
||||
dateMatcherSchema,
|
||||
orMatcherSchema,
|
||||
startDateMatcherSchema,
|
||||
templateMatcherSchema,
|
||||
titleMatcherSchema,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user