From be05d836eaa5f0e9f6e9af3098d7ddade7225c32 Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Sun, 31 Jul 2022 21:53:37 +0200 Subject: [PATCH 1/6] :construction: working file upload --- lib.js | 131 +++++++++++++++++++++++++++++----------------- package-lock.json | 129 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 5 +- 3 files changed, 214 insertions(+), 51 deletions(-) diff --git a/lib.js b/lib.js index b8cbaac..06b1aab 100644 --- a/lib.js +++ b/lib.js @@ -1,6 +1,11 @@ -const _fetch = require("node-fetch"); +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"; @@ -15,14 +20,14 @@ 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) { +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 + "Cookie": cookies, }, body: (method != "GET" && data) ? JSON.stringify(data) : undefined }); @@ -112,54 +117,86 @@ class User { * 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 + ) + + console.log(await res) + } } /** diff --git a/package-lock.json b/package-lock.json index 848077b..fbb1d6f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,78 @@ { "name": "cohost", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "cohost", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "dependencies": { + "mime-types": "^2.1.35", + "needle": "^3.1.0", "node-fetch": "2" } }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "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/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/ms": { + "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", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -31,6 +92,16 @@ } } }, + "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/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -52,6 +123,50 @@ } }, "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "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" + } + }, + "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" + } + }, + "ms": { + "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", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -60,6 +175,16 @@ "whatwg-url": "^5.0.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==" + }, "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", diff --git a/package.json b/package.json index 573af73..d73bd6b 100644 --- a/package.json +++ b/package.json @@ -10,12 +10,13 @@ "author": "mogery", "license": "MIT", "dependencies": { + "mime-types": "^2.1.35", + "needle": "^3.1.0", "node-fetch": "2" }, "directories": { "lib": "lib" }, - "devDependencies": {}, "repository": { "type": "git", "url": "git+https://github.com/mogery/cohost.js.git" @@ -24,4 +25,4 @@ "url": "https://github.com/mogery/cohost.js/issues" }, "homepage": "https://github.com/mogery/cohost.js#readme" -} +} \ No newline at end of file From d2d41e089e32ce1f16407fe1f326442c319e55e9 Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Sun, 31 Jul 2022 21:54:56 +0200 Subject: [PATCH 2/6] run prettier --- lib.js | 376 ++++++++++++++++++++++-------------------- lib/b64arraybuffer.js | 33 ++-- 2 files changed, 214 insertions(+), 195 deletions(-) diff --git a/lib.js b/lib.js index 06b1aab..2484405 100644 --- a/lib.js +++ b/lib.js @@ -1,17 +1,16 @@ -const _fetch = require('node-fetch') +const _fetch = require("node-fetch"); const crypto = require("crypto"); -const needle = require('needle'); +const needle = require("needle"); const { decode } = require("./lib/b64arraybuffer"); -const fs = require('fs'); -const path = require('path'); -const mime = require('mime-types'); - +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 @@ -20,97 +19,104 @@ 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, headers = {}) { - 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 + }); + } } /** @@ -160,7 +166,7 @@ class Project { 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) { @@ -175,27 +181,32 @@ class Project { { filename: path.basename(filename), content_type: fileContentType, - content_length: fileContentLength, + content_length: fileContentLength } ); - await needle('post', S3Parameters.url, { - ...S3Parameters.requiredFields, - file: { - file: filename, - content_type: fileContentType - } - }, {multipart: true}); + await needle( + "post", + S3Parameters.url, + { + ...S3Parameters.requiredFields, + file: { + file: filename, + content_type: fileContentType + } + }, + { multipart: true } + ); - const res = fetch( + const res = fetch( "POST", `/project/${encodeURIComponent( this.handle )}/posts/${postId}/attach/finish/${S3Parameters.attachmentId}`, this.user.sessionCookie - ) + ); - console.log(await res) + console.log(await res); } } @@ -203,103 +214,104 @@ class Project { * 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 + */ - /** - * @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? 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) + */ - /** - * - * @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 await Post.getById(project, postId); + return postId; + } - // Endpoint is disabled; - // static async getById(project, id) { - // let data = await fetch( - // "GET", - // `/project_posts/${id}`, - // project.user.sessionCookie - // ); + // Endpoint is disabled; + // static async getById(project, id) { + // let data = await fetch( + // "GET", + // `/project_posts/${id}`, + // project.user.sessionCookie + // ); - // return new Post(user, data); - // } + // 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; - } + /** + * @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 }; From ab0d4a93dd8b2dd43166e2b26a61b060849ffbea Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Sun, 31 Jul 2022 22:43:25 +0200 Subject: [PATCH 3/6] return attachment data in uploadAttachment --- lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib.js b/lib.js index 2484405..fa5c248 100644 --- a/lib.js +++ b/lib.js @@ -206,7 +206,7 @@ class Project { this.user.sessionCookie ); - console.log(await res); + return await res; } } From 54568fa886397cfb1e00e22757f86634fde11bd6 Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Sun, 31 Jul 2022 22:43:36 +0200 Subject: [PATCH 4/6] update jsdoc --- lib.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib.js b/lib.js index fa5c248..555f7c6 100644 --- a/lib.js +++ b/lib.js @@ -228,6 +228,7 @@ class Post { * @typedef {Object} PostAttachmentBlock * @property {string} fileURL * @property {string} attachmentId + * @property {string} altText */ /** @@ -239,7 +240,7 @@ class Post { /** * @typedef {Object} PostCreate - * @property {number} postState 1 for published, 0 for draft? or pending maybe? + * @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) From b54d0dabb0195d47056bc66c12edc6e77ec294c1 Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Sun, 31 Jul 2022 22:44:15 +0200 Subject: [PATCH 5/6] add Post#update method --- lib.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib.js b/lib.js index 555f7c6..a61bfdc 100644 --- a/lib.js +++ b/lib.js @@ -262,7 +262,18 @@ class Post { data ); - // return await Post.getById(project, postId); + return postId; + } + + /** see {@link create} */ + static async update(project, postId, data) { + await fetch( + "PUT", + `/project/${encodeURIComponent(project.handle)}/posts/${postId}`, + project.user.sessionCookie, + data + ); + return postId; } From 3492167bb8fdb1a7b1249148346936d81f792fb0 Mon Sep 17 00:00:00 2001 From: Damien Erambert Date: Fri, 9 Sep 2022 15:51:10 +0200 Subject: [PATCH 6/6] Update README.md --- README.md | 88 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 32 deletions(-) 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