Merge pull request #2 from eramdam/files-upload

Files upload
pull/1/head
Gergő Móricz 2022-09-09 16:23:32 +02:00 committed by GitHub
commit d068fda0fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 508 additions and 287 deletions

View File

@ -1,7 +1,9 @@
# cohost.js
Unofficial API for cohost.org
## Install
```bash
npm i cohost
```
@ -11,46 +13,68 @@ npm i cohost
```js
const cohost = require("cohost");
(async function() {
// Create User and authenticate
let user = new cohost.User();
await user.login("YOUR_EMAIL", "YOUR_PASSWORD");
(async function () {
// Create User and authenticate
let user = new cohost.User();
await user.login("YOUR_EMAIL", "YOUR_PASSWORD");
// Get first Project of user
let [ project ] = await user.getProjects();
// Get first Project of user
let [project] = await user.getProjects();
// Create Post
await cohost.Post.create(project, {
postState: 1,
headline: "hello world from cohost.js",
adultContent: false,
blocks: [],
cws: [],
tags: [],
});
// Create Post
await cohost.Post.create(project, {
postState: 1,
headline: "hello world from cohost.js",
adultContent: false,
blocks: [],
cws: [],
tags: []
});
// Get Posts of Project
let posts = await project.getPosts();
// Get Posts of Project
let posts = await project.getPosts();
// Create a draft with attachments
// 1. Create a draft
const draftId = await cohost.Post.create(myProject, basePost);
// 2. Upload the attachment
const attachmentData = await myProject.uploadAttachment(
draftId,
path.resolve(__dirname, "./02-15_One_pr.png")
);
// 3. Add the attachment block to the draft and publish it
await cohost.Post.update(myProject, draftId, {
...basePost,
postState: 1,
blocks: [
...basePost.blocks,
{ type: "attachment", attachment: { ...attachmentData } }
]
});
})();
```
## Features
Works:
* Logging in
* Getting the posts of a project
* Creating a post
- Logging in
- Getting the posts of a project
- Creating a post
Doesn't work:
* Editing a post: possible, haven't done it
* Sharing a post: possible, haven't done it
* Liking a post: possible, haven't done it
* Getting notifications: possible, haven't done it
* Uploading attachments: possible, haven't done it
* Getting the home feed: possible, haven't done it
* Editing profiles: possible, haven't done it
* Getting followers and following: possible, haven't done it
* Getting bookmarks and bookmarking: possible, haven't done it
* Getting a post by its ID: **seems impossible? endpoint seems to be disabled**
* Getting posts from a tag: haven''t checked
* ...everything else
- Editing a post: possible, haven't done it
- Sharing a post: possible, haven't done it
- Liking a post: possible, haven't done it
- Getting notifications: possible, haven't done it
- Getting the home feed: possible, haven't done it
- Editing profiles: possible, haven't done it
- Getting followers and following: possible, haven't done it
- Getting bookmarks and bookmarking: possible, haven't done it
- Getting a post by its ID: **seems impossible? endpoint seems to be disabled**
- Getting posts from a tag: haven''t checked
- ...everything else

477
lib.js
View File

@ -1,12 +1,16 @@
const _fetch = require("node-fetch");
const crypto = require("crypto");
const needle = require("needle");
const { decode } = require("./lib/b64arraybuffer");
const fs = require("fs");
const path = require("path");
const mime = require("mime-types");
const API_BASE = "https://cohost.org/api/v1";
/**
* Fetches an API endpoint.
*
*
* @private
* @param {string} method HTTP method to use
* @param {string} endpoint Relative endpoint to fetch
@ -15,254 +19,311 @@ const API_BASE = "https://cohost.org/api/v1";
* @param {boolean} [complex=false] Whether to return {headers, body}, or just the body
* @returns Response, JSON parsed if parsable, string if not
*/
async function fetch(method, endpoint, cookies = "", data, complex = false) {
let url = API_BASE + endpoint + (method == "GET" && data ? "?" + new URLSearchParams(data).toString() : "");
async function fetch(
method,
endpoint,
cookies = "",
data,
complex = false,
headers = {}
) {
let url =
API_BASE +
endpoint +
(method == "GET" && data ? "?" + new URLSearchParams(data).toString() : "");
let req = await _fetch(url, {
method,
headers: {
"Content-Type": "application/json",
"Cookie": cookies
},
body: (method != "GET" && data) ? JSON.stringify(data) : undefined
});
let req = await _fetch(url, {
method,
headers: {
"Content-Type": "application/json",
Cookie: cookies
},
body: method != "GET" && data ? JSON.stringify(data) : undefined
});
let res = await req.text();
try {
res = JSON.parse(res);
} catch (_) {}
let res = await req.text();
try {
res = JSON.parse(res);
} catch (_) {}
if (req.status >= 400) {
throw JSON.stringify(res);
if (req.status >= 400) {
throw JSON.stringify(res);
} else {
if (complex) {
return {
headers: req.headers,
body: res
};
} else {
if (complex) {
return {
headers: req.headers,
body: res
};
} else {
return res;
}
return res;
}
}
}
/**
* Represents a cohost User (e.g. john.doe@gmail.com)
*/
class User {
/**
* Authenticates the User.
* This should always be called before using this instance or its references.
*
* @param {string} email E-mail address
* @param {string} password Password
*/
async login(email, password) {
const { salt } = await fetch(
"GET",
"/login/salt",
undefined,
{ email }
);
const hash = crypto.pbkdf2Sync(Buffer.from(password, "utf8"), decode(salt), 200000, 128, "sha384")
const clientHash = Buffer.from(hash).toString("base64");
/**
* Authenticates the User.
* This should always be called before using this instance or its references.
*
* @param {string} email E-mail address
* @param {string} password Password
*/
async login(email, password) {
const { salt } = await fetch("GET", "/login/salt", undefined, { email });
const res = await fetch(
"POST",
"/login",
undefined,
{ email, clientHash },
true
);
const hash = crypto.pbkdf2Sync(
Buffer.from(password, "utf8"),
decode(salt),
200000,
128,
"sha384"
);
const clientHash = Buffer.from(hash).toString("base64");
this.sessionCookie = res.headers.get("set-cookie").split(";")[0];
const res = await fetch(
"POST",
"/login",
undefined,
{ email, clientHash },
true
);
this.userId = res.body.userId;
this.email = res.body.email;
}
this.sessionCookie = res.headers.get("set-cookie").split(";")[0];
/**
* Get Projects the User has edit permissions on.
*
* @returns {Project[]} User's projects
*/
async getProjects() {
return (await fetch(
"GET",
"/projects/edited",
this.sessionCookie
)).projects.map(x => new Project(this, x));
}
this.userId = res.body.userId;
this.email = res.body.email;
}
/**
* Get Notifications of the User. Docs TBD
*/
async getNotifications(offset = 0, limit = 20) {
return (await fetch(
"GET",
"/notifications/list",
this.sessionCookie,
{ offset, limit }
))
}
/**
* Get Projects the User has edit permissions on.
*
* @returns {Project[]} User's projects
*/
async getProjects() {
return (
await fetch("GET", "/projects/edited", this.sessionCookie)
).projects.map(x => new Project(this, x));
}
/**
* Get Notifications of the User. Docs TBD
*/
async getNotifications(offset = 0, limit = 20) {
return await fetch("GET", "/notifications/list", this.sessionCookie, {
offset,
limit
});
}
}
/**
* Represents a cohost Project (e.g. @mog)
*/
class Project {
constructor(user, data) {
this.user = user;
this.populate(data);
}
constructor(user, data) {
this.user = user;
this.populate(data);
}
/**
* Creates a Project. Docs TBD
*/
static async create(user, data) {
return await fetch(
"POST",
"/project",
user.sessionCookie,
data
);
}
/**
* Creates a Project. Docs TBD
*/
static async create(user, data) {
return await fetch("POST", "/project", user.sessionCookie, data);
}
/**
* @private
*/
populate(data) {
this.id = data.projectId;
this.handle = data.handle;
this.displayName = data.displayName;
this.dek = data.dek;
this.description = data.description;
this.avatarURL = data.avatarURL;
this.headerURL = data.headerURL;
this.privacy = data.privacy;
this.pronouns = data.pronouns;
this.url = data.url;
this.flags = data.flags;
this.avatarShape = data.avatarShape;
}
/**
* @private
*/
populate(data) {
this.id = data.projectId;
this.handle = data.handle;
this.displayName = data.displayName;
this.dek = data.dek;
this.description = data.description;
this.avatarURL = data.avatarURL;
this.headerURL = data.headerURL;
this.privacy = data.privacy;
this.pronouns = data.pronouns;
this.url = data.url;
this.flags = data.flags;
this.avatarShape = data.avatarShape;
}
/**
* @param {number} [page=0] Page of posts to get, 20 posts per page
* @returns {object[]}
*/
async getPosts(page = 0) {
let res = await fetch(
"GET",
`/project/${encodeURIComponent(this.handle)}/posts?page=${encodeURIComponent(page.toString())}`,
this.user.sessionCookie
);
/**
* @param {number} [page=0] Page of posts to get, 20 posts per page
* @returns {object[]}
*/
async getPosts(page = 0) {
let res = await fetch(
"GET",
`/project/${encodeURIComponent(
this.handle
)}/posts?page=${encodeURIComponent(page.toString())}`,
this.user.sessionCookie
);
return res.items.map(x => new Post(this.user, x));
}
return res.items.map(x => new Post(this.user, x));
}
async uploadAttachment(postId, filename) {
const fileContentType = mime.lookup(filename);
const fileContentLength = fs.statSync(filename).size;
const S3Parameters = await fetch(
"POST",
`/project/${encodeURIComponent(
this.handle
)}/posts/${postId}/attach/start`,
this.user.sessionCookie,
{
filename: path.basename(filename),
content_type: fileContentType,
content_length: fileContentLength
}
);
await needle(
"post",
S3Parameters.url,
{
...S3Parameters.requiredFields,
file: {
file: filename,
content_type: fileContentType
}
},
{ multipart: true }
);
const res = fetch(
"POST",
`/project/${encodeURIComponent(
this.handle
)}/posts/${postId}/attach/finish/${S3Parameters.attachmentId}`,
this.user.sessionCookie
);
return await res;
}
}
/**
* Represents a cohost Post
*/
class Post {
constructor(user, data) {
this.user = user;
this.populate(data);
}
constructor(user, data) {
this.user = user;
this.populate(data);
}
/**
* @typedef {Object} PostMarkdownBlock
* @property {string} content
*/
/**
* @typedef {Object} PostMarkdownBlock
* @property {string} content
*/
/**
* @typedef {Object} PostAttachmentBlock
* @property {string} fileURL
* @property {string} attachmentId
*/
/**
* @typedef {Object} PostAttachmentBlock
* @property {string} fileURL
* @property {string} attachmentId
* @property {string} altText
*/
/**
* @typedef {Object} PostBlock
* @property {string} type Type of block. Currently available: 'markdown' and 'attachment'
* @property {PostMarkdownBlock} [markdown] Should only be present if type is 'markdown'
* @property {PostAttachmentBlock} [attachment] Should only be present if type is 'attachment'
*/
/**
* @typedef {Object} PostBlock
* @property {string} type Type of block. Currently available: 'markdown' and 'attachment'
* @property {PostMarkdownBlock} [markdown] Should only be present if type is 'markdown'
* @property {PostAttachmentBlock} [attachment] Should only be present if type is 'attachment'
*/
/**
* @typedef {Object} PostCreate
* @property {number} postState 1 for published, 0 for draft? or pending maybe?
* @property {string} headline Headline
* @property {boolean} adultContent Does the post contain adult content?
* @property {PostBlock[]} blocks Blocks (docs TBD)
* @property {string[]} cws Content Warnings
* @property {string[]} tags Tags (docs TBD)
*/
/**
* @typedef {Object} PostCreate
* @property {number} postState 1 for published, 0 for draft
* @property {string} headline Headline
* @property {boolean} adultContent Does the post contain adult content?
* @property {PostBlock[]} blocks Blocks (docs TBD)
* @property {string[]} cws Content Warnings
* @property {string[]} tags Tags (docs TBD)
*/
/**
*
* @param {Project} project Project to post to
* @param {PostCreate} data
* @returns
*/
static async create(project, data) {
let { postId } = await fetch(
"POST",
`/project/${encodeURIComponent(project.handle)}/posts`,
project.user.sessionCookie,
data
);
/**
*
* @param {Project} project Project to post to
* @param {PostCreate} data
* @returns
*/
static async create(project, data) {
let { postId } = await fetch(
"POST",
`/project/${encodeURIComponent(project.handle)}/posts`,
project.user.sessionCookie,
data
);
// return await Post.getById(project, postId);
return postId;
}
return postId;
}
// Endpoint is disabled;
// static async getById(project, id) {
// let data = await fetch(
// "GET",
// `/project_posts/${id}`,
// project.user.sessionCookie
// );
/** see {@link create} */
static async update(project, postId, data) {
await fetch(
"PUT",
`/project/${encodeURIComponent(project.handle)}/posts/${postId}`,
project.user.sessionCookie,
data
);
// return new Post(user, data);
// }
return postId;
}
/**
* @private
*/
populate(data) {
this.id = data.postId;
this.headline = data.headline;
this.publishedAt = new Date(data.publishedAt);
this.filename = data.filename;
this.transparentShareOfPostId = data.transparentShareOfPostId;
this.state = data.state;
this.numComments = data.numComments;
this.numSharedComments = data.numSharedComments;
this.cws = data.cws;
this.tags = data.tags;
this.blocks = data.blocks;
this.plainTextBody = data.plainTextBody;
this.project = new Project(this.user, data.postingProject);
this.shareTree = data.shareTree;
this.relatedProjects = data.relatedProjects;
this.effectiveAdultContent = data.effectiveAdultContent;
this.isEditor = data.isEditor;
this.contributorBlockIncomingOrOutgoing = data.contributorBlockIncomingOrOutgoing;
this.hasAnyContributorMuted = data.hasAnyContributorMuted;
this.isLiked = data.isLiked;
this.canShare = data.canShare;
this.canPublish = data.canPublish;
this.singlePostPageUrl = data.singlePostPageUrl;
this.renderInIframe = data.renderInIframe;
this.postPreviewIFrameUrl = data.postPreviewIFrameUrl;
this.postEditUrl = data.postEditUrl;
}
// Endpoint is disabled;
// static async getById(project, id) {
// let data = await fetch(
// "GET",
// `/project_posts/${id}`,
// project.user.sessionCookie
// );
// return new Post(user, data);
// }
/**
* @private
*/
populate(data) {
this.id = data.postId;
this.headline = data.headline;
this.publishedAt = new Date(data.publishedAt);
this.filename = data.filename;
this.transparentShareOfPostId = data.transparentShareOfPostId;
this.state = data.state;
this.numComments = data.numComments;
this.numSharedComments = data.numSharedComments;
this.cws = data.cws;
this.tags = data.tags;
this.blocks = data.blocks;
this.plainTextBody = data.plainTextBody;
this.project = new Project(this.user, data.postingProject);
this.shareTree = data.shareTree;
this.relatedProjects = data.relatedProjects;
this.effectiveAdultContent = data.effectiveAdultContent;
this.isEditor = data.isEditor;
this.contributorBlockIncomingOrOutgoing =
data.contributorBlockIncomingOrOutgoing;
this.hasAnyContributorMuted = data.hasAnyContributorMuted;
this.isLiked = data.isLiked;
this.canShare = data.canShare;
this.canPublish = data.canPublish;
this.singlePostPageUrl = data.singlePostPageUrl;
this.renderInIframe = data.renderInIframe;
this.postPreviewIFrameUrl = data.postPreviewIFrameUrl;
this.postEditUrl = data.postEditUrl;
}
}
module.exports = {
User,
Project,
Post,
};
User,
Project,
Post
};

View File

@ -8,28 +8,35 @@ for (var i = 0; i < chars.length; i++) {
function encode(arraybuffer) {
var bytes = new Uint8Array(arraybuffer),
i, len = bytes.length, base64 = "";
i,
len = bytes.length,
base64 = "";
for (i = 0; i < len; i+=3) {
for (i = 0; i < len; i += 3) {
base64 += chars[bytes[i] >> 2];
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
base64 += chars[bytes[i + 2] & 63];
}
if ((len % 3) === 2) {
if (len % 3 === 2) {
base64 = base64.substring(0, base64.length - 1) + "=";
} else if (len % 3 === 1) {
base64 = base64.substring(0, base64.length - 2) + "==";
}
return base64;
};
}
function decode(base64) {
var bufferLength = base64.length * 0.75,
len = base64.length, i, p = 0,
encoded1, encoded2, encoded3, encoded4;
len = base64.length,
i,
p = 0,
encoded1,
encoded2,
encoded3,
encoded4;
if (base64[base64.length - 1] === "=") {
bufferLength--;
@ -39,13 +46,13 @@ function decode(base64) {
}
var arraybuffer = new ArrayBuffer(bufferLength),
bytes = new Uint8Array(arraybuffer);
bytes = new Uint8Array(arraybuffer);
for (i = 0; i < len; i+=4) {
for (i = 0; i < len; i += 4) {
encoded1 = lookup[base64.charCodeAt(i)];
encoded2 = lookup[base64.charCodeAt(i+1)];
encoded3 = lookup[base64.charCodeAt(i+2)];
encoded4 = lookup[base64.charCodeAt(i+3)];
encoded2 = lookup[base64.charCodeAt(i + 1)];
encoded3 = lookup[base64.charCodeAt(i + 2)];
encoded4 = lookup[base64.charCodeAt(i + 3)];
bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
@ -53,6 +60,6 @@ function decode(base64) {
}
return arraybuffer;
};
}
module.exports = {encode, decode};
module.exports = { encode, decode };

195
package-lock.json generated
View File

@ -9,10 +9,12 @@
"version": "0.0.2",
"license": "MIT",
"dependencies": {
"mime-types": "^2.1.35",
"needle": "^3.1.0",
"node-fetch": "2"
},
"devDependencies": {
"husky": "^8.0.1",
"husky": "^7.0.0",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1"
}
@ -175,20 +177,11 @@
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
"ms": "^2.1.1"
}
},
"node_modules/eastasianwidth": {
@ -260,20 +253,31 @@
}
},
"node_modules/husky": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz",
"integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==",
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
"integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
"dev": true,
"bin": {
"husky": "lib/bin.js"
},
"engines": {
"node": ">=14"
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/typicode"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@ -361,6 +365,29 @@
"url": "https://opencollective.com/lint-staged"
}
},
"node_modules/lint-staged/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/lint-staged/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/listr2": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz",
@ -616,6 +643,25 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
@ -629,10 +675,25 @@
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/needle": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz",
"integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==",
"dependencies": {
"debug": "^3.2.6",
"iconv-lite": "^0.6.3",
"sax": "^1.2.4"
},
"bin": {
"needle": "bin/needle"
},
"engines": {
"node": ">= 4.4.x"
}
},
"node_modules/node-fetch": {
"version": "2.6.7",
@ -828,6 +889,16 @@
"tslib": "^2.1.0"
}
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -1197,12 +1268,11 @@
}
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"requires": {
"ms": "2.1.2"
"ms": "^2.1.1"
}
},
"eastasianwidth": {
@ -1256,11 +1326,19 @@
"dev": true
},
"husky": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/husky/-/husky-8.0.1.tgz",
"integrity": "sha512-xs7/chUH/CKdOCs7Zy0Aev9e/dKOMZf3K1Az1nar3tzlv0jfqnYtu235bstsWTmXOR0EfINrPa97yy4Lz6RiKw==",
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
"integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
"dev": true
},
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@ -1316,6 +1394,23 @@
"pidtree": "^0.6.0",
"string-argv": "^0.3.1",
"yaml": "^2.1.1"
},
"dependencies": {
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dev": true,
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
}
}
},
"listr2": {
@ -1503,6 +1598,19 @@
"picomatch": "^2.3.1"
}
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="
},
"mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"requires": {
"mime-db": "1.52.0"
}
},
"mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
@ -1510,10 +1618,19 @@
"dev": true
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"needle": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/needle/-/needle-3.1.0.tgz",
"integrity": "sha512-gCE9weDhjVGCRqS8dwDR/D3GTAeyXLXuqp7I8EzH6DllZGXSUyxuqqLh+YX9rMAWaaTFyVAg6rHGL25dqvczKw==",
"requires": {
"debug": "^3.2.6",
"iconv-lite": "^0.6.3",
"sax": "^1.2.4"
}
},
"node-fetch": {
"version": "2.6.7",
@ -1636,6 +1753,16 @@
"tslib": "^2.1.0"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@ -12,6 +12,8 @@
"author": "mogery",
"license": "MIT",
"dependencies": {
"mime-types": "^2.1.35",
"needle": "^3.1.0",
"node-fetch": "2"
},
"directories": {