feat: get variation categories automatically

This commit is contained in:
Tobias Klemp
2025-11-05 20:08:26 +01:00
parent e7bd070b3e
commit c829f9f57b
6 changed files with 137 additions and 40 deletions

View File

@@ -0,0 +1,13 @@
import { Effect } from 'effect'
import type { Page } from 'puppeteer'
import { getSelected } from '.'
import { getBatteryVariations } from '../variations/battery'
export const getBattery = (page: Page) =>
Effect.gen(function* () {
const batteryVariations = yield* getBatteryVariations(page)
const selectedCapacity = yield* getSelected(batteryVariations ?? [])
return selectedCapacity.label
})

View File

@@ -9,6 +9,7 @@ import { getColor } from './color'
import { getSim } from './sim' import { getSim } from './sim'
import { getStockLevel } from './stockLevel' import { getStockLevel } from './stockLevel'
import { getDevices } from './devices' import { getDevices } from './devices'
import { getBattery } from './battery'
export class ExtractSelectedVariationError extends Data.TaggedError( export class ExtractSelectedVariationError extends Data.TaggedError(
'ExtractSelectedVariationError', 'ExtractSelectedVariationError',
@@ -49,6 +50,7 @@ export type PageData = {
color: string color: string
sim: string sim: string
stockLevel: string stockLevel: string
battery: string
devices: DeviceData[] devices: DeviceData[]
} }
@@ -76,8 +78,11 @@ export const getPageData = (page: Page) =>
const sim = yield* getSim(page) const sim = yield* getSim(page)
const stockLevel = yield* getStockLevel(page) const stockLevel = yield* getStockLevel(page)
const devices = yield* getDevices(page) const devices = yield* getDevices(page)
const battery = yield* getBattery(page)
const pageData: PageData = { const pageData: PageData = {
price, price,
productName, productName,
@@ -87,6 +92,7 @@ export const getPageData = (page: Page) =>
sim, sim,
stockLevel, stockLevel,
devices, devices,
battery,
} }
return pageData return pageData

View File

@@ -1,14 +1,15 @@
import { Data, Effect, pipe } from 'effect' import { Data, Effect } from 'effect'
import { openPage } from '../openPage' import { openPage } from '../openPage'
import * as Crawler from '../index' import * as Crawler from '../index'
import { withOperation } from '$lib/server/utils' import { withOperation } from '$lib/server/utils'
import { getProductName } from './data/productName' import { getProductName } from './data/productName'
import { getAllVariations, VariationEnum, type Variation } from './variations' import {
import { getPageData, type PageData } from './data' getAllVariations,
import type { Page } from 'puppeteer' getVariationCategory,
VariationEnum,
} from './variations'
import { import {
getAllVariationCategoryData, getAllVariationCategoryData,
getAllVariationData,
type VariationCategory, type VariationCategory,
} from './variationData' } from './variationData'
@@ -30,37 +31,9 @@ export const crawlClevertronik = Effect.gen(function* () {
yield* Effect.logInfo('opened clevertronik page') yield* Effect.logInfo('opened clevertronik page')
let productName = yield* getProductName(page) const variationCategory = yield* getVariationCategory(page)
const variations = yield* getAllVariations(page) yield* Effect.logInfo(`Got tree`, { variationCategory })
// const variationCategory: VariationCategory = {
// name: 'Capacity',
// variations: variations.capacities,
// subVariationCategory: {
// name: 'Color',
// variations: variations.colors,
// subVariationCategory: {
// name: 'Sim',
// variations: variations.sims,
// subVariationCategory: {
// name: 'Condition',
// variations: variations.conditions,
// subVariationCategory: {
// name: 'Battery',
// variations: variations.battery,
// },
// },
// },
// },
// }
const variationCategory: VariationCategory = {
name: VariationEnum.CAPACITY,
subVariationCategory: {
name: VariationEnum.COLOR,
},
}
const variationData = yield* getAllVariationCategoryData( const variationData = yield* getAllVariationCategoryData(
page, page,
@@ -70,9 +43,11 @@ export const crawlClevertronik = Effect.gen(function* () {
yield* Effect.promise(() => page.browser().close()) yield* Effect.promise(() => page.browser().close())
return { return {
productName, variationCategory,
...variations,
variationData, variationData,
// productName,
// ...variations,
// variationData,
} }
}).pipe( }).pipe(
Effect.provide(Crawler.CrawlerLayer()), Effect.provide(Crawler.CrawlerLayer()),

View File

@@ -34,7 +34,7 @@ export const getAllVariationCategoryData = (
const variations = const variations =
yield* getVariationHandler[variationCategory.name](variationPage) yield* getVariationHandler[variationCategory.name](variationPage)
for (const variation of variations.filter((v) => v.active)) { for (const variation of variations?.filter((v) => v.active) ?? []) {
if (!variation.selected) { if (!variation.selected) {
yield* gotoVariation(variationPage, variation) yield* gotoVariation(variationPage, variation)
} }

View File

@@ -6,5 +6,7 @@ const BATTERY_CONTAINER_SELECTOR = '.new_battery_options'
export const getBatteryVariations = (page: Page) => export const getBatteryVariations = (page: Page) =>
Effect.gen(function* () { Effect.gen(function* () {
return yield* getVariations(page, BATTERY_CONTAINER_SELECTOR, 'DIV') return yield* getVariations(page, BATTERY_CONTAINER_SELECTOR, 'DIV').pipe(
Effect.catchTag('VariationParseError', () => Effect.succeed(undefined)),
)
}) })

View File

@@ -6,6 +6,7 @@ import { getColorVariations } from './color'
import { getSimVariations } from './sim' import { getSimVariations } from './sim'
import { getConditionVariations } from './condition' import { getConditionVariations } from './condition'
import { getBatteryVariations } from './battery' import { getBatteryVariations } from './battery'
import type { VariationCategory } from '../variationData'
export type Variation = { export type Variation = {
label: string label: string
@@ -27,12 +28,19 @@ export class VariationParseError extends Data.TaggedError(
message?: string message?: string
}> {} }> {}
export class VariationCategoryError extends Data.TaggedError(
'VariationCategoryError',
)<{
cause?: unknown
message?: string
}> {}
export enum VariationEnum { export enum VariationEnum {
CAPACITY = 'Capacity', CAPACITY = 'Capacity',
COLOR = 'Color', COLOR = 'Color',
SIM = 'Sim', SIM = 'Sim',
CONDITION = 'CONDITION', CONDITION = 'CONDITION',
BATTERY = 'BATTER', BATTERY = 'BATTERY',
} }
export const getVariationHandler = { export const getVariationHandler = {
@@ -116,3 +124,96 @@ export const getAllVariations = (page: Page) =>
battery, battery,
} }
}) })
const attributeVariationMapping: Record<string, VariationEnum> = {
prop_19: VariationEnum.CAPACITY,
prop_17: VariationEnum.COLOR,
prop_39: VariationEnum.SIM,
condition: VariationEnum.CONDITION,
}
export const getVariationCategory = (page: Page) =>
Effect.gen(function* () {
const attributes: string[] = yield* Effect.tryPromise({
try: () =>
page.evaluate(() => {
const divs = document.querySelectorAll('[data-scrollel]')
const attributes: string[] = []
divs.forEach((div) => {
const val = div.getAttribute('data-scrollel')
if (!val) return
attributes.push(val)
})
return attributes
}),
catch: (cause) =>
new VariationParseError({
message: 'Could not get variation attributes',
cause,
}),
})
const hasBatteryVariation = yield* Effect.tryPromise({
try: () =>
page.evaluate(() => {
const batteryDiv: HTMLDivElement | null = document.querySelector(
'.new_battery_options',
)
return Boolean(batteryDiv)
}),
catch: (cause) =>
new VariationParseError({
message: 'Error getting possible battery variation',
cause,
}),
})
let variationCategory: VariationCategory | undefined
let currentCategory: VariationCategory | undefined
for (const attr of attributes) {
const variationKey = attributeVariationMapping[attr]
if (!variationKey) continue
if (!variationCategory) {
variationCategory = {
name: variationKey,
}
currentCategory = variationCategory
continue
}
if (!currentCategory) {
continue
}
const newCategory: VariationCategory = {
name: variationKey,
}
currentCategory.subVariationCategory = newCategory
currentCategory = newCategory
}
if (currentCategory && hasBatteryVariation) {
currentCategory.subVariationCategory = {
name: VariationEnum.BATTERY,
}
}
if (!variationCategory) {
return yield* Effect.fail(
new VariationCategoryError({
message: 'Unable to get variation category tree',
}),
)
}
return variationCategory
})