Parsování jídel na celý týden
This commit is contained in:
@@ -2,7 +2,6 @@ import axios from "axios";
|
||||
import { load } from 'cheerio';
|
||||
import { Food } from "../../types";
|
||||
import { getMenuSladovnickaMock, getMenuTechTowerMock, getMenuUMotlikuMock } from "./mock";
|
||||
import { getDayOfWeekIndex } from "./utils";
|
||||
|
||||
// Fráze v názvech jídel, které naznačují že se jedná o polévku
|
||||
const SOUP_NAMES = ['polévka', 'česnečka', 'česnekový krém', 'cibulačka', 'vývar']
|
||||
@@ -48,181 +47,184 @@ const getHtml = async (url: string): Promise<any> => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Získá obědovou nabídku Sladovnické pro předané datum.
|
||||
* Získá obědovou nabídku Sladovnické pro jeden týden.
|
||||
*
|
||||
* @param date datum, pro které získat menu
|
||||
* @param firstDayOfWeek první den v týdnu, pro který získat menu
|
||||
* @param mock zda vrátit mock data
|
||||
* @returns seznam jídel pro dané datum
|
||||
* @returns seznam jídel pro daný týden
|
||||
*/
|
||||
export const getMenuSladovnicka = async (date: Date = new Date(), mock: boolean = false): Promise<Food[]> => {
|
||||
export const getMenuSladovnicka = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
|
||||
if (mock) {
|
||||
return getMenuSladovnickaMock(date);
|
||||
}
|
||||
const todayDayIndex = getDayOfWeekIndex(date);
|
||||
if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
|
||||
return [];
|
||||
return getMenuSladovnickaMock();
|
||||
}
|
||||
|
||||
const html = await getHtml(SLADOVNICKA_URL);
|
||||
const $ = load(html);
|
||||
// Najdeme index pro vstupní datum (např. při svátcích bude posunutý)
|
||||
// TODO validovat, že vstupní datum je v aktuálním týdnu
|
||||
// TODO tenhle způsob je zbytečně komplikovaný - stačilo by hledat rovnou v div.tab-content, protože každý den tam má datum taky (akorát je print-only)
|
||||
|
||||
const list = $('ul.tab-links').children();
|
||||
const searchedDayText = `${date.getDate()}.${date.getMonth() + 1}.${capitalize(DAYS_IN_WEEK[todayDayIndex])}`;
|
||||
let index = undefined;
|
||||
list.each((i, dayRow) => {
|
||||
const rowText = $(dayRow).first().text().trim();
|
||||
if (rowText === searchedDayText) {
|
||||
index = i;
|
||||
return;
|
||||
const result: Food[][] = [];
|
||||
for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
|
||||
const currentDate = new Date(firstDayOfWeek);
|
||||
currentDate.setDate(firstDayOfWeek.getDate() + dayIndex);
|
||||
const searchedDayText = `${currentDate.getDate()}.${currentDate.getMonth() + 1}.${capitalize(DAYS_IN_WEEK[dayIndex])}`;
|
||||
// Najdeme index pro vstupní datum (např. při svátcích bude posunutý)
|
||||
// TODO validovat, že vstupní datum je v aktuálním týdnu
|
||||
// TODO tenhle způsob je zbytečně komplikovaný - stačilo by hledat rovnou v div.tab-content, protože každý den tam má datum taky (akorát je print-only)
|
||||
let index = undefined;
|
||||
list.each((i, dayRow) => {
|
||||
const rowText = $(dayRow).first().text().trim();
|
||||
if (rowText === searchedDayText) {
|
||||
index = i;
|
||||
return;
|
||||
}
|
||||
})
|
||||
if (index === undefined) {
|
||||
// Pravděpodobně svátek, nebo je zavřeno
|
||||
result[dayIndex] = [{
|
||||
amount: undefined,
|
||||
name: "Pro daný den nebyla nalezena denní nabídka",
|
||||
price: "",
|
||||
isSoup: false,
|
||||
}];
|
||||
continue;
|
||||
}
|
||||
})
|
||||
if (index === undefined) {
|
||||
// Pravděpodobně svátek, nebo je zavřeno
|
||||
return [{
|
||||
amount: undefined,
|
||||
name: "Pro daný den nebyla nalezena denní nabídka",
|
||||
price: "",
|
||||
isSoup: false,
|
||||
}];
|
||||
}
|
||||
|
||||
// Dle dohledaného indexu najdeme správný tabpanel
|
||||
const rows = $('div.tab-content').children();
|
||||
if (index >= rows.length) {
|
||||
throw Error("V HTML nebyl nalezen řádek menu pro index " + index);
|
||||
}
|
||||
const tabPanel = $(rows.get(index));
|
||||
|
||||
// Opětovná validace, že daný tabpanel je pro vstupní datum
|
||||
const headers = tabPanel.find('h2');
|
||||
if (headers.length !== 3) {
|
||||
throw Error("Neočekávaný počet elementů h2 v menu pro datum " + searchedDayText + ", očekávány 3, ale nalezeno bylo " + headers.length);
|
||||
}
|
||||
const dayText = $(headers.get(0)).text().trim();
|
||||
if (dayText !== searchedDayText) {
|
||||
throw Error("Neočekávaný datum na řádce nalezeného dne: '" + dayText + "', ale očekáváno bylo '" + searchedDayText + "'");
|
||||
}
|
||||
|
||||
// V tabpanelu očekáváme dvě tabulky - pro polévku a pro hlavní jídlo
|
||||
const tables = tabPanel.find('table');
|
||||
if (tables.length !== 2) {
|
||||
throw Error("Neočekávaný počet tabulek na řádce nalezeného dne: " + tables.length + ", ale očekávány byly 2");
|
||||
}
|
||||
const results: Food[] = [];
|
||||
// Polévka - div -> table -> tbody -> tr -> 3x td
|
||||
const soupCells = $(tables.get(0)).children().first().children().first().children();
|
||||
if (soupCells.length !== 3) {
|
||||
throw Error("Neočekávaný počet buněk v tabulce polévky: " + soupCells.length + ", ale očekávány byly 3");
|
||||
}
|
||||
results.push({
|
||||
amount: sanitizeText($(soupCells.get(0)).text()),
|
||||
name: sanitizeText($(soupCells.get(1)).text()),
|
||||
price: sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')),
|
||||
isSoup: true,
|
||||
});
|
||||
// Hlavní jídla - div -> table -> tbody -> 3x tr
|
||||
const mainCourseRows = $(tables.get(1)).children().first().children();
|
||||
// Záměrně zakomentováno - občas je ve Sladovnické jídel méně
|
||||
// if (mainCourseRows.length !== 3) {
|
||||
// throw Error("Neočekávaný počet řádek jídel: " + mainCourseRows.length + ", ale očekávány byly 3");
|
||||
// }
|
||||
mainCourseRows.each((i, foodRow) => {
|
||||
const foodCells = $(foodRow).children();
|
||||
if (foodCells.length !== 3) {
|
||||
throw Error("Neočekávaný počet buněk v řádku jídla: " + foodCells.length + ", ale očekávány byly 3");
|
||||
// Dle dohledaného indexu najdeme správný tabpanel
|
||||
const rows = $('div.tab-content').children();
|
||||
if (index >= rows.length) {
|
||||
throw Error("V HTML nebyl nalezen řádek menu pro index " + index);
|
||||
}
|
||||
results.push({
|
||||
amount: sanitizeText($(foodCells.get(0)).text()),
|
||||
name: sanitizeText($(foodCells.get(1)).text()),
|
||||
price: sanitizeText($(foodCells.get(2)).text().replace(' ', '\xA0')),
|
||||
isSoup: false,
|
||||
const tabPanel = $(rows.get(index));
|
||||
|
||||
// Opětovná validace, že daný tabpanel je pro vstupní datum
|
||||
const headers = tabPanel.find('h2');
|
||||
if (headers.length !== 3) {
|
||||
throw Error("Neočekávaný počet elementů h2 v menu pro datum " + searchedDayText + ", očekávány 3, ale nalezeno bylo " + headers.length);
|
||||
}
|
||||
const dayText = $(headers.get(0)).text().trim();
|
||||
if (dayText !== searchedDayText) {
|
||||
throw Error("Neočekávaný datum na řádce nalezeného dne: '" + dayText + "', ale očekáváno bylo '" + searchedDayText + "'");
|
||||
}
|
||||
|
||||
// V tabpanelu očekáváme dvě tabulky - pro polévku a pro hlavní jídlo
|
||||
const tables = tabPanel.find('table');
|
||||
if (tables.length !== 2) {
|
||||
throw Error("Neočekávaný počet tabulek na řádce nalezeného dne: " + tables.length + ", ale očekávány byly 2");
|
||||
}
|
||||
const currentDayFood: Food[] = [];
|
||||
// Polévka - div -> table -> tbody -> tr -> 3x td
|
||||
const soupCells = $(tables.get(0)).children().first().children().first().children();
|
||||
if (soupCells.length !== 3) {
|
||||
throw Error("Neočekávaný počet buněk v tabulce polévky: " + soupCells.length + ", ale očekávány byly 3");
|
||||
}
|
||||
currentDayFood.push({
|
||||
amount: sanitizeText($(soupCells.get(0)).text()),
|
||||
name: sanitizeText($(soupCells.get(1)).text()),
|
||||
price: sanitizeText($(soupCells.get(2)).text().replace(' ', '\xA0')),
|
||||
isSoup: true,
|
||||
});
|
||||
})
|
||||
return results;
|
||||
// Hlavní jídla - div -> table -> tbody -> 3x tr
|
||||
const mainCourseRows = $(tables.get(1)).children().first().children();
|
||||
mainCourseRows.each((i, foodRow) => {
|
||||
const foodCells = $(foodRow).children();
|
||||
if (foodCells.length !== 3) {
|
||||
throw Error("Neočekávaný počet buněk v řádku jídla: " + foodCells.length + ", ale očekávány byly 3");
|
||||
}
|
||||
currentDayFood.push({
|
||||
amount: sanitizeText($(foodCells.get(0)).text()),
|
||||
name: sanitizeText($(foodCells.get(1)).text()),
|
||||
price: sanitizeText($(foodCells.get(2)).text().replace(' ', '\xA0')),
|
||||
isSoup: false,
|
||||
});
|
||||
})
|
||||
result[index] = currentDayFood;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Získá obědovou nabídku restaurace U Motlíků pro předané datum.
|
||||
* Získá obědovou nabídku restaurace U Motlíků pro jeden týden.
|
||||
*
|
||||
* @param date datum, pro které získat menu
|
||||
* @param firstDayOfWeek první den v týdnu, pro který získat menu
|
||||
* @param mock zda vrátit mock data
|
||||
* @returns seznam jídel pro dané datum
|
||||
*/
|
||||
export const getMenuUMotliku = async (date: Date = new Date(), mock: boolean = false): Promise<Food[]> => {
|
||||
export const getMenuUMotliku = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
|
||||
if (mock) {
|
||||
return getMenuUMotlikuMock(date);
|
||||
}
|
||||
const todayDayIndex = getDayOfWeekIndex(date);
|
||||
if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
|
||||
return [];
|
||||
return getMenuUMotlikuMock();
|
||||
}
|
||||
|
||||
const html = await getHtml(U_MOTLIKU_URL);
|
||||
const $ = load(html);
|
||||
|
||||
const table = $('table.table.table-hover.Xtable-striped').first();
|
||||
const body = table.children().first();
|
||||
const rows = body.children();
|
||||
const results: Food[] = [];
|
||||
let parsing = false;
|
||||
let isSoup = false;
|
||||
rows.each((i, row) => {
|
||||
const firstChild = $(row).children().get(0);
|
||||
if (firstChild?.name == 'th') {
|
||||
const childText = $(firstChild).text();
|
||||
if (capitalize(DAYS_IN_WEEK[todayDayIndex]) === childText) { // Našli jsme dnešek
|
||||
parsing = true;
|
||||
} else if (parsing) {
|
||||
// Narazili jsme na další den - konec parsování
|
||||
parsing = false;
|
||||
return;
|
||||
}
|
||||
} else if (parsing) { // Jsme aktuálně na dnešním dni
|
||||
const children = $(row).children();
|
||||
if (children.length === 1) { // Nadpis "Polévka" nebo "Hlavní jídlo"
|
||||
const foodType = children.first().text();
|
||||
if (foodType === 'Polévka') {
|
||||
isSoup = true;
|
||||
} else if (foodType === 'Hlavní jídlo') {
|
||||
isSoup = false;
|
||||
} else {
|
||||
throw Error("Neočekáváný typ jídla: " + foodType);
|
||||
}
|
||||
} else {
|
||||
if (children.length !== 3) {
|
||||
throw Error("Neočekávaný počet child elementů pro jídlo: " + children.length + ", očekávány 3");
|
||||
}
|
||||
const amount = sanitizeText($(children.get(0)).text());
|
||||
const name = sanitizeText($(children.get(1)).text());
|
||||
const price = sanitizeText($(children.get(2)).text()).replace(',-', '').replace(' ', '\xA0');
|
||||
results.push({
|
||||
amount,
|
||||
name,
|
||||
price,
|
||||
isSoup,
|
||||
})
|
||||
}
|
||||
|
||||
const result: Food[][] = [];
|
||||
for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
|
||||
if (!(dayIndex in result)) {
|
||||
result[dayIndex] = [];
|
||||
}
|
||||
})
|
||||
return results;
|
||||
let parsing = false;
|
||||
let isSoup = false;
|
||||
rows.each((i, row) => {
|
||||
const firstChild = $(row).children().get(0);
|
||||
if (firstChild?.name == 'th') {
|
||||
const childText = $(firstChild).text();
|
||||
if (capitalize(DAYS_IN_WEEK[dayIndex]) === childText) {
|
||||
parsing = true;
|
||||
} else if (parsing) {
|
||||
// Narazili jsme na další den - konec parsování
|
||||
parsing = false;
|
||||
return;
|
||||
}
|
||||
} else if (parsing) {
|
||||
const children = $(row).children();
|
||||
if (children.length === 1) { // Nadpis "Polévka" nebo "Hlavní jídlo"
|
||||
const foodType = children.first().text();
|
||||
if (foodType === 'Polévka') {
|
||||
isSoup = true;
|
||||
} else if (foodType === 'Hlavní jídlo') {
|
||||
isSoup = false;
|
||||
} else {
|
||||
throw Error("Neočekáváný typ jídla: " + foodType);
|
||||
}
|
||||
} else {
|
||||
if (children.length !== 3) {
|
||||
throw Error("Neočekávaný počet child elementů pro jídlo: " + children.length + ", očekávány 3");
|
||||
}
|
||||
const amount = sanitizeText($(children.get(0)).text());
|
||||
const name = sanitizeText($(children.get(1)).text());
|
||||
const price = sanitizeText($(children.get(2)).text()).replace(',-', '').replace(' ', '\xA0');
|
||||
result[dayIndex].push({
|
||||
amount,
|
||||
name,
|
||||
price,
|
||||
isSoup,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Získá obědovou nabídku TechTower pro předané datum.
|
||||
* Získá obědovou nabídku TechTower pro jeden týden.
|
||||
*
|
||||
* @param date datum, pro které získat menu
|
||||
* @param firstDayOfWeek první den v týdnu, pro který získat menu
|
||||
* @param mock zda vrátit mock data
|
||||
* @returns seznam jídel pro dané datum
|
||||
*/
|
||||
export const getMenuTechTower = async (date: Date = new Date(), mock: boolean = false) => {
|
||||
export const getMenuTechTower = async (firstDayOfWeek: Date, mock: boolean = false): Promise<Food[][]> => {
|
||||
if (mock) {
|
||||
return getMenuTechTowerMock(date);
|
||||
}
|
||||
const todayDayIndex = getDayOfWeekIndex(date);
|
||||
if (todayDayIndex == 5 || todayDayIndex == 6) { // Víkend
|
||||
return [];
|
||||
return getMenuTechTowerMock();
|
||||
}
|
||||
|
||||
const html = await getHtml(TECHTOWER_URL);
|
||||
const $ = load(html);
|
||||
|
||||
const fonts = $('font.wsw-41');
|
||||
let font = undefined;
|
||||
fonts.each((i, f) => {
|
||||
@@ -236,38 +238,44 @@ export const getMenuTechTower = async (date: Date = new Date(), mock: boolean =
|
||||
// TODO validovat, že v textu nalezeného <font> je rozsah, do kterého spadá vstupní datum
|
||||
const siblings = $(font).parent().parent().siblings();
|
||||
let parsing = false;
|
||||
const results: Food[] = [];
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
const text = $(siblings.get(i)).text().trim().replace('\t', '').replace('\n', ' ');
|
||||
if (DAYS_IN_WEEK.includes(text)) {
|
||||
if (text === DAYS_IN_WEEK[todayDayIndex]) {
|
||||
// Našli jsme dnešní den, odtud začínáme parsovat jídla
|
||||
parsing = true;
|
||||
continue
|
||||
const result: Food[][] = [];
|
||||
// TODO toto je kvůli poslednímu "línému" refaktoru neoptimální, stačilo by to projít jedním cyklem
|
||||
for (let dayIndex = 0; dayIndex < 5; dayIndex++) {
|
||||
if (!(dayIndex in result)) {
|
||||
result[dayIndex] = [];
|
||||
}
|
||||
for (let i = 0; i < siblings.length; i++) {
|
||||
const text = $(siblings.get(i)).text().trim().replace('\t', '').replace('\n', ' ');
|
||||
if (DAYS_IN_WEEK.includes(text)) {
|
||||
if (text === DAYS_IN_WEEK[dayIndex]) {
|
||||
// Našli jsme dnešní den, odtud začínáme parsovat jídla
|
||||
parsing = true;
|
||||
continue
|
||||
} else if (parsing) {
|
||||
// Už parsujeme jídla, ale narazili jsme na následující den - končíme
|
||||
break;
|
||||
}
|
||||
} else if (parsing) {
|
||||
// Už parsujeme jídla, ale narazili jsme na následující den - končíme
|
||||
break;
|
||||
if (text.length == 0) {
|
||||
// Prázdná řádka - končíme (je za pátečním menu TechTower)
|
||||
break;
|
||||
}
|
||||
let price = '? Kč';
|
||||
let name = text;
|
||||
if (text.toLowerCase().endsWith('kč')) {
|
||||
const tmp = text.replace('\xA0', ' ').split(' ');
|
||||
const split = [tmp.slice(0, -2).join(' ')].concat(tmp.slice(-2));
|
||||
price = `${split.slice(1)[0]}\xA0Kč`
|
||||
name = split[0]
|
||||
}
|
||||
result[dayIndex].push({
|
||||
amount: '-',
|
||||
name,
|
||||
price,
|
||||
isSoup: isTextSoupName(name),
|
||||
})
|
||||
}
|
||||
} else if (parsing) {
|
||||
if (text.length == 0) {
|
||||
// Prázdná řádka - končíme (je za pátečním menu TechTower)
|
||||
break;
|
||||
}
|
||||
let price = '? Kč';
|
||||
let name = text;
|
||||
if (text.toLowerCase().endsWith('kč')) {
|
||||
const tmp = text.replace('\xA0', ' ').split(' ');
|
||||
const split = [tmp.slice(0, -2).join(' ')].concat(tmp.slice(-2));
|
||||
price = `${split.slice(1)[0]}\xA0Kč`
|
||||
name = split[0]
|
||||
}
|
||||
results.push({
|
||||
amount: '-',
|
||||
name,
|
||||
price,
|
||||
isSoup: isTextSoupName(name),
|
||||
})
|
||||
}
|
||||
}
|
||||
return results;
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user