gancio-upstream/server/helpers.js

298 lines
10 KiB
JavaScript
Raw Normal View History

2021-01-11 00:17:56 +01:00
const ical = require('ical.js')
2019-10-11 18:34:14 +02:00
const settingsController = require('./api/controller/settings')
2021-09-30 11:06:59 +02:00
const express = require('express')
2020-11-13 00:13:44 +01:00
const dayjs = require('dayjs')
2022-08-05 18:08:03 +02:00
const timezone = require('dayjs/plugin/timezone')
dayjs.extend(timezone)
2020-11-17 00:34:56 +01:00
2021-09-27 10:42:17 +02:00
const config = require('./config')
2021-03-05 14:17:10 +01:00
const log = require('./log')
2019-10-30 14:58:40 +01:00
const pkg = require('../package.json')
const path = require('path')
const sharp = require('sharp')
const axios = require('axios')
const crypto = require('crypto')
2020-10-10 00:40:47 +02:00
const Microformats = require('microformat-node')
const get = require('lodash/get')
2019-10-11 18:34:14 +02:00
2020-02-10 00:40:23 +01:00
const DOMPurify = require('dompurify')
const { JSDOM } = require('jsdom')
const { window } = new JSDOM('<!DOCTYPE html>')
const domPurify = DOMPurify(window)
2023-02-08 09:20:48 +01:00
const URL = require('url')
2020-02-10 00:40:23 +01:00
domPurify.addHook('beforeSanitizeElements', node => {
if (node.hasAttribute && node.hasAttribute('href')) {
const href = node.getAttribute('href')
const text = node.textContent
2020-11-06 11:05:05 +01:00
// remove FB tracking param
2020-02-10 00:40:23 +01:00
if (href.includes('fbclid=')) {
try {
2023-02-08 09:20:48 +01:00
const url = new URL.URL(href)
2020-02-10 00:40:23 +01:00
url.searchParams.delete('fbclid')
node.setAttribute('href', url.href)
if (text.includes('fbclid=')) {
node.textContent = url.href
}
} catch (e) {
return node
}
}
}
return node
})
2019-10-11 18:34:14 +02:00
module.exports = {
2021-10-18 15:46:38 +02:00
randomString(length = 12) {
2022-11-04 12:22:21 +01:00
const wishlist = '0123456789abcdefghijklmnopqrstuvwxyz'
2021-10-18 15:46:38 +02:00
return Array.from(crypto.randomFillSync(new Uint32Array(length)))
.map(x => wishlist[x % wishlist.length])
.join('')
},
sanitizeHTML(html) {
2020-02-10 00:40:23 +01:00
return domPurify.sanitize(html, {
2021-10-25 13:17:49 +02:00
ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'h4', 'h5', 'br', 'i', 'span',
2020-02-10 00:40:23 +01:00
'h6', 'b', 'a', 'li', 'ul', 'ol', 'code', 'blockquote', 'u', 's', 'strong'],
ALLOWED_ATTR: ['href', 'target']
2020-02-10 00:40:23 +01:00
})
},
2020-01-27 00:47:03 +01:00
2022-02-26 21:27:40 +01:00
async initSettings(_req, res, next) {
2019-10-11 18:34:14 +02:00
// initialize settings
// res.locals.settings = cloneDeep(settingsController.settings)
const settings = settingsController.settings
res.locals.settings = {
title: settings.title || config.title,
description: settings.description || config.description,
baseurl: config.baseurl,
hostname: config.hostname,
version: pkg.version,
instance_timezone: settings.instance_timezone,
instance_locale: settings.instance_locale,
instance_name: settings.instance_name,
instance_place: settings.instance_place,
allow_registration: settings.allow_registration,
allow_anon_event: settings.allow_anon_event,
allow_recurrent_event: settings.allow_recurrent_event,
allow_multidate_event: settings.allow_multidate_event,
recurrent_event_visible: settings.recurrent_event_visible,
enable_federation: settings.enable_federation,
enable_resources: settings.enable_resources,
hide_boosts: settings.hide_boosts,
enable_trusted_instances: settings.enable_trusted_instances,
trusted_instances: settings.trusted_instances,
trusted_instances_label: settings.trusted_instances_label,
'theme.is_dark': settings['theme.is_dark'],
'theme.primary': settings['theme.primary'],
hide_thumbs: settings.hide_thumbs,
hide_calendar: settings.hide_calendar,
allow_geolocation: settings.allow_geolocation,
geocoding_provider_type: settings.geocoding_provider_type,
geocoding_provider: settings.geocoding_provider,
geocoding_countrycodes: settings.geocoding_countrycodes,
tilelayer_provider: settings.tilelayer_provider,
tilelayer_provider_attribution: settings.tilelayer_provider_attribution,
footerLinks: settings.footerLinks,
about: settings.about
}
2022-02-26 21:27:40 +01:00
// set user locale
2022-11-29 14:40:19 +01:00
// res.locals.user_locale = settingsController.user_locale[res.locals.acceptedLocale]
2022-08-05 18:08:03 +02:00
dayjs.tz.setDefault(res.locals.settings.instance_timezone)
2020-01-27 00:47:03 +01:00
next()
},
serveStatic() {
2021-09-30 11:06:59 +02:00
const router = express.Router()
2022-05-02 16:47:56 +02:00
// serve images/thumb
router.use('/media/', express.static(config.upload_path, { immutable: true, maxAge: '1y' }), (_req, res) => res.sendStatus(404))
2022-09-13 09:06:40 +02:00
router.use('/download/:filename', (req, res) => {
2022-10-31 17:05:46 +01:00
res.download(req.params.filename, undefined, { root: config.upload_path }, err => {
2022-09-13 09:06:40 +02:00
if (err) {
2022-10-31 17:05:46 +01:00
// Check if headers have been sent
if(res.headersSent) {
log.warn(err)
} else {
res.status(404).send('Not found (but nice try 😊)')
2023-01-10 18:15:33 +01:00
}
2022-09-13 09:06:40 +02:00
}
2023-01-10 18:15:33 +01:00
})
2022-09-05 13:03:35 +02:00
})
2022-10-28 12:01:59 +02:00
router.use('/fallbackimage.png', (req, res, next) => {
const fallbackImagePath = settingsController.settings.fallback_image || './static/noimg.svg'
2022-11-29 23:03:36 +01:00
return express.static(fallbackImagePath)(req, res, next)
2022-10-28 12:01:59 +02:00
})
router.use('/headerimage.png', (req, res, next) => {
const headerImagePath = settingsController.settings.header_image || './static/noimg.svg'
return express.static(headerImagePath)(req, res, next)
})
2021-09-30 11:06:59 +02:00
router.use('/logo.png', (req, res, next) => {
2022-10-28 12:01:59 +02:00
const logoPath = settingsController.settings.logo || './static/gancio'
2022-11-29 23:03:36 +01:00
return express.static(logoPath + '.png')(req, res, next)
2021-09-30 11:06:59 +02:00
})
router.use('/favicon.ico', (req, res, next) => {
2022-03-07 17:47:31 +01:00
const faviconPath = res.locals.settings.logo ? res.locals.settings.logo + '.png' : './assets/favicon.ico'
2022-11-29 23:03:36 +01:00
return express.static(faviconPath)(req, res, next)
2021-09-30 11:06:59 +02:00
})
return router
},
logRequest(req, _res, next) {
2021-09-30 11:06:59 +02:00
log.debug(`${req.method} ${req.path}`)
next()
},
col(field) {
if (config.db.dialect === 'postgres') {
return '"' + field.split('.').join('"."') + '"'
} else if (config.db.dialect === 'mariadb') {
return '`' + field.split('.').join('`.`') + '`'
} else {
return field
}
},
async getImageFromURL(url) {
2021-03-05 14:17:10 +01:00
log.debug(`getImageFromURL ${url}`)
2022-05-03 12:08:10 +02:00
const filename = crypto.randomBytes(16).toString('hex')
const sharpStream = sharp({ failOnError: true })
const promises = [
sharpStream.clone().resize(500, null, { withoutEnlargement: true }).jpeg({ effort: 6, mozjpeg: true }).toFile(path.resolve(config.upload_path, 'thumb', filename + '.jpg')),
sharpStream.clone().resize(1200, null, { withoutEnlargement: true }).jpeg({ quality: 95, effort: 6, mozjpeg: true }).toFile(path.resolve(config.upload_path, filename + '.jpg')),
2022-05-03 12:08:10 +02:00
]
const response = await axios({ method: 'GET', url: encodeURI(url), responseType: 'stream' })
response.data.pipe(sharpStream)
return Promise.all(promises)
.then(res => {
const info = res[1]
return {
destination: config.upload_path,
filename: filename + '.jpg',
path: path.resolve(config.upload_path, filename + '.jpg'),
height: info.height,
width: info.width,
size: info.size,
}
})
.catch(err => {
log.error(err)
req.err = err
cb(null)
})
2020-10-10 00:40:47 +02:00
},
2021-01-11 00:17:56 +01:00
/**
* Import events from url
* It does supports ICS and H-EVENT
*/
async importURL(req, res) {
2023-03-14 16:16:52 +01:00
const url = req.query.URL
2020-10-10 00:40:47 +02:00
try {
2023-03-14 16:16:52 +01:00
const response = await axios.get(url)
2021-01-11 00:17:56 +01:00
const contentType = response.headers['content-type']
if (contentType.includes('text/html')) {
Microformats.get({ html: response.data, filter: ['h-event'] }, (err, data) => {
if (err || !data.items.length || !data.items[0].properties) {
return res.sendStatus(404)
}
const events = data.items.map(e => {
const props = e.properties
2022-06-22 17:51:42 +02:00
let media = get(props, 'featured[0]')
if (media) {
2023-03-14 16:16:52 +01:00
media = URL.resolve(url, media)
2022-06-22 17:51:42 +02:00
}
2021-01-11 00:17:56 +01:00
return {
title: get(props, 'name[0]', ''),
description: get(props, 'description[0]', ''),
2021-06-07 00:02:45 +02:00
place: {
2022-06-22 17:51:42 +02:00
name: get(props, 'location[0].properties.name[0].value', '') || get(props, 'location[0].properties.name', '') || get(props, 'location[0]'),
address: get(props, 'location[0].properties.street-address[0]') || get(props, 'location[0].properties.street-address')
2021-06-07 00:02:45 +02:00
},
start_datetime: dayjs(get(props, 'start[0]', '')).unix(),
end_datetime: dayjs(get(props, 'end[0]', '')).unix(),
2021-01-11 00:17:56 +01:00
tags: get(props, 'category', []),
2022-06-22 17:51:42 +02:00
media: media ? [{ name: get(props, 'name[0]', ''), url: media, focalpoint: [0, 0] }] : []
2021-01-11 00:17:56 +01:00
}
})
return res.json(events)
2020-10-10 00:40:47 +02:00
})
2021-01-11 00:17:56 +01:00
} else if (contentType.includes('text/calendar')) {
const ret = ical.parse(response.data)
const component = new ical.Component(ret)
const events = component.getAllSubcomponents('vevent')
return res.json(events.map(e => {
const event = new ical.Event(e)
return {
title: get(event, 'summary', ''),
description: get(event, 'description', ''),
2022-06-22 17:51:42 +02:00
place: { name: get(event, 'location', '') },
start_datetime: dayjs(get(event, 'startDate', null)).unix(),
end_datetime: dayjs(get(event, 'endDate', null)).unix()
2021-01-11 00:17:56 +01:00
}
}))
}
2020-11-06 11:05:05 +01:00
} catch (e) {
2021-07-08 20:41:56 +02:00
log.error('[Import URL]', e)
res.status(400).json(e.toString())
2020-10-10 00:40:47 +02:00
}
2021-06-07 00:02:45 +02:00
},
2020-10-10 00:40:47 +02:00
getWeekdayN(date, n, weekday) {
2021-06-07 00:02:45 +02:00
let cursor
if (n === -1) {
cursor = date.endOf('month')
cursor = cursor.day(weekday)
if (cursor.month() !== date.month()) {
cursor = cursor.subtract(1, 'week')
}
} else {
cursor = date.startOf('month')
cursor = cursor.add(cursor.day() <= date.day() ? n - 1 : n, 'week')
cursor = cursor.day(weekday)
}
cursor = cursor.hour(date.hour()).minute(date.minute()).second(0)
log.debug(cursor)
return cursor
},
async APRedirect(req, res, next) {
const eventController = require('../server/api/controller/event')
2022-06-01 14:13:58 +02:00
const acceptJson = req.accepts('html', 'application/activity+json') === 'application/activity+json'
if (acceptJson) {
2022-02-21 10:46:39 +01:00
const event = await eventController._get(req.params.slug)
if (event) {
return res.redirect(`/federation/m/${event.id}`)
}
}
next()
},
async feedRedirect(req, res, next) {
const accepted = req.accepts('html', 'application/rss+xml', 'text/calendar')
if (['application/rss+xml', 'text/calendar'].includes(accepted) && /^\/(tag|place|collection)\/.*/.test(req.path)) {
return res.redirect((accepted === 'application/rss+xml' ? '/feed/rss' : '/feed/ics') + req.path)
}
next()
2023-01-10 18:15:33 +01:00
},
async isGeocodingEnabled(req, res, next) {
if (res.locals.settings.allow_geolocation) {
next()
} else {
res.sendStatus(403)
}
2019-10-11 18:34:14 +02:00
}
2019-10-30 14:58:40 +01:00
}