parent
a410c13d89
commit
9106af7e24
@ -1,2 +1,11 @@
|
||||
# super-hog
|
||||
|
||||
Super Hog is a process runner. Tasks will be configured in a File by Yaml.
|
||||
|
||||
```
|
||||
name: "Lorem Ipsum RSS"
|
||||
url: "https://lorem-rss.herokuapp.com/feed"
|
||||
cron: "0 1 * * *"
|
||||
actions:
|
||||
- class: "RssExample"
|
||||
```
|
||||
|
@ -1,5 +0,0 @@
|
||||
# super-hog
|
||||
|
||||
```
|
||||
yarn install
|
||||
```
|
@ -0,0 +1,24 @@
|
||||
import Action from './../../packages/runner/actions/action.ts'
|
||||
|
||||
/**
|
||||
* getting json from url, parse response and send to console
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
|
||||
class JsonExample extends Action {
|
||||
async run() {
|
||||
const response = await fetch(this.config.url)
|
||||
|
||||
if (response.status == 200) {
|
||||
this.result = await response.json()
|
||||
}
|
||||
|
||||
console.log(this.result)
|
||||
}
|
||||
}
|
||||
|
||||
export default JsonExample
|
@ -0,0 +1,25 @@
|
||||
import { XMLParser } from 'fast-xml-parser'
|
||||
import Action from './../../packages/runner/actions/action.ts'
|
||||
|
||||
/**
|
||||
* getting rss feed from url, parse response and send to console
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
|
||||
class RssExample extends Action {
|
||||
async run() {
|
||||
const response = await fetch(this.config.url)
|
||||
const body = await response.text()
|
||||
|
||||
const parser = new XMLParser()
|
||||
this.result = parser.parse(body)
|
||||
|
||||
console.log(this.result)
|
||||
}
|
||||
}
|
||||
|
||||
export default RssExample
|
@ -0,0 +1,5 @@
|
||||
name: "Lorem Ipsum RSS"
|
||||
url: "https://lorem-rss.herokuapp.com/feed"
|
||||
cron: "0 1 * * *"
|
||||
actions:
|
||||
- class: "RssExample"
|
File diff suppressed because it is too large
Load Diff
@ -1,28 +1,10 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "runner",
|
||||
"version": "0.1.0",
|
||||
"name": "super-hog",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node src/runner/index.js"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@directus/sdk": "^10.3.3",
|
||||
"better-sqlite3": "^8.2.0",
|
||||
"chalk": "^5.1.2",
|
||||
"dayjs": "^1.11.7",
|
||||
"dotenv": "^16.0.3",
|
||||
"es6-interface": "^3.2.1",
|
||||
"fast-xml-parser": "^4.1.3",
|
||||
"got": "^12.5.2",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lowdb": "^5.1.0",
|
||||
"node-cron": "^3.0.2",
|
||||
"node-html-parser": "^6.1.5",
|
||||
"pino": "^8.7.0",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"puppeteer": "^19.8.3",
|
||||
"turndown": "^7.1.2",
|
||||
"yargs": "^17.6.0"
|
||||
"start": "bun run packages/runner/index.ts"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,4 @@
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
// getting .env
|
||||
dotenv.config({ path: path.join(path.resolve(), '/.env') })
|
@ -0,0 +1,48 @@
|
||||
import { JSONFilePreset } from 'lowdb/node'
|
||||
import merge from 'deepmerge'
|
||||
|
||||
const db = await JSONFilePreset('./storage/db.json', { runs: [] })
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
|
||||
class Db {
|
||||
|
||||
public constructor(slug) {
|
||||
this.slug = slug
|
||||
this.data = db.data.runs.find((runs) => runs.slug === this.slug)
|
||||
}
|
||||
|
||||
public async get() {
|
||||
if (this.data === undefined) {
|
||||
this.data = {
|
||||
'slug': this.slug,
|
||||
'date_created_at': dayjs().toISOString(),
|
||||
'date_run_at': dayjs().toISOString()
|
||||
}
|
||||
|
||||
db.data.runs.push(run)
|
||||
await db.write()
|
||||
}
|
||||
|
||||
return this.data
|
||||
}
|
||||
|
||||
public async update(data) {
|
||||
db.data.runs.find((run, index) => {
|
||||
if (run.slug === this.slug) {
|
||||
db.data.runs[index] = merge(db.data.runs[index], data)
|
||||
}
|
||||
})
|
||||
|
||||
await db.write()
|
||||
}
|
||||
}
|
||||
|
||||
export default Db
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Docket
|
||||
*
|
||||
* Is used to hold all data and configs that run through the actions
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
class Docket {
|
||||
|
||||
// config for action
|
||||
private config: object
|
||||
|
||||
// data
|
||||
private data: object
|
||||
private options: object
|
||||
private db: object
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param object config
|
||||
*
|
||||
*/
|
||||
public constructor(config: object, db: object) {
|
||||
this.config = config
|
||||
this.db = db
|
||||
}
|
||||
|
||||
public getData() {
|
||||
return this.data
|
||||
}
|
||||
|
||||
public setData(data: object) {
|
||||
return this.data = data
|
||||
}
|
||||
|
||||
public setOptions(options): object {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
public getConfig(): object {
|
||||
return this.config
|
||||
}
|
||||
|
||||
public getDb(): object {
|
||||
return this.db
|
||||
}
|
||||
}
|
||||
|
||||
export default Docket
|
@ -0,0 +1,57 @@
|
||||
import dayjs from 'dayjs'
|
||||
import Db from './_db.ts'
|
||||
import Docket from './_docket.ts'
|
||||
|
||||
import { resolveActionClass } from './helpers/resolver.ts'
|
||||
import logger from './helpers/logger.ts'
|
||||
|
||||
/**
|
||||
* run through a single config, getting each action with config
|
||||
* and run them, the docket will be used to hold data and configs
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
async function run(config: object) {
|
||||
|
||||
let db = new Db(config.slug)
|
||||
let docket = new Docket(config, db)
|
||||
|
||||
logger(config.slug).info('has started')
|
||||
|
||||
for (const actionConfig of config.actions) {
|
||||
|
||||
// resolve action class
|
||||
const ActionClass = await import(resolveActionClass(actionConfig.class))
|
||||
|
||||
// options are exists, add to docket
|
||||
if (actionConfig.hasOwnProperty('options')) {
|
||||
docket.setOptions(action.options)
|
||||
}
|
||||
|
||||
try {
|
||||
const action = new ActionClass.default(docket)
|
||||
|
||||
logger(config.slug).info('action / ' + actionConfig.class)
|
||||
|
||||
await action.run()
|
||||
docket = action.getDocket()
|
||||
|
||||
} catch(error) {
|
||||
logger(config.slug).error('action / ' + actionConfig.class + ' / ' + error)
|
||||
}
|
||||
}
|
||||
|
||||
logger(config.slug).info('has finished')
|
||||
|
||||
// update db
|
||||
await db.update({
|
||||
'date_run_at': dayjs().toISOString()
|
||||
})
|
||||
|
||||
return docket
|
||||
}
|
||||
|
||||
export default run
|
@ -0,0 +1,27 @@
|
||||
import logger from './helpers/logger.ts'
|
||||
|
||||
/**
|
||||
* Webhook
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
|
||||
class Webhook {
|
||||
static send(message: string) {
|
||||
|
||||
const query = new URLSearchParams({
|
||||
'message': message
|
||||
})
|
||||
|
||||
try {
|
||||
fetch(process.env.WEBHOOK_URL + '?' + query.toString())
|
||||
} catch(error) {
|
||||
logger().error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Webhook
|
@ -0,0 +1,46 @@
|
||||
import ActionInterface from './actionInterface.ts'
|
||||
import Docket from './../docket.ts'
|
||||
|
||||
/**
|
||||
* Action
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
class Action implements ActionInterface {
|
||||
|
||||
private docket: Docket
|
||||
private data: object
|
||||
private result: object
|
||||
private options: object
|
||||
private config: object
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
public constructor(docket: Docket, options: object = {}) {
|
||||
this.docket = docket
|
||||
this.data = docket.getData()
|
||||
|
||||
// current data will be set as data
|
||||
this.result = this.data
|
||||
|
||||
this.options = options
|
||||
this.config = docket.getConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* set result as data
|
||||
*
|
||||
*/
|
||||
public getDocket() {
|
||||
this.docket.setData(this.result)
|
||||
return this.docket
|
||||
}
|
||||
}
|
||||
|
||||
export default Action
|
@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Interface for Action
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
|
||||
interface ActionInterface {
|
||||
async run(): any
|
||||
}
|
||||
|
||||
export default ActionInterface
|
@ -0,0 +1,36 @@
|
||||
import pino from 'pino'
|
||||
import pretty from 'pino-pretty'
|
||||
import dayjs from 'dayjs'
|
||||
import { mkdirSync } from "node:fs"
|
||||
import slug from 'slug'
|
||||
|
||||
/**
|
||||
* Wrapper for logger
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
function logger(slug?: string) {
|
||||
|
||||
let destination = './storage/logs/'
|
||||
|
||||
// if name is set, adding name as directory
|
||||
if (slug) {
|
||||
destination += slug + '/'
|
||||
mkdirSync(destination, { recursive: true })
|
||||
}
|
||||
|
||||
destination += dayjs().format('DD-MM-YYYY') + '.log'
|
||||
|
||||
return pino({
|
||||
timestamp: () => {
|
||||
return `, "time":"${new Date(Date.now()).toISOString()}"`
|
||||
},
|
||||
},
|
||||
pino.destination(destination)
|
||||
)
|
||||
}
|
||||
|
||||
export default logger
|
@ -0,0 +1,71 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import yaml from 'js-yaml'
|
||||
import slug from 'slug'
|
||||
|
||||
/**
|
||||
* resolve action class
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
function resolveActionClass(className) {
|
||||
|
||||
let classPath = path.join(path.resolve(), 'resources/actions/' + className + '.ts')
|
||||
let result = undefined
|
||||
|
||||
if (fs.existsSync(classPath)) {
|
||||
result = classPath
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Action Class ' + className + ' / ' + classPath + ' not found!')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* loading all configs from enabled directory
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
function resolveEnabledConfig(name?: string) {
|
||||
|
||||
const configs = []
|
||||
const directoryPath = path.join(path.resolve(), 'resources/config')
|
||||
|
||||
// load files from enabled
|
||||
const files = fs.readdirSync(directoryPath + '/enabled')
|
||||
|
||||
for (let index in files) {
|
||||
let file = files[index]
|
||||
|
||||
if (!file.endsWith('.yml')) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (name !== undefined && file !== (name + '.yml')) {
|
||||
continue
|
||||
}
|
||||
|
||||
const result = yaml.load(fs.readFileSync(directoryPath + '/enabled/' + file))
|
||||
|
||||
if (result) {
|
||||
result.slug = slug(result.name)
|
||||
configs.push(result)
|
||||
}
|
||||
}
|
||||
|
||||
return configs
|
||||
}
|
||||
|
||||
export {
|
||||
resolveActionClass,
|
||||
resolveEnabledConfig
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import cron from 'node-cron'
|
||||
|
||||
import { resolveEnabledConfig } from './helpers/resolver.ts'
|
||||
import logger from './helpers/logger.ts'
|
||||
|
||||
import run from './_run.ts'
|
||||
|
||||
/**
|
||||
* Run all Configs that are in Directory /resources/configs/enabled
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
|
||||
// get all configs from resolveEnabledConfig
|
||||
const configs = resolveEnabledConfig()
|
||||
|
||||
// check if there is any valid config
|
||||
if (configs.length === 0) {
|
||||
throw new Error('No valid config found!')
|
||||
}
|
||||
|
||||
// running through configs
|
||||
for (const config in configs)
|
||||
|
||||
// check for cron
|
||||
if (!config.hasOwnProperty('cron')) {
|
||||
return
|
||||
}
|
||||
|
||||
/**
|
||||
* adding task to schedule, using cron
|
||||
*
|
||||
*/
|
||||
const task = cron.schedule(config.cron, async() => {
|
||||
const docket = await run(config, docket)
|
||||
})
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "runner",
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"chalk": "^5.1.2",
|
||||
"dayjs": "^1.11.7",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dotenv": "^16.0.3",
|
||||
"fast-xml-parser": "^4.1.3",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lowdb": "^7.0.1",
|
||||
"node-cron": "^3.0.2",
|
||||
"pino": "^8.7.0",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"slug": "^9.0.0",
|
||||
"yargs": "^17.6.0"
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
import chalk from 'chalk'
|
||||
|
||||
import { resolveEnabledConfig } from './helpers/resolver.ts'
|
||||
import Docket from './docket.ts'
|
||||
import logger from './helpers/logger.ts'
|
||||
|
||||
import run from './_run.ts'
|
||||
|
||||
/**
|
||||
* Run single Config that is in Directory /resources/configs/enabled
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://git.node001.net/HerrHase/super-hog.git
|
||||
*
|
||||
*/
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
if (args[0] === undefined) {
|
||||
console.log(chalk.red('Required name for config'))
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// get all configs from resolveEnabledConfig
|
||||
const configs = resolveEnabledConfig(args[0])
|
||||
|
||||
// check if there is any valid config
|
||||
if (configs.length === 0) {
|
||||
console.log(chalk.red('Config ' + args[0] + ' not found!'))
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const config = configs[0]
|
||||
const docket = await run(config)
|
@ -1,58 +0,0 @@
|
||||
import got from 'got'
|
||||
import url from 'url'
|
||||
import path from 'path'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { createWriteStream } from 'fs'
|
||||
|
||||
import stream from 'node:stream'
|
||||
import { promisify } from 'util'
|
||||
|
||||
import Action from './../../src/runner/actions/action.js'
|
||||
import logger from './../../src/runner/helpers/logger.js'
|
||||
import EventStore from './../directus/event.js'
|
||||
|
||||
import { Directus } from '@directus/sdk';
|
||||
|
||||
/**
|
||||
* Download Episodes from a RSS-Feed,
|
||||
*
|
||||
*
|
||||
* @extends Action
|
||||
*
|
||||
*/
|
||||
class MindenErlebenApi extends Action {
|
||||
async run() {
|
||||
const eventStore = new EventStore()
|
||||
|
||||
// adding result
|
||||
let result
|
||||
|
||||
if (this.lastResult) {
|
||||
for (let data of this.lastResult) {
|
||||
if (data.mediaUrl) {
|
||||
|
||||
// import media to directus
|
||||
const media = await eventStore.importMediaFromUrl(data.mediaUrl)
|
||||
|
||||
// if import successfull add to data
|
||||
if (media) {
|
||||
data.media = {
|
||||
id: media
|
||||
}
|
||||
}
|
||||
|
||||
delete data.mediaUrl
|
||||
}
|
||||
|
||||
result = await eventStore.create(data)
|
||||
|
||||
if (result && result.id) {
|
||||
logger.info(data.source_host + ' / update, create ' + result.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MindenErlebenApi
|
@ -1,138 +0,0 @@
|
||||
import got from 'got'
|
||||
import path from 'path'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
import { createWriteStream } from 'fs'
|
||||
|
||||
import stream from 'node:stream'
|
||||
import { promisify } from 'util'
|
||||
|
||||
import crypto from 'crypto'
|
||||
import { parse } from 'node-html-parser'
|
||||
|
||||
import Action from './../../src/runner/actions/action.js'
|
||||
import logger from './../../src/runner/helpers/logger.js'
|
||||
|
||||
import EventStore from './../directus/event.js'
|
||||
|
||||
import { createHash } from 'node:crypto'
|
||||
import url from 'node:url'
|
||||
|
||||
import TurndownService from 'turndown'
|
||||
|
||||
/**
|
||||
* Download Episodes from a RSS-Feed,
|
||||
*
|
||||
*
|
||||
* @extends Action
|
||||
*
|
||||
*/
|
||||
class OberealtstadtEvents extends Action
|
||||
{
|
||||
async run() {
|
||||
|
||||
const results = []
|
||||
const turndownService = new TurndownService()
|
||||
const eventStore = new EventStore()
|
||||
|
||||
const currentUrl = new URL(this.config.url)
|
||||
|
||||
for (let item of this.data) {
|
||||
|
||||
const hash = createHash('sha256').update(item.id.toString()).digest('hex')
|
||||
|
||||
if (item.status !== 'publish') {
|
||||
continue
|
||||
}
|
||||
|
||||
const event = await eventStore.findOneBySource(currentUrl.host, hash)
|
||||
|
||||
if (event.data.length > 0 && event.data[0].date_updated_source <= item.modified) {
|
||||
continue
|
||||
}
|
||||
|
||||
let result = {
|
||||
title: item.title.rendered,
|
||||
date_updated_source: item.modified,
|
||||
content: turndownService.turndown(item.content.rendered),
|
||||
source_host: currentUrl.host,
|
||||
source_url: item.link,
|
||||
source_hash: hash
|
||||
}
|
||||
|
||||
const htmlString = await got(item.guid.rendered, {
|
||||
resolveBodyOnly: true
|
||||
})
|
||||
|
||||
const html = parse(htmlString)
|
||||
|
||||
result = this._parseDates(html, result)
|
||||
result = this._parseMedia(html, result)
|
||||
|
||||
results.push(result)
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
_parseDates(html, result) {
|
||||
const dates = html.querySelectorAll('.wpem-event-date-time-text')
|
||||
|
||||
if (dates[0]) {
|
||||
result.start = this._getDate(dates[0])
|
||||
}
|
||||
|
||||
if (dates[1]) {
|
||||
result.end = this._getDate(dates[1])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_getDate(string) {
|
||||
|
||||
const dateString = string.textContent.replace('um ', '').trim()
|
||||
const dateArray = dateString.split(' ')
|
||||
|
||||
|
||||
if (dateArray.length === 2) {
|
||||
let time = dateString.split(' ')[1]
|
||||
let day = dateString.split(' ')[0]
|
||||
|
||||
// getting day and time
|
||||
day = day.split('.')[2] + '-' + day.split('.')[1] + '-' + day.split('.')[0]
|
||||
time = time.split(':')[0] + ':' + time.split(':')[1]
|
||||
|
||||
const date = dayjs(day + ' ' + time)
|
||||
|
||||
if (date.isValid()) {
|
||||
return date.format()
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
_parseMedia(html, result) {
|
||||
const img = html.querySelector('.wpem-event-single-image img')
|
||||
|
||||
if (img) {
|
||||
result.mediaUrl = img.getAttribute('src')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
export default OberealtstadtEvents
|
@ -1,9 +0,0 @@
|
||||
name: "Insert Moin"
|
||||
url: "https://steadyhq.com/rss/insertmoin?auth=d37bffc9-9a84-4eed-95f6-3b6cb77c2406"
|
||||
cron: "0 1 * * *"
|
||||
request:
|
||||
class: "rss"
|
||||
actions:
|
||||
- class: "podcast"
|
||||
options:
|
||||
destination: "/home/herrhase/Downloads/x"
|
@ -1,3 +0,0 @@
|
||||
notifications:
|
||||
type: "webhook"
|
||||
url: ""
|
@ -1,8 +0,0 @@
|
||||
name: "oberealtstadt.de"
|
||||
url: "https://oberealtstadt.de/wp-json/wp/v2/event_listing"
|
||||
cron: "0 */6 * * *"
|
||||
request:
|
||||
class: "json"
|
||||
actions:
|
||||
- class: "oberealtstadtEvents"
|
||||
- class: "mindenErlebenApi"
|
@ -1,40 +0,0 @@
|
||||
import { Directus } from '@directus/sdk';
|
||||
|
||||
/**
|
||||
* Abstract Class for handling Directus Api
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://gitea.node001.net/HerrHase/super-fastify-directus.git
|
||||
*
|
||||
*/
|
||||
|
||||
class DirectusAbstractStore {
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param {string} endpoint
|
||||
*
|
||||
*/
|
||||
constructor(endpoint) {
|
||||
this.endpoint = endpoint
|
||||
|
||||
// if endpoint not set throw Error
|
||||
if (!this.endpoint) {
|
||||
throw new Error('Endpoint in ' + this.constructor.name + ' missing!')
|
||||
}
|
||||
|
||||
this.directus = new Directus(process.env.DIRECTUS_API_URL, {
|
||||
auth: {
|
||||
staticToken: process.env.DIRECTUS_API_TOKEN
|
||||
}
|
||||
})
|
||||
|
||||
// create items
|
||||
this.items = this.directus.items(this.endpoint)
|
||||
}
|
||||
}
|
||||
|
||||
export default DirectusAbstractStore
|
@ -1,98 +0,0 @@
|
||||
import DirectusAbstractStore from './abstract.js'
|
||||
import logger from './../../src/runner/helpers/logger.js'
|
||||
|
||||
/**
|
||||
* Pages from Directus
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
* @license http://opensource.org/licenses/MIT The MIT License
|
||||
* @link https://gitea.node001.net/HerrHase/super-fastify-directus.git
|
||||
*
|
||||
*/
|
||||
|
||||
class EventStore extends DirectusAbstractStore {
|
||||
|
||||
/**
|
||||
* init PageStore
|
||||
*
|
||||
*
|
||||
* @param {string} endpoint
|
||||
*
|
||||
*/
|
||||
constructor() {
|
||||
super('event')
|
||||
}
|
||||
|
||||
/**
|
||||
* getting page by permalink
|
||||
*
|
||||
*
|
||||
* @param {string} permalink
|
||||
* @return {object}
|
||||
*
|
||||
*/
|
||||
findOneBySource(sourceHost, sourceHash) {
|
||||
return this.items.readByQuery({
|
||||
fields: [
|
||||
'title',
|
||||
'status',
|
||||
'source_host',
|
||||
'source_url',
|
||||
'source_hash',
|
||||
'start',
|
||||
'end',
|
||||
'excerpt',
|
||||
'content',
|
||||
'tags',
|
||||
'parent',
|
||||
'media.id',
|
||||
'media.description',
|
||||
'permalink',
|
||||
'date_updated_source'
|
||||
],
|
||||
filter: {
|
||||
source_host : sourceHost,
|
||||
source_hash : sourceHash
|
||||
},
|
||||
limit: 1
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* import media from url
|
||||
*
|
||||
* @param {string} url
|
||||
* @return {object}
|
||||
*/
|
||||
async importMediaFromUrl(url) {
|
||||
|
||||
try {
|
||||
const file = await this.directus.files.import({
|
||||
url: url
|
||||
})
|
||||
|
||||
if (file) {
|
||||
return file.id
|
||||
}
|
||||
} catch(error) {
|
||||
logger.error(error)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* getting page by permalink
|
||||
*
|
||||
*
|
||||
* @param {object} data
|
||||
* @return {object}
|
||||
*
|
||||
*/
|
||||
create(data) {
|
||||
return this.items.createOne(data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default EventStore
|
@ -1,25 +0,0 @@
|
||||
import got from 'got'
|
||||
|
||||
import RequestHandler from './../../src/runner/requests/requestHandler.js'
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
*
|
||||
*/
|
||||
|
||||
class JsonHandler extends RequestHandler {
|
||||
|
||||
/**
|
||||
* getting rss feed from url
|
||||
*
|
||||
*
|
||||
*/
|
||||
async send() {
|
||||
const json = await got(this.config.url, {}).json()
|
||||
this.processActions(json)
|
||||
}
|
||||
}
|
||||
|
||||
export default JsonHandler
|
@ -1,36 +0,0 @@
|
||||
import got from 'got'
|
||||
import { XMLParser } from 'fast-xml-parser'
|
||||
|
||||
import RequestHandler from './../../src/runner/requests/requestHandler.js'
|
||||
|
||||
/**
|
||||
* RssHandler
|
||||
*
|
||||
* request rss from url and parse to object
|
||||
*
|
||||
* @author Björn Hase <me@herr-hase.wtf>
|
||||
*
|
||||
*/
|
||||
|
||||
class RssHandler extends RequestHandler {
|
||||
|
||||
/**
|
||||
* getting rss feed from url
|
||||
*
|
||||
*
|
||||
*/
|
||||
async send() {
|
||||
|
||||
const buffer = await got(this.config.url, {
|
||||
responseType: 'buffer',
|
||||
resolveBodyOnly: true
|
||||
})
|
||||
|
||||
const parser = new XMLParser()
|
||||
const feed = parser.parse(buffer.toString())
|
||||
|
||||
this.processActions(feed)
|
||||
}
|
||||
}
|
||||
|
||||
export default RssHandler
|
@ -1,37 +0,0 @@
|
||||
import Interface from 'es6-interface'
|
||||
|
||||
import ActionInterface from './actionInterface.js'
|
||||
import StateStore from './../stores/state.js'
|
||||
|
||||
/**
|
||||
* Single Action to process Data from Remote Server
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class Action extends Interface(ActionInterface) {
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {[type]} config
|
||||
* @param {[type]} local
|
||||
* @param {[type]} data
|
||||
* @param {[type]} lastResult
|
||||
*
|
||||
*/
|
||||
constructor(config, options, data, lastResult) {
|
||||
super()
|
||||
|
||||
this.config = config
|
||||
this.options = options
|
||||
this.data = data
|
||||
this.lastResult = lastResult
|
||||
|
||||
this.stateStore = new StateStore()
|
||||
this.state = this.stateStore.findOneByName(this.config.name)
|
||||
}
|
||||
}
|
||||
|
||||
export default Action
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const ActionInterface = {
|
||||
run: function() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export default ActionInterface
|
@ -1,32 +0,0 @@
|
||||
import Database from 'better-sqlite3'
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @return {[Object]}
|
||||
*
|
||||
*/
|
||||
|
||||
function getDB() {
|
||||
const db = new Database('./storage/db/main.db')
|
||||
db.pragma('journal_mode = WAL')
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
/**
|
||||
* creating db
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
function migrateDB() {
|
||||
const db = getDB()
|
||||
db.prepare('CREATE TABLE IF NOT EXISTS states (id INTEGER PRIMARY KEY, name TEXT NOT NULL, date_lastrun TEXT)').run()
|
||||
}
|
||||
|
||||
|
||||
export {
|
||||
migrateDB,
|
||||
getDB
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import yaml from 'js-yaml'
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
function loadEnabledConfig() {
|
||||
|
||||
const configs = []
|
||||
const directoryPath = path.join(path.resolve(), 'resources/config')
|
||||
|
||||
// load file for main config
|
||||
const mainConfig = yaml.load(fs.readFileSync(directoryPath + '/config.yml'))
|
||||
|
||||
// load files from enabled
|
||||
const configFiles = fs.readdirSync(directoryPath + '/enabled')
|
||||
|
||||
configFiles.forEach((file, index) => {
|
||||
const result = yaml.load(fs.readFileSync(directoryPath + '/enabled/' + file))
|
||||
|
||||
if (result) {
|
||||
configs.push(result)
|
||||
}
|
||||
})
|
||||
|
||||
return configs
|
||||
}
|
||||
|
||||
export {
|
||||
loadEnabledConfig
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import pino from 'pino'
|
||||
import pretty from 'pino-pretty'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export default pino({
|
||||
timestamp: () => {
|
||||
return `, "time":"${new Date(Date.now()).toISOString()}"`
|
||||
},
|
||||
},
|
||||
pino.destination('./storage/logs/' + dayjs().format('DD-MM-YYYY') + '.log')
|
||||
)
|
@ -1,31 +0,0 @@
|
||||
import path from 'path'
|
||||
import fs from 'fs'
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {[string]} prefix
|
||||
* @param {[string]} className
|
||||
* @return {[string]}
|
||||
*
|
||||
*/
|
||||
|
||||
function resolveClassPath(prefix, className) {
|
||||
|
||||
let classPath = path.join(path.resolve(), 'resources/' + prefix + '/' + className + '.js')
|
||||
let result = undefined
|
||||
|
||||
if (fs.existsSync(classPath)) {
|
||||
result = classPath
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
throw new Error('Class ' + prefix + ' / ' + className + ' / ' + classPath + ' not found!')
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export {
|
||||
resolveClassPath
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
import cron from 'node-cron'
|
||||
import path from 'path'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
import { loadEnabledConfig } from './helpers/config.js'
|
||||
import { resolveClassPath } from './helpers/resolver.js'
|
||||
import { migrateDB, getDB } from './db.js'
|
||||
import logger from './helpers/logger.js'
|
||||
|
||||
// loading config-files from /resources/configs/enabled
|
||||
const configs = loadEnabledConfig()
|
||||
|
||||
// getting .env
|
||||
dotenv.config({ path: path.join(path.resolve(), '/.env') })
|
||||
|
||||
// check if there is any valid config
|
||||
if (configs.length === 0) {
|
||||
throw new Error('No valid config found!')
|
||||
}
|
||||
|
||||
// running migrate for prepate db
|
||||
migrateDB()
|
||||
|
||||
// let it rain and add schedule for each config a cron and start
|
||||
configs.forEach(async (config) => {
|
||||
|
||||
// const task = cron.schedule(config.cron, async () => {
|
||||
|
||||
const requestClass = resolveClassPath('requests', config.request.class)
|
||||
|
||||
const RequestClass = await import(requestClass)
|
||||
const request = new RequestClass.default(config)
|
||||
|
||||
//try {
|
||||
request.send()
|
||||
// } catch(error) {
|
||||
// logger.error(error)
|
||||
// }
|
||||
|
||||
//})
|
||||
//
|
||||
// task.start()
|
||||
})
|
@ -1,11 +0,0 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class Webhook {
|
||||
webhook(url) {
|
||||
|
||||
}
|
||||
}
|
@ -1,64 +0,0 @@
|
||||
import Interface from 'es6-interface'
|
||||
|
||||
import RequestHandlerInterface from './requestHandlerInterface.js'
|
||||
import { resolveClassPath } from './../helpers/resolver.js'
|
||||
import logger from './../helpers/logger.js'
|
||||
import StateStore from './../stores/state.js'
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
class RequestHandler extends Interface(RequestHandlerInterface)
|
||||
{
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @param {object} config
|
||||
*
|
||||
*/
|
||||
constructor(config) {
|
||||
super()
|
||||
|
||||
this.config = config
|
||||
|
||||
this.stateStore = new StateStore()
|
||||
this.state = this.stateStore.findOneByName(this.config.name)
|
||||
}
|
||||
|
||||
/**
|
||||
* process actions that are saved in a source
|
||||
*
|
||||
*
|
||||
* @param {object} data
|
||||
*
|
||||
*/
|
||||
async processActions(data)
|
||||
{
|
||||
//
|
||||
let lastResult = false
|
||||
|
||||
for (const config of this.config.actions) {
|
||||
|
||||
// getting class path
|
||||
const classPath = resolveClassPath('actions', config.class)
|
||||
|
||||
// import class from action
|
||||
const Action = await import(classPath)
|
||||
|
||||
// create action an call run
|
||||
const action = new Action.default(this.config, config.options, data, lastResult)
|
||||
|
||||
try {
|
||||
lastResult = await action.run()
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default RequestHandler
|
@ -1,19 +0,0 @@
|
||||
/**
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
const RequestHandlerInterface = {
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
send: function() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default RequestHandlerInterface
|
Loading…
Reference in new issue