Skip to content
This repository has been archived by the owner on Jul 9, 2019. It is now read-only.

Commit

Permalink
add initial (very bad) microsub support!
Browse files Browse the repository at this point in the history
  • Loading branch information
grantcodes committed Nov 28, 2017
1 parent 6e7fb96 commit 6f6d6e7
Show file tree
Hide file tree
Showing 24 changed files with 1,029 additions and 76 deletions.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"cors": "^2.8.4",
"express": "^4.16.2",
"immutable": "^3.8.2",
"isomorphic-fetch": "^2.2.1",
"leaflet": "^1.2.0",
"material-ui": "^1.0.0-beta.13",
"material-ui-icons": "^1.0.0-beta.17",
Expand All @@ -22,7 +23,8 @@
"react-leaflet": "^1.7.3",
"react-redux": "^5.0.6",
"react-router-dom": "^4.2.2",
"redux": "^3.7.2"
"redux": "^3.7.2",
"url-search-params-polyfill": "^2.0.1"
},
"scripts": {
"start": "npm-run-all --parallel backend frontend",
Expand Down
43 changes: 24 additions & 19 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,36 @@
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const Micropub = require('micropub-helper');
const fetch = require('isomorphic-fetch');
const micropubRoute = require('./lib/middlewear/micropub');
const microsubRoute = require('./lib/middlewear/microsub');
const relScraper = require('./lib/rel-scraper');

const app = express();

app.use(cors());
app.use(bodyParser.json());

app.all('/micropub/:method', (req, res, next) => {
req.body.clientId = 'http://together.com';
let micropub = new Micropub(req.body);
micropub[req.params.method](req.body.param)
.then((result) => {
res.json({
result: result,
options: micropub.options,
});
})
.catch((err) => {
let status = 500;
if (err.status) {
status = err.status;
}
res.status(status);
res.json(err);
});
app.all('/micropub/:method', micropubRoute);
app.all('/microsub/:method', microsubRoute);

app.post('/rels', (req, res, next) => {
if (!req.body.url) {
res.status(400);
res.json({ error: 'missing url' });
} else {
fetch(req.body.url)
.then(result => result.text())
.then((html) => {
res.json({
rels: relScraper(html, req.body.url),
});
})
.catch((err) => {
res.status(500);
res.json({ error: 'Error getting rels' });
})
}
});

app.listen(process.env.PORT || 8080);
Expand Down
215 changes: 215 additions & 0 deletions server/lib/microsub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
const fetch = require('isomorphic-fetch');
const Micropub = require('micropub-helper');
const { URL } = require('url');

const defaultSettings = {
me: '',
scope: 'post create delete update',
token: '',
authEndpoint: '',
tokenEndpoint: '',
microsubEndpoint: '',
};

class Microsub extends Micropub {
constructor(userSettings = {}) {
super();
this.options = Object.assign(defaultSettings, userSettings);

// Bind all the things
this.getChannels = this.getChannels.bind(this);
this.search = this.search.bind(this);
this.createChannel = this.createChannel.bind(this);
this.follow = this.follow.bind(this);
this.unfollow = this.unfollow.bind(this);
this.preview = this.preview.bind(this);
this.getFollowing = this.getFollowing.bind(this);
this.getTimeline = this.getTimeline.bind(this);
}

getChannels() {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'channels');
fetch(url.toString(), {
method: 'GET',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((channels) => {
resolve(channels.channels);
})
.catch((err) => {
reject(err);
});
});
}

createChannel(channelName) {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'channels');
url.searchParams.append('name', channelName);
fetch(url.toString(), {
method: 'POST',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((newChannel) => {
resolve(newChannel);
})
.catch((err) => {
reject(err);
});
});
}

search(text) {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'search');
url.searchParams.append('query', text);
fetch(url.toString(), {
method: 'POST',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((results) => {
resolve(results.results);
})
.catch((err) => {
reject(err);
});
});
}

follow(feed, channel = 'default') {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'follow');
url.searchParams.append('url', feed);
if (channel) {
url.searchParams.append('channel', channel);
}
fetch(url.toString(), {
method: 'POST',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((results) => {
resolve(results);
})
.catch((err) => {
reject(err);
});
});
}

