init
This commit is contained in:
commit
84365f816d
55
.eslintrc.json
Normal file
55
.eslintrc.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2017,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": false
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": "error",
|
||||
"block-scoped-var": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": ["error", "stroustrup", {}],
|
||||
"camelcase": "error",
|
||||
"comma-dangle": ["error", "never"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"comma-style": [1, "last"],
|
||||
"consistent-this": [1, "_this"],
|
||||
"curly": [1, "multi"],
|
||||
"eol-last": 1,
|
||||
"eqeqeq": 1,
|
||||
"func-names": 1,
|
||||
"indent": ["error", 2, { "SwitchCase": 1 }],
|
||||
"lines-around-comment": ["error", { "beforeBlockComment": true, "allowArrayStart": true }],
|
||||
"max-len": [1, 180, 2], // 2 spaces per tab, max 80 chars per line
|
||||
"new-cap": 1,
|
||||
"newline-before-return": "error",
|
||||
"no-array-constructor": 1,
|
||||
"no-inner-declarations": [1, "both"],
|
||||
"no-mixed-spaces-and-tabs": 1,
|
||||
"no-multi-spaces": 2,
|
||||
"no-new-object": 1,
|
||||
"no-shadow-restricted-names": 1,
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"padded-blocks": ["error", { "blocks": "never", "switches": "always" }],
|
||||
"prefer-const": "error",
|
||||
"prefer-template": "error",
|
||||
"one-var": 0,
|
||||
"quote-props": ["error", "always"],
|
||||
"quotes": [1, "single"],
|
||||
"radix": 1,
|
||||
"semi": [1, "always"],
|
||||
"space-before-blocks": [1, "always"],
|
||||
"space-infix-ops": 1,
|
||||
"vars-on-top": 1,
|
||||
"no-multiple-empty-lines": ["error", { "max": 1, "maxEOF": 1 }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["/"] }]
|
||||
}
|
||||
|
||||
}
|
148
.gitignore
vendored
Normal file
148
.gitignore
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
.idea/
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Elastic Beanstalk Files
|
||||
.elasticbeanstalk/*
|
||||
!.elasticbeanstalk/*.cfg.yml
|
||||
!.elasticbeanstalk/*.global.yml
|
||||
/src/bundle.js
|
||||
/src/bundle.js.map
|
||||
/live/
|
||||
!/output/
|
33
ecosystem.json
Normal file
33
ecosystem.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
/**
|
||||
* Application configuration section
|
||||
* http://pm2.keymetrics.io/docs/usage/application-declaration/
|
||||
*/
|
||||
apps: [
|
||||
// First application
|
||||
{
|
||||
"name": "Jubilee",
|
||||
"script": "server.js",
|
||||
"cwd": "/home/martind2000/dev/jubilee",
|
||||
"watch": false,
|
||||
"ignore_watch" : ["node_modules"],
|
||||
"merge_logs" : true,
|
||||
"autorestart" : true,
|
||||
"restart_delay" : 3500,
|
||||
"max_memory_restart" : "300M",
|
||||
env: {
|
||||
COMMON_VARIABLE: "true"
|
||||
},
|
||||
env_production: {
|
||||
NODE_ENV: "production"
|
||||
}
|
||||
}
|
||||
],
|
||||
/**
|
||||
* Deployment section
|
||||
* http://pm2.keymetrics.io/docs/usage/deployment/
|
||||
*/
|
||||
deploy: {
|
||||
|
||||
}
|
||||
}
|
9
gulpfile.js
Normal file
9
gulpfile.js
Normal file
@ -0,0 +1,9 @@
|
||||
const gulp = require('gulp');
|
||||
|
||||
const requireDir = require('require-dir');
|
||||
|
||||
requireDir('./gulp');
|
||||
|
||||
gulp.task('default', ['bundleBackbone', 'styles', 'copy', 'customMUI', 'vendor', 'fonts', 'gotham', 'fujicons']);
|
||||
|
||||
gulp.task('live', ['liveBackbone', 'styles', 'copy', 'customMUI', 'vendor', 'fonts', 'gotham', 'fujicons']);
|
11795
package-lock.json
generated
Normal file
11795
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
90
package.json
Normal file
90
package.json
Normal file
@ -0,0 +1,90 @@
|
||||
{
|
||||
"name": "jubilee",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"engines": {
|
||||
"node": "=8.17.0"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"apicache": "^1.6.3",
|
||||
"backbone": "^1.4.0",
|
||||
"browserify": "^17.0.0",
|
||||
"cheerio": "^1.0.0-rc.2",
|
||||
"cors": "^2.8.5",
|
||||
"dark-sky-api": "^0.6.32",
|
||||
"dateformat": "^5.0.2",
|
||||
"debug-logger": "^0.4.1",
|
||||
"del": "^6.0.0",
|
||||
"elapsed": "0.0.7",
|
||||
"eslint": "^8.3.0",
|
||||
"express": "^4.17.1",
|
||||
"fecha": "^4.2.1",
|
||||
"feedme": "^2.0.2",
|
||||
"geolocation": "^0.2.0",
|
||||
"gulp-autoprefixer": "^8.0.0",
|
||||
"gulp-bump": "^3.2.0",
|
||||
"gulp-cache": "^1.1.3",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-cssnano": "^2.1.3",
|
||||
"gulp-html-replace": "^1.6.2",
|
||||
"gulp-htmlmin": "^5.0.1",
|
||||
"gulp-inject": "^5.0.5",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"hh-mm-ss": "^1.2.0",
|
||||
"humanize-duration": "^3.27.0",
|
||||
"jquery": "^3.6.0",
|
||||
"jsonfile": "^6.1.0",
|
||||
"leaflet": "^1.7.1",
|
||||
"lodash": "^4.17.21",
|
||||
"log4js": "^6.3.0",
|
||||
"log4js-node-mongodb": "^2.2.1",
|
||||
"loggy": "^1.0.8",
|
||||
"memory-cache": "^0.2.0",
|
||||
"moment": "^2.29.1",
|
||||
"muicss": "^0.10.3",
|
||||
"node-foursquare-venues": "^1.1.0",
|
||||
"openweather-apis": "^4.4.2",
|
||||
"redaxios": "^0.4.1",
|
||||
"request-json": "^0.6.5",
|
||||
"request-promise-native": "^1.0.9",
|
||||
"strict-uri-encode": "^2.0.0",
|
||||
"string": "^3.3.3",
|
||||
"sugar": "^2.0.6",
|
||||
"twitter": "^1.7.1",
|
||||
"uglifyify": "^5.0.2",
|
||||
"underscore": "^1.13.1",
|
||||
"winston": "^3.3.3",
|
||||
"winston-mongodb": "^5.0.7",
|
||||
"yelp-fusion": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"expect.js": "^0.3.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-google-webfonts": "^4.1.0",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-sass": "^5.0.0",
|
||||
"gulp-scss": "^1.4.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-strip-debug": "^4.0.0",
|
||||
"gulp-uglify-es": "^3.0.0",
|
||||
"lodash.assign": "^4.2.0",
|
||||
"mocha": "^9.1.3",
|
||||
"node-fetch": "^3.1.0",
|
||||
"node-geocoder": "^3.28.0",
|
||||
"require-dir": "^1.2.0",
|
||||
"requirejs": "^2.3.6",
|
||||
"sinon": "^12.0.1",
|
||||
"tape": "^5.3.2",
|
||||
"tape-promise": "^4.0.0",
|
||||
"vinyl-buffer": "^1.0.1",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"watchify": "^4.0.0",
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
}
|
||||
}
|
288
server.js
Normal file
288
server.js
Normal file
@ -0,0 +1,288 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const apicache = require('apicache');
|
||||
const logger = require('log4js').getLogger('Server');
|
||||
const weather = require('./server/weather');
|
||||
const euronews = require('./server/euronews');
|
||||
const foursquare = require('./server/foursquare');
|
||||
const rightbyme = require('./server/RightByMe');
|
||||
const agenda = require('./server/agenda');
|
||||
const directions = require('./server/directions');
|
||||
const geocode = require('./server/geocode');
|
||||
|
||||
const cors = require('cors');
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
const app = express();
|
||||
|
||||
app.use(cors());
|
||||
|
||||
const port = process.env.PORT || 8110;
|
||||
|
||||
const sitePath = 'live';
|
||||
|
||||
apicache.options({ 'debug': true });
|
||||
const cache = apicache.middleware;
|
||||
const standardError = {
|
||||
'error':'There was an error'
|
||||
};
|
||||
|
||||
app.use(express.static(path.join(__dirname, sitePath)));
|
||||
|
||||
// app.get('/weather', cache('15 minutes'), (req, res) => {
|
||||
|
||||
const asyncMiddleware = fn =>
|
||||
(req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next))
|
||||
.catch(next);
|
||||
};
|
||||
|
||||
app.get('/weather', (req, res) => {
|
||||
if (req.query.hasOwnProperty('ll'))
|
||||
|
||||
weather.doGetOpenWeather(req.query.ll)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=1800');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'weather', 'e':e }));
|
||||
});
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('Weather: LL missing');
|
||||
res.status(500).send('LL Missing');
|
||||
}
|
||||
});
|
||||
|
||||
// app.get('/forecast', cache('15 minutes'), (req, res) => {
|
||||
app.get('/forecast', (req, res) => {
|
||||
logger.info('/forecast');
|
||||
if (req.query.hasOwnProperty('ll'))
|
||||
|
||||
weather.doGetFullForcast(req.query.ll)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=1800');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'forecast', 'e':e }));
|
||||
});
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('Weather: LL missing');
|
||||
res.status(500).send('LL Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/weatheralert', cache('15 minutes'), (req, res) => {
|
||||
if (req.query.hasOwnProperty('ll'))
|
||||
// weather.doGetOpenWeather(req.query.ll)
|
||||
// doGetDarkSkyWeather
|
||||
weather.doGetDarkSkyWeather(req.query.ll)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=1800');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'weatheralert', 'e':e }));
|
||||
});
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('Weather: LL missing');
|
||||
res.status(500).send('LL Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/fsexplore', cache('15 minutes'), (req, res) => {
|
||||
if (req.query.hasOwnProperty('ll')) {
|
||||
const ll = req.query.ll;
|
||||
const limit = req.query.hasOwnProperty('ll') ? req.query.limit : 3;
|
||||
const section = req.query.hasOwnProperty('section') ? req.query.section : 'topPicks';
|
||||
const query = req.query.hasOwnProperty('query') ? req.query.query : '';
|
||||
console.log('req.query', req.query);
|
||||
foursquare.doGetFourSquareExplore(ll, limit, section, query)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=900');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'fsexplore', 'e':e }));
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('FS: LL missing');
|
||||
res.status(500).send('LL Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/geocode', /* cache('15 minutes'),*/ (req, res) => {
|
||||
if (req.query.hasOwnProperty('ll')) {
|
||||
const ll = req.query.ll;
|
||||
console.log('ll', ll);
|
||||
geocode.doGetGeocode(ll)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=900');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'geocode', 'e':e }));
|
||||
});
|
||||
}
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('FS: LL missing');
|
||||
res.status(500).send('LL Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/rightbyme', cache('86400 seconds'), (req, res) => {
|
||||
if (req.query.hasOwnProperty('ll'))
|
||||
rightbyme.doGetRightByMe(req.query.ll)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=86400');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'rightbyme', 'e':e }));
|
||||
});
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('FS: LL missing');
|
||||
res.status(500).send('LL Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/nearbydetail', cache('15 minutes'), (req, res) => {
|
||||
if (req.query.hasOwnProperty('id'))
|
||||
rightbyme.doGetMoreDetail(req.query.id)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=1800');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'nearbydetail', 'e':e }));
|
||||
});
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('FS: LL missing');
|
||||
res.status(500).send('LL Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/news', cache('15 minutes'), (req, res) => {
|
||||
euronews.getEuroNews().then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=1800');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
if (e.message === '304:Not changed') {
|
||||
logger.info('Euronews ', e.message);
|
||||
res.status(304).send();
|
||||
}
|
||||
else {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'news', 'e':e }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/article', cache('1 hour'), (req, res) => {
|
||||
if (req.query.hasOwnProperty('guid')) {
|
||||
logger.debug('Beofre', req.query.guid);
|
||||
|
||||
euronews.getArticle(req.query.guid)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=3600');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'article', 'e':e }));
|
||||
});
|
||||
}
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('FS: GUID missing');
|
||||
res.status(500).send('GUID Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/agenda', cache('15 minutes'), (req, res) => {
|
||||
agenda.doTodaysAgenda()
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=900');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'agenda', 'e':e }));
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/oldtraffic', cache('5 minutes'), (req, res) => {
|
||||
logger.debug(req.query);
|
||||
if (req.query.hasOwnProperty('olat'))
|
||||
|
||||
directions.getTraffic(req.query.olat, req.query.olon, req.query.dlat, req.query.dlon)
|
||||
.then((d) => {
|
||||
res.set('Cache-Control', 'public, max-age=900');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'oldtraffic', 'e':e }));
|
||||
});
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('FS: oLat missing');
|
||||
res.status(500).send('oLat Missing');
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/incidents', cache('5 minutes'), (req, res) => {
|
||||
logger.debug(req.query);
|
||||
directions.getIncidents()
|
||||
.then((d) => {
|
||||
logger.debug(d);
|
||||
res.set('Cache-Control', 'public, max-age=300');
|
||||
res.send(d);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'incidents', 'e':e }));
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/traffic', cache('5 minutes'), asyncMiddleware(async (req, res, next) => {
|
||||
logger.debug(req.query);
|
||||
|
||||
if (req.query.hasOwnProperty('olat'))
|
||||
|
||||
await directions.doGetEstDirectionsWithIncidents(req.query.olat, req.query.olon, req.query.dlat, req.query.dlon).then((b) => {
|
||||
// logger.debug('b', b);
|
||||
res.set('Cache-Control', 'public, max-age=300');
|
||||
res.send(b);
|
||||
}).catch((e) => {
|
||||
logger.error(e);
|
||||
res.status(500).send(Object.assign(standardError, { 'source':'traffic', 'e':e }));
|
||||
});
|
||||
|
||||
else {
|
||||
// throw new Error('Weather: LL missing');
|
||||
logger.warn('FS: oLat missing');
|
||||
res.status(500).send('oLat Missing');
|
||||
}
|
||||
}));
|
||||
|
||||
app.listen(port, (err) => {
|
||||
if (err)
|
||||
return logger.error('Server error:', err);
|
||||
|
||||
logger.info(`Jubilee Server is listening on ${port}`);
|
||||
});
|
407
server/RightByMe.js
Normal file
407
server/RightByMe.js
Normal file
@ -0,0 +1,407 @@
|
||||
const logger = require('log4js').getLogger('RightByMe');
|
||||
const foursquare = require('node-foursquare-venues')('IXXFUGW3NC3DEVS2V5EU4NV4CL5E12AYGUPIR2D3U3B5DX4B', 'MZRIJDCEKUMVERA1OKVAIZI0TYAEBD3W2A2AGPTPI5TOLL1D', '20181111');
|
||||
const Twitter = require('twitter');
|
||||
const yelp = require('yelp-fusion');
|
||||
const jsonfile = require('jsonfile');
|
||||
const dateformat = require('dateformat');
|
||||
|
||||
const client = yelp.client('YlF_b6D149xr_xnrrYudlSnpn1A53b67vALlIK2HnD0ymBXQocRvPW3KjGN8jZNw0KnyAqxGaOzU7CLVPr84_KbnTxutNRXFVR9axmRqGN6ccda1xahoZo58KC2GWnYx');
|
||||
|
||||
const { get, isEmpty, has } = require('lodash');
|
||||
|
||||
const { reduceExplore, reduceYelp, reduceFullFS, reduceTwitter, reducePhotos } = require('./reducers/rightbyme');
|
||||
|
||||
const twitterClient = new Twitter({
|
||||
'consumer_key': 'bJvwgjA9O52j7rC6mqoeefPLO',
|
||||
'consumer_secret': 'NB6ASJxxMI9yaOgTAlWEpd18J1BdtIOYb4iz1HivIVpPqSLBY5',
|
||||
'access_token_key': '4773651-Ix1Qemdg4k02UV6A4ZRmwgD94eEPmYb6Y77QkzpWZA',
|
||||
'access_token_secret': 'RweUNv147YCAglOqk8myOVNuoVBvo6F9HPT3kM5ERQtR2'
|
||||
});
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
// google api key AIzaSyBl7O9LHIthCagcqIaDkQ4um_hghYG5reE
|
||||
|
||||
function nowTS() {
|
||||
const now = new Date();
|
||||
|
||||
// return dateformat(now, 'yymmddMMHH');
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function doFSVenueSearch(ll, data = {}) {
|
||||
let payLoad = Object.assign({}, data);
|
||||
logger.debug('>> doFSVenueSearch');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const fsObj = {
|
||||
'll': ll,
|
||||
'radius': 15,
|
||||
'v': '20170801',
|
||||
'limit': 1
|
||||
};
|
||||
|
||||
if (isEmpty(payLoad))
|
||||
foursquare.venues.search(fsObj, function(err, fsData) {
|
||||
if (err) {
|
||||
logger.debug('doFSVenueSearch', err);
|
||||
|
||||
return reject(err);
|
||||
}
|
||||
else {
|
||||
const venues = get(fsData, 'response.venues');
|
||||
if (venues.length > 0) {
|
||||
fsP1 = venues[0];
|
||||
|
||||
payLoad = reduceExplore(fsP1);
|
||||
|
||||
jsonfile.writeFileSync(`output/${payLoad.id}-FSVenueSearch.json`, fsData);
|
||||
|
||||
return resolve(payLoad);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
else
|
||||
|
||||
return resolve(payLoad);
|
||||
});
|
||||
}
|
||||
|
||||
function doFSVenueExplore(ll) {
|
||||
let payLoad = {};
|
||||
logger.debug('>> doFSVenueExplore', ll);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const fsObj = {
|
||||
'll': ll,
|
||||
'radius': 35,
|
||||
'v': '20170801',
|
||||
'limit': 1
|
||||
};
|
||||
|
||||
foursquare.venues.explore(fsObj, function(err, fsData) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
|
||||
return reject(err);
|
||||
}
|
||||
else {
|
||||
const groups = get(fsData, 'response.groups');
|
||||
const items = groups[0];
|
||||
const venues = items.items[0];
|
||||
|
||||
if (venues) {
|
||||
fsP1 = venues.venue;
|
||||
|
||||
payLoad = reduceExplore(fsP1);
|
||||
}
|
||||
|
||||
logger.debug(payLoad);
|
||||
// jsonfile.writeFileSync(`output/${payLoad.id}-FSVenueExplore.json`, fsData);
|
||||
|
||||
return resolve(payLoad);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doYelpSearch(data = {}) {
|
||||
const payLoad = Object.assign({}, data);
|
||||
logger.debug('>> doYelpSearch');
|
||||
|
||||
const yelpSearch = {};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!isEmpty(payLoad)) {
|
||||
yelpSearch.term = payLoad.name;
|
||||
yelpSearch.location = payLoad.address;
|
||||
yelpSearch.latitude = payLoad.latitude;
|
||||
yelpSearch.longitude = payLoad.longitude;
|
||||
|
||||
yelpSearch.radius = 250;
|
||||
yelpSearch.sort_by = 'distance';
|
||||
logger.debug(yelpSearch);
|
||||
client.search(yelpSearch).then(response => {
|
||||
const respArray = get(response, 'jsonBody.businesses');
|
||||
yelpReply = (respArray.length > 0) ? respArray[0] : {};
|
||||
|
||||
reduceYelp(yelpReply);
|
||||
|
||||
payLoad.yelp = reduceYelp(yelpReply);
|
||||
|
||||
jsonfile.writeFileSync(`output/${payLoad.id}-yelp.json`, response);
|
||||
|
||||
return resolve(payLoad);
|
||||
}).catch(e => {
|
||||
console.error('doYelpSearch', e);
|
||||
|
||||
return reject(e);
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
return resolve(payLoad);
|
||||
});
|
||||
}
|
||||
|
||||
function doFSGetFullVenue(data = {}) {
|
||||
const payLoad = Object.assign({}, data);
|
||||
logger.debug('>> doFSGetFullVenue');
|
||||
|
||||
// https://api.foursquare.com/v2/venues/4c5ff51213791b8d0d5c4eaf?client_id=IXXFUGW3NC3DEVS2V5EU4NV4CL5E12AYGUPIR2D3U3B5DX4B&client_secret=MZRIJDCEKUMVERA1OKVAIZI0TYAEBD3W2A2AGPTPI5TOLL1D&v=20180224
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!isEmpty(payLoad)) {
|
||||
// stuff
|
||||
// more
|
||||
const id = payLoad.id;
|
||||
foursquare.venues.venue(id, {}, function(err, fsData) {
|
||||
if (err)
|
||||
return reject(err);
|
||||
else {
|
||||
// console.log(JSON.stringify(fsData));
|
||||
const initPayload = (has(payLoad, 'name')) ? {} : reduceExplore(get(fsData, 'response.venue'));
|
||||
const partPayload = reduceFullFS(get(fsData, 'response.venue'));
|
||||
const newPayload = Object.assign(payLoad, initPayload, partPayload);
|
||||
|
||||
jsonfile.writeFileSync(`output/${payLoad.id}-FSGetFullVenue.json`, fsData);
|
||||
|
||||
return resolve(newPayload);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
return resolve(payLoad);
|
||||
});
|
||||
}
|
||||
|
||||
function doFSGetPhotos(data = {}) {
|
||||
const payLoad = Object.assign({}, data);
|
||||
logger.debug('>> doFSGetPhotos');
|
||||
|
||||
// https://api.foursquare.com/v2/venues/4c5ff51213791b8d0d5c4eaf?client_id=IXXFUGW3NC3DEVS2V5EU4NV4CL5E12AYGUPIR2D3U3B5DX4B&client_secret=MZRIJDCEKUMVERA1OKVAIZI0TYAEBD3W2A2AGPTPI5TOLL1D&v=20180224
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!isEmpty(payLoad)) {
|
||||
// stuff
|
||||
// more
|
||||
const id = payLoad.id;
|
||||
foursquare.venues.photos(id, { 'limit':16 }, function(err, fsData) {
|
||||
if (err)
|
||||
return reject(err);
|
||||
else {
|
||||
// console.log(JSON.stringify(fsData));
|
||||
jsonfile.writeFileSync('output/FSGetPhotos.json', fsData);
|
||||
logger.debug('fsData', JSON.stringify(fsData));
|
||||
const initPayload = (has(payLoad, 'name')) ? {} : reduceExplore(get(fsData, 'response.venue'));
|
||||
const partPayload = reducePhotos(fsData);
|
||||
const newPayload = Object.assign(payLoad, initPayload, partPayload);
|
||||
|
||||
// payLoad.images = partPayload;
|
||||
|
||||
jsonfile.writeFileSync(`output/${payLoad.id}-FSGetPhotos.json`, fsData);
|
||||
|
||||
return resolve(newPayload);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
return resolve(payLoad);
|
||||
});
|
||||
}
|
||||
|
||||
function doTweetSearch(data = {}) {
|
||||
const payLoad = Object.assign({}, data);
|
||||
logger.debug('>> doTweetSearch');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!isEmpty(payLoad)) {
|
||||
console.log('payLoad.twitter', payLoad.twitter);
|
||||
if (payLoad.twitter !== '') {
|
||||
const params = { 'screen_name': payLoad.twitter };
|
||||
|
||||
twitterClient.get('statuses/user_timeline', params, function(error, tweets, response) {
|
||||
if (error) {
|
||||
console.error('doTweetSearch', error);
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
else {
|
||||
// const partPayload = reduceFullFS(get(fsData, 'response.venue'));
|
||||
// const newPayload = Object.assign(payLoad, partPayload);
|
||||
|
||||
// return resolve(newPayload);
|
||||
const reducedTweets = reduceTwitter(tweets);
|
||||
|
||||
payLoad.tweets = reducedTweets;
|
||||
|
||||
jsonfile.writeFileSync(`output/${payLoad.id}-Tweets.json`, tweets);
|
||||
|
||||
return resolve(payLoad);
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
return resolve(payLoad);
|
||||
}
|
||||
else
|
||||
|
||||
return resolve(payLoad);
|
||||
});
|
||||
}
|
||||
|
||||
function doGetRightByMe(ll) {
|
||||
return new Promise((resolve, reject) => {
|
||||
doFSVenueExplore(ll)
|
||||
.then((d) => {
|
||||
return doFSVenueSearch(ll, d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
return doYelpSearch(d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
// return doYelpSearch(d)
|
||||
return doTweetSearch(d);
|
||||
})
|
||||
.then((d) => {
|
||||
// return doYelpSearch(d)
|
||||
return doFSGetFullVenue(d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
logger.info('Final', d.name, d.id);
|
||||
jsonfile.writeFileSync(`output/${d.id}-doGetRightByMe.json`, d);
|
||||
|
||||
return resolve(d);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doGetMoreDetail(id) {
|
||||
return new Promise((resolve, reject) => {
|
||||
doFSGetFullVenue({ 'id':id })
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
return doYelpSearch(d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
// return doYelpSearch(d)
|
||||
return doTweetSearch(d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
logger.info('Final', (d.name || '***** Name Missing'), d.id);
|
||||
jsonfile.writeFileSync(`output/${d.id}-doGetMoreDetail.json`, d);
|
||||
|
||||
return resolve(d);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function test(ll, near) {
|
||||
return new Promise((resolve, reject) => {
|
||||
doFSVenueExplore(ll)
|
||||
.then((d) => {
|
||||
return doFSVenueSearch(ll, d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
return doYelpSearch(d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
// return doYelpSearch(d)
|
||||
return doTweetSearch(d);
|
||||
})
|
||||
.then((d) => {
|
||||
// return doYelpSearch(d)
|
||||
return doFSGetFullVenue(d);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
// res.status(500).send('There was an error!');
|
||||
})
|
||||
.then((d) => {
|
||||
logger.info('Final', d.name, d.id);;
|
||||
|
||||
return resolve(d);
|
||||
});
|
||||
});
|
||||
}
|
||||
module.exports = { doGetRightByMe, doGetMoreDetail };
|
||||
|
||||
const tests = {
|
||||
'cruachan' : '56.3946,-5.1166',
|
||||
'morrisonsGarage' : '55.9429,-4.5622',
|
||||
'tescohelensburgh' : '56.003466,-4.733689',
|
||||
'howlinwolf' : '55.863991,-4.257788'
|
||||
};
|
||||
|
||||
// doGetRightByMe('56.3946,-5.1166'); // cruachan
|
||||
|
||||
// doGetRightByMe('55.9429,-4.5622'); // morrisons garage
|
||||
|
||||
// 56.3890134, "lng" : -5.0939317
|
||||
|
||||
// cruachan 56.3946,-5.1166
|
||||
|
||||
// tesco helensburgh
|
||||
// const location = { 'homeDistance':12.941340256604686, 'workDistance':33.47577415510536, 'latitude':56.003466, 'longitude':-4.733689, 'atHome':false, 'atWork':false, 'timestamp':1519638618383, 'll':'56.003466,-4.733689', 'llFixed':'56.003,-4.734', 'city':'Helensburgh', 'cityCC':'Helensburgh,GB', 'address':'9-13 Sinclair St, Helensburgh G84, UK' };
|
||||
|
||||
// test('55.863991,-4.257788');
|
||||
|
||||
// howling wolf http://localhost:8110/rightbyme?ll=55.863991,-4.257788
|
||||
|
||||
/*
|
||||
{"response":{"result":[{"cards":[{"card_id":"83a014c3-c0a4-3242-b5a8-79d011b677d6","rendering_engine":"custom","type":"WEATHER","type_display_name":"Weather","ttl":1519810720,"layout":{"template":"weather"},"data":{},"modules":{},"reason":"","notify":false,"ranking_arguments":{"STREAM_TYPE:MAIN":1.0,"rule_score":3.0,"TIER":3.0,"TIME_OF_DAY_RANGE:MORNING":1.0,"score":0.6},"instrumentation":{"rid":"13kfji9d97ld0","bucket":"ga"}},{"card_id":"c81eac4b-4553-3d60-85a6-178968bd5065","rendering_engine":"custom","type":"NEWS_DIGEST","type_display_name":"News Digest","ttl":1519810720,"layout":{"template":"news_digest"},"data":{},"modules":{},"reason":"","notify":false,"ranking_arguments":{"STREAM_TYPE:MAIN":1.0,"rule_score":2.0,"TIER":2.0,"TIME_OF_DAY_RANGE:MORNING":1.0,"score":0.55},"instrumentation":{"rid":"13kfji9d97ld0","bucket":"ga"}},{"card_id":"4b5b8c71-58d2-3f26-abce-318e3d3a01d6","rendering_engine":"custom","type":"MORNING_NIGHT","type_display_name":"Set Alarm","ttl":1519810720,"layout":{"template":"morning_night"},"data":{},"modules":{},"reason":"","notify":false,"ranking_arguments":{"USER_LOCATION:OTHER":1.0,"USER_SPEED:STILL":1.0,"STREAM_TYPE:MAIN":1.0,"rule_score":3.0,"TIER":3.0,"TIME_OF_DAY_RANGE:MORNING":1.0,"score":0.5},"instrumentation":{"rid":"13kfji9d97ld0","bucket":"ga"}},{"card_id":"bcacee7a-d79a-3e4e-a5b8-77c5b6480a8c","rendering_engine":"custom","type":"CALENDAR","type_display_name":"Calendar","ttl":1519810720,"layout":{"template":"calendar"},"data":{},"modules":{},"reason":"","notify":false,"ranking_arguments":{"USER_LOCATION:OTHER":1.0,"STREAM_TYPE:MAIN":1.0,"rule_score":2.0,"TIER":2.0,"TIME_OF_DAY_RANGE:MORNING":1.0,"score":0.25},"instrumentation":{"rid":"13kfji9d97ld0","bucket":"ga"}},{"card_id":"9953c0e9-5b77-3162-bcf4-e19fcd1f541f","rendering_engine":"custom","type":"VENUE_INFO","type_display_name":"Venues","ttl":1519641520,"layout":{"template":"venue_info"},"data":{"name":"Tesco","category":"Grocery Store","iconUrl":"https://ss3.4sqi.net/img/categories_v2/shops/food_grocery_64.png","id":"4c5ff51213791b8d0d5c4eaf","provider":"foursquare","tips":["Prepare to be deafened by relief calls over the loudspeaker"],"images":[],"address":"23-25 Sinclair Street","city":"Helensburgh","state":"Argyll and Bute","zip":"G84 8SR","twitter":{"handle":"uktescooffers","viewIntent":"https://twitter.com/uktescooffers#Intent;action=android.intent.action.VIEW;package=com.twitter.android;end","tweetIntent":"#Intent;action=android.intent.action.SEND;component=com.twitter.android/.PostActivity;S.android.intent.extra.TEXT=@uktescooffers;end"},"yelp":{"url":"https://m.yelp.com/biz/tesco-stores-helensburgh?adjust_creative=ogmBMO91tbdmscbTIaQEdA&utm_campaign=yelp_api&utm_medium=api_v2_search&utm_source=ogmBMO91tbdmscbTIaQEdA","rating":3.5,"reviewCount":4,"viewIntent":"https://m.yelp.com/biz/tesco-stores-helensburgh?adjust_creative=ogmBMO91tbdmscbTIaQEdA&utm_campaign=yelp_api&utm_medium=api_v2_search&utm_source=ogmBMO91tbdmscbTIaQEdA#Intent;action=android.intent.action.VIEW;end"}},"modules":{},"reason":"","notify":false,"ranking_arguments":{"poi_latitude":56.00348039818216,"poi_longitude":-4.733884334564209,"USER_LOCATION:OTHER":1.0,"req_longitude":-4.7336884,"req_latitude":56.003464,"STREAM_TYPE:MAIN":1.0,"USER_LOCATION_POI_CONFIDENCE:HIGH":1.0,"rule_score":0.0,"TIER":0.0,"USER_DIST_FROM_REQ_LOC:WITHIN_100M":1.0,"score":0.25},"instrumentation":{"rid":"13kfji9d97ld0","bucket":"ga"}},{"card_id":"eea4f59a-e8a2-3e12-a917-96e254c416d5","rendering_engine":"custom","type":"VENUE_CHOOSER","type_display_name":"Around Me","ttl":1519641520,"layout":{"template":"venue_chooser"},"data":{"venues":[{"name":"Lido's Fish & Chips","category":"Fish & Chips Shop","iconUrl":"https://ss3.4sqi.net/img/categories_v2/food/fishandchips_64.png","id":"4b9a9a77f964a5207ec535e3","provider":"foursquare","eid":"{\"card\":\"venue\",\"id\":\"4b9a9a77f964a5207ec535e3\"}"},{"name":"Mr Kebab","category":"Fast Food Restaurant","iconUrl":"https://ss3.4sqi.net/img/categories_v2/food/fastfo
|
||||
|
||||
{"response":{"result":[{"cards":[{"card_id":"9953c0e9-5b77-3162-bcf4-e19fcd1f541f","rendering_engine":"custom","type":"VENUE_INFO","type_display_name":"Venues","ttl":1519654292,"layout":{"template":"venue_info"},"data":{"name":"Speirs Wharf","category":"Pool","iconUrl":"https://ss3.4sqi.net/img/categories_v2/parks_outdoors/pool_64.png","id":"5211c15a11d2c83298e7f8c2","provider":"foursquare","tips":[],"images":["https://igx.4sqi.net/img/general/640x640/72939115_8s_CraZskGLT5uJz5DzRiyBvKXgy6BobQBH5u0Vcsy4.jpg","https://igx.4sqi.net/img/general/960x364/62519220_Z_xAdlllLg7Mb1BUt1b9G18IKTpF1jyeNqGCMo0WhRs.jpg","https://igx.4sqi.net/img/general/960x541/24390977_Ws2HHTyM1zKBlvejccG1BxGTt0hKBURtWQOPPxYc7IM.jpg","https://igx.4sqi.net/img/general/960x960/149524__j5bRwPX5zy5RmOu-uleVtQ-KekAS-rXAJLgc_JjipY.jpg","https://igx.4sqi.net/img/general/640x640/4894760_ZOelH_F7ljq0Mjk6Zzs7KHH8-Dn470lbq4IfFg7WGiM.jpg","https://igx.4sqi.net/img/general/640x640/4894760_kW7NmETQYJxkGfBBnhHhQdDHcVouBj7OWpZBbyy0OTM.jpg"],"telephone":"","address":"","city":"","state":"","zip":"","latitude":55.872669822119796,"longitude":-4.257480441815151,"twitter":{"handle":null,"viewIntent":null,"tweetIntent":null},"yelp":{"url":null,"rating":0.0,"reviewCount":0,"viewIntent":null}},"modules":{},"reason":"","notify":false,"ranking_arguments":{"poi_latitude":55.872669822119796,"poi_longitude":-4.257480441815151,"USER_LOCATION:OTHER":1.0,"req_longitude":-4.2577868,"req_latitude":55.86399,"USER_LOCATION_POI_CONFIDENCE:HIGH":1.0,"STREAM_TYPE":1.0,"rule_score":0.0,"TIER":0.0,"USER_DIST_FROM_REQ_LOC:WITHIN_100M":1.0,"score":0.0},"instrumentation":{"rid":"fn62krdd981s4","bucket":"ga"}}]}],"error":null}}
|
||||
*/
|
||||
|
40
server/agenda.js
Normal file
40
server/agenda.js
Normal file
@ -0,0 +1,40 @@
|
||||
const calHandler = require('./lib/calHandler');
|
||||
const logger = require('log4js').getLogger('Agenda');
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
function compare(a, b) {
|
||||
if (a.ts < b.ts)
|
||||
return -1;
|
||||
|
||||
if (a.ts > b.ts)
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
async function getTodaysAgenda() {
|
||||
let three = [];
|
||||
for (const item of calHandler.calendars)
|
||||
await calHandler.getAdvancedCalV3(item)
|
||||
.then((d) => {
|
||||
three = three.concat(d.three);
|
||||
})
|
||||
.catch((e) => {
|
||||
'use strict';
|
||||
logger.error(e);
|
||||
});
|
||||
|
||||
three = three.sort(compare);
|
||||
return three;
|
||||
}
|
||||
|
||||
async function doTodaysAgenda() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const today = getTodaysAgenda();
|
||||
|
||||
return resolve(today);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { doTodaysAgenda };
|
120
server/directions.js
Normal file
120
server/directions.js
Normal file
@ -0,0 +1,120 @@
|
||||
const request = require('request');
|
||||
const FeedMe = require('feedme');
|
||||
const http = require('http');
|
||||
|
||||
const logger = require('log4js').getLogger('Directions');
|
||||
logger.level = 'debug';
|
||||
|
||||
const { reduceEstDirections, reduceIncidents, combine } = require('./reducers/directions');
|
||||
|
||||
module.exports = {
|
||||
'getTraffic': doGetEstDirections,
|
||||
'getIncidents' : doGetTraffic,
|
||||
doGetEstDirectionsWithIncidents
|
||||
};
|
||||
|
||||
const headers = {};
|
||||
const lastGood = {};
|
||||
|
||||
//
|
||||
// https://maps.googleapis.com/maps/api/directions/json?origin=Toronto&destination=Montreal&key=AIzaSyBl7O9LHIthCagcqIaDkQ4um_hghYG5reE
|
||||
|
||||
async function doGetEstDirectionsWithIncidents(olat, olon, dlat, dlon) {
|
||||
let traff = await doGetEstDirections(olat, olon, dlat, dlon);
|
||||
let incid = await doGetTraffic();
|
||||
|
||||
logger.debug('after...');
|
||||
|
||||
let combined = combine(traff, incid);
|
||||
return combined;
|
||||
}
|
||||
|
||||
async function doGetEstDirections(olat, olon, dlat, dlon) {
|
||||
logger.debug('doGetEstDirections');
|
||||
const url = `https://sgws2.maps.yahoo.com/Directions?time=now&cache=n&flags=J&olat=${olat}&olon=${olon}&dlat=${dlat}&dlon=${dlon}&mode=11`;
|
||||
|
||||
logger.debug(url);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request(url, function(err, resp, body) {
|
||||
if (err)
|
||||
// Logger.error(err);
|
||||
return reject(err);
|
||||
// Throw err;
|
||||
|
||||
const output = reduceEstDirections(body);
|
||||
output.fullBody = JSON.parse(body);
|
||||
output.timestamp = new Date().getTime();
|
||||
|
||||
console.log(output);
|
||||
|
||||
return resolve(output);
|
||||
}, function(error, response, body) {
|
||||
console.log(response);
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(response.statusCode);
|
||||
logger.error(body);
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function doGetTraffic() {
|
||||
logger.debug('doGetTraffic');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// https://trafficscotland.org/rss/feeds/currentincidents.aspx
|
||||
const options = {
|
||||
'hostname': 'trafficscotland.org',
|
||||
'path': '/rss/feeds/currentincidents.aspx',
|
||||
'method': 'GET',
|
||||
'headers': headers
|
||||
};
|
||||
|
||||
http.get(options, (res) => {
|
||||
const { statusCode } = res;
|
||||
const contentType = res.headers['content-type'];
|
||||
const reqLastModified = res.headers['date'];
|
||||
|
||||
logger.debug(res.headers);
|
||||
let error;
|
||||
|
||||
logger.debug('contentType', contentType);
|
||||
if (statusCode !== 200 && statusCode !== 304)
|
||||
error = new Error('Request Failed.\n' +
|
||||
`Status Code: ${statusCode}`);
|
||||
|
||||
else if (!/^application\/rss\+xml/.test(contentType) && statusCode === 200)
|
||||
error = new Error('Invalid content-type.\n' +
|
||||
`Expected application/rss+xml but received ${contentType}`);
|
||||
|
||||
if (error) {
|
||||
logger.error(error.message);
|
||||
// consume response data to free up memory
|
||||
res.resume();
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
if ( statusCode === 200) {
|
||||
headers['If-Modified-Since'] = reqLastModified;
|
||||
const parser = new FeedMe(true);
|
||||
res.pipe(parser);
|
||||
parser.on('end', () => {
|
||||
lastGood.page = reduceIncidents(parser.done());
|
||||
logger.info('Traffic Feed cached');
|
||||
// return resolve(parser.done());
|
||||
return resolve(lastGood.page);
|
||||
});
|
||||
}
|
||||
else if (statusCode === 304) {
|
||||
logger.info('Traffic Feed changed');
|
||||
|
||||
return resolve(lastGood.page);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
143
server/euronews.js
Normal file
143
server/euronews.js
Normal file
@ -0,0 +1,143 @@
|
||||
const FeedMe = require('feedme');
|
||||
const fecha = require('fecha');
|
||||
const request = require('request');
|
||||
const http = require('http');
|
||||
|
||||
const { reduceArticle, reduceArticleV2 } = require('./reducers/euronews');
|
||||
|
||||
const logger = require('log4js').getLogger('Euronews');
|
||||
logger.level = 'debug';
|
||||
|
||||
module.exports = {
|
||||
'getEuroNews': doGetEuroNews,
|
||||
'render': render,
|
||||
'getArticle' : doGetArticle
|
||||
|
||||
};
|
||||
|
||||
const headers = {};
|
||||
const lastGood = {};
|
||||
|
||||
class Template {
|
||||
constructor(item) {
|
||||
// "pubdate": "Tue, 06 Feb 2018 17:05:00 +0100",
|
||||
const pubdateSrc = fecha.parse(item.pubdate, 'ddd, DD MMM YYYY HH:mm:SS ZZ');
|
||||
const pubdate = fecha.format(pubdateSrc, 'dddd MMMM Do, YYYY');
|
||||
const description = item.description.replace(/(<script(\s|\S)*?<\/script>)|(<style(\s|\S)*?<\/style>)|(<!--(\s|\S)*?-->)|(<\/?(\s|\S)*?>)/g, '');
|
||||
this.data = `<article>
|
||||
<header>
|
||||
<a href="${item.guid.text}">${item.title}</a>
|
||||
<time class="published">${pubdate}</time>
|
||||
</header>
|
||||
<p class="description">${description}</p>
|
||||
</article>`;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return this.data;
|
||||
}
|
||||
}
|
||||
|
||||
function doGetEuroNews() {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.info('doGetEuroNews:Retrieving Euronews Headlines..');
|
||||
|
||||
// http://feeds.feedburner.com/euronews/en/home/
|
||||
// http://feeds.feedburner.com/euronews/en/news/
|
||||
const options = {
|
||||
'hostname': 'feeds.feedburner.com',
|
||||
'path': '/euronews/en/home/',
|
||||
'method': 'GET',
|
||||
'headers': headers
|
||||
};
|
||||
|
||||
// http.get('http://feeds.feedburner.com/euronews/en/news/', (res) => {
|
||||
http.get(options, (res) => {
|
||||
const { statusCode } = res;
|
||||
const contentType = res.headers['content-type'];
|
||||
const reqLastModified = res.headers['last-modified'];
|
||||
|
||||
logger.debug(res.headers);
|
||||
let error;
|
||||
|
||||
logger.debug('contentType', contentType);
|
||||
if (statusCode !== 200 && statusCode !== 304)
|
||||
error = new Error('Request Failed.\n' +
|
||||
`Status Code: ${statusCode}`);
|
||||
|
||||
else if (!/^text\/xml/.test(contentType) && statusCode === 200)
|
||||
error = new Error('Invalid content-type.\n' +
|
||||
`Expected text/xml but received ${contentType}`);
|
||||
|
||||
if (error) {
|
||||
logger.error(error.message);
|
||||
// consume response data to free up memory
|
||||
res.resume();
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
if ( statusCode === 200) {
|
||||
headers['If-Modified-Since'] = reqLastModified;
|
||||
const parser = new FeedMe(true);
|
||||
res.pipe(parser);
|
||||
parser.on('end', () => {
|
||||
lastGood.page = parser.done();
|
||||
logger.info('Euronews page cached');
|
||||
// return resolve(parser.done());
|
||||
return resolve(lastGood.page);
|
||||
});
|
||||
}
|
||||
else if (statusCode === 304) {
|
||||
logger.info('Euronews not changed');
|
||||
|
||||
return resolve(lastGood.page);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doGetArticle(guid = '') {
|
||||
const splitURL = /([--:\w?@%&+~#=]*\.[a-z]{2,4}\/{0,2})((?:[?&](?:\w+)=(?:\w+))+|[--:\w?@%&+~#=]+)?/g;
|
||||
const url = splitURL.exec(decodeURI(guid));
|
||||
logger.debug('Converting:', guid);
|
||||
// logger.debug('Split', url);
|
||||
|
||||
const ampURL = `${url[1]}amp/${url[2]}`;
|
||||
|
||||
logger.debug('ampURL', ampURL);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request(ampURL, function(err, resp, body) {
|
||||
if (err)
|
||||
// Logger.error(err);
|
||||
return reject(err);
|
||||
// Throw err;
|
||||
|
||||
const output = reduceArticleV2(body);
|
||||
|
||||
logger.debug(JSON.stringify(output));
|
||||
|
||||
return resolve(output);
|
||||
}, function(error, response, body) {
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(response.statusCode);
|
||||
logger.error(body);
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function render(data) {
|
||||
logger.debug('Rendering euronews');
|
||||
// logger.debug(JSON.stringify(data));
|
||||
|
||||
const html = [];
|
||||
const items = data.slice(0, 10);
|
||||
for (const item of items)
|
||||
html.push(new Template(item).toString());
|
||||
|
||||
return(html.join(''));
|
||||
}
|
34
server/foursquare.js
Normal file
34
server/foursquare.js
Normal file
@ -0,0 +1,34 @@
|
||||
const logger = require('log4js').getLogger('FSQ');
|
||||
const foursquare = require('node-foursquare-venues')('IXXFUGW3NC3DEVS2V5EU4NV4CL5E12AYGUPIR2D3U3B5DX4B', 'MZRIJDCEKUMVERA1OKVAIZI0TYAEBD3W2A2AGPTPI5TOLL1D', '20170801');
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
function doGetFourSquareExplore(ll, limit = 3, section = 'topPicks', query = '') {
|
||||
const [lat, long ] = ll.split(',');
|
||||
|
||||
console.log('>> query', query);
|
||||
return new Promise((resolve, reject) => {
|
||||
const fsObj = {
|
||||
'll': ll,
|
||||
'v': '20170801',
|
||||
'limit': limit,
|
||||
'radius': 800
|
||||
};
|
||||
|
||||
if (query === '' )
|
||||
fsObj.section = section;
|
||||
else
|
||||
fsObj.query = query;
|
||||
|
||||
console.log('>> fsObj', fsObj);
|
||||
foursquare.venues.explore(fsObj, function(err, fsData) {
|
||||
logger.error(err);
|
||||
if (err)
|
||||
return reject(err);
|
||||
else
|
||||
return resolve(fsData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { doGetFourSquareExplore };
|
96
server/geocode.js
Normal file
96
server/geocode.js
Normal file
@ -0,0 +1,96 @@
|
||||
const NodeGeocoder = require('node-geocoder');
|
||||
const logger = require('log4js').getLogger('GeoCode');
|
||||
|
||||
const { reduceOpencage } = require('./reducers/opencage');
|
||||
logger.level = 'debug';
|
||||
|
||||
const options = {
|
||||
'provider': 'opencage',
|
||||
|
||||
// Optional depending on the providers
|
||||
'httpAdapter': 'https', // Default
|
||||
'apiKey': '893ab539eca84b5ca7a54cb03ef23443', // for Mapquest, OpenCage, Google Premier
|
||||
'formatter': null // 'gpx', 'string', ...
|
||||
};
|
||||
|
||||
const geocoder = NodeGeocoder(options);
|
||||
|
||||
function doGetGeocode(ll) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const [lat, lon ] = ll.split(',');
|
||||
|
||||
const latlong = { lat, lon };
|
||||
|
||||
logger.debug(latlong);
|
||||
|
||||
geocoder.reverse(latlong)
|
||||
.then(function(res) {
|
||||
if (res.hasOwnProperty('raw')) {
|
||||
const result = reduceOpencage(res.raw);
|
||||
|
||||
return resolve(result[0]);
|
||||
}
|
||||
else
|
||||
return resolve(res[0]);
|
||||
})
|
||||
.catch(function(err) {
|
||||
logger.error(err);
|
||||
|
||||
return reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { doGetGeocode };
|
||||
|
||||
/*
|
||||
|
||||
opencage
|
||||
|
||||
{
|
||||
"latitude": 51.508751,
|
||||
"longitude": -0.067457,
|
||||
"country": "United Kingdom",
|
||||
"city": "London",
|
||||
"state": "England",
|
||||
"zipcode": "SE15",
|
||||
"streetName": "Vaughan Way",
|
||||
"countryCode": "gb",
|
||||
"suburb": "St.George in the East",
|
||||
"extra": {
|
||||
"flag": "🇬🇧",
|
||||
"confidence": 9,
|
||||
"confidenceKM": 0.5,
|
||||
"map": "https://www.openstreetmap.org/?mlat=51.50875&mlon=-0.06746#map=17/51.50875/-0.06746"
|
||||
}
|
||||
}
|
||||
|
||||
google
|
||||
|
||||
[
|
||||
{
|
||||
"administrativeLevels": {
|
||||
"level1long": "England",
|
||||
"level1short": "England",
|
||||
"level2long": "Northamptonshire",
|
||||
"level2short": "Northamptonshire"
|
||||
},
|
||||
"city": "Northampton",
|
||||
"country": "United Kingdom",
|
||||
"countryCode": "GB",
|
||||
"extra": {
|
||||
"confidence": 0.7,
|
||||
"establishment": "Daventy depot",
|
||||
"googlePlaceId": "ChIJI8H0WFUVd0gRIIFzNwDQAuM",
|
||||
"neighborhood": "Kilsby",
|
||||
"premise": null,
|
||||
"subpremise": null
|
||||
},
|
||||
"formattedAddress": "Daventy depot, Kilsby, Northampton NN6 7GY, UK",
|
||||
"latitude": 52.3546726,
|
||||
"longitude": -1.1741823,
|
||||
"provider": "google",
|
||||
"zipcode": "NN6 7GY"
|
||||
}
|
||||
]
|
||||
*/
|
679
server/lib/calHandler.js
Normal file
679
server/lib/calHandler.js
Normal file
@ -0,0 +1,679 @@
|
||||
const request = require('request');
|
||||
const log4js = require('log4js');
|
||||
const logger = log4js.getLogger('calHandler');
|
||||
const STRING = require('string');
|
||||
const util = require('util');
|
||||
const Elapsed = require('elapsed');
|
||||
const Sugar = require('sugar');
|
||||
const { inRange } = require('lodash');
|
||||
const moment = require('moment');
|
||||
|
||||
Sugar.extend();
|
||||
|
||||
logger.level = 'error';
|
||||
|
||||
moment.updateLocale('en', {
|
||||
'calendar' : {
|
||||
'lastDay' : '[Yesterday\n] HH:mm',
|
||||
'sameDay' : '[Today\n] HH:mm',
|
||||
'nextDay' : '[Tomorrow\n] HH:mm',
|
||||
'lastWeek' : '[last] dddd [at] HH:mm',
|
||||
'nextWeek' : 'dddd[\n] HH:mm',
|
||||
'sameElse' : 'YYYY-MM-DD'
|
||||
}
|
||||
});
|
||||
|
||||
function processICAL(ical) {
|
||||
'use strict';
|
||||
logger.info('+ processICAL');
|
||||
let workingBlock = [];
|
||||
const segments = {
|
||||
'meetingStartID': 'DTSTART;TZID=Europe/London:',
|
||||
'meetingStartAlt': 'DTSTART:',
|
||||
'meetingStartAltOther': 'DTSTART;VALUE=DATE:',
|
||||
'meetingEndID': 'DTEND;TZID=Europe/London:',
|
||||
'meetingEndAlt': 'DTEND:',
|
||||
'meetingEndAltOther': 'DTEND;VALUE=DATE:',
|
||||
'meetingDescID': 'DESCRIPTION:',
|
||||
'summaryID': 'SUMMARY:',
|
||||
'begin': 'BEGIN:VEVENT',
|
||||
'end': 'END:VEVENT',
|
||||
'beginAlarm': 'BEGIN:VALARM',
|
||||
'endAlarm': 'END:VALARM',
|
||||
'recur': 'RRULE:'
|
||||
};
|
||||
|
||||
const rules = ['FREQ', 'WKST', 'UNTIL', 'BYMONTH', 'BYMONTHDAY', 'INTERVAL', 'BYDAY'];
|
||||
|
||||
function nThDayOfMonth(monthsAhead, wantedDay) {
|
||||
const now = new Date();
|
||||
|
||||
for(let t = 0; t < monthsAhead; t++) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function processRecurrence(workBlock) {
|
||||
const _workBlock = Object.assign({}, workBlock);
|
||||
const dateLimit = new Date().reset('month').addMonths(2);
|
||||
const recurArray = [];
|
||||
logger.info('---===---');
|
||||
logger.debug('>> Processing recurrence...');
|
||||
logger.debug(`Expanding ${_workBlock.summary}`);
|
||||
logger.debug('DateLimit:', dateLimit);
|
||||
// logger.debug('Processing recurrence...');
|
||||
const weekBits = { 'SU': 0, 'MO': 1, 'TU': 2, 'WE': 3, 'TH': 4, 'FR': 5, 'SA': 6 };
|
||||
|
||||
const now = new Date();
|
||||
const day = now.getDate();
|
||||
const dayNum = now.getDay();
|
||||
const month = now.getMonth();
|
||||
const year = now.getFullYear();
|
||||
const byDayRegEx = /(\d)(\w.)/;
|
||||
|
||||
const recurSettings = { 'freq': null, 'wkst': null, 'until': null, 'bymonth': null, 'bymonthday': null, 'interval': null, 'byday': null };
|
||||
|
||||
const firstSplit = _workBlock.recur.split(';');
|
||||
|
||||
for (let t = 0; t < firstSplit.length; t++) {
|
||||
const ws = firstSplit[t].split('=');
|
||||
if (rules.indexOf(ws[0]) > -1)
|
||||
recurSettings[ws[0].toLowerCase()] = ws[1];
|
||||
}
|
||||
|
||||
// if all null discard..
|
||||
|
||||
if (recurSettings.freq === null && recurSettings.wkst === null && recurSettings.until === null && recurSettings.byday === null && recurSettings.bymonth === null && recurSettings.bymonthday === null && recurSettings.interval === null)
|
||||
return null;
|
||||
|
||||
if (recurSettings.until !== null) {
|
||||
// have we expired?
|
||||
const _until = Date.create(recurSettings.until).isPast();
|
||||
if (_until) {
|
||||
logger.warn('EXPIRED!!');
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (recurSettings.freq !== null) {
|
||||
// logger.debug(_workBlock);
|
||||
let newStart, newEnd;
|
||||
|
||||
const origStart = new Date(_workBlock.dtstart);
|
||||
const origEnd = new Date(_workBlock.dtend);
|
||||
|
||||
const _dstart = new Date(_workBlock.dtstart);
|
||||
const _dend = new Date(_workBlock.dtend);
|
||||
logger.debug('>> origStart', origStart);
|
||||
|
||||
const _d = origStart.getDate();
|
||||
const _m = origStart.getMonth();
|
||||
const _h = origStart.getHours();
|
||||
const _min = origStart.getMinutes();
|
||||
const _secs = origStart.getSeconds();
|
||||
const distance = origEnd - origStart;
|
||||
|
||||
_workBlock.details = {
|
||||
_d,
|
||||
_m,
|
||||
_h,
|
||||
_min,
|
||||
_secs,
|
||||
distance
|
||||
};
|
||||
|
||||
logger.debug('freq:', recurSettings.freq);
|
||||
if (recurSettings.freq === 'YEARLY') {
|
||||
logger.warn('YEARLY');
|
||||
if (recurSettings.bymonth !== null && recurSettings.bymonthday !== null) {
|
||||
let _yearCount = year;
|
||||
logger.debug('>> Yearly with specific month / day');
|
||||
let newBlock;
|
||||
newStart = new Date().set({ 'year':_yearCount, 'month': recurSettings.bymonth - 1, 'day': recurSettings.bymonthday, 'hour':_h, 'minutes':_min, 'seconds':_secs });
|
||||
newEnd = new Date(_dstart).addMilliseconds(distance);
|
||||
do {
|
||||
newBlock = Object.assign({}, _workBlock);
|
||||
newBlock.dstart = new Date(newStart);
|
||||
newBlock.dend = new Date(newEnd);
|
||||
|
||||
recurArray.push(newBlock);
|
||||
_yearCount++;
|
||||
newStart = new Date().set({ 'year':_yearCount, 'month': recurSettings.bymonth - 1, 'day': recurSettings.bymonthday, 'hour':_h, 'minutes':_min, 'seconds':_secs });
|
||||
newEnd = new Date(_dstart).addMilliseconds(distance);
|
||||
}
|
||||
while(newStart < dateLimit);
|
||||
}
|
||||
else if (recurSettings.bymonth === null && recurSettings.bymonthday === null) {
|
||||
logger.debug('>> Yearly no specific month / day');
|
||||
// extract month and year from dtstart
|
||||
let newBlock;
|
||||
do {
|
||||
newBlock = Object.assign({}, _workBlock);
|
||||
newBlock.dtstart = new Date(_dstart);
|
||||
newBlock.dtend = new Date(_dend);
|
||||
// logger.info(newBlock.dtstart.medium());
|
||||
recurArray.push(newBlock);
|
||||
|
||||
_dstart.advance({ 'year':1 });
|
||||
_dend.advance({ 'year':1 });
|
||||
}
|
||||
while(_dstart < dateLimit);
|
||||
}
|
||||
|
||||
// logger.info('** recurArray', recurArray);
|
||||
|
||||
return recurArray;
|
||||
}
|
||||
|
||||
if (recurSettings.freq === 'MONTHLY' ) {
|
||||
const interval = parseInt(recurSettings.interval, 10) || 1;
|
||||
logger.warn(`MONTHLY - ${interval}`);
|
||||
let newBlock;
|
||||
if (recurSettings.byday === null)
|
||||
do {
|
||||
newBlock = Object.assign({}, _workBlock);
|
||||
newBlock.dtstart = new Date(_dstart);
|
||||
newBlock.dtend = new Date(_dend);
|
||||
// logger.info(newBlock.dtstart.medium());
|
||||
recurArray.push(newBlock);
|
||||
|
||||
_dstart.addMonths(interval);
|
||||
_dend.addMonths(interval);
|
||||
}
|
||||
while(_dstart < dateLimit);
|
||||
|
||||
else {
|
||||
logger.warn('BYDAY!!! ', recurSettings.byday);
|
||||
_dstart.setUTC(true);
|
||||
const byday = byDayRegEx.exec(recurSettings.byday);
|
||||
const dayNumber = weekBits[byday[2]];
|
||||
const stepCount = parseInt(byday[1], 10) - 1;
|
||||
|
||||
do {
|
||||
const _findSun = new Date.create(_dstart, { 'fromUTC': true } ).reset('month');
|
||||
|
||||
const firstDay = _findSun.getDay();
|
||||
// find first occurance of the wanted day.
|
||||
|
||||
if (firstDay !== dayNumber) {
|
||||
const add = (dayNumber - firstDay + 7) ;
|
||||
_findSun.addDays(add).addWeeks(stepCount);
|
||||
}
|
||||
else
|
||||
_findSun.addWeeks(stepCount);
|
||||
|
||||
newBlock = Object.assign({}, _workBlock);
|
||||
newBlock.dtstart = new Date(_findSun).addMilliseconds(_workBlock.timeStartMS);
|
||||
newBlock.dtend = new Date(_findSun).addMilliseconds(_workBlock.timeEndMS);
|
||||
// logger.info(newBlock.dtstart.medium());
|
||||
recurArray.push(newBlock);
|
||||
// all done, next.
|
||||
_dstart.reset('month').addDays(32);
|
||||
_dend.reset('month').addDays(32);
|
||||
}
|
||||
while(_dstart < dateLimit);
|
||||
|
||||
// something
|
||||
}
|
||||
|
||||
return recurArray;
|
||||
}
|
||||
|
||||
if (recurSettings.freq === 'WEEKLY') {
|
||||
const interval = parseInt(recurSettings.interval, 10) || 1;
|
||||
logger.warn(`WEEKLY - ${interval}`);
|
||||
let newBlock;
|
||||
|
||||
do {
|
||||
newBlock = Object.assign({}, _workBlock);
|
||||
newBlock.dtstart = new Date(_dstart);
|
||||
newBlock.dtend = new Date(_dend);
|
||||
// logger.info(newBlock.dtstart.medium());
|
||||
recurArray.push(newBlock);
|
||||
|
||||
_dstart.addWeeks(interval);
|
||||
_dend.addWeeks(interval);
|
||||
}
|
||||
while(_dstart < dateLimit);
|
||||
}
|
||||
|
||||
return recurArray;
|
||||
}
|
||||
|
||||
// if we get here we've skipped everything just return the _workblock
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
function processBlock(block) {
|
||||
logger.debug('>> processBlock');
|
||||
let _wb;
|
||||
const workBlock = {
|
||||
'summary': '',
|
||||
'dtstart': null,
|
||||
'dtend': null,
|
||||
'description': '',
|
||||
'timeStart': null,
|
||||
'timeEnd': null,
|
||||
'duration': 0,
|
||||
'combined': '',
|
||||
'recur': null,
|
||||
'timeStartMS': null,
|
||||
'timeEndMS': null,
|
||||
'ts' : null,
|
||||
'allday' : false
|
||||
};
|
||||
let alarmFlag = false, ws, blockStep;
|
||||
for (let step = 0; step < block.length; step++) {
|
||||
blockStep = block[step];
|
||||
if (blockStep.indexOf(segments.recur) >= 0)
|
||||
workBlock.recur = STRING(block[step].split(segments.recur)[1]).collapseWhitespace().s;
|
||||
// logger.debug(workBlock.recur);
|
||||
|
||||
if (blockStep.indexOf(segments.summaryID) >= 0)
|
||||
workBlock.summary = STRING(block[step].split(segments.summaryID)[1]).collapseWhitespace().s;
|
||||
|
||||
if (blockStep.indexOf(segments.meetingStartID) >= 0) {
|
||||
ws = STRING(block[step].split(segments.meetingStartID)[1]).collapseWhitespace().s;
|
||||
// workBlock.dtstart = Date.create(ws);
|
||||
workBlock.dtstart = new Sugar.Date(ws).raw;
|
||||
}
|
||||
if (blockStep.indexOf(segments.meetingEndID) >= 0) {
|
||||
ws = STRING(block[step].split(segments.meetingEndID)[1]).collapseWhitespace().s;
|
||||
// workBlock.dtend = Date.create(ws);
|
||||
workBlock.dtend = new Sugar.Date(ws).raw;
|
||||
}
|
||||
if (blockStep.indexOf(segments.meetingStartAlt) >= 0) {
|
||||
ws = STRING(block[step].split(segments.meetingStartAlt)[1]).collapseWhitespace().s;
|
||||
// console.log('>> ws', ws);
|
||||
// workBlock.dtstart = Date.create(ws);
|
||||
// let d = new Sugar.Date();
|
||||
workBlock.dtstart = new Sugar.Date(ws).raw;
|
||||
// console.log('>> date', workBlock.dtstart);
|
||||
}
|
||||
if (blockStep.indexOf(segments.meetingEndAlt) >= 0) {
|
||||
ws = STRING(block[step].split(segments.meetingEndAlt)[1]).collapseWhitespace().s;
|
||||
// workBlock.dtend = Date.create(ws);
|
||||
workBlock.dtend = new Sugar.Date(ws).raw;
|
||||
// console.log('>> date', workBlock.dtend);
|
||||
}
|
||||
|
||||
if (blockStep.indexOf(segments.meetingStartAltOther) >= 0) {
|
||||
ws = STRING(block[step].split(segments.meetingStartAltOther)[1]).collapseWhitespace().s;
|
||||
// workBlock.dtstart = Date.create(ws);
|
||||
workBlock.dtstart = new Sugar.Date(ws).raw;
|
||||
}
|
||||
if (blockStep.indexOf(segments.meetingEndAltOther) >= 0) {
|
||||
ws = STRING(block[step].split(segments.meetingEndAltOther)[1]).collapseWhitespace().s;
|
||||
// console.log('>> ws', ws);
|
||||
// workBlock.dtend = Date.create(ws);
|
||||
workBlock.dtend = new Sugar.Date(ws).raw;
|
||||
}
|
||||
|
||||
if (blockStep.indexOf(segments.meetingDescID) >= 0)
|
||||
if (!alarmFlag) {
|
||||
workBlock.description = STRING(block[step].split(segments.meetingDescID)[1]).collapseWhitespace().s;
|
||||
}
|
||||
|
||||
if (blockStep.indexOf(segments.beginAlarm) >= 0)
|
||||
alarmFlag = true;
|
||||
}
|
||||
|
||||
workBlock.summary = workBlock.summary.replace(/\\/g, '');
|
||||
workBlock.description = workBlock.description.replace(/\\/g, '');
|
||||
|
||||
if (workBlock.dtstart !== null) {
|
||||
// workBlock.timeStart = workBlock.dtstart.format('{24hr}:{mm}:{ss}');
|
||||
workBlock.timeStart = Sugar.Date(workBlock.dtstart).format('{24hr}:{mm}:{ss}').raw;
|
||||
|
||||
workBlock.timeStartMS = ((workBlock.dtstart.getHours() * 60 * 60 ) + (workBlock.dtstart.getMinutes() * 60 ) + workBlock.dtstart.getSeconds()) * 1000;
|
||||
// console.log('>> workBlock.timeStart', workBlock.timeStart);
|
||||
workBlock.combined = `<em>${workBlock.timeStart}</em> - '`;
|
||||
workBlock.long = `<em>${Sugar.Date(workBlock.dtstart).format('{Weekday}').raw}, ${workBlock.timeStart}</em> - `;
|
||||
// console.log('>> workBlock.long', workBlock.long);
|
||||
}
|
||||
workBlock.combined = workBlock.combined + workBlock.summary;
|
||||
workBlock.longcombined = workBlock.long + workBlock.summary;
|
||||
if (workBlock.dtend !== null) {
|
||||
workBlock.timeEnd = Sugar.Date(workBlock.dtend).format('{24hr}:{mm}:{ss}').raw;
|
||||
workBlock.timeEndMS = ((workBlock.dtend.getHours() * 60 * 60 ) + (workBlock.dtend.getMinutes() * 60 ) + workBlock.dtend.getSeconds()) * 1000;
|
||||
}
|
||||
|
||||
if (workBlock.dtstart !== null && workBlock.dtend !== null) {
|
||||
const elapsedTime = new Elapsed(workBlock.dtstart, workBlock.dtend);
|
||||
workBlock.duration = elapsedTime.optimal;
|
||||
workBlock.combined = `${workBlock.combined }, ${ elapsedTime.optimal}`;
|
||||
workBlock.longcombined = `${workBlock.longcombined }, ${ elapsedTime.optimal}`;
|
||||
}
|
||||
|
||||
workBlock.allday = (workBlock.timeStart === '0:00:00' && workBlock.timeEnd === '0:00:00');
|
||||
|
||||
return workBlock;
|
||||
}
|
||||
|
||||
function buildRecurranceArray(wb) {
|
||||
const _wb = processRecurrence(wb);
|
||||
|
||||
return _wb;
|
||||
}
|
||||
|
||||
const lines = ical.split('\r\n'), l = lines.length;
|
||||
let counter = 0;
|
||||
|
||||
let alarmed = false;
|
||||
while (counter < l)
|
||||
if (lines[counter].indexOf(segments.begin) < 0)
|
||||
counter++;
|
||||
else {
|
||||
let subcounter = 0;
|
||||
const subBlock = [];
|
||||
alarmed = false;
|
||||
while (subcounter < 75)
|
||||
if (lines[counter + subcounter].indexOf(segments.end) < 0) {
|
||||
if (lines[counter + subcounter].indexOf(segments.beginAlarm) > -1)
|
||||
alarmed = true;
|
||||
|
||||
if (!alarmed)
|
||||
subBlock.push(lines[counter + subcounter]);
|
||||
|
||||
if (lines[counter + subcounter].indexOf(segments.endAlarm) > -1)
|
||||
alarmed = false;
|
||||
|
||||
subcounter++;
|
||||
}
|
||||
else
|
||||
break;
|
||||
|
||||
counter = counter + subcounter;
|
||||
const b = processBlock(subBlock);
|
||||
|
||||
if (!b.recur ) {
|
||||
if (Array.isArray(b))
|
||||
logger.error('!returned an array...');
|
||||
else
|
||||
if (b.dtstart !== null) {
|
||||
b.ts = b.dtstart.format('{X}');
|
||||
workingBlock.push(b);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// logger.warn('We need to spread the recurrance!!');
|
||||
// logger.debug(b);
|
||||
let recurBlocks = buildRecurranceArray(b);
|
||||
|
||||
if (recurBlocks)
|
||||
recurBlocks = recurBlocks.map((item) => {
|
||||
if (item.dtstart)
|
||||
item.ts = item.dtstart.format('{X}');
|
||||
|
||||
return item;
|
||||
});
|
||||
|
||||
if (recurBlocks && recurBlocks.length > 0)
|
||||
workingBlock = workingBlock.concat(recurBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
logger.info('- processICAL');
|
||||
// If (workingBlock.dtstart == null) return {};
|
||||
|
||||
return workingBlock;
|
||||
}
|
||||
|
||||
/*
|
||||
['https://calendar.google.com/calendar/ical/martind2000%40gmail.com/private-40cfebc9f7dcfa7fde6b9bf2f0092c93/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/mt5pgdhknvgoc8usfnrso9vkv0%40group.calendar.google.com/private-58876002af9f302a593acfa6fa792dcf/basic.ics',
|
||||
'https://www.tripit.com/feed/ical/private/DB96E4BB-94A9BD8F9CC1CF51C6CC0D920840F4F5/tripit.ics',
|
||||
'https://calendar.google.com/calendar/ical/en.uk%23holiday%40group.v.calendar.google.com/public/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/i8dglj12p5nuv20sbjmun5s588%40group.calendar.google.com/private-c8adccb41e56d6a2f285078aaed313f5/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/qppj4ebvdur1qui4v0fdpl7l70%40group.calendar.google.com/private-b5071cb2c3fe49544ffbbd08645088f1/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/8h3vi3rd5rvpfe11klvgre0q4c%40group.calendar.google.com/private-e9df93163a7046658946be45fb08db6f/basic.ics',
|
||||
'https://calendar.google.com/calendar/embed?src=family18216414236505453132%40group.calendar.google.com&ctz=Europe%2FLondon']
|
||||
*/
|
||||
module.exports = {
|
||||
'calendars': ['https://calendar.google.com/calendar/ical/martind2000%40gmail.com/private-40cfebc9f7dcfa7fde6b9bf2f0092c93/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/mt5pgdhknvgoc8usfnrso9vkv0%40group.calendar.google.com/private-58876002af9f302a593acfa6fa792dcf/basic.ics',
|
||||
'https://www.tripit.com/feed/ical/private/DB96E4BB-94A9BD8F9CC1CF51C6CC0D920840F4F5/tripit.ics',
|
||||
'https://calendar.google.com/calendar/ical/en.uk%23holiday%40group.v.calendar.google.com/public/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/i8dglj12p5nuv20sbjmun5s588%40group.calendar.google.com/private-c8adccb41e56d6a2f285078aaed313f5/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/qppj4ebvdur1qui4v0fdpl7l70%40group.calendar.google.com/private-b5071cb2c3fe49544ffbbd08645088f1/basic.ics',
|
||||
'https://calendar.google.com/calendar/ical/8h3vi3rd5rvpfe11klvgre0q4c%40group.calendar.google.com/private-e9df93163a7046658946be45fb08db6f/basic.ics',
|
||||
'https://calendar.google.com/calendar/embed?src=family18216414236505453132%40group.calendar.google.com&ctz=Europe%2FLondon'],
|
||||
'jsonBlock': [],
|
||||
'getTodaysSimple': function() {
|
||||
'use strict';
|
||||
logger.info('+ getTodaysSimple');
|
||||
const today = {
|
||||
'entries': []
|
||||
};
|
||||
|
||||
const _td = new Date.create('today');
|
||||
const _tm = new Date.create('tomorrow') - 1 ;
|
||||
|
||||
today.entries = this.jsonBlock.filter((item) => {
|
||||
if (!item || !item.dtstart || !item.dtend) return false;
|
||||
|
||||
return item.dtstart.isBetween(_td, _tm) || item.dtend.isBetween(_td, _tm);
|
||||
});
|
||||
|
||||
logger.info('- getTodaysSimple');
|
||||
|
||||
return today;
|
||||
},
|
||||
'getTomorrow': function() {
|
||||
'use strict';
|
||||
logger.info('+ getTomorrow');
|
||||
const today = {
|
||||
'entries': []
|
||||
};
|
||||
|
||||
const _tm = new Date.create('tomorrow');
|
||||
const _da = new Date.create('tomorrow').addDays(1);
|
||||
today.entries = this.jsonBlock.filter((item) => {
|
||||
if (!item || !item.dtstart || !item.dtend) return false;
|
||||
|
||||
return item.dtstart.isBetween(_tm, _da) || item.dtend.isBetween(_tm, _da);
|
||||
});
|
||||
|
||||
logger.info('- getTomorrow');
|
||||
|
||||
return today;
|
||||
},
|
||||
'getThreeDays': function() {
|
||||
logger.info('+ getThreeDays');
|
||||
const three = {
|
||||
'entries': []
|
||||
};
|
||||
|
||||
const _tdms = new Date.create('today').getTime();
|
||||
const _tmms = new Date.create('tomorrow').addDays(2).getTime() - 1 ;
|
||||
|
||||
three.entries = this.jsonBlock.filter((item) => {
|
||||
if (!item || !item.dtstart || !item.dtend) return false;
|
||||
return inRange(item.dtstart.getTime(), _tdms, _tmms) || inRange(item.dtend.getTime(), _tdms, _tmms);
|
||||
});
|
||||
|
||||
|
||||
three.entries = three.entries.map( obj => {
|
||||
obj.readDate = moment(obj.dtstart.getTime()).calendar();
|
||||
if (obj.allday) {
|
||||
let fixReadDate = obj.readDate.split('\n');
|
||||
if (fixReadDate.length === 2) {
|
||||
obj.readDate = `${fixReadDate[0]}\nAll Day`;
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
});
|
||||
logger.info('- getThreeDays');
|
||||
|
||||
return three;
|
||||
},
|
||||
'getWeek': function() {
|
||||
'use strict';
|
||||
logger.info('+ getWeek');
|
||||
const today = {
|
||||
'entries': []
|
||||
};
|
||||
|
||||
const now = new Date.create('today');
|
||||
logger.debug('>> now', now);
|
||||
const twoDays = new Date.create('today').addDays(2).beginningOfDay();
|
||||
logger.debug('>> twoDays', twoDays);
|
||||
const sevenDays = new Date.create('today').addDays(7).beginningOfDay();
|
||||
logger.debug('>> sevenDays', sevenDays);
|
||||
logger.debug('>> trip', { now, twoDays, sevenDays });
|
||||
|
||||
/* for (let t = 0; t < this.jsonBlock.length; t++)
|
||||
// logger.debug('>> between', Sugar.Date(this.jsonBlock[t].dtstart).raw, Sugar.Date(this.jsonBlock[t].dtstart).isBetween(twoDays, sevenDays));
|
||||
if (Date(this.jsonBlock[t].dtstart).isBetween(twoDays, sevenDays))
|
||||
today.entries.push(this.jsonBlock[t]);*/
|
||||
|
||||
today.entries = this.jsonBlock.filter((item) => {
|
||||
if (!item || !item.dtstart || !item.dtend) return false;
|
||||
|
||||
return item.dtstart.isBetween(twoDays, sevenDays) || item.dtend.isBetween(twoDays, sevenDays);
|
||||
});
|
||||
|
||||
logger.info('- getWeek');
|
||||
|
||||
return today;
|
||||
},
|
||||
'getTodaysMeetings': function() {
|
||||
'use strict';
|
||||
logger.info('+ getTodaysMeetings');
|
||||
const today = {
|
||||
'previous': [], 'upcoming': [], 'current': {}
|
||||
};
|
||||
const now = new Date();
|
||||
|
||||
for (let t = 0; t < this.jsonBlock.length; t++)
|
||||
if (Sugar.Date(this.jsonBlock[t].dtstart).isToday().raw) {
|
||||
if (Sugar.Date(this.jsonBlock[t].dtstart).isAfter(now).raw)
|
||||
today.upcoming.push(this.jsonBlock[t]);
|
||||
else
|
||||
today.previous.push(this.jsonBlock[t]);
|
||||
|
||||
if (now.isBetween(this.jsonBlock[t].dtstart, this.jsonBlock[t].dtend))
|
||||
today.current = this.jsonBlock[t];
|
||||
}
|
||||
|
||||
// logger.debug(today);
|
||||
logger.info('- getTodaysMeetings');
|
||||
|
||||
return today;
|
||||
}, 'getSimpleCalV2': function(url, cb) {
|
||||
'use strict';
|
||||
const self = this;
|
||||
|
||||
// Var calJson = [];
|
||||
try {
|
||||
request(url, function(err, res, body) {
|
||||
if (err) {
|
||||
logger.error('Get remote Calendar Request failed');
|
||||
// Callback.call(null, new Error('Request failed'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
self.jsonBlock = processICAL(body);
|
||||
|
||||
// logger.debug(self.jsonBlock);
|
||||
const st = self.getTodaysSimple();
|
||||
|
||||
if (typeof cb === 'function')
|
||||
cb(st);
|
||||
}, function(error, response, body) {
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(response.statusCode);
|
||||
logger.error(body);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}, 'getSimpleCalV3': function(url) {
|
||||
'use strict';
|
||||
const self = this;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
try {
|
||||
request(url, function(err, res, body) {
|
||||
if (err)
|
||||
// logger.error(err);
|
||||
return reject(err);
|
||||
// Throw err;
|
||||
|
||||
self.jsonBlock = processICAL(body);
|
||||
|
||||
logger.debug(self.jsonBlock);
|
||||
const st = self.getTodaysSimple();
|
||||
|
||||
return resolve(st);
|
||||
}, function(error, response, body) {
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(response.statusCode);
|
||||
// logger.error(body);
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Var calJson = [];
|
||||
}, 'getAdvancedCalV3': function(url) {
|
||||
'use strict';
|
||||
const self = this;
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
try {
|
||||
request(url, function(err, res, body) {
|
||||
if (err)
|
||||
// logger.error(err);
|
||||
return reject(err);
|
||||
// Throw err;
|
||||
|
||||
self.jsonBlock = processICAL(body);
|
||||
|
||||
logger.debug('jsonBlock length', self.jsonBlock.length);
|
||||
// logger.debug(self.jsonBlock);
|
||||
const st = self.getTodaysSimple().entries;
|
||||
const tom = self.getTomorrow().entries;
|
||||
const week = self.getWeek().entries;
|
||||
const three = self.getThreeDays().entries;
|
||||
|
||||
const obj = { 'today': st, 'tomorrow': tom, 'week': week, 'three':three };
|
||||
|
||||
// logger.warn(obj);
|
||||
|
||||
return resolve(obj);
|
||||
}, function(error, response, body) {
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(response.statusCode);
|
||||
// logger.error(body);
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
logger.error(e);
|
||||
|
||||
return reject(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Var calJson = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Created by Martin on 16/02/2016.
|
||||
*/
|
||||
};
|
11
server/lib/logger.js
Normal file
11
server/lib/logger.js
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
const log4js = require('log4js'),
|
||||
mongoAppender = require('log4js-node-mongodb');
|
||||
|
||||
log4js.addAppender(
|
||||
mongoAppender.appender(
|
||||
{ 'connectionString': 'localhost:27017/jubilee' }),
|
||||
'audit'
|
||||
);
|
||||
|
||||
module.exports = { log4js };
|
358
server/lib/readability.js
Normal file
358
server/lib/readability.js
Normal file
@ -0,0 +1,358 @@
|
||||
var readabilityVersion = '2';
|
||||
var readStyle = 'style-ebook';
|
||||
var readSize = 'size-medium';
|
||||
var readMargin = 'margin-wide';
|
||||
(function() {
|
||||
// removing all existing scripts so they don't cause conflicts...
|
||||
var docscripts = document.getElementsByTagName('script');
|
||||
for (k = 0;k < docscripts.length; k++)
|
||||
if (docscripts[k].src != null && ! docscripts[k].src.match(/readability|[Cc]lippability/))
|
||||
docscripts[k].parentNode.removeChild(docscripts[k]);
|
||||
|
||||
// let's just load jQuery and get it over with
|
||||
var gjs = document.createElement('SCRIPT');
|
||||
gjs.type = 'text/javascript';
|
||||
gjs.src = 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js';
|
||||
document.getElementsByTagName('head')[0].appendChild(gjs);
|
||||
gjs.onload = gjs.onreadystatechange = function() {
|
||||
$('script').each(function() {
|
||||
// jQuery gets scripts inside of conditional comments far more easily than I could figure out
|
||||
if (! this.src.match(/readability|[Cc]lippability|jquery\.min\.js$/)) $(this).remove();
|
||||
});
|
||||
};
|
||||
|
||||
var objOverlay = document.createElement('div');
|
||||
var objinnerDiv = document.createElement('div');
|
||||
|
||||
objOverlay.id = 'readOverlay';
|
||||
objinnerDiv.id = 'readInner';
|
||||
|
||||
// Apply user-selected styling:
|
||||
document.body.className = readStyle;
|
||||
objOverlay.className = readStyle;
|
||||
objinnerDiv.className = `${readMargin } ${ readSize}`;
|
||||
|
||||
objinnerDiv.appendChild(grabArticle()); // Get the article and place it inside the inner Div
|
||||
objOverlay.appendChild(objinnerDiv); // Insert the inner div into the overlay
|
||||
|
||||
// For totally hosed HTML, add body node that can't be found because of bad HTML or something.
|
||||
if(document.body == null) {
|
||||
body = document.createElement('body');
|
||||
document.body = body;
|
||||
}
|
||||
|
||||
document.body.innerHTML = '';
|
||||
|
||||
// Inserts the new content :
|
||||
|
||||
document.body.insertBefore(objOverlay, document.body.firstChild);
|
||||
var o = document.body.firstChild;
|
||||
|
||||
return o.innerHTML;
|
||||
})();
|
||||
|
||||
function getElementsByClassName(classname, node) {
|
||||
if(!node) node = document.getElementsByTagName('body')[0];
|
||||
var a = [];
|
||||
var re = new RegExp(`\\b${ classname }\\b`);
|
||||
var els = node.getElementsByTagName('*');
|
||||
for(var i = 0, j = els.length; i < j; i++)
|
||||
if(re.test(els[i].className))a.push(els[i]);
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
function grabArticle() {
|
||||
var allParagraphs = document.getElementsByTagName('p');
|
||||
var topDivCount = 0;
|
||||
var topDiv = null;
|
||||
var topDivParas;
|
||||
|
||||
var articleContent = document.createElement('DIV');
|
||||
var articleTitle = document.createElement('H1');
|
||||
var articleFooter = document.createElement('DIV');
|
||||
|
||||
// Replace all doubled-up <BR> tags with <P> tags, and remove fonts.
|
||||
var pattern = new RegExp ('<br/?>[ \r\n\s]*<br/?>', 'g');
|
||||
document.body.innerHTML = document.body.innerHTML.replace(pattern, '</p><p>').replace(/<\/?font[^>]*>/g, '');
|
||||
|
||||
// Grab the title from the <title> tag and inject it as the title.
|
||||
articleTitle.innerHTML = document.title;
|
||||
articleContent.appendChild(articleTitle);
|
||||
|
||||
// Study all the paragraphs and find the chunk that has the best score.
|
||||
// A score is determined by things like: Number of <p>'s, commas, special classes, etc.
|
||||
for (var j = 0; j < allParagraphs.length; j++) {
|
||||
parentNode = allParagraphs[j].parentNode;
|
||||
|
||||
// Initialize readability data
|
||||
if(typeof parentNode.readability === 'undefined') {
|
||||
parentNode.readability = { 'contentScore': 0 };
|
||||
|
||||
// Look for a special classname
|
||||
if(parentNode.className.match(/(comment|meta|footer|footnote)/))
|
||||
parentNode.readability.contentScore -= 50;
|
||||
else if(parentNode.className.match(/((^|\\s)(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)(\\s|$))/))
|
||||
parentNode.readability.contentScore += 25;
|
||||
|
||||
// Look for a special ID
|
||||
if(parentNode.id.match(/(comment|meta|footer|footnote)/))
|
||||
parentNode.readability.contentScore -= 50;
|
||||
else if(parentNode.id.match(/^(post|hentry|entry[-]?(content|text|body)?|article[-]?(content|text|body)?)$/))
|
||||
parentNode.readability.contentScore += 25;
|
||||
}
|
||||
|
||||
// Add a point for the paragraph found
|
||||
if(getInnerText(allParagraphs[j]).length > 10)
|
||||
parentNode.readability.contentScore++;
|
||||
|
||||
// Add points for any commas within this paragraph
|
||||
parentNode.readability.contentScore += getCharCount(allParagraphs[j]);
|
||||
}
|
||||
|
||||
// Assignment from index for performance. See http://www.peachpit.com/articles/article.aspx?p=31567&seqNum=5
|
||||
for(nodeIndex = 0; (node = document.getElementsByTagName('*')[nodeIndex]); nodeIndex++)
|
||||
if(typeof node.readability !== 'undefined' && (topDiv == null || node.readability.contentScore > topDiv.readability.contentScore))
|
||||
topDiv = node;
|
||||
|
||||
if(topDiv == null) {
|
||||
topDiv = document.createElement('div');
|
||||
topDiv.innerHTML = 'Sorry, clippable was unable to parse this page for content. If you feel like it should have been able to, please <a href="http://brettterpstra.com/contact">let us know.</a>';
|
||||
}
|
||||
|
||||
// REMOVES ALL STYLESHEETS ...
|
||||
for (var k = 0;k < document.styleSheets.length; k++)
|
||||
if (document.styleSheets[k].href != null && document.styleSheets[k].href.lastIndexOf('readability') == -1)
|
||||
document.styleSheets[k].disabled = true;
|
||||
|
||||
var sh = getElementsByClassName('syntaxhighlighter');
|
||||
for (var i = 0;i < sh.length;i++) {
|
||||
var bar = getElementsByClassName('toolbar', sh[i]);
|
||||
if (bar.length > 0)
|
||||
for (var bn = 0;bn < bar.length;bn++)
|
||||
bar[bn].parentNode.removeChild(bar[bn]);
|
||||
|
||||
var numbers = getElementsByClassName('number', sh[i]);
|
||||
if (numbers.length > 0)
|
||||
for (var num = 0;num < numbers.length;num++)
|
||||
numbers[num].parentNode.removeChild(numbers[num]);
|
||||
}
|
||||
|
||||
var dp = getElementsByClassName('dp-highlighter');
|
||||
for (var d = 0;d < dp.length;d++)
|
||||
dp[d].parentNode.removeChild(dp[d]);
|
||||
|
||||
var sth = getElementsByClassName('standardLighter');
|
||||
for (d = 0;d < sth.length;d++)
|
||||
sth[d].parentNode.removeChild(sth[d]);
|
||||
|
||||
// Remove all style tags in head (not doing this on IE) :
|
||||
var styleTags = document.getElementsByTagName('style');
|
||||
for (var l = 0;l < styleTags.length; l++)
|
||||
if (navigator.appName != 'Microsoft Internet Explorer')
|
||||
styleTags[l].textContent = '';
|
||||
|
||||
topDiv = killCodeSpans(topDiv); // removes span tags
|
||||
cleanStyles(topDiv); // Removes all style attributes
|
||||
topDiv = killDivs(topDiv); // Goes in and removes DIV's that have more non <p> stuff than <p> stuff
|
||||
topDiv = killBreaks(topDiv); // Removes any consecutive <br />'s into just one <br />
|
||||
|
||||
// Cleans out junk from the topDiv just in case:
|
||||
topDiv = clean(topDiv, 'form');
|
||||
// topDiv = clean(topDiv, "object");
|
||||
topDiv = clean(topDiv, 'table', 8);
|
||||
topDiv = clean(topDiv, 'h1');
|
||||
// topDiv = clean(topDiv, "h2");
|
||||
topDiv = clean(topDiv, 'iframe');
|
||||
|
||||
// Add the footer and contents:
|
||||
articleFooter.id = 'readFooter';
|
||||
articleFooter.innerHTML = `\
|
||||
<a href='http://lab.arc90.com/experiments/readability'><img src='http://lab.arc90.com/experiments/readability/images/footer-readability.png'></a>\
|
||||
<a href='http://www.arc90.com'><img src='http://lab.arc90.com/experiments/readability/images/footer-arc90.png'></a>\
|
||||
<a href='http://www.twitter.com/arc90' class='footer-twitterLink'>Follow us on Twitter »</a>\
|
||||
<div class='footer-right' >\
|
||||
<span class='version'>Readability version ${ readabilityVersion }</span>\
|
||||
</div>\
|
||||
`;
|
||||
|
||||
articleContent.appendChild(topDiv);
|
||||
// articleContent.appendChild(articleFooter);
|
||||
document.onkeyup = docOnKeyup;
|
||||
|
||||
return articleContent;
|
||||
}
|
||||
|
||||
function docOnKeyup(ev) {
|
||||
var keyID = null;
|
||||
if (navigator.appName == 'Microsoft Internet Explorer')
|
||||
keyID = event.keyCode;
|
||||
else
|
||||
keyID = (window.event) ? event.keyCode : ev.keyCode;
|
||||
|
||||
var bgcolor, fgcolor, acolor;
|
||||
switch (keyID) {
|
||||
|
||||
case 27: // escape
|
||||
document.location.reload(true);
|
||||
break;
|
||||
case 37: // left arrow
|
||||
bgcolor = '#222';
|
||||
fgcolor = '#F3EFCE';
|
||||
acolor = '#A19F89';
|
||||
break;
|
||||
case 39: // right arrow
|
||||
bgcolor = '#fff';
|
||||
fgcolor = '#333';
|
||||
acolor = '#276F78';
|
||||
break;
|
||||
case 46: // delete
|
||||
bgcolor = '#eee';
|
||||
fgcolor = '#333';
|
||||
acolor = '#blue';
|
||||
break;
|
||||
|
||||
}
|
||||
body = document.getElementById('readOverlay');
|
||||
// body.className = body.className.replace('/\blightened\b/','') + " darkened";
|
||||
body.style.backgroundColor = bgcolor;
|
||||
body.style.color = fgcolor;
|
||||
var alinks = body.getElementsByTagName('a');
|
||||
for (var lc = 0;lc < alinks.length;lc++)
|
||||
alinks[lc].style.color = acolor;
|
||||
}
|
||||
|
||||
// Get the inner text of a node - cross browser compatibly.
|
||||
function getInnerText(e) {
|
||||
if (navigator.appName == 'Microsoft Internet Explorer')
|
||||
return e.innerText;
|
||||
else
|
||||
return e.textContent;
|
||||
}
|
||||
|
||||
// Get character count
|
||||
function getCharCount ( e, s ) {
|
||||
s = s || ',';
|
||||
|
||||
return getInnerText(e).split(s).length;
|
||||
}
|
||||
|
||||
function cleanStyles( e ) {
|
||||
e = e || document;
|
||||
var cur = e.firstChild;
|
||||
|
||||
// If we had a bad node, there's not much we can do.
|
||||
if(!e)
|
||||
return;
|
||||
|
||||
// Remove any root styles, if we're able.
|
||||
if(typeof e.removeAttribute === 'function')
|
||||
e.removeAttribute('style');
|
||||
|
||||
// Go until there are no more child nodes
|
||||
while ( cur != null ) {
|
||||
if ( cur.nodeType == 1 ) {
|
||||
// Remove style attribute(s) :
|
||||
cur.removeAttribute('style');
|
||||
cleanStyles( cur );
|
||||
}
|
||||
cur = cur.nextSibling;
|
||||
}
|
||||
}
|
||||
|
||||
function killDivs ( e ) {
|
||||
var divsList = e.getElementsByTagName( 'div' );
|
||||
var curDivLength = divsList.length;
|
||||
|
||||
// Gather counts for other typical elements embedded within.
|
||||
// Traverse backwards so we can remove nodes at the same time without effecting the traversal.
|
||||
for (var i = curDivLength - 1; i >= 0; i--) {
|
||||
var p = divsList[i].getElementsByTagName('p').length;
|
||||
var img = divsList[i].getElementsByTagName('img').length;
|
||||
var li = divsList[i].getElementsByTagName('li').length;
|
||||
var a = divsList[i].getElementsByTagName('a').length;
|
||||
var embed = divsList[i].getElementsByTagName('embed').length;
|
||||
var object = divsList[i].getElementsByTagName('object').length;
|
||||
var pre = divsList[i].getElementsByTagName('pre').length;
|
||||
var code = divsList[i].getElementsByTagName('code').length;
|
||||
var divId = divsList[i].id;
|
||||
var divClass = divsList[i].className;
|
||||
var sphereit = divsList[i].innerHTML.match('<!-- sphereit') == null ? 0 : 1;
|
||||
// If the number of commas is less than 10 (bad sign) ...
|
||||
if ( getCharCount(divsList[i]) < 10 )
|
||||
// And the number of non-paragraph elements is more than paragraphs
|
||||
// or other ominous signs :
|
||||
if (( img > p || li > p || a > p || p == 0 || divId.match('comment') != null || divClass.match('comment') != null || divId.match('share') != null || divClass.match('share') != null) && ( pre == 0 && code == 0 && embed == 0 && object == 0 && sphereit == 0 )) {
|
||||
if (!p == 0 && img == 1) divsList[i].parentNode.removeChild(divsList[i]);
|
||||
}
|
||||
|
||||
var stopwords = ['comment', 'share', 'footer', '^ad'];
|
||||
for (var sw = 0;sw < stopwords.length;sw++) {
|
||||
regex = new RegExp(stopwords[sw]);
|
||||
if (divId.match(regex) != null || divClass.match(regex) != null) {
|
||||
console.log(`matched ${stopwords[sw]}`);
|
||||
divsList[i].parentNode.removeChild(divsList[i]);
|
||||
}
|
||||
}
|
||||
// if (divId.match("comment") != null || divClass.match("comment") != null || divId.match("share") != null || divClass.match("share") != null || divClass.match("footer") != null || divId.match("footer") != null || divClass.match(/^ad/) != null || divId.match(/^ad/) != null) {
|
||||
// divsList[i].parentNode.removeChild(divsList[i]);
|
||||
// }
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
function killBreaks ( e ) {
|
||||
e.innerHTML = e.innerHTML.replace(/(<br\s*\/?>(\s| ?)*){1,}/g, '<br />');
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
function killCodeSpans ( e ) {
|
||||
e.innerHTML = e.innerHTML.replace(/<\/?\s?span(?:[^>]+)?>/g, '');
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
function clean(e, tags, minWords) {
|
||||
var targetList;
|
||||
var y;
|
||||
if (tags == 'table') {
|
||||
targetList = e.getElementsByTagName( tags );
|
||||
minWords = minWords || 1000000;
|
||||
for (y = 0; y < targetList.length; y++) {
|
||||
// If the text content isn't laden with words, remove the child:
|
||||
cells = targetList[y].getElementsByTagName('td').length;
|
||||
if (cells < minWords)
|
||||
targetList[y].parentNode.removeChild(targetList[y]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
targetList = e.getElementsByTagName( tags );
|
||||
minWords = minWords || 1000000;
|
||||
|
||||
for (y = 0; y < targetList.length; y++)
|
||||
// If the text content isn't laden with words, remove the child:
|
||||
if (getCharCount(targetList[y], ' ') < minWords && targetList[y].tagName != 'pre')
|
||||
targetList[y].parentNode.removeChild(targetList[y]);
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
|
||||
function convert(e, tagId) {
|
||||
var children, parent, newNode;
|
||||
var elems = document.getElementsByTagName(tagId);
|
||||
for (y = 0; y < elems.length; y++) {
|
||||
children = elems[y].childNodes;
|
||||
parent = elems[y].parentNode;
|
||||
newNode = document.createElement('span');
|
||||
newNode.setAttribute('style', 'font-weight:bold');
|
||||
for(var i = 0;i < children.length;i++)
|
||||
newNode.appendChild(children[i]);
|
||||
|
||||
parent.replaceChild(newNode, elems[y]);
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
94
server/reducers/directions.js
Normal file
94
server/reducers/directions.js
Normal file
@ -0,0 +1,94 @@
|
||||
const logger = require('log4js').getLogger('Directions 🔧');
|
||||
|
||||
const { get, isEmpty, has, uniq } = require('lodash');
|
||||
const humanizeDuration = require('humanize-duration');
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
const htmlTidy = /<(\/*?)(?!(em|p|br\s*\/|strong|h1|h2|h3))\w+?.+?>/gim;
|
||||
|
||||
function reduceEstDirections(body = '') {
|
||||
if (body === '') return {};
|
||||
|
||||
// logger.debug(body);
|
||||
const jBody = JSON.parse(body);
|
||||
const obj = {};
|
||||
const { ResultSet } = jBody;
|
||||
const streets = [];
|
||||
const steps = [];
|
||||
|
||||
if (has(ResultSet, 'Result')) {
|
||||
const directions = get(ResultSet, 'Result.yahoo_driving_directions');
|
||||
const route = get(directions, 'directions.route_leg');
|
||||
|
||||
obj.totalTime = parseFloat(get(directions, 'total_time'));
|
||||
obj.totalTimeWithTraffic = parseFloat(get(directions, 'total_time_with_traffic'));
|
||||
obj.readable = humanizeDuration((obj.totalTimeWithTraffic !== 0 ? obj.totalTimeWithTraffic : obj.totalTime) * 60 * 1000);
|
||||
|
||||
obj.timePercentage = ((obj.totalTime * ( obj.totalTimeWithTraffic / 100 )) + obj.totalTime) - 100;
|
||||
|
||||
if ( obj.totalTimeWithTraffic > (obj.totalTime * 1.75)) {
|
||||
obj.traffic = 'heavy traffic';
|
||||
obj.className = 'trafficHeavy';
|
||||
}
|
||||
else if ( obj.totalTimeWithTraffic > (obj.totalTime * 1.5)) {
|
||||
obj.traffic = 'some traffic';
|
||||
obj.className = 'trafficMedium';
|
||||
}
|
||||
else if ( obj.totalTimeWithTraffic > (obj.totalTime * 1.25)) {
|
||||
obj.traffic = 'light traffic';
|
||||
obj.className = 'trafficLight';
|
||||
}
|
||||
else {
|
||||
obj.traffic = 'no traffic';
|
||||
obj.className = 'trafficNone';
|
||||
}
|
||||
|
||||
for (const item of route) {
|
||||
if (item.hasOwnProperty('street')) {
|
||||
const street = item.street.split(',');
|
||||
if (street[0] !== '') streets.push(street[0]);
|
||||
}
|
||||
|
||||
if (has(item, 'description')) {
|
||||
steps.push(item.description);
|
||||
}
|
||||
}
|
||||
obj.directions = steps;
|
||||
obj.streets = uniq(streets);
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function reduceIncidents(body = '') {
|
||||
if (body === '') return [];
|
||||
const workObj = Object.assign({}, body);
|
||||
const incidents = [];
|
||||
|
||||
const items = get(workObj, 'items');
|
||||
|
||||
for (const item of items) {
|
||||
const title = item.title.split(' ');
|
||||
incidents.push({
|
||||
'title' : item.title,
|
||||
'description': item.description,
|
||||
'road' : title[0]
|
||||
});
|
||||
}
|
||||
|
||||
return incidents;
|
||||
}
|
||||
|
||||
function combine(traffic, incidents) {
|
||||
const workObj = Object.assign({ 'incidents':[] }, traffic);
|
||||
|
||||
for (const item of incidents)
|
||||
|
||||
if (workObj.streets.indexOf(item.road) > -1)
|
||||
workObj.incidents.push(item);
|
||||
|
||||
return workObj;
|
||||
}
|
||||
|
||||
module.exports = { reduceEstDirections, reduceIncidents, combine };
|
134
server/reducers/euronews.js
Normal file
134
server/reducers/euronews.js
Normal file
@ -0,0 +1,134 @@
|
||||
const cheerio = require('cheerio');
|
||||
|
||||
const logger = require('log4js').getLogger('Euronews 🔧');
|
||||
|
||||
const { get, isEmpty } = require('lodash');
|
||||
logger.level = 'debug';
|
||||
|
||||
const htmlTidy = /<(\/*?)(?!(em|p|br\s*\/|strong|h1|h2|h3))\w+?.+?>/gim;
|
||||
|
||||
function reduceArticle(body = '') {
|
||||
if (body === '') return {};
|
||||
|
||||
const obj = {};
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
const title = $('meta[property="og:title"]').attr('content');
|
||||
const image = `https://image.silvrtree.co.uk/640,fit,q80/${ $('meta[property="og:image"]').attr('content')}`;
|
||||
|
||||
const stuff = $('[itemprop="articleBody"]');
|
||||
|
||||
const html = [];
|
||||
|
||||
const content = $('div.c-article-content');
|
||||
const ampAddStripper = /<\s*amp-ad(\s+.*?>|>).*?<\s*\/\s*amp-ad\s*>/ig;
|
||||
|
||||
$(content).find('amp-ad').remove();
|
||||
|
||||
$(content).find('div.widget__wrapper').remove();
|
||||
$(content).find('div.widget').remove();
|
||||
|
||||
for (let top = 0, topLen = content.length; top < topLen; top++) {
|
||||
const children = $(content[top]).children();
|
||||
|
||||
for (let index = 0, len = children.length; index < len; index++) {
|
||||
let line = $.html($(children[index])).replace('amp-img', 'img');
|
||||
const tag = children[index].name;
|
||||
|
||||
const symbol = /src=(['"])(http[s]?:\/\/)/.exec(line) || [];
|
||||
|
||||
|
||||
|
||||
if (tag === 'amp-twitter') {
|
||||
const tweetid = $(children[index]).data('tweetid');
|
||||
line = `<amp-twitter width="375"
|
||||
height="472"
|
||||
layout="responsive" data-tweetid="${tweetid}" > </amp-twitter>`;
|
||||
}
|
||||
// logger.debug(symbol);
|
||||
|
||||
if (symbol.length !== 0)
|
||||
line = line.replace(/src=['"]http[s]?:\/\//, `src=${symbol[1]}https://image.silvrtree.co.uk/640,fit,q80/${symbol[2]}`);
|
||||
|
||||
html.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
html.push('<div class="endbumper"></div>');
|
||||
// const outputHTML = html.join('').replace(htmlTidy, '');
|
||||
const outputHTML = html.join('');
|
||||
|
||||
console.log(outputHTML);
|
||||
|
||||
obj.title = title;
|
||||
obj.image = image;
|
||||
obj.html = outputHTML;
|
||||
|
||||
logger.debug(JSON.stringify(obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function reduceArticleV2(body = '') {
|
||||
if (body === '') return {};
|
||||
|
||||
const obj = {};
|
||||
const $ = cheerio.load(body);
|
||||
|
||||
$('amp-ad').remove();
|
||||
|
||||
const title = $('meta[property="og:title"]').attr('content');
|
||||
const image = `https://image.silvrtree.co.uk/640,fit,q80/${ $('meta[property="og:image"]').attr('content')}`;
|
||||
|
||||
const stuff = $('[itemprop="articleBody"]');
|
||||
|
||||
const html = [];
|
||||
|
||||
const content = $('div.c-article-content');
|
||||
const ampAddStripper = /<\s*amp-ad(\s+.*?>|>).*?<\s*\/\s*amp-ad\s*>/ig;
|
||||
|
||||
$(content).find('amp-ad').remove();
|
||||
|
||||
$(content).find('div.widget__wrapper').remove();
|
||||
|
||||
for (let top = 0, topLen = content.length; top < topLen; top++) {
|
||||
const children = $(content[top]).children();
|
||||
|
||||
for (let index = 0, len = children.length; index < len; index++) {
|
||||
let line = $.html($(children[index])).replace('amp-img', 'img');
|
||||
const tag = children[index].name;
|
||||
|
||||
const symbol = /src=(['"])(http[s]?:\/\/)/.exec(line) || [];
|
||||
|
||||
if (tag === 'amp-twitter') {
|
||||
const tweetid = $(children[index]).data('tweetid');
|
||||
line = `<amp-twitter width="375"
|
||||
height="472"
|
||||
layout="responsive" data-tweetid="${tweetid}" > </amp-twitter>`;
|
||||
}
|
||||
// logger.debug(symbol);
|
||||
|
||||
if (symbol.length !== 0)
|
||||
line = line.replace(/src=['"]http[s]?:\/\//, `src=${symbol[1]}https://image.silvrtree.co.uk/640,fit,q80/${symbol[2]}`);
|
||||
|
||||
html.push(line);
|
||||
}
|
||||
}
|
||||
|
||||
html.push('<div class="endbumper"></div>');
|
||||
// const outputHTML = html.join('').replace(htmlTidy, '');
|
||||
const outputHTML = html.join('');
|
||||
|
||||
console.log(outputHTML);
|
||||
|
||||
obj.title = title;
|
||||
obj.image = image;
|
||||
obj.html = outputHTML;
|
||||
|
||||
logger.debug(JSON.stringify(obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
module.exports = { reduceArticle, reduceArticleV2 };
|
||||
|
60
server/reducers/opencage.js
Normal file
60
server/reducers/opencage.js
Normal file
@ -0,0 +1,60 @@
|
||||
const logger = require('log4js').getLogger('GeoCode 🔧');
|
||||
|
||||
const { get, isEmpty, has, uniq } = require('lodash');
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
var ConfidenceInKM = {
|
||||
'10': 0.25,
|
||||
'9': 0.5,
|
||||
'8': 1,
|
||||
'7': 5,
|
||||
'6': 7.5,
|
||||
'5': 10,
|
||||
'4': 15,
|
||||
'3': 20,
|
||||
'2': 25,
|
||||
'1': Number.POSITIVE_INFINITY,
|
||||
'0': Number.NaN
|
||||
};
|
||||
|
||||
function formatResult (result) {
|
||||
var confidence = result.confidence || 0;
|
||||
|
||||
return {
|
||||
'latitude': result.geometry.lat,
|
||||
'longitude': result.geometry.lng,
|
||||
'country': result.components.country,
|
||||
'city': result.components.city,
|
||||
'state': result.components.state,
|
||||
'zipcode': result.components.postcode,
|
||||
'streetName': result.components.road,
|
||||
'streetNumber': result.components.house_number,
|
||||
'countryCode': result.components.country_code,
|
||||
'county': result.components.county,
|
||||
'suburb': result.components.suburb,
|
||||
'neighbourhood' : result.components.neighbourhood,
|
||||
'village' : result.components.village,
|
||||
'formatted' : result.formatted,
|
||||
'extra': {
|
||||
'flag' : result.annotations.flag,
|
||||
'confidence': confidence,
|
||||
'confidenceKM': ConfidenceInKM[result.confidence] || Number.NaN,
|
||||
'map' : result.annotations.OSM.url
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function reduceOpencage(result) {
|
||||
logger.debug(JSON.stringify(result));
|
||||
const results = [];
|
||||
|
||||
if (result && result.results instanceof Array)
|
||||
for (let i = 0; i < result.results.length; i++)
|
||||
results.push(formatResult(result.results[i]));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
module.exports = { reduceOpencage };
|
165
server/reducers/rightbyme.js
Normal file
165
server/reducers/rightbyme.js
Normal file
@ -0,0 +1,165 @@
|
||||
const logger = require('log4js').getLogger('FS 🔧');
|
||||
|
||||
const { get, isEmpty } = require('lodash');
|
||||
|
||||
// Bearer YlF_b6D149xr_xnrrYudlSnpn1A53b67vALlIK2HnD0ymBXQocRvPW3KjGN8jZNw0KnyAqxGaOzU7CLVPr84_KbnTxutNRXFVR9axmRqGN6ccda1xahoZo58KC2GWnYx'
|
||||
|
||||
|
||||
// google api key AIzaSyBl7O9LHIthCagcqIaDkQ4um_hghYG5reE
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
function reduceExplore(data) {
|
||||
const cleaner = /\((.*?)\)/g;
|
||||
const obj = {};
|
||||
if (typeof data === 'undefined' || isEmpty(data)) return obj;
|
||||
const { categories, location, contact } = data;
|
||||
|
||||
// console.log(contact);
|
||||
// make copy of object;
|
||||
|
||||
const localObj = Object.assign({}, data);
|
||||
|
||||
const iconPrefix = get(categories[0], 'icon.prefix', '');
|
||||
const iconSuffix = get(categories[0], 'icon.suffix', '');
|
||||
|
||||
obj.name = get(localObj, 'name', '');
|
||||
obj.description = get(localObj, 'description', '');
|
||||
obj.category = get(categories[0], 'shortName', '');
|
||||
obj.icon = (iconPrefix !== '') ? `${iconPrefix}64${iconSuffix}` : '';
|
||||
obj.id = get(localObj, 'id', '');
|
||||
obj.provider = 'foursquare';
|
||||
obj.address = get(location, 'formattedAddress', []).join(', ').replace(cleaner, '');
|
||||
obj.city = get(location, 'city', '');
|
||||
obj.state = get(location, 'state', '');
|
||||
obj.postcode = get(location, 'postalCode', '');
|
||||
obj.twitter = get(contact, 'twitter', '');
|
||||
obj.facebook = get(contact, 'facebookName', '');
|
||||
obj.url = get(localObj, 'canonicalUrl', '');
|
||||
obj.latitude = get(location, 'lat', '');
|
||||
obj.longitude = get(location, 'lng', '');
|
||||
|
||||
// logger.debug(JSON.stringify(obj));
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function reduceYelp(data) {
|
||||
const obj = {};
|
||||
|
||||
if (typeof data === 'undefined' || isEmpty(data)) return obj;
|
||||
const yelpUrlfixer = /([--:\w?@%&+~#=]*\.[a-z]{2,4}\/{0,2})((?:[?&](?:\w+)=(?:\w+))+|[--:\w?@%&+~#=]+)?/g;
|
||||
|
||||
const localObj = Object.assign({}, data);
|
||||
|
||||
obj.url = get(localObj, 'url', '');
|
||||
obj.rating = get(localObj, 'rating', '');
|
||||
obj.reviewCount = get(localObj, 'review_count', '');
|
||||
|
||||
if (obj.url !== '') {
|
||||
const url = yelpUrlfixer.exec(obj.url);
|
||||
const urlBit = url[2];
|
||||
obj.viewIntent = `https://m.yelp.com/${urlBit}`;
|
||||
}
|
||||
else
|
||||
obj.viewIntent = '';
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function reduceFullFS(data) {
|
||||
const obj = {};
|
||||
if (typeof data === 'undefined' || isEmpty(data)) return obj;
|
||||
const localObj = Object.assign({}, data);
|
||||
const photoBlob = get(localObj, 'photos.groups');
|
||||
let photoItems;
|
||||
|
||||
for (const i of photoBlob)
|
||||
if (i.type === 'venue')
|
||||
photoItems = i.items;
|
||||
|
||||
const photosCount = (typeof(photoItems) !== 'undefined' && photoItems !== null) ? photoItems.length : 0;
|
||||
const tipsCount = get(localObj, 'tips.count', 0);
|
||||
|
||||
if (photosCount > 0)
|
||||
obj.images = photoItems.map(item => {
|
||||
const prefix = get(item, 'prefix', '');
|
||||
const suffix = get(item, 'suffix', '');
|
||||
const width = get(item, 'width', 640);
|
||||
const height = get(item, 'height', 480);
|
||||
|
||||
const ratio = width / 640;
|
||||
let ratioHeight = ~~(height / ratio);
|
||||
if (ratioHeight <= 0) ratioHeight = 640;
|
||||
|
||||
const fsImgPath = `${prefix}${width}x${height}${suffix}`;
|
||||
|
||||
console.log(`https://image.silvrtree.co.uk/${640}x${ratioHeight},fit,q80/${fsImgPath}`);
|
||||
|
||||
// return `${prefix}${640}x${ratioHeight}${suffix}`;
|
||||
|
||||
return `https://image.silvrtree.co.uk/${640}x${ratioHeight},fit,q80/${fsImgPath}`;
|
||||
});
|
||||
|
||||
if (tipsCount > 0) {
|
||||
const tipItems = get(localObj, 'tips.groups[0].items');
|
||||
obj.tips = tipItems.map(item => {
|
||||
return get(item, 'text', '');
|
||||
});
|
||||
}
|
||||
|
||||
obj.menuUrl = get(localObj, 'menu.mobileUrl');
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function reduceTwitter(data) {
|
||||
const urlReg = /(http|ftp|https):\/\/([\w+?\.\w+])+([a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?/;
|
||||
let obj = [];
|
||||
if (data.length > 0)
|
||||
obj = data.map(item => {
|
||||
let text = get(item, 'text');
|
||||
const urlTest = urlReg.exec(text);
|
||||
|
||||
if (urlTest !== null) {
|
||||
const newUrl = `<a hef='${urlTest[0]}'>${urlTest[0]}</a>`;
|
||||
text = text.replace(urlTest[0], newUrl);
|
||||
}
|
||||
|
||||
return text;
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
function reducePhotos(data) {
|
||||
const obj = {};
|
||||
if (typeof data === 'undefined' || isEmpty(data)) return obj;
|
||||
const localObj = Object.assign({}, data);
|
||||
const photoBlob = get(localObj, 'response.photos');
|
||||
let photoItems;
|
||||
|
||||
photoItems = photoBlob.items;
|
||||
|
||||
const photosCount = (typeof(photoItems) !== 'undefined' && photoItems !== null) ? photoItems.length : 0;
|
||||
|
||||
if (photosCount > 0)
|
||||
|
||||
obj.images = photoItems.map(item => {
|
||||
const prefix = get(item, 'prefix', '');
|
||||
const suffix = get(item, 'suffix', '');
|
||||
const width = get(item, 'width', 640);
|
||||
const height = get(item, 'height', 480);
|
||||
|
||||
const ratio = width / 640;
|
||||
let ratioHeight = ~~(height / ratio);
|
||||
if (ratioHeight <= 0) ratioHeight = 640;
|
||||
console.log(`${width}, ${height} => ${640}, ${ratioHeight}`);
|
||||
|
||||
return `${prefix}${640}x${ratioHeight}${suffix}`;
|
||||
});
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
module.exports = { reduceExplore, reduceYelp, reduceFullFS, reduceTwitter, reducePhotos };
|
154
server/reducers/weather.js
Normal file
154
server/reducers/weather.js
Normal file
@ -0,0 +1,154 @@
|
||||
const logger = require('log4js').getLogger('Weather 🔧');
|
||||
const fecha = require('fecha');
|
||||
|
||||
const { get } = require('lodash');
|
||||
|
||||
logger.level = 'debug';
|
||||
|
||||
function getTodaysForcast(hourlyData) {
|
||||
const data = get(hourlyData, 'data', []);
|
||||
|
||||
const output = [];
|
||||
const now = new Date().getTime();
|
||||
const nowToHour = now - (now % 3600000);
|
||||
const tomorrow = nowToHour + (60 * 1000 * 60 * 24);
|
||||
|
||||
for (const item of data) {
|
||||
const time = item.time * 1000;
|
||||
if (!(time < nowToHour || time > tomorrow)) {
|
||||
// logger.debug(item);
|
||||
const newItem = { 'time': fecha.format(time, 'h A'), 'icon': item.icon, 'temp': item.temperature, 'precip': item.precipProbability };
|
||||
output.push(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function getDailyForcast(dailyData) {
|
||||
const data = get(dailyData, 'data', []);
|
||||
|
||||
const output = [];
|
||||
|
||||
const h24 = (60 * 1000 * 60 * 24);
|
||||
const now = new Date().getTime();
|
||||
|
||||
const startODBase = new Date();
|
||||
startODBase.setHours(0, 0, 0, 0);
|
||||
|
||||
const startOD = startODBase.getTime();
|
||||
const endOD = startOD + h24 - 1;
|
||||
|
||||
logger.debug('startOD', fecha.format(new Date(startOD), 'default'));
|
||||
logger.debug('endOD', fecha.format(new Date(endOD), 'default'));
|
||||
|
||||
for (const item of data) {
|
||||
const time = item.time * 1000;
|
||||
if (!(time < endOD)) {
|
||||
// logger.debug(item);
|
||||
const newItem = {
|
||||
'time': fecha.format(time, 'dddd'),
|
||||
'icon': item.icon,
|
||||
'tempHigh': item.temperatureHigh,
|
||||
'tempLow': item.temperatureLow,
|
||||
'precip': item.precipProbability,
|
||||
'precipType': item.precipType
|
||||
};
|
||||
output.push(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function toCompass(degrees) {
|
||||
return ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE', 'S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW', 'N'][Math.round(degrees / 11.25 / 2)];
|
||||
}
|
||||
|
||||
function moonCalc(moonPhase) {
|
||||
let output = '';
|
||||
if (moonPhase === 0.0)
|
||||
output = '🌕 New moon';
|
||||
else if (moonPhase >= 0.1 && moonPhase < 0.25)
|
||||
output = '🌔 waxing crescent';
|
||||
else if (moonPhase === 0.25)
|
||||
output = '🌓 First Quarter';
|
||||
else if (moonPhase > 0.25 && moonPhase < 0.5)
|
||||
output = '🌒 waxing gibbous';
|
||||
else if (moonPhase === 0.5)
|
||||
output = '🌑 Full moon';
|
||||
else if (moonPhase > 0.5 && moonPhase < 0.75)
|
||||
output = '🌘 Waning gibbous';
|
||||
else if (moonPhase === 0.5)
|
||||
output = '🌗 Third quarter';
|
||||
else if (moonPhase > 0.75)
|
||||
output = '🌖 Waning crescent';
|
||||
|
||||
return output;
|
||||
// ['🌑', '🌘', '🌗', '🌖', '🌕', '🌔', '🌓', '🌒']
|
||||
// a value of 0 corresponds to a new moon, 0.25 to a first quarter moon, 0.5 to a full moon, and 0.75 to a last quarter moon. (The ranges
|
||||
// in between these represent waxing crescent, waxing gibbous, waning gibbous, and waning crescent moons, respectively.)
|
||||
}
|
||||
function getDetails(dailyData) {
|
||||
const data = get(dailyData, 'data', []);
|
||||
|
||||
const today = data[0];
|
||||
|
||||
const output = {};
|
||||
|
||||
output.summary = dailyData.summary;
|
||||
output.icon = dailyData.icon;
|
||||
output.humidity = today.humidity;
|
||||
output.visibility = today.visibility;
|
||||
output.uvIndex = today.uvIndex;
|
||||
output.sunriseTime = fecha.format(today.sunriseTime * 1000, 'shortTime');
|
||||
output.sunsetTime = fecha.format(today.sunsetTime * 1000, 'shortTime');
|
||||
output.moonphase = moonCalc(today.moonPhase);
|
||||
output.moonPhaseVal = today.moonPhase;
|
||||
output.windSpeed = today.windSpeed;
|
||||
output.pressure = today.pressure;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function reduceWeather(body = '') {
|
||||
if (body === '') return {};
|
||||
const obj = {};
|
||||
const { currently, daily, hourly } = body;
|
||||
const today = daily.data[0];
|
||||
|
||||
const outCurrent = {};
|
||||
|
||||
outCurrent.icon = get(currently, 'icon');
|
||||
outCurrent.temperature = get(currently, 'temperature');
|
||||
outCurrent.summary = get(currently, 'summary');
|
||||
outCurrent.precip = get(currently, 'precipProbability');
|
||||
outCurrent.precipType = get(currently, 'precipType');
|
||||
outCurrent.tempMax = get(today, 'temperatureMax');
|
||||
outCurrent.tempMin = get(today, 'temperatureMin');
|
||||
outCurrent.windBearing = get(today, 'windBearing');
|
||||
outCurrent.windBearingRead = toCompass(get(today, 'windBearing'));
|
||||
|
||||
const forcastToday = getTodaysForcast(hourly);
|
||||
const dailyForecast = getDailyForcast(daily);
|
||||
const details = getDetails(daily);
|
||||
|
||||
obj.currently = outCurrent;
|
||||
obj.forcastToday = forcastToday;
|
||||
obj.dailyForecast = dailyForecast;
|
||||
obj.details = details;
|
||||
obj.time = get(currently, 'time');
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
module.exports = { reduceWeather };
|
||||
|
||||
//
|
||||
/*
|
||||
moonPhase optional, only on daily
|
||||
The fractional part of the lunation number during the given day: a value of 0 corresponds to a new moon, 0.25 to a first quarter
|
||||
moon, 0.5 to a full moon, and 0.75 to a last quarter moon. (The ranges in between these represent waxing crescent, waxing gibbous,
|
||||
waning gibbous, and waning crescent moons, respectively.)
|
||||
|
||||
*/
|
38
server/tides.js
Normal file
38
server/tides.js
Normal file
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Created by WebStorm.
|
||||
* User: martin
|
||||
* Date: 07/07/2020
|
||||
* Time: 16:40
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Look here https://admiraltyapi.portal.azure-api.net/docs/services/uk-tidal-api/operations/TidalEvents_GetTidalEvents?
|
||||
|
||||
it's an api
|
||||
|
||||
reduce admiralitySites.json to something better for quick searching against lat long.
|
||||
|
||||
helensburgh 0403
|
||||
|
||||
|
||||
Primary key
|
||||
b49297c9dacc4c6e92026d0dbec82948
|
||||
|
||||
Secondary key
|
||||
d51efd816c2446b29e8be0f019c06f9c
|
||||
|
||||
|
||||
https://admiraltyapi.azure-api.net/uktidalapi/api/V1/Stations/{stationId}/TidalEvents[?duration]
|
||||
|
||||
HTTP request
|
||||
============
|
||||
|
||||
GET https://admiraltyapi.azure-api.net/uktidalapi/api/V1/Stations/0403/TidalEvents?duration=3 HTTP/1.1
|
||||
Host: admiraltyapi.azure-api.net
|
||||
Ocp-Apim-Subscription-Key: b49297c9dacc4c6e92026d0dbec82948
|
||||
|
||||
|
||||
|
||||
*/
|
118
server/weather.js
Normal file
118
server/weather.js
Normal file
@ -0,0 +1,118 @@
|
||||
const Client = require('request-json');
|
||||
|
||||
const logger = require('log4js').getLogger('Weather');
|
||||
const weather = require('openweather-apis');
|
||||
const { reduceWeather } = require('./reducers/weather');
|
||||
const request = require('request');
|
||||
|
||||
logger.level = 'debug';
|
||||
logger.label = 'Weather';
|
||||
|
||||
const openWeatherApiKey = process.env.openweatherAPI || '936a0ed9eb23b95cf08fc9f693c24264';
|
||||
|
||||
const darkskyApiKey = process.env.darkskyApiKey || '9ad2a41d420f3cf4960571bb886f710c';
|
||||
|
||||
const dsURL = `https://api.darksky.net/forecast/${ darkskyApiKey }/`;
|
||||
const DSclient = Client.createClient(`https://api.darksky.net/forecast/${ darkskyApiKey }/`);
|
||||
|
||||
weather.setAPPID(openWeatherApiKey);
|
||||
weather.setLang('en');
|
||||
// weather.setCity('Glasgow City');
|
||||
|
||||
function doGetOpenWeather(ll) {
|
||||
const [lat, long ] = ll.split(',');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
weather.setCoordinate(lat, long);
|
||||
weather.getWeatherForecast( function(err, wData) {
|
||||
if (err)
|
||||
return reject(err);
|
||||
else
|
||||
return resolve(wData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doGetOpenWeatherForecast(ll) {
|
||||
const [lat, long ] = ll.split(',');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
weather.setCoordinate(lat, long);
|
||||
weather.getWeatherForecastForDays(5, function(err, wData) {
|
||||
if (err)
|
||||
return reject(err);
|
||||
else
|
||||
return resolve(wData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doGetDarkSkyWeather(ll) {
|
||||
const query = `${ll}?units=uk2&exclude=daily,flags,minutely,hourly`;
|
||||
logger.debug(`https://api.darksky.net/forecast/${ darkskyApiKey }/${query}`);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
DSclient.get(query, function(err, res, body) {
|
||||
if (err || !body || !body.currently)
|
||||
return reject(err);
|
||||
|
||||
return resolve(body);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doGetFullForcast(ll) {
|
||||
const query = `${ll}?units=uk2&exclude=flags,minutely`;
|
||||
logger.debug('doGetFullForcast');
|
||||
logger.debug(query);
|
||||
return new Promise((resolve, reject) => {
|
||||
DSclient.get(query, function(err, res, body) {
|
||||
if (err || !body || !body.currently)
|
||||
return reject(err);
|
||||
|
||||
const output = reduceWeather(body);
|
||||
|
||||
return resolve(output);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function doGetFullForcastV2(ll) {
|
||||
const query = `${ll}?units=uk2&exclude=flags,minutely`;
|
||||
logger.debug('doGetFullForcastV2');
|
||||
const url = `${dsURL}${query}`;
|
||||
|
||||
logger.debug(url);
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
request.get({ 'url':url }, function(err, resp, body) {
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
|
||||
return reject(err);
|
||||
}
|
||||
// Logger.error(err);
|
||||
// return reject(err);
|
||||
// Throw err;
|
||||
|
||||
console.log;
|
||||
const output = reduceWeather(body);
|
||||
output.fullBody = JSON.parse(body);
|
||||
output.timestamp = new Date().getTime();
|
||||
|
||||
console.log(output);
|
||||
|
||||
return resolve(output);
|
||||
}, function(error, response, body) {
|
||||
console.log(response);
|
||||
if (response.statusCode !== 200) {
|
||||
logger.error(response.statusCode);
|
||||
logger.error(body);
|
||||
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { doGetOpenWeather, doGetOpenWeatherForecast, doGetDarkSkyWeather, doGetFullForcast, doGetFullForcastV2 };
|
Loading…
Reference in New Issue
Block a user