Restore v1 of proxy API, add new v2 API

This commit is contained in:
Yarmo Mackenbach 2021-04-12 14:36:44 +02:00
parent c5ad812101
commit 9ff781a234
No known key found for this signature in database
GPG key ID: 37367F4AF4087AD1
2 changed files with 392 additions and 88 deletions

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2020 Yarmo Mackenbach Copyright 2021 Yarmo Mackenbach
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
@ -14,7 +14,14 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
const router = require('express').Router() const router = require('express').Router()
const fetcher = require('../../../fetcher') const dns = require('dns')
const bent = require('bent')
const bentReq = bent('GET')
const validUrl = require('valid-url')
const jsdom = require('jsdom')
const { client, xml } = require('@xmpp/client')
const debug = require('@xmpp/debug')
const irc = require("irc-upd")
require('dotenv').config() require('dotenv').config()
const xmpp_service = process.env.XMPP_SERVICE || null const xmpp_service = process.env.XMPP_SERVICE || null
@ -25,7 +32,9 @@ const matrix_instance = process.env.MATRIX_INSTANCE || null
const matrix_access_token = process.env.MATRIX_ACCESS_TOKEN || null const matrix_access_token = process.env.MATRIX_ACCESS_TOKEN || null
const irc_nick = process.env.IRC_NICK || null const irc_nick = process.env.IRC_NICK || null
let xmpp_enabled = true, let xmpp = null,
iqCaller = null,
xmpp_enabled = true,
twitter_enabled = false, twitter_enabled = false,
matrix_enabled = false, matrix_enabled = false,
irc_enabled = false irc_enabled = false
@ -43,10 +52,31 @@ if (irc_nick) {
irc_enabled = true irc_enabled = true
} }
const xmppStart = async (xmpp_service, xmpp_username, xmpp_password) => {
return new Promise((resolve, reject) => {
const xmpp = client({
service: xmpp_service,
username: xmpp_username,
password: xmpp_password,
})
if (process.env.NODE_ENV !== 'production') {
debug(xmpp, true)
}
const { iqCaller } = xmpp
xmpp.start()
xmpp.on('online', (address) => {
console.log('online', address.toString())
resolve({ xmpp: xmpp, iqCaller: iqCaller })
})
xmpp.on('error', (error) => {
reject(error)
})
})
}
router.get('/', async (req, res) => { router.get('/', async (req, res) => {
return res.status(400).json({ res.status(200).json({
data: [], message:
error:
'Available endpoints: /json/:url, /text/:url, /dns/:hostname, /xmpp/:xmppid, /twitter/:tweetid, /matrix/:roomid/:eventid, /irc/:ircserver/:ircnick', 'Available endpoints: /json/:url, /text/:url, /dns/:hostname, /xmpp/:xmppid, /twitter/:tweetid, /matrix/:roomid/:eventid, /irc/:ircserver/:ircnick',
}) })
}) })
@ -55,7 +85,7 @@ router.param('url', async (req, res, next, url) => {
req.params.url = decodeURI(url) req.params.url = decodeURI(url)
if (!validUrl.isUri(req.params.url)) { if (!validUrl.isUri(req.params.url)) {
return res.status(400).json({ error: 'URL provided was not valid' }) return res.status(400).send({ message: 'URL provided was not valid' })
} }
next() next()
@ -67,7 +97,7 @@ router.param('xmppid', async (req, res, next, xmppid) => {
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/.test(req.params.xmppid)) { if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/.test(req.params.xmppid)) {
next() next()
} else { } else {
return res.status(400).json({ error: 'XMPP_ID was not valid' }) return res.status(400).json({ message: 'XMPP_ID was not valid' })
} }
}) })
@ -86,9 +116,8 @@ router.param('xmppdata', async (req, res, next, xmppdata) => {
] ]
if (!allowedData.includes(req.params.xmppdata)) { if (!allowedData.includes(req.params.xmppdata)) {
return res.status(400).send({ return res.status(400).json({
data: [], message:
error:
'Allowed data are: FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC', 'Allowed data are: FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC',
}) })
} }
@ -100,118 +129,176 @@ router.get('/get/json/:url', (req, res) => {
bentReq(req.params.url, 'json', { bentReq(req.params.url, 'json', {
Accept: 'application/json', Accept: 'application/json',
}) })
.then(async (result) => { .then(async (result) => {
return await result.json() return await result.json()
}) })
.then(async (data) => { .then(async (result) => {
return res.status(200).send(data) return res.status(200).json({ url: req.params.url, content: result })
}) })
.catch((err) => { .catch((e) => {
return res.status(400).json({ error: err }) return res.status(400).send({ error: e })
}) })
}) })
router.get('/get/text/:url', (req, res) => { router.get('/get/text/:url', (req, res) => {
bentReq(req.params.url) bentReq(req.params.url)
.then(async (result) => { .then(async (result) => {
return await result.text() return await result.text()
}) })
.then(async (result) => { .then(async (result) => {
return res.status(200).send(result) return res.status(200).json({ url: req.params.url, content: result })
}) })
.catch((err) => { .catch((e) => {
return res.status(400).json({ error: err }) return res.status(400).send({ error: e })
}) })
}) })
router.get('/get/dns/:hostname', (req, res) => { router.get('/get/dns/:hostname', async (req, res) => {
fetcher dns.resolveTxt(req.params.hostname, (err, records) => {
.dns(req.params.hostname) const out = {
.then((data) => { hostname: req.params.hostname,
return res.status(200).send(data) records: {
}) txt: records,
.catch((err) => { },
return res.status(400).json({ error: err }) }
}) return res.status(200).json(out)
})
}) })
router.get('/get/xmpp/:xmppid', async (req, res) => { router.get('/get/xmpp/:xmppid', async (req, res) => {
return res.status(400).send({ return res
data: [], .status(400)
error: .json(
'Data request parameter missing (FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC)', 'Data request parameter missing (FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC)'
}) )
}) })
router.get('/get/xmpp/:xmppid/:xmppdata', async (req, res) => { router.get('/get/xmpp/:xmppid/:xmppdata', async (req, res) => {
if (!xmpp_enabled) { if (!xmpp_enabled) {
return res.status(501).json({ error: 'XMPP not enabled on server' }) return res.status(500).json('XMPP not enabled on server')
}
if (!xmpp) {
const xmppStartRes = await xmppStart(
xmpp_service,
xmpp_username,
xmpp_password
)
xmpp = xmppStartRes.xmpp
iqCaller = xmppStartRes.iqCaller
} }
fetcher const response = await iqCaller.request(
.xmpp(req.params.xmppid, req.params.xmppdata, { xml(
service: xmpp_service, 'iq',
username: xmpp_username, { type: 'get', to: req.params.xmppid },
password: xmpp_password, xml('vCard', 'vcard-temp')
}) ),
.then((data) => { 30 * 1000
return res.status(200).send(data) )
})
.catch((err) => { const vcardRow = response.getChild('vCard', 'vcard-temp').toString()
return res.status(400).json({ error: err.message ? err.message : err })
}) const dom = new jsdom.JSDOM(vcardRow)
try {
let vcard
switch (req.params.xmppdata.toLowerCase()) {
case 'desc':
case 'note':
vcard = dom.window.document.querySelector('note text')
if (!vcard) {
vcard = dom.window.document.querySelector('DESC')
}
if (vcard) {
vcard = vcard.textContent
} else {
throw new Error('No DESC or NOTE field found in vCard')
}
break
default:
vcard = dom.window.document.querySelector(req.params.xmppdata).textContent
break
}
return res.status(200).json(vcard)
} catch (error) {
return res.status(400).json({ message: 'Request could not be fulfilled', error: error })
}
}) })
router.get('/get/twitter/:tweetid', async (req, res) => { router.get('/get/twitter/:tweetid', async (req, res) => {
if (!twitter_enabled) { if (!twitter_enabled) {
return res.status(501).json({ error: 'Twitter not enabled on server' }) return res.status(500).json('Twitter not enabled on server')
} }
fetcher bentReq(`https://api.twitter.com/1.1/statuses/show.json?id=${req.params.tweetid}`, null, {
.twitter(req.params.tweetid, { Accept: 'application/json',
bearerToken: twitter_bearer_token, Authorization: `Bearer ${twitter_bearer_token}`
}) })
.then((data) => { .then(async (data) => {
return res.status(200).send(data) return await data.json()
}) })
.catch((err) => { .then((data) => {
return res.status(400).json({ error: err }) return res.status(200).json({ data: data, message: 'Success', error: {} })
}) })
.catch((error) => {
return res.status(error.statusCode || 400).json({ data: [], message: 'Request could not be fulfilled', error: error })
})
}) })
router.get('/get/matrix/:matrixroomid/:matrixeventid', async (req, res) => { router.get('/get/matrix/:matrixroomid/:matrixeventid', async (req, res) => {
if (!matrix_enabled) { if (!matrix_enabled) {
return res.status(501).json({ error: 'Matrix not enabled on server' }) return res.status(500).json('Matrix not enabled on server')
} }
fetcher const url = `https://${matrix_instance}/_matrix/client/r0/rooms/${req.params.matrixroomid}/event/${req.params.matrixeventid}?access_token=${matrix_access_token}`
.matrix(req.params.matrixroomid, req.params.matrixeventid, {
instance: process.env.MATRIX_INSTANCE, bentReq(url, null, {
accessToken: process.env.MATRIX_ACCESS_TOKEN, Accept: 'application/json'
}) })
.then((data) => { .then(async (data) => {
return res.status(200).send(data) return await data.json()
}) })
.catch((err) => { .then((data) => {
return res.status(400).json({ error: err }) return res.status(200).json({ data: data, message: 'Success', error: {} })
}) })
.catch((error) => {
return res.status(error.statusCode || 400).json({ data: [], message: 'Request could not be fulfilled', error: error })
})
}) })
router.get('/get/irc/:ircserver/:ircnick', async (req, res) => { router.get('/get/irc/:ircserver/:ircnick', async (req, res) => {
if (!irc_enabled) { if (!irc_enabled) {
return res.status(501).json({ error: 'IRC not enabled on server' }) return res.status(500).json('IRC not enabled on server')
} }
try {
const client = new irc.Client(req.params.ircserver, irc_nick, {
port: 6697,
secure: true,
channels: [],
})
const reKey = /[a-zA-Z0-9\-\_]+\s+:\s(openpgp4fpr\:.*)/
const reEnd = /End\sof\s.*\staxonomy./
let keys = []
fetcher client.addListener('registered', (message) => {
.irc(req.params.ircserver, req.params.ircnick, { client.send(`PRIVMSG NickServ :TAXONOMY ${req.params.ircnick}`)
nick: 'doipver148927',
}) })
.then((data) => { client.addListener('notice', (nick, to, text, message) => {
return res.status(200).send(data) if (reKey.test(text)) {
}) const match = text.match(reKey)
.catch((err) => { keys.push(match[1]);
return res.status(400).json({ error: err }) }
if (reEnd.test(text)) {
client.disconnect()
return res.status(200).json({ data: keys, message: 'Success', error: {} })
}
}) })
} catch (error) {
return res.status(400).json({ data: [], message: 'Request could not be fulfilled', error: error })
}
}) })
module.exports = router module.exports = router

