import { ServerResponse } from 'http' export default class Sitemap { private static allowedChangefreq = ['always', 'hourly', 'daily', 'weekly', 'monthly', 'yearly', 'never'] private datas = '' public constructor( private domain: string, private options?: { response?: ServerResponse } ) { if (this.options?.response) { this.options.response.setHeader('Content-Type', 'text/xml') this.options.response.write(this.datas) } } /** * Add a new Entry into the Sitemap * @param path the url path * @param options aditional datas you want in the sitemap for the `path` */ public addEntry(path: string, options?: { changefreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never' lastmod?: Date priority?: 1 | 0.9 | 0.8 | 0.7 | 0.6 | 0.5 | 0.4 | 0.3 | 0.2 | 0.1 | 0 images?: Array<{ location: string caption?: string geoLocation?: string title?: string license?: string }> }) { let entryString = '' const url = `${this.domain}${path}` .replace(/&/g, '&') .replace(/'/g, ''') .replace(/"/g, '"') .replace(/>/g, '>') .replace(/${url}` if (options) { if (options.changefreq) { if (!Sitemap.allowedChangefreq.includes(options.changefreq)) { console.warn(`changefreq is not valid (${options.changefreq})`) } else { entryString += `${options.changefreq}` } } if (options.lastmod) { entryString += `${options.lastmod.toISOString()}` } if (options.priority) { if (options.priority <= 1 && options.priority >= 0 && options.priority.toString().length <= 3) { entryString += `${options.priority}` } else { console.warn(`Priority is not valid (${options.priority})`) } } if (options.images) { if (options.images.length > 1000) { console.warn('image cant have more than 1000 images, skipping') } else { for (const image of options.images) { if (!image.location) { console.warn('Images need a Location') continue } entryString += '' entryString += `${image.location.startsWith('/') ? `${this.domain}${image.location}` : image.location}` entryString += this.optionalEntry('image:caption', image.caption) entryString += this.optionalEntry('image:geo_location', image.geoLocation) entryString += this.optionalEntry('image:title', image.title) entryString += this.optionalEntry('image:license', image.license) entryString += '' } } } } entryString += '' if (this.options?.response) { this.options.response.write(entryString) } else { this.datas += entryString } } /** * Finish the Sitemap */ public build(): string { if (this.options?.response) { this.options.response.write('') this.options.response.end() return '' } return this.datas + '' } private optionalEntry(tag: string, entry?: string) { return entry ? `<${tag}>${entry}` : '' } }