|
@@ -1,7 +1,7 @@
|
|
|
import axios from 'axios';
|
|
import axios from 'axios';
|
|
|
import { parseString } from 'xml2js';
|
|
import { parseString } from 'xml2js';
|
|
|
import { promisify } from 'util';
|
|
import { promisify } from 'util';
|
|
|
-import { SitemapUrl, SitePlatform } from '../types';
|
|
|
|
|
|
|
+import { SitemapUrl, SitePlatform, ContentType, ALL_CONTENT_TYPES } from '../types';
|
|
|
import { logger } from '../utils/logger';
|
|
import { logger } from '../utils/logger';
|
|
|
import { getUserAgent } from '../version';
|
|
import { getUserAgent } from '../version';
|
|
|
|
|
|
|
@@ -198,15 +198,10 @@ export class SitemapParser {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Filter URLs to find specific page types
|
|
|
|
|
|
|
+ * Keyword arrays for URL classification by content type
|
|
|
*/
|
|
*/
|
|
|
- static filterUrlsByType(urls: SitemapUrl[]): {
|
|
|
|
|
- shipping: SitemapUrl[];
|
|
|
|
|
- contacts: SitemapUrl[];
|
|
|
|
|
- terms: SitemapUrl[];
|
|
|
|
|
- faq: SitemapUrl[];
|
|
|
|
|
- } {
|
|
|
|
|
- const shippingKeywords = [
|
|
|
|
|
|
|
+ private static readonly keywordMap: Record<string, string[]> = {
|
|
|
|
|
+ shipping: [
|
|
|
// English terms
|
|
// English terms
|
|
|
'shipping', 'delivery', 'ship', 'deliver', 'postage', 'courier',
|
|
'shipping', 'delivery', 'ship', 'deliver', 'postage', 'courier',
|
|
|
'logistics', 'transport', 'freight', 'dispatch', 'fulfillment',
|
|
'logistics', 'transport', 'freight', 'dispatch', 'fulfillment',
|
|
@@ -223,31 +218,27 @@ export class SitemapParser {
|
|
|
'futarszolgalat', 'futarszallitas', 'hazhozszallitas', 'ingyenes-szallitas',
|
|
'futarszolgalat', 'futarszallitas', 'hazhozszallitas', 'ingyenes-szallitas',
|
|
|
'expressz-szallitas', 'gyors-szallitas', 'nemzetkozi-szallitas', 'hazai-szallitas',
|
|
'expressz-szallitas', 'gyors-szallitas', 'nemzetkozi-szallitas', 'hazai-szallitas',
|
|
|
'postai-szallitas', 'csomagpont', 'csomagautomata', 'pickup-pont'
|
|
'postai-szallitas', 'csomagpont', 'csomagautomata', 'pickup-pont'
|
|
|
- ];
|
|
|
|
|
|
|
+ ],
|
|
|
|
|
|
|
|
- const contactKeywords = [
|
|
|
|
|
|
|
+ contacts: [
|
|
|
// English terms
|
|
// English terms
|
|
|
'contact', 'contact-us', 'contact-info', 'contact-page', 'get-in-touch',
|
|
'contact', 'contact-us', 'contact-info', 'contact-page', 'get-in-touch',
|
|
|
'reach-us', 'find-us', 'locate-us', 'visit-us', 'where-to-find-us',
|
|
'reach-us', 'find-us', 'locate-us', 'visit-us', 'where-to-find-us',
|
|
|
- 'about', 'about-us', 'about-company', 'who-we-are', 'our-story',
|
|
|
|
|
- 'company-info', 'business-info', 'team', 'our-team', 'meet-the-team',
|
|
|
|
|
'email', 'phone', 'telephone', 'address', 'location', 'directions',
|
|
'email', 'phone', 'telephone', 'address', 'location', 'directions',
|
|
|
'office', 'store-location', 'headquarters', 'branch', 'outlet',
|
|
'office', 'store-location', 'headquarters', 'branch', 'outlet',
|
|
|
- 'customer-service', 'customer-support', 'support', 'help-center',
|
|
|
|
|
|
|
+ 'customer-service', 'customer-support',
|
|
|
'impressum', 'imprint', 'legal-notice', 'company-details',
|
|
'impressum', 'imprint', 'legal-notice', 'company-details',
|
|
|
|
|
|
|
|
// Hungarian terms
|
|
// Hungarian terms
|
|
|
'kapcsolat', 'kapcsolatfelvetel', 'elerhetoseg', 'elerheto', 'kontakt',
|
|
'kapcsolat', 'kapcsolatfelvetel', 'elerhetoseg', 'elerheto', 'kontakt',
|
|
|
'kapcsolatok', 'kapcsolat-felvetel', 'elerhetosegek', 'megkozelites',
|
|
'kapcsolatok', 'kapcsolat-felvetel', 'elerhetosegek', 'megkozelites',
|
|
|
- 'rolunk', 'magunkrol', 'cegunkrol', 'tortenelem', 'cegtortenet',
|
|
|
|
|
- 'bemutatkozas', 'csapat', 'csapatunk', 'munkatarsak', 'kollektiva',
|
|
|
|
|
'telefon', 'telefonszam', 'email', 'e-mail', 'cim', 'lakcim', 'szekhely',
|
|
'telefon', 'telefonszam', 'email', 'e-mail', 'cim', 'lakcim', 'szekhely',
|
|
|
'iroda', 'uzlet', 'bolt', 'telephelyunk', 'fiokunk', 'kepviselet',
|
|
'iroda', 'uzlet', 'bolt', 'telephelyunk', 'fiokunk', 'kepviselet',
|
|
|
- 'ugyfelszolgalat', 'vevoszolgalat', 'segitseg', 'tamogatas', 'support',
|
|
|
|
|
|
|
+ 'ugyfelszolgalat', 'vevoszolgalat',
|
|
|
'impresszum', 'jogi-nyilatkozat', 'cegadatok', 'vallalkozasi-adatok'
|
|
'impresszum', 'jogi-nyilatkozat', 'cegadatok', 'vallalkozasi-adatok'
|
|
|
- ];
|
|
|
|
|
|
|
+ ],
|
|
|
|
|
|
|
|
- const termsKeywords = [
|
|
|
|
|
|
|
+ terms: [
|
|
|
// English terms
|
|
// English terms
|
|
|
'terms', 'conditions', 'terms-and-conditions', 'terms-of-service', 'terms-of-use',
|
|
'terms', 'conditions', 'terms-and-conditions', 'terms-of-service', 'terms-of-use',
|
|
|
'tos', 'toc', 'tc', 'user-agreement', 'user-terms', 'service-terms',
|
|
'tos', 'toc', 'tc', 'user-agreement', 'user-terms', 'service-terms',
|
|
@@ -270,9 +261,9 @@ export class SitemapParser {
|
|
|
'visszaterites', 'visszavételar', 'cserepolitika', 'reklamacio',
|
|
'visszaterites', 'visszavételar', 'cserepolitika', 'reklamacio',
|
|
|
'szerzoi-jog', 'vedjegy', 'jogok', 'impresszum', 'impressum',
|
|
'szerzoi-jog', 'vedjegy', 'jogok', 'impresszum', 'impressum',
|
|
|
'gdpr', 'adatvedelmi-rendelet', 'megfeleloseg', 'jogszabaly'
|
|
'gdpr', 'adatvedelmi-rendelet', 'megfeleloseg', 'jogszabaly'
|
|
|
- ];
|
|
|
|
|
|
|
+ ],
|
|
|
|
|
|
|
|
- const faqKeywords = [
|
|
|
|
|
|
|
+ faq: [
|
|
|
// English terms
|
|
// English terms
|
|
|
'faq', 'frequently-asked-questions', 'frequently-asked', 'f-a-q',
|
|
'faq', 'frequently-asked-questions', 'frequently-asked', 'f-a-q',
|
|
|
'questions', 'common-questions', 'popular-questions', 'most-asked',
|
|
'questions', 'common-questions', 'popular-questions', 'most-asked',
|
|
@@ -294,18 +285,97 @@ export class SitemapParser {
|
|
|
'utmutato', 'utmutatók', 'kezikonyv', 'manual', 'dokumentacio',
|
|
'utmutato', 'utmutatók', 'kezikonyv', 'manual', 'dokumentacio',
|
|
|
'felhasznaloi-utmutato', 'hogyan', 'hibaelharitas', 'problema',
|
|
'felhasznaloi-utmutato', 'hogyan', 'hibaelharitas', 'problema',
|
|
|
'megoldas', 'valaszok', 'kozosseg', 'forum', 'megbeszeles'
|
|
'megoldas', 'valaszok', 'kozosseg', 'forum', 'megbeszeles'
|
|
|
- ];
|
|
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ services: [
|
|
|
|
|
+ 'services', 'szolgaltatas', 'treatments', 'kezeles', 'offerings',
|
|
|
|
|
+ 'megoldasok', 'solutions', 'leistungen'
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ about: [
|
|
|
|
|
+ 'about', 'rolunk', 'history', 'our-story', 'tortenet',
|
|
|
|
|
+ 'ars-poetica', 'uber-uns', 'cegunkrol'
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ team: [
|
|
|
|
|
+ 'team', 'csapat', 'munkatars', 'our-team', 'doctors',
|
|
|
|
|
+ 'staff', 'orvos', 'specialist', 'mitarbeiter'
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ blog: [
|
|
|
|
|
+ 'blog', 'articles', 'hirek', 'cikkek', 'posts',
|
|
|
|
|
+ 'news', 'magazin', 'artikel'
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ pricing: [
|
|
|
|
|
+ 'pricing', 'prices', 'araink', 'arajanlat', 'plans',
|
|
|
|
|
+ 'packages', 'fees', 'dijszabas', 'preise', 'price-list'
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ testimonials: [
|
|
|
|
|
+ 'testimonials', 'reviews', 'velemenyek', 'mondtak', 'kamera-elott',
|
|
|
|
|
+ 'feedback', 'ertekelesek', 'bewertungen'
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ gallery: [
|
|
|
|
|
+ 'gallery', 'kepek', 'photos', 'pictures', 'portfolio',
|
|
|
|
|
+ 'esetbemutatas', 'case-stud', 'album', 'galerie'
|
|
|
|
|
+ ],
|
|
|
|
|
+
|
|
|
|
|
+ landing: [
|
|
|
|
|
+ 'akcio', 'promo', 'campaign', 'landing', 'offer',
|
|
|
|
|
+ 'ajanlat', 'special', 'deal', 'angebot'
|
|
|
|
|
+ ]
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ private static containsKeyword(url: string, keywords: string[]): boolean {
|
|
|
|
|
+ const lowerUrl = url.toLowerCase();
|
|
|
|
|
+ return keywords.some(keyword => lowerUrl.includes(keyword));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- const containsKeyword = (url: string, keywords: string[]): boolean => {
|
|
|
|
|
- const lowerUrl = url.toLowerCase();
|
|
|
|
|
- return keywords.some(keyword => lowerUrl.includes(keyword));
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Classify a single URL into a content type
|
|
|
|
|
+ */
|
|
|
|
|
+ static classifyUrl(url: string): ContentType {
|
|
|
|
|
+ for (const [type, keywords] of Object.entries(this.keywordMap)) {
|
|
|
|
|
+ if (this.containsKeyword(url, keywords)) {
|
|
|
|
|
+ return type as ContentType;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return 'other';
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- return {
|
|
|
|
|
- shipping: urls.filter(u => containsKeyword(u.loc, shippingKeywords)),
|
|
|
|
|
- contacts: urls.filter(u => containsKeyword(u.loc, contactKeywords)),
|
|
|
|
|
- terms: urls.filter(u => containsKeyword(u.loc, termsKeywords)),
|
|
|
|
|
- faq: urls.filter(u => containsKeyword(u.loc, faqKeywords))
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Classify multiple URLs, grouping them by content type
|
|
|
|
|
+ */
|
|
|
|
|
+ static classifyUrls(urls: string[]): Map<ContentType, string[]> {
|
|
|
|
|
+ const result = new Map<ContentType, string[]>();
|
|
|
|
|
+ for (const ct of ALL_CONTENT_TYPES) {
|
|
|
|
|
+ result.set(ct, []);
|
|
|
|
|
+ }
|
|
|
|
|
+ for (const url of urls) {
|
|
|
|
|
+ const type = this.classifyUrl(url);
|
|
|
|
|
+ result.get(type)!.push(url);
|
|
|
|
|
+ }
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Filter URLs to find specific page types
|
|
|
|
|
+ */
|
|
|
|
|
+ static filterUrlsByType(urls: SitemapUrl[]): Record<ContentType, SitemapUrl[]> {
|
|
|
|
|
+ const result: Record<string, SitemapUrl[]> = {};
|
|
|
|
|
+ for (const ct of ALL_CONTENT_TYPES) {
|
|
|
|
|
+ result[ct] = [];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ for (const u of urls) {
|
|
|
|
|
+ const type = this.classifyUrl(u.loc);
|
|
|
|
|
+ if (type !== 'other') {
|
|
|
|
|
+ result[type].push(u);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result as Record<ContentType, SitemapUrl[]>;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|