commit
d068fda0fc
88
README.md
88
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
|
||||
|
|
477
lib.js
477
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,
|
||||
};
|
||||
User,
|
||||
Project,
|
||||
Post
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
"author": "mogery",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-types": "^2.1.35",
|
||||
"needle": "^3.1.0",
|
||||
"node-fetch": "2"
|
||||
},
|
||||
"directories": {
|
||||
|
|
Loading…
Reference in New Issue