217
src/proxy/api/v2/index.js Normal file
View file

@ -0,0 +1,217 @@
/*
Copyright 2021 Yarmo Mackenbach
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
const router = require('express').Router()
const fetcher = require('../../../fetcher')
require('dotenv').config()
const xmpp_service = process.env.XMPP_SERVICE || null
const xmpp_username = process.env.XMPP_USERNAME || null
const xmpp_password = process.env.XMPP_PASSWORD || null
const twitter_bearer_token = process.env.TWITTER_BEARER_TOKEN || null
const matrix_instance = process.env.MATRIX_INSTANCE || null
const matrix_access_token = process.env.MATRIX_ACCESS_TOKEN || null
const irc_nick = process.env.IRC_NICK || null
let xmpp_enabled = true,
twitter_enabled = false,
matrix_enabled = false,
irc_enabled = false
if (!xmpp_service || !xmpp_username || !xmpp_password) {
xmpp_enabled = false
}
if (twitter_bearer_token) {
twitter_enabled = true
}
if (matrix_instance && matrix_access_token) {
matrix_enabled = true
}
if (irc_nick) {
irc_enabled = true
}
router.get('/', async (req, res) => {
return res.status(400).json({
data: [],
error:
'Available endpoints: /json/:url, /text/:url, /dns/:hostname, /xmpp/:xmppid, /twitter/:tweetid, /matrix/:roomid/:eventid, /irc/:ircserver/:ircnick',
})
})
router.param('url', async (req, res, next, url) => {
req.params.url = decodeURI(url)
if (!validUrl.isUri(req.params.url)) {
return res.status(400).json({ error: 'URL provided was not valid' })
}
next()
})
router.param('xmppid', async (req, res, next, xmppid) => {
req.params.xmppid = xmppid
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,})+$/.test(req.params.xmppid)) {
next()
} else {
return res.status(400).json({ error: 'XMPP_ID was not valid' })
}
})
router.param('xmppdata', async (req, res, next, xmppdata) => {
req.params.xmppdata = xmppdata.toUpperCase()
const allowedData = [
'FN',
'NUMBER',
'USERID',
'URL',
'BDAY',
'NICKNAME',
'NOTE',
'DESC',
]
if (!allowedData.includes(req.params.xmppdata)) {
return res.status(400).send({
data: [],
error:
'Allowed data are: FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC',
})
}
next()
})
router.get('/get/json/:url', (req, res) => {
bentReq(req.params.url, 'json', {
Accept: 'application/json',
})
.then(async (result) => {
return await result.json()
})
.then(async (data) => {
return res.status(200).send(data)
})
.catch((err) => {
return res.status(400).json({ error: err })
})
})
router.get('/get/text/:url', (req, res) => {
bentReq(req.params.url)
.then(async (result) => {
return await result.text()
})
.then(async (result) => {
return res.status(200).send(result)
})
.catch((err) => {
return res.status(400).json({ error: err })
})
})
router.get('/get/dns/:hostname', (req, res) => {
fetcher
.dns(req.params.hostname)
.then((data) => {
return res.status(200).send(data)
})
.catch((err) => {
return res.status(400).json({ error: err })
})
})
router.get('/get/xmpp/:xmppid', async (req, res) => {
return res.status(400).send({
data: [],
error:
'Data request parameter missing (FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC)',
})
})
router.get('/get/xmpp/:xmppid/:xmppdata', async (req, res) => {
if (!xmpp_enabled) {
return res.status(501).json({ error: 'XMPP not enabled on server' })
}
fetcher
.xmpp(req.params.xmppid, req.params.xmppdata, {
service: xmpp_service,
username: xmpp_username,
password: xmpp_password,
})
.then((data) => {
return res.status(200).send(data)
})
.catch((err) => {
return res.status(400).json({ error: err.message ? err.message : err })
})
})
router.get('/get/twitter/:tweetid', async (req, res) => {
if (!twitter_enabled) {
return res.status(501).json({ error: 'Twitter not enabled on server' })
}
fetcher
.twitter(req.params.tweetid, {
bearerToken: twitter_bearer_token,
})
.then((data) => {
return res.status(200).send(data)
})
.catch((err) => {
return res.status(400).json({ error: err })
})
})
router.get('/get/matrix/:matrixroomid/:matrixeventid', async (req, res) => {
if (!matrix_enabled) {
return res.status(501).json({ error: 'Matrix not enabled on server' })
}
fetcher
.matrix(req.params.matrixroomid, req.params.matrixeventid, {
instance: process.env.MATRIX_INSTANCE,
accessToken: process.env.MATRIX_ACCESS_TOKEN,
})
.then((data) => {
return res.status(200).send(data)
})
.catch((err) => {
return res.status(400).json({ error: err })
})
})
router.get('/get/irc/:ircserver/:ircnick', async (req, res) => {
if (!irc_enabled) {
return res.status(501).json({ error: 'IRC not enabled on server' })
}
fetcher
.irc(req.params.ircserver, req.params.ircnick, {
nick: 'doipver148927',
})
.then((data) => {
return res.status(200).send(data)
})
.catch((err) => {
return res.status(400).json({ error: err })
})
})
module.exports = router