-
Notifications
You must be signed in to change notification settings - Fork 2
/
putnew
executable file
·143 lines (115 loc) · 5.12 KB
/
putnew
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env node
'use strict';
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');
const ed = require('@noble/ed25519');
const { constants, findKnownKeys, minify, s83Request } = require('./common');
const cliOpts = require('yargs/yargs')(process.argv.slice(2))
.default({
shorten: true,
shortenBoardLinks: false,
minify: true,
postHost: 'https://0l0.lol',
share: false
})
.parse();
const umsDate = new Date();
const putOneBytes = xOneBytes.bind(null, 'PUT');
const postOneBytes = xOneBytes.bind(null, 'POST', cliOpts.postHost);
async function preprocess (privKeyHex, htmlFileBytes, pubKeyHex) {
const htmlParsed = cheerio.load(htmlFileBytes);
const ogByteLen = htmlFileBytes.length;
const append = (s) => {
htmlParsed('html').append(s);
htmlFileBytes = Buffer.from(htmlParsed.root().html(), 'utf8');
};
// append required <time> tag if not already present
if (htmlParsed('time').length === 0) {
append(`<time datetime="${umsDate.toISOString().replace(/\.\d{3}Z$/, 'Z')}">`);
}
// append do-not-share meta tag if not present
if (!cliOpts.share && htmlParsed('meta[name=spring:share]').length === 0) {
append('<meta name="spring:share" content="false">');
}
console.log(`${pubKeyHex}.html: appended tags added ${htmlFileBytes.length - ogByteLen} bytes`);
const preMiniByteLen = htmlFileBytes.length;
if (cliOpts.shorten) {
const extraHeaders = {};
const disabled = [];
if (!cliOpts.minify) {
disabled.push('minify');
}
if (!cliOpts.shortenBoardLinks) {
disabled.push('shorten-board-links');
}
if (disabled.length) {
extraHeaders[constants.headerNames.shortenerDisable] = disabled.join(',');
}
htmlFileBytes = Buffer.from(await postOneBytes(privKeyHex, htmlFileBytes, pubKeyHex, extraHeaders), 'utf8');
} else if (cliOpts.minify) {
htmlFileBytes = await minify(htmlFileBytes);
}
const minifiedBytes = preMiniByteLen - htmlFileBytes.length;
console.log(`${pubKeyHex}.html: minified ${Number((minifiedBytes / preMiniByteLen) * 100).toFixed(0)}%` +
` (${minifiedBytes} bytes) -> ${htmlFileBytes.length} total`);
if (htmlFileBytes.length > constants.maximumContentLength) {
console.error(`${pubKeyHex}.html: ERRRO, still too large! By ` +
`${htmlFileBytes.length - constants.maximumContentLength} bytes (${htmlFileBytes.length})!`);
process.exit(-1);
}
return htmlFileBytes;
}
async function xOneBytes (method, host, privKeyHex, htmlFileBytes, pubKeyHex, extraHeaders = {}) {
const unmodifiedSince = umsDate.toUTCString();
const sigBytes = await ed.sign(htmlFileBytes, privKeyHex);
const sigHex = Buffer.from(sigBytes).toString('hex');
const fetchRes = await s83Request(method, host, pubKeyHex, unmodifiedSince, sigHex, extraHeaders, htmlFileBytes);
if (!fetchRes.ok) {
console.error(`ERROR: ${method} ${host} ${fetchRes.status} "${fetchRes.statusText}"`);
} else {
const isPost = method === 'POST';
console.log(`${method} ${host} ${fetchRes.status} ` + (isPost ? '' : `-> ${host}/${pubKeyHex}`));
if (isPost) {
return fetchRes.text();
}
const fedTo = fetchRes.headers.get(constants.headerNames.federatedTo);
if (fedTo) {
console.log(`Board will also be shared with: ${fedTo}`);
}
}
}
async function putOne (host, privKeyHex, htmlFile) {
const pubKeyHex = Buffer.from(await ed.getPublicKey(privKeyHex)).toString('hex');
const htmlFileBytes = await preprocess(privKeyHex, fs.readFileSync(htmlFile), pubKeyHex);
return putOneBytes(host, privKeyHex, htmlFileBytes, pubKeyHex);
}
async function main () {
if (cliOpts._.length === 2) {
let [boardsPath, postToHostsCSV] = cliOpts._;
postToHostsCSV = postToHostsCSV.split(',');
const boardKeys = await findKnownKeys(path.resolve(boardsPath), true);
console.log(`Posting ${Object.keys(boardKeys).length} boards to: ${postToHostsCSV.join(', ')}`);
await Promise.all(Object.entries(await findKnownKeys(path.resolve(boardsPath), true))
.map(async ([, { body, metadata }]) => {
const bodyPreProc = await preprocess(metadata.private, body, metadata.public);
return Promise.all(postToHostsCSV.map(async (host) =>
await putOneBytes(host, metadata.private, bodyPreProc, metadata.public)));
}));
process.exit(0);
}
if (cliOpts._.length !== 3) {
const execName = path.parse(process.argv[1]).name;
console.log(`To put a single board: ${execName} host privKeyHex htmlFile`);
console.log(`To put a path of boards: ${execName} boardPath hostsCommaSeperated`);
console.log('\nBoards will be minified & have HTTP(S) links shortened by default (except Springboard links).');
console.log('Pass --no-minify and/or --no-shorten to disable these behaviors.');
console.log('To also shorten Springboard links, pass --shortenBoardLinks true');
console.log('\nBy default, boards will be marked to NOT be shared with other federated hosts.');
console.log('\tThis adds at least 42 bytes.');
console.log('To instead mark your board to be shared, pass: --share true');
process.exit(0);
}
await putOne(...cliOpts._);
}
main();