unfollow(feed, channel = false) {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'unfollow');
url.searchParams.append('url', feed);
if (channel) {
url.searchParams.append('channel', channel);
}
fetch(url.toString(), {
method: 'POST',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((results) => {
resolve(results);
})
.catch((err) => {
reject(err);
});
});
}

preview(feed) {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'preview');
url.searchParams.append('url', feed);
fetch(url.toString(), {
method: 'GET',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((results) => {
resolve(results);
})
.catch((err) => {
reject(err);
});
});
}

getFollowing(channel) {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'follow');
url.searchParams.append('channel', channel);
fetch(url.toString(), {
method: 'GET',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((results) => {
resolve(results);
})
.catch((err) => {
reject(err);
});
});
}

getTimeline(channel = 'default', after = false, before = false, limit = 20) {
return new Promise((resolve, reject) => {
const url = new URL(this.options.microsubEndpoint);
url.searchParams.append('action', 'timeline');
if (channel) {
url.searchParams.append('channel', channel);
}
if (limit) {
url.searchParams.append('limit', limit);
}
if (after) {
url.searchParams.append('after', after);
}
if (before) {
url.searchParams.append('before', before);
}
fetch(url.toString(), {
method: 'GET',
headers: new Headers({
'Authorization': 'Bearer ' + this.options.token,
}),
})
.then((res) => res.json())
.then((results) => {
resolve(results);
})
.catch((err) => {
reject(err);
});
});
}
}

module.exports = Microsub;
21 changes: 21 additions & 0 deletions server/lib/middlewear/micropub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const Micropub = require('micropub-helper');

module.exports = (req, res, next) => {
req.body.clientId = 'http://together.com';
let micropub = new Micropub(req.body);
micropub[req.params.method](req.body.param)
.then((result) => {
res.json({
result: result,
options: micropub.options,
});
})
.catch((err) => {
let status = 500;
if (err.status) {
status = err.status;
}
res.status(status);
res.json(err);
});
}
22 changes: 22 additions & 0 deletions server/lib/middlewear/microsub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const Microsub = require('../microsub');

module.exports = (req, res, next) => {
req.body.clientId = 'http://together.com';
let microsub = new Microsub(req.body);
let params = req.body.params || [];
microsub[req.params.method](...params)
.then((result) => {
res.json({
result: result,
options: microsub.options,
});
})
.catch((err) => {
let status = 500;
if (err.status) {
status = err.status;
}
res.status(status);
res.json(err);
});
}
34 changes: 34 additions & 0 deletions server/lib/rel-scraper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
module.exports = function (htmlString, url) {
let rels = {};
let baseUrl = url;

const doc = new DOMParser().parseFromString(htmlString, 'text/html');
const baseEl = doc.querySelector('base[href]');
const relEls = doc.querySelectorAll('[rel][href]');

if (baseEl) {
const value = baseEl.getAttribute('href');
const url = new URL(value, url);
baseUrl = url.toString();
}

if (relEls.length) {
relEls.forEach((relEl) => {
const names = relEl.getAttribute('rel').toLowerCase().split("\\s+");
const value = relEl.getAttribute('href');
if (names.length && value !== null) {
names.forEach((name) => {
if (!rels[name]) {
rels[name] = [];
}
const url = new URL(value, baseUrl).toString();
if (rels[name].indexOf(url) === -1) {
rels[name].push(url);
}
});
}
});
}

return rels;
}
4 changes: 3 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import PostKindMenu from './components/post-kind-menu.js';
import ChannelMenu from './components/channel-menu.js';
import Settings from './components/settings';
import Login from './components/login';
import AddFeed from './components/add-feed';

const theme = createMuiTheme({
palette: {
Expand Down Expand Up @@ -42,10 +43,11 @@ class App extends Component {
<Route exact path="/" render={() => (
<div className={this.props.classes.main}>
<Timeline className={this.props.classes.timeline} />
<AddFeed />
</div>
)} />
<Route path="/settings" component={Settings} />
</div>
</div>
</MuiThemeProvider>
</Router>
);
Expand Down
Loading

0 comments on commit 6f6d6e7

Please sign in to comment.