📦 2.3.0

This commit is contained in:
release bot
2023-07-17 20:37:27 +00:00
parent 027f89563f
commit 4489504b7a
4 changed files with 117 additions and 24 deletions

View File

@@ -4,7 +4,7 @@ author: "platane"
runs: runs:
using: docker using: docker
image: docker://platane/snk@sha256:bd0f7538482216785abbee29da431738f5ea9aff9fc3a4b8df37708a808f0968 image: docker://platane/snk@sha256:2115ffeb538e355aa155630e6e32b6d77ea2345fa8584645c41ace7f5ad667fc
inputs: inputs:
github_user_name: github_user_name:

View File

@@ -1,7 +1,7 @@
{ {
"name": "snk", "name": "snk",
"description": "Generates a snake game from a github user contributions grid", "description": "Generates a snake game from a github user contributions grid",
"version": "2.2.1", "version": "2.3.0",
"private": true, "private": true,
"repository": "github:platane/snk", "repository": "github:platane/snk",
"devDependencies": { "devDependencies": {

View File

@@ -1425,6 +1425,20 @@ const isDomainOrSubdomain = function isDomainOrSubdomain(destination, original)
return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest); return orig === dest || orig[orig.length - dest.length - 1] === '.' && orig.endsWith(dest);
}; };
/**
* isSameProtocol reports whether the two provided URLs use the same protocol.
*
* Both domains must already be in canonical form.
* @param {string|URL} original
* @param {string|URL} destination
*/
const isSameProtocol = function isSameProtocol(destination, original) {
const orig = new URL$1(original).protocol;
const dest = new URL$1(destination).protocol;
return orig === dest;
};
/** /**
* Fetch function * Fetch function
* *
@@ -1456,7 +1470,7 @@ function fetch(url, opts) {
let error = new AbortError('The user aborted a request.'); let error = new AbortError('The user aborted a request.');
reject(error); reject(error);
if (request.body && request.body instanceof Stream.Readable) { if (request.body && request.body instanceof Stream.Readable) {
request.body.destroy(error); destroyStream(request.body, error);
} }
if (!response || !response.body) return; if (!response || !response.body) return;
response.body.emit('error', error); response.body.emit('error', error);
@@ -1497,9 +1511,43 @@ function fetch(url, opts) {
req.on('error', function (err) { req.on('error', function (err) {
reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err)); reject(new FetchError(`request to ${request.url} failed, reason: ${err.message}`, 'system', err));
if (response && response.body) {
destroyStream(response.body, err);
}
finalize(); finalize();
}); });
fixResponseChunkedTransferBadEnding(req, function (err) {
if (signal && signal.aborted) {
return;
}
if (response && response.body) {
destroyStream(response.body, err);
}
});
/* c8 ignore next 18 */
if (parseInt(process.version.substring(1)) < 14) {
// Before Node.js 14, pipeline() does not fully support async iterators and does not always
// properly handle when the socket close/end events are out of order.
req.on('socket', function (s) {
s.addListener('close', function (hadError) {
// if a data listener is still present we didn't end cleanly
const hasDataListener = s.listenerCount('data') > 0;
// if end happened before close but the socket didn't emit an error, do it now
if (response && hasDataListener && !hadError && !(signal && signal.aborted)) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
response.body.emit('error', err);
}
});
});
}
req.on('response', function (res) { req.on('response', function (res) {
clearTimeout(reqTimeout); clearTimeout(reqTimeout);
@@ -1571,7 +1619,7 @@ function fetch(url, opts) {
size: request.size size: request.size
}; };
if (!isDomainOrSubdomain(request.url, locationURL)) { if (!isDomainOrSubdomain(request.url, locationURL) || !isSameProtocol(request.url, locationURL)) {
for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) { for (const name of ['authorization', 'www-authenticate', 'cookie', 'cookie2']) {
requestOpts.headers.delete(name); requestOpts.headers.delete(name);
} }
@@ -1664,6 +1712,13 @@ function fetch(url, opts) {
response = new Response(body, response_options); response = new Response(body, response_options);
resolve(response); resolve(response);
}); });
raw.on('end', function () {
// some old IIS servers return zero-length OK deflate responses, so 'data' is never emitted.
if (!response) {
response = new Response(body, response_options);
resolve(response);
}
});
return; return;
} }
@@ -1683,6 +1738,44 @@ function fetch(url, opts) {
writeToStream(req, request); writeToStream(req, request);
}); });
} }
function fixResponseChunkedTransferBadEnding(request, errorCallback) {
let socket;
request.on('socket', function (s) {
socket = s;
});
request.on('response', function (response) {
const headers = response.headers;
if (headers['transfer-encoding'] === 'chunked' && !headers['content-length']) {
response.once('close', function (hadError) {
// tests for socket presence, as in some situations the
// the 'socket' event is not triggered for the request
// (happens in deno), avoids `TypeError`
// if a data listener is still present we didn't end cleanly
const hasDataListener = socket && socket.listenerCount('data') > 0;
if (hasDataListener && !hadError) {
const err = new Error('Premature close');
err.code = 'ERR_STREAM_PREMATURE_CLOSE';
errorCallback(err);
}
});
}
});
}
function destroyStream(stream, err) {
if (stream.destroy) {
stream.destroy(err);
} else {
// node < 8
stream.emit('error', err);
stream.end();
}
}
/** /**
* Redirect code matching * Redirect code matching
* *

View File

@@ -78,27 +78,27 @@ const getGithubUserContribution = async (userName, options = {}) => {
return parseUserPage(resText); return parseUserPage(resText);
}; };
const parseUserPage = (content) => { const parseUserPage = (content) => {
// take roughly the svg block // take roughly the table block
const block = content const block = content
.split(`class="js-calendar-graph-svg"`)[1] .split(`aria-describedby="contribution-graph-description"`)[1]
.split("</svg>")[0]; .split("<tbody>")[1]
let x = 0; .split("</tbody>")[0];
let lastYAttribute = 0; const cells = block.split("</tr>").flatMap((inside, y) => inside.split("</td>").flatMap((m) => {
const rects = Array.from(block.matchAll(/<rect[^>]*>[^<]*<\/rect>/g)).map(([m]) => { const date = m.match(/data-date="([^"]+)"/)?.[1];
const date = m.match(/data-date="([^"]+)"/)[1]; const literalLevel = m.match(/data-level="([^"]+)"/)?.[1];
const level = +m.match(/data-level="([^"]+)"/)[1]; const literalX = m.match(/data-ix="([^"]+)"/)?.[1];
const yAttribute = +m.match(/y="([^"]+)"/)[1]; const literalCount = m.match(/(No|\d+) contributions? on/)?.[1];
const literalCount = m.match(/(No|\d+) contributions? on/)[1]; if (date && literalLevel && literalX && literalCount)
const count = literalCount === "No" ? 0 : +literalCount; return [
if (lastYAttribute > yAttribute) {
x++; x: +literalX,
lastYAttribute = yAttribute; y,
return { date, count, level, x, yAttribute }; date,
}); count: +literalCount,
const yAttributes = Array.from(new Set(rects.map((c) => c.yAttribute)).keys()).sort(); level: +literalLevel,
const cells = rects.map(({ yAttribute, ...c }) => ({ },
y: yAttributes.indexOf(yAttribute), ];
...c, return [];
})); }));
return cells; return cells;
}; };