diff --git a/README.md b/README.md index d530144..a23231d 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/lib.js b/lib.js index b8cbaac..a61bfdc 100644 --- a/lib.js +++ b/lib.js @@ -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, -}; \ No newline at end of file + User, + Project, + Post +}; diff --git a/lib/b64arraybuffer.js b/lib/b64arraybuffer.js index 0e73fcb..0700577 100644 --- a/lib/b64arraybuffer.js +++ b/lib/b64arraybuffer.js @@ -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 }; diff --git a/package-lock.json b/package-lock.json index aca6e93..64e7ee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index f6bf616..f0e5d04 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "author": "mogery", "license": "MIT", "dependencies": { + "mime-types": "^2.1.35", + "needle": "^3.1.0", "node-fetch": "2" }, "directories": {