diff --git a/materialious/package-lock.json b/materialious/package-lock.json index 9d2240f0..2a93b292 100644 --- a/materialious/package-lock.json +++ b/materialious/package-lock.json @@ -1,12 +1,12 @@ { "name": "materialious", - "version": "1.16.7", + "version": "1.16.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "materialious", - "version": "1.16.7", + "version": "1.16.8", "hasInstallScript": true, "dependencies": { "@capacitor-community/electron": "^5.0.1", @@ -49,6 +49,7 @@ "mysql2": "^3.18.2", "pg": "^8.19.0", "pg-hstore": "^2.3.4", + "safe-regex2": "^5.0.0", "sequelize": "^6.37.7", "shaka-player": "^4.16.14", "sponsorblock-api": "^0.2.4", @@ -16051,6 +16052,15 @@ "node": ">=8" } }, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -16287,6 +16297,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-regex2": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.0.0.tgz", + "integrity": "sha512-YwJwe5a51WlK7KbOJREPdjNrpViQBI3p4T50lfwPuDhZnE3XGVTlGvi+aolc5+RvxDD6bnUmjVsU9n1eboLUYw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", diff --git a/materialious/package.json b/materialious/package.json index 161b19dc..7b67e903 100644 --- a/materialious/package.json +++ b/materialious/package.json @@ -87,6 +87,7 @@ "mysql2": "^3.18.2", "pg": "^8.19.0", "pg-hstore": "^2.3.4", + "safe-regex2": "^5.0.0", "sequelize": "^6.37.7", "shaka-player": "^4.16.14", "sponsorblock-api": "^0.2.4", @@ -98,4 +99,4 @@ "youtubei.js": "^16.0.1", "zod": "^4.3.6" } -} \ No newline at end of file +} diff --git a/materialious/src/lib/components/player/Player.svelte b/materialious/src/lib/components/player/Player.svelte index 5fc458ab..b0337d4c 100644 --- a/materialious/src/lib/components/player/Player.svelte +++ b/materialious/src/lib/components/player/Player.svelte @@ -4,7 +4,7 @@ import { videoLength } from '$lib/numbers'; import { generateChapterWebVTT, type ParsedDescription } from '$lib/description'; import { Capacitor, SystemBars, SystemBarsStyle, SystemBarType } from '@capacitor/core'; - import { error, type Page } from '@sveltejs/kit'; + import { error } from '@sveltejs/kit'; import Mousetrap from 'mousetrap'; import { CapacitorMusicControls } from 'capacitor-music-controls-plugin'; import shaka from 'shaka-player/dist/shaka-player.ui'; diff --git a/materialious/src/lib/filtering.ts b/materialious/src/lib/filtering.ts new file mode 100644 index 00000000..0bb71e96 --- /dev/null +++ b/materialious/src/lib/filtering.ts @@ -0,0 +1,79 @@ +import { z } from 'zod'; +import type { FeedItem } from './feed'; +import isSafeRegex from 'safe-regex2'; + +const zFilterOperatorEnum = z.enum([ + 'equals', // equal to + 'in', // in a set of values + 'like', // contains (string matching) + 'gt', // greater than + 'lt', // less than + 'regex' // regular expression matching +]); + +// Filter condition schema +const zFilterCondition = z.object({ + field: z.string(), // Field to filter + operator: zFilterOperatorEnum, // Operator + value: z.union([ + // Value to compare against + z.string(), + z.number(), + z.array(z.string()), + z.array(z.number()), + z.string().regex(/.*/) + ]) +}); + +// Logical grouping operator +const zFilterGroup = z.object({ + conditions: z.array(zFilterCondition), // A list of conditions to apply + operator: z.enum(['AND', 'OR']).optional() // Logical grouping of conditions +}); + +const zBlockListSchema = z.array(zFilterGroup); + +export function filterByBlocklist( + data: T[], + blocklist: z.infer +): T[] { + return data.filter((item) => { + return blocklist.every((filterGroup) => { + return filterGroup.conditions.every((condition) => { + const fieldValue = item[condition.field as keyof T]; + + switch (condition.operator) { + case 'equals': + return fieldValue === condition.value; + case 'in': + return ( + Array.isArray(condition.value) && (condition.value as any[]).includes(fieldValue) + ); + case 'like': + return ( + typeof fieldValue === 'string' && + typeof condition.value === 'string' && + fieldValue.includes(condition.value) + ); + case 'gt': + return ( + typeof fieldValue === 'number' && + typeof condition.value === 'number' && + fieldValue > condition.value + ); + case 'lt': + return ( + typeof fieldValue === 'number' && + typeof condition.value === 'number' && + fieldValue < condition.value + ); + case 'regex': + if (typeof condition.value !== 'string' || !isSafeRegex(condition.value)) return false; + return typeof fieldValue === 'string' && new RegExp(condition.value).test(fieldValue); + default: + return false; + } + }); + }); + }); +} diff --git a/materialious/src/routes/(no-layout)/tv/[slug]/+layout.svelte b/materialious/src/routes/(no-layout)/tv/[slug]/+layout.svelte index 6289ebd2..affae6b3 100644 --- a/materialious/src/routes/(no-layout)/tv/[slug]/+layout.svelte +++ b/materialious/src/routes/(no-layout)/tv/[slug]/+layout.svelte @@ -14,7 +14,7 @@ -{#await navigating.completed} +{#await navigating.complete} {:then